This is challenge number 12 in the 2021 SANS Holiday Hack Challenge (https://2021.kringlecon.com/). Objective:
Investigate Frost Tower’s website for security issues. This source code will be useful in your analysis. In Jack Frost’s TODO list, what job position does >Jack plan to offer Santa? Ribb Bonbowford, in Santa’s dining room, may have some pointers for you.
This challenge starts by Ribb Bonbowford telling us to lookup the documentation for (express-session) https://www.npmjs.com/package/express-session and mysqljs (https://github.com/mysqljs/mysql). Other than that clue, it is left up to us to download the source code and make our way from there. After downloading the source, we see that we are working with a nodejs service
Opening up the main server.js lets us take a look at all of the endpoints exposed by the server. The /testsite looks like an interesting path to try
This takes us to a page which has some interesting features. After browsing through I found a “Contact Us” page, which led to a “Dashboard Login” page
A quick signin attempt with random data shows an Invalid username and password.
So, we’ve gathered that we have a test website with some basic functionality and an ability to login. If there is the ability to login, there may be administrative pages. Back to the source code! The admin dashboard certainly sounds interesting
We can see by reading the code that we will likely be redirected to /login if we have not established a “session.uniqueID”. So, what is a session.uniqueID? Let’s read the documentation (https://github.com/expressjs/session#genid). A session id is set by the express-session library. It is simply an ID that gets generated by the library that is supposed to be unique within the app, and returned back as a cookie called connect.sid by default. The session.uniqueID is a piece of information that the app sets within the session. Looking back at the server.js code we find the configuration for sessions
We find a secret value which we could potentially use to generate/highlight valid server sessions. Also interesting is “resave” and “saveUninitialized”. What are these? According to the documentation:
saveUninitialized -
Forces a session that is “uninitialized” to be saved to the store. A session is uninitialized when it is new but not modified. Choosing false is useful for implementing login sessions, reducing server storage usage, or complying with laws that require permission before setting a cookie. Choosing false will also help with race conditions where a client makes multiple parallel requests without a session.
resave -
Forces the session to be saved back to the session store, even if the session was never modified during the request. Depending on your store this may be >necessary, but it can also create race conditions where a client makes two parallel requests to your server and changes made to the session in one request >may get overwritten when the other request ends, even if it made no changes (this behavior also depends on what store you’re using).
These values being set to true are certainly suspect. How could we trigger a race condition to get a valid session set? Searching through the code we come across a couple of places where the unique session id gets set. Specifically, /postcontact looks interesting
So what if we give that endpoint a call?
It tells us that the data has been saved to the database. Can we access the dashboard page now?
Nope, it just redirects us back to the login page :( But if we think about what saveUninitialized says - “Forces a session that is “uninitialized” to be saved to the store. A session is uninitialized when it is new but not modified.”. What if we POST our contact information again?
It tells us that our email already exists. What if we try accessing the dashboard again?
Alright, we have bypassed auth! Specfically, we hit the code block which populates the uniqueID
We’ve opened up the ability to look at user details and edit them
So let’s go back to the source code and find our place. One common denominator that we see in all of the endpoints is that they are making SQL queries back to a database. There is a good chance this app could be vulnerable to SQL injection attacks. Earlier Ribb Bonbowford pointed us to the msqljs library which adds further suspicion we may be looking at SQL injection. We notice that the app owners are trying to be good security people and sanitize the user controlled input that goes into the queries. And it looks like they are using the mysqljs library to handle escaping. Time to read the documentation! Specifically, there is a section on escaping - https://github.com/mysqljs/mysql#escaping-query-values and we find words of caution
To generate objects with a toSqlString method, the mysql.raw() method can be used. This creates an object that will be left un-touched when using in a ? placeholder, useful for using functions as dynamic values:
Caution The string provided to mysql.raw() will skip all escaping functions when used, so be careful when passing in unvalidated input.
Are they using mysql.raw() anywhere? It turns out they are!
At a glance, it appears that they escape the raw input
But a look at the documentation tells us:
Objects that have a toSqlString method will have .toSqlString() called and the returned value is used as the raw SQL.
So this looks like our place to start crafting a SQL injection payload. Rather than trying to perform blind injection against the server, I setup a local instance of the server so I had better visibility on what the final payload looked like. I first created a database using the encontact_db.sql file
Modified the modconnection.js file to point to localhost
And started the server.js app
I created a console.log statement in the app.get(‘/detail/:id’) endpoint so that I could see what the final query looks like. I also changed “if (session.uniqueID)” to “if (1)” so I would not need to worry about trying to bypass session authentication logic.
We can see that the app is stripping out “,” and replacing it with “OR id=”. Fortunately, I found a post about crafting SQL strings without the need for commas - https://book.hacktricks.xyz/pentesting-web/sql-injection/mysql-injection#mysqlinjection-without-commas Using knowledge of crafting queries without commas, and a tip from the PortSwigger SQL injection cheat sheet (https://portswigger.net/web-security/sql-injection/cheat-sheet) about how to find tables & column values in mysql databases - I was able to craft the payload
Dump tables from information_schema.tables
Dump columns in the todo table
Dump notes
And here we see that Jack was planning to offer Santa a job as a “clerk” which is the answer to the challenge question!