HKCERTCTF 2021 Writeup
Background
12 Nov 2021, 18:00 HKT — 14 Nov 2021, 18:00 HKT
Format: Jeopardy
Official URL: https://ctf.hkcert.org/
Organisers: HKCERT and HKPC
Co-organisers: AiTLE, BlackB6a, eLC, ISOCHK, PISA, VXRL
CTFtime: https://ctftime.org/event/1432
I played this HK local CTF last week with my friends and end up with the 5-th in the open category. I learnt a lot but also realised that I really need to improve more of my skills, especially in crypto. (I really want to solve the crypto challs next year)
This is a well-organized CTF with good theme and platform plus the HK songs, and all of the high quality challenges, big thanks to BlackB6a! I am going to share some of the challenges that I was able to / almost solve (also as the requirement for the top 5 teams to complete some selected writeup).
Challenges
Freedom (Cipher Mode Picker) - Crypto (100 points)
Challenge :
Freedom where’s our freedom? Freedom what would it be Can you tell me what’s the reason? Reason that meant to be
Every slightest mistake in cryptography would lead to a disastrous result. Let’s see what will happen when you allow end-users to pick the mode of operation… nc chalp.hkcert21.pwnable.hk 28102
Files :
freedom_ff0173b179d746386dca0e93e6c00d47.zip
Solution :
From the provided chall.py
, we can observe:
- The length of flag is
80
. - A fix
key
andIV
are used for each connection - There are 5 mode of operations implemented (‘ECB’, ‘CBC’, ‘CFB’, ‘OFB’ and ‘CTR’) and you can either provide data to the server for the encryption or let the server encrypting the flag, for at most 5 times per connection. But you can only use 1 mode per connection.
If you are familiar with these operation, you may spot that the weakness is the reuse of IV… Let’s have a look into the mode ‘CFB’ and ‘OFB’:
As we can see, the encryption procedure of CFB and OFB are actually the same. Therefore if we first provide all zero as the data to encrypt with one mode (e.g., CFB), it will return the encryption of each block itself. Then we request for the encrypted flag with another mode (e.g., OFB) and xor them together, we will get the flag!
For CFB:
|
|
For OFB:
|
|
|
|
Solve:
|
|
FLAG : hkcert21{w3_sh0u1d_n0t_g1v3_much_fr3ed0m_t0_us3r5_wh3n_1t_c0m3s_t0_cryp70gr4phy}
所有遺失的東西 (All Missing) - pwn (150 points)
Challenge :
You lose all the things, including the chance of getting out of the jail of python. nc chalp.hkcert21.pwnable.hk 28004
Files :
pyjail1_f7be93352498ebd158a0a9fc069b30e9.zip
Solution :
I was working on some other challenge while my teammate asked for help, so I checked this challenge. It is a Pythohn jail escape challenge. __builtins__
is clear and the square brackets[]
are not allowed to use.
I did some googling and found some payloads such as ().__class__.__base__.__subclasses__()
or ''.__class__.__mro__.__getitem__(1).__subclasses__()
to get some “benign” subclasses from modules imported in memory by default, even we dont have __builtins__
.Moreover, we can use __getitem__(i)
or pop(i)
to replace the use of []
.
After some checking, we found the class <class 'os._wrap_close'>
which has the method popen
. We first call __init__
to initiate with __globals__
to get the method as dictionary.
Solve:
|
|
FLAG : hkcert21{nev3r_uSe_pYth0n_45_sanDBox}
留下來的人 (The Remaining One) - pwn, misc (300 points)
Challenge :
Find out the only one who always stand by you, that’s the key of the escape. nc chalp.hkcert21.pwnable.hk 28005
Files :
pyjail2_5ce6175d2c2cc1469d1188f029c356cb.zip
Solution :
It was at the midnight after finished the first Python jailbreak chall above. I decided to have a look into the second part as well and got the first blood luckily.
The difference between this and the last chall is that the payload was restricted within length 59
. Therefore we cannot “reuse” the payload in the first part. However, is it really the case?
Actually the another diff alerted me, unlike the first part that you can only have 1 input, you can have infinite round of input. This made me thinking about “splitting and storing” the payload in each round and call it at the end. First I tried something like a = "xxx"
but it returned a SyntaxError, as it is inside the eval
you cannot assign variable like that.
Then I realised that although __builtins__
was clear, this dict still exist and can be used. To assign the value for dictionary, we can use update
. Therefore, what we need to do is just split the previous payload, use update
to store the payload to the dict and …Done!
Solve:
|
|
FLAG : hkcert21{cr0sS_namesP4se__builtin__breaK_the_JAIL}
最難行的路 (The Hardest Path) - reverse, misc (300 points)
Challenge :
寧願不揀最易的路 行極還未到 寧願你最後未傾慕 但信念安好 在意的 不再是 愛的煩惱 是哪樣做人 更清高 餘生那段旅途 與哪類人共舞
When you think reverse engineering is hard, try working on reverse engineering challenges those need your algorithmic thinking skills!
nc chalp.hkcert21.pwnable.hk 28005
Files :
the-hardest-path_e00c6aa7b64b8dc2a06e577937b5b07c.zip
Solution :
This is my favourite challenge in this CTF since I made a huge mistake when solving it…
Two python files were provided, chall.py
and lost.py
. The chall.py
serves as a server, receving a Proof-of-Work and some “inputs”. If the input is correct, the flag will be returned. lost.py
contains the real chall and logic, with some slightly obfuscated codes/variables.
To put it simply, after the reverse,
- Line 3 defined 4 instructions of movements, i.e., North (‘N’), East (‘E’), West (‘W’) and South (‘S’).
|
|
- Line 5 defined a function to represent a dead-end.
|
|
- Line 7 - 15 defined the checking on whether the instructions is one of the “NEWS”, as well as whether we have arrived the end after all provided instructions
|
|
- The remaining lines are the paths / relationship of each node, telling you the neighbour of each direction you go from this node (_f42fb5e137443877[0] => whether it is the end,_f42fb5e137443877[1], _f42fb5e137443877[2], _f42fb5e137443877[3], _f42fb5e137443877[4] -> ‘N’, ‘E’, ‘W’, ‘S’)
|
|
Therefore it is actually a chall that finding a path from the starting point _328518a497015157
to the end _8b0eb6f195ae182a
, with the movement as the instructions (a list of “NNNNEEEEWWWWSSSS”).
First I copied the lines after line 15 to another file path.txt
for parsing. As we dont want to move into a dead-end, I simply ignored all of them (i.e. the node = _29aa7a86665899ed
), otherwise, I stored it with the neighbour into a dictionary. Then I walk through the dict again to find out all the dead-end node.
|
|
Next we need to walk and get a path from the starting point to the end. I implemented a DFS (Depth-first search) to get the path
|
|
Then I got an error below
|
|
and I did the biggest mistake in my life, that is adding sys.setrecursionlimit(2000)
to my code to extend the limit of recursion depth, so that my program can work… and it did output the solution
|
|
and then I got a no good!
instead of the flag from the server. What could be wrong? (Someone: everything can go wrong will go wrong) After some dry run I am pretty sure that it is a valid path and it was around 3/4 at midnight so I decided let my brain to have some rest. On the next day I tried to change the code in chall.py
to not catch the error and I got the same error again:
|
|
Then I realised the problem is my solution is not optimized, i.e. we need to find the shortest path. Therefore, BFS (Breadth-First Search) should be used. (There is no weight/same weight for each edge in this chall)
|
|
and…we got the shorter path!
|
|
Solve:
process.py
|
|
solve.py
|
|
FLAG : hkcert21{4lw4ys_l0ok_4t_s74ck_0verf1ow_wh3n_y0u_w4nt_t0_4v01d_s7ack_0v3rfl0ws}
P.S. I sent this meme to the author after solved this challenge :)
純孩兒 (babyXSS) - Web (100 points)
Challenge :
Have you tried the infant xss challenge in the training platform? If you did, then you can try out this BABY XSS CHALLENGE…
http://babyxss-m7neh9.hkcert21.pwnable.hk
XSS Bot: http://xssbot-cxild5.hkcert21.pwnable.hk
Solution :
Checking the source code of the webpage, we can obtain some obfuscated javascript.
|
|
Tried to deobfuscate the code using https://deobfuscate.io/, we can get:
|
|
Next let’s rewrite the CONVERT
function:
|
|
From the CONVERT
function, we know that it will take the input from the URL after #
and convert to uppercase, i.e. for http://babyxss-m7neh9.hkcert21.pwnable.hk/#%3Cscript%3Ealert('XSS')%3C/script%3E
, it will be converted to <SCRIPT>ALERT('XSS')</SCRIPT>
which is not a valid javascript as shown below:
Therefore we need to find some function that is also defined in uppercase.
We can use html encoding to bypass the toUpperCase
.
Solve:
|
|
FLAG : hkcert21{zOMG_MY_KEYBOARD_IS_BROKEN_CANNOT_TURN_OFF_CAPSLOCK111111111}
荊棘海 (The Wilderness) - Web (100 points)
Challenge :
就在回望一刻總有哀 世界已不再 誰偏偏一再 等待 到終於不記得等待
Mystiz likes PHP most. He has been programming in PHP at the time PHP 5 was released. Time flies and here comes PHP 8. He decided to craft a Docker image as a sandbox… What can go wrong?
http://chalp.hkcert21.pwnable.hk:28364/
Files :
sea-of-thorns_e045a87b1909724e7292510354cc1f3b.zip
Solution :
From the index.php
we know that the flag is in the comment block of the source code, but there is no other useful stuff inside…so let’s have a look at the Docker file.
From the Dockerfile
, it is weird that the PHP is installed by downloading a specific version of PHP from Github (wget https://github.com/php/php-src/archive/c730aa26bd52829a49f2ad284b181b7e82a68d7d.zip
). By Googling the hash we can find that it is the affected version of the hacked PHP, which the PHP Git server was hacked and a RCE backdoor was injected into the source code. Therefore we can exploit the RCE to get the content of the index.php
, i.e. the flag by adding the HTTP header User-Agentt
with the command prepending the magic word zerodium
.
Solve:curl http://chalp.hkcert21.pwnable.hk:28364/ -H "User-Agentt: zerodiumsystem('cat index.php');"
FLAG : hkcert21{vu1n3r1b1li7ie5_m1gh7_c0m3_fr0m_7h3_5upp1y_ch41n}
樂園 (JQ Playground) - Web (200 points)
Challenge :
I wrote a simple testbed for the JSON processor jq!
The flag is written in the file /flag.
http://chalp.hkcert21.pwnable.hk:28370/
Solution :
At the bottom of the webpage, we can see the button View Source
and get the key source code:
|
|
We can see that we are providing parameters and input to call the jq
and our goal is to get the file /flag
. With some Googling I found that we can use --rawfile
to get the file content. With some trials to crafting the payload, we can get the flag by reflecting the content out with the command echo {"data":{"update":null}}' | jq --rawfile a /flag '.data.update = $a'
.
Solve:curl -X POST 'http://chalp.hkcert21.pwnable.hk:28370/' -F 'filter=.data.update=$a' -F 'json={"data":{"update":null}}' -F 'options[]=--rawfile' -F 'options[]=a' -F 'options[]=/flag'
FLAG : hkcert21{y0u\are\n0w\jq\expert!}
回到12歲 (scratch-tic-tac-toe) - Misc (200 points)
Challenge :
If you can beat me in the game I’ll give you the flag!
https://scratch.mit.edu/projects/596813541/
Solution :
This is a Tic-Tac-Toe game written in Scratch. It did bring me back to my F.4 life which I learnt about Scratch in my ICT lesson.
We can click the See inside
button to view the source code of this project. By selecting the Title
, we can see a yellow notes and if you remove it, you can see the key logic of getting the flag.
We can see that if we input the correct flag, the program will perform some process and check if it is equal to 03vx{_ihq0xhh7svtx}t{sv180x{r
. Therefore, by reversing the logic, we can get the flag.
Solve:
|
|
FLAG : hkcert21{he11o_caesar_cipher}