The TL;DR of this challenge was SQL injection and overriding javascript (to skip the encrypt
/decrypt
functions).
Challenge
Challenge Name: Blog from the future
Challenge Description:
My friend Bob likes sockets so much, he made his own blog to talk about them. Can you check it out and make sure that it’s secure like he assured me it is?
Link
Solution
JS Overriding
Rule #1 of web CTFs: ALWAYS check robots.txt first! This time I found:
User-Agent: *
Disallow: /admin
And in /admin there is a comment saying you need to use TOTP (Time Based OTP. Who does not use a TOTP for 2-factor authentication in 2020?)
Okay, so back to the home page. It was using websockets to fetch blog posts. On Inspect-Elementing/View-Sourcing you find main.js which is obfuscated. I just used the default Prettifier in Chrome (the {}
button in Sources tab) to deobfuscate it.
According to the Network tab, the packets were encrypted:
I was going to rewrite the encrypt
and decrypt
functions in Python but that would’ve wasted a lot of time. So I just decided to override JS instead.
You have to enable overriding support first in Google Chrome (no idea about Firefox, I don’t use it), and here is the official guide.
Then, you need to right click on the tab (main.js
in this case) and you’ll see Save for overrides
. Press that, and you’re ready!
While overriding, console.log()
is your friend. Use it everywhere and see what’s happening. I saw a function that decrypted the received data and I added my console.log()
there:
|
|
Cool! Now it printed everything! Now, I had to modify the sent requests. Just below the above line, there was:
|
|
And I found just what I wanted: a method to send data. In this case, it was getting the post ID from the URL, and sending that to get the post. Time for SQL Injection!
SQL Injection
To ensure I could do a SQL Injection attack, I made it:
|
|
And yes, it worked! I got all the posts on the site. Output of console.log
:
|
|
Explanation: I assumed the SQL statement was something like SELECT * FROM posts WHERE id=<ID HERE>
.
So, my input made it: SELECT * FROM posts WHERE id=1 OR 5=5;--
. Select everything from table posts where ID is 1 or 5=5 (which evaluates to True), aka fetch all posts. ;--
skips whatever is at the end, and terminates the query.
After that, I used UNION
to join another SELECT
query to get user information. In UNION
, you have to make sure the number of columns match and hence the 1,1
thing:
|
|
I didn’t find users
table or *,1,1,0
on first attempt. It took like 20-30 attempts to get it right.
First I did UNION SELECT 1,1,1,1,1,1
but that didn’t work cause the last variable hidden
was 1
, and it didn’t return that. UNION SELECT 1,1,1,1,1,0
returned what was expected.
Then I tried to get table names from information_schema.tables
but that wasn’t working. I tried to use some “common” table names and users
worked. Then I did UNION SELECT *,0 from users;--
, and kept adding ,1
till it worked. (Remember, UNION
means same number of columns)
Here was the output:
|
|
I took the TOTP secret key for bob
and plugged it into a TOTP Generator.
On the admin page, I Inspect-Elemented, and uncommented the TOTP input field and deleted password field. After entering the username (bob
) and the generated TOTP, I was able to log in and get the flag.
Flag is: rtcp{WebSock3t5_4r3_SQLi_vu1n3r4b1e_t00_bacfe0}
. GGWP.
Conclusion
It greatly helps SQL injection if you can create a local SQL table on your end and try creating SQL statements that work.
And, it’s sometimes far more efficient to override javascript than to manually simulate requests (using curl
/python
) like many beginners do.
Ask me in the comments for any issues! And thanks jammy for the challenge!