 The 2020 Google CTF had a challenge called log me in. The challenge description says log in to get the flag and there's a link to a website and an attachment. Let's take a look at how I solved this challenge. Let's start by looking at the home page. My first thought when seeing this home page was of course to click the yellow flag button. Unfortunately when I click on it it tells me you must be logged in to access that. The profile page also tells me to log in. We can access the rather generic home and a boat pages without logging in but they don't appear to be helpful right now. Clicking on log in allows us to log in but there doesn't seem to be a way to create an account. We'll probably need to find a way to bypass this authentication check or determine a working username and password combination. I try just entering in some common username password combinations and admin-admin unexpectedly actually worked. Unfortunately when I clicked flag it told me only Michelle's account has the flag. I then remember that this is the same Michelle name that's also listed as giving a testimonial as a premium customer on the about page. It looks like we're going to need to figure out how to log in as Michelle. Alas my luck had run dry when it came to guessing Michelle's password. Next let's take a look at what happens when we log in with the correct username and password in detail. The login page is just a simple HTML form. When the submit button is pressed a post request is made to slash log in with the username, password and a CSRF or cross site request forgery token. However the CSRF token isn't actually set to anything and just ends up being an empty string. Interesting. In the HTTP response back from the server we are redirected to the profile page slash me. However two cookies are also set session and session dot seg. I was able to tell right away since it began with EY which is how an opening double brace gets encoded with base 64 and JSON objects almost always start with an opening double brace. Decoding the JSON results in an object with three keys CSRF username and flag. CSRF is a random UUID probably intended to prevent another website from making requests that the login user didn't intend to do. The login name is the name of the login user, admin in this case, flag is what's shown on the flag page. It appears that this can even be HTML which isn't a skate and while we can change the session cookie to say change our username to Michelle doing so will cause us to be logged out. This is because session dot sig cookie holds a signature for the session cookie. If we change the session cookie then we have to sign it with the signing keys that appear to only be known to the server. If we try tampering with the session cookie the server detects this tampering and ignores the invalid cookie. In fact, even if we could change the session cookie and determine the correct signature it wouldn't be much help. Since the flag is stored in the cookie that's generated when we log in, changing our username to Michelle after we log in wouldn't be much help since the flag would still be stored in the cookie all we would be able to do is control what username and flag value is displayed not know what the correct flag is. Next let's take a look at the file that was attached to the challenge. You can see it's a dot zip file that when extracted contains one file in it app.js. This file was last modified on November 30th, 1979 according to the zip metadata. I didn't actually notice this modification date when doing the challenge. I only noticed it while writing the script. Perhaps that means something. It looks like zip files store time in a legacy DOS format and all zeros in this legacy format equals November 30th, 1979. Nope. Anyways, let's take a look at app.js. It's a pretty simple Node.js server written using the express framework and using MySQL as the backend database. This is the backend server we've been interacting with. We can see that there's a flag value which has presumably changed the actual flag on the CTF servers and a target user is set to Michelle which we learned about earlier. Neat. We can also see that the session cookie is signed and verified with the cookie session and cookie parser libraries. This code is probably okay as the comment even says don't even bother. Next we see the body parser middleware is used to parse query strings. Express doesn't have support for parsing form bodies into objects built in so you have to use the body parser middleware to do it. Interestingly, extended parsing is enabled. Extended parsing accidentally being enabled was the issue in another challenge, pasteurize. When enabled it uses the QS library to automatically parse query strings into objects. For example, A, opening bracket B, closing bracket C is parsed as an object with a key of A, B and a value of C in normal mode, but in an extended mode it's an object with a key of A and a value of another nested object containing the key B and the value C. Arrays can also be constructed with this extended syntax. Accidentally using extended mode can result in what code thinks is a string actually being an object or array since JavaScript doesn't have any static variable types. Unlike languages like C which enforce the type of a variable can't change, JavaScript allows variables to hold any type and the type that a variable holds can even change at runtime. Next we see how authentication is done. If request.session is set by the cookie session library, request.session will hold the signed cookie data. If there is signed data it's stored in Res.locals where it can be used by a templating engine later on. This allows the username and flag to be inserted into the page at the right spot. Next we see that files in the static directory are served as static files and HTTP requests are redirected to HTTPS requests. After that some middleware is defined but not yet used. These functions can be used by some upcoming routes to perform additional logic on some pages. CSRF validates that the CSRF token provided matches the ones stored in the cookies to prevent cross-site request forgery. No cache disables caching for a route. Finally, auth displays the you must be logged in to access that error message if the user isn't logged in. Finally in the source file are various routes. None of these routes are interesting except the one that handles post requests to log in. This is the code that handles login requests so it would be great if we could trick it into logging us into Michelle's account. It takes the username and password from the post body, creates a MySQL database connection and constructs an SQL query to check the user's table for users matching the provided username and password. If a user is found then it checks if the user has logged in as the target user, Michelle. If so, it sends the flag in the user session to the actual flag, otherwise it just sets it to HTML saying that only Michelle's account has the flag and then redirects to the profile page. Let's take a closer look at this login handling code. When I saw that the username and password being used directly from the body object there I remember that extended parsing was enabled. Perhaps we could exploit this by making the username or password an object or array instead of the string that's probably expected here. Let's log out and open up the Firefox dev tools. I'm switching to the network tab and logging in with admin-admin. I can right click the request and click edit and reset to modify the data that gets sent along with the request quite easily. This feature is pretty neat. I can edit the request URL headers in body. I can change the username string to an object and see what happens when I send the request. It looks like this results in an error message saying unknown error, error, error, bad field error, unknown column ABC and where clause. It looks like we were able to inject ABC into the SQL query unescaped. But can we abuse this to log in as an arbitrary user? Well, let's take a look at what exactly is going on with the database access in app.js. The server is using the MySQL library to escape the query so let's see what that library does. I read through the readme for the library and noticed a few things. The documentation says you can use question mark characters as placeholders for values you would like to have escaped. It also notes that it isn't using prepared statements which the library doesn't support. While prepared statements are implemented by the database and result in replacement of the question mark with the corresponding text securely, this library is just transforming the raw SQL and sending that modified SQL to the database. This has the benefit of supporting some additional features here that we can take advantage of. The library has several interesting ways that values are modified before being inserted into the SQL. It says that strings are safely escaped but we can also pass an arrays in objects. Arrays are converted into the syntax for SQL lists or group lists if they're nested. Objects with a to SQL string function will have that function called and their return value inserted into the output SQL. And objects without a to SQL string function will result in each key value pair being transformed into the key followed by an equal sign followed by the escaped value. This is probably intended to allow usage where the keys were controlled by the programmer and the values by the user but we can control both the keys and the values. At this point I installed the MySQL and QS library locally and started playing around with various inputs so I could see how my strings were parsed by QS and how MySQL escaped the various objects. I quickly noticed that MySQL didn't just insert the raw table name when passed an object but instead always wrapped it in back text. While SQL only requires back text when a table name has special characters, this library always adds back text. After some tests, abusing arrays didn't seem too promising. I was able to get invalid SQL but I wanted SQL that always resulted in the first row being Michelle since that's what the login code checks for. It also didn't seem possible to abuse two SQL strings being called since it's ignored if it's not a function and QS never returned functions in its output. However, using a normal object seemed promising. After some experimentation I found that an object with the key of password generated SQL that contained password equals password equals xyz for the password check. This triple comparison is actually valid SQL that will never match your row. Password equals password is evaluated first and always resolves to true since all passwords are equal to themselves. True is then compared to xyz and is never equal since xyz isn't a Boolean value. Next, after playing around with various values of xyz, I found that setting it to 1 caused the expression to always match. The string 1 gets automatically converted into the Boolean true when compared which means that it will always be a match. I went to the request page and edited the sent request again in dev tools. In the request body, I kept the username equals Michelle but changed the password to password password equals 1. I decoded the set cookie header in the response and there was the flag. Overall, this was a pretty fun challenge to do. Thanks for watching.