ALLES!CTF 2021 Writeup
Background
04 Sept 2021, 00:00 HKT — 05 Sept 2021, 12:00 HKT
Format: Jeopardy
Official URL: https://ctf.alles.team/
Event organizers: ALLES!
CTFtime: https://ctftime.org/event/1313
I joint Black Bauhinia in this CTF and result in the 3rd place! Hard carried by the team and learnt a lot from them. I am going to share some of the challenges below that I was able to / almost solve.
Challenges
Sanity Check - Web (104 points)
Challenge :
You aren’t a 🤖, right?
Solution :
A standard web challenge, 🤖 implies to check robots.txt
.
FLAG : ALLES!{1_nice_san1ty_ch3k}
J(ust)-S(erving)-P(ages) - Web (144 points)
Challenge :
What could possibly go wrong with a website that is just serving pages? 🤔
Files :
Solution :
A JSP web challenge which will display the flag at home page if you are admin, with the relevant code in home.jsp
as shown below:
|
|
After some analysis on the logic (main.js
), the app would store the password in MD5(password)
during registration, and send the password in the format SHA1(MD5(password))
during login:
|
|
Also, the login handling logic is under UserLoginServlet.java
which will call userDao.checkLogin
instead. I deployed the app and tested locally and observed a weird thing after set the debug mode that I could not login even providing a “correct” password 🤔
Turns out from the UserDAO.java
, we can notice the following vulnerable code:
|
|
The server would check if the provided password (in SHA1 format already) is equal to SHA1 of stored password or not, which seems legitimate. However, if it’s in the debug mode, The digest
method was called before the real checking and from the documentation of the Class MessageDiges, it stated that
The digest method can be called once for a given number of updates. After digest has been called, the MessageDigest object is reset to its initialized state.
Therefore the passwordBytes
is comparing with SHA1 of empty string instead of the real password, thus we can provide the hash da39a3ee5e6b4b0d3255bfef95601890afd80709
and login as admin to get the flag.
Solve:
Set Debug Mode:
curl 'https://[challenge]/config' -H 'Cookie: JSESSIONID=[...]' -X POST --data '{"debugMode":true}'
Login as admin:
curl 'https://[challenge]/login' -H 'Cookie: JSESSIONID=[...]' -X POST --data 'username=admin&password=da39a3ee5e6b4b0d3255bfef95601890afd80709'
FLAG : ALLES!{ohh-b0y-java-y-u-do-th1s-t0-m3???!?}
Thanks to Mystiz, ozetta and TWY for brainstorming together
EntrAPI - Misc (415 points)
Challenge :
A very simple stegano tool that estimates the entropy of sections of a file by counting unique bytes in a range. Here’s a snippet of the Dockerfile to get you started:
COPY main.js index.html flag /
RUN deno cache main.js
EXPOSE 1024
CMD deno run -A main.js
Happy guessing! :^)
Solution :
This is the most interesting challenge in this CTF in my opinion and we spent around 12 hours to solve this (and got the first blood).
From the description we knew the three key files filename and the app is run with deno
. The webpage is simple, visualizing the entropy of the selected sections of the file:
By reading the source code of index.html
, we can understand the /query
function is similar to len(set(f.open("path").read()[start:end]))
in Python.
|
|
This challenge reminds me on some algorithm relating to finding substrings or something else. Also, as we know that the flag is in the format of ALLES!{...}
, we do know the actually entropy from [0..7] of /flag
.
My first thought is to identify the duplicated character by going throught different sections of the file and observing the change of the entropy. Below I will try to illustrate the idea step by step:
- First,
CL
andCR
are 0 andEntropy
is 1 obviously. - Next,
CR
is increased by 1 andEntropy
is also increased by 1, meaning a new character is found. - Next,
CR
is increased by 1 again butEntropy
remain unchanged. This implies a duplicated character is found.
3.5 You can see theEntropy
should be increased by 1 if there’s no duplicated character. - We fix
CR
and increaseCL
by 1. TheEntropy
should decrease by 1. - Next,
CL
is increased by 1 butEntropy
remain unchanged. This implies we found the duplicate character position.
The whole process is illustrated by the flowchart below, with my PoC to find the duplicated character position (🚨poor code warning🚨):
|
|
The result is [92, 2, 0, 0, 93, 0, 0, 10, 33, 12, 20, 19, 23, 29, 22, 82, 30, 40, 77, 27, 24, 36, 35, 26, 50, 63, 34, 51, 67, 31, 65, 39, 0, 44, 37, 73, 45, 46, 0, 72, 61, 0, 47, 57, 59, 54, 48, 0, 52, 98, 68, 53, 56, 74, 55, 58, 62, 0, 64, 60, 80, 78, 66, 0, 76, 75, 71, 70, 0, 0, 0, 79, 81, 0, 85, 83, 0, 0, 0, 0, 86, 84, 0, 101, 0, 0, 0, 0, 102, 0, 91, 0, 0, 0, 100, 104, 0, 0, 0, 105, 0, 0, 0, 106, 0, 0, 0, 0, 0]
, or in this way for the first few characters: [0 1 1 2 3 4]
(recall the flag format is ALLE!{...
.
However we can see the first few characters that we knew are not showing up in the later part of the flag frequently. We failed to recover the flag directly…
The next thing we tried is to leak the source code of main.js
, with the result [0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 2, 2, 9, 0, 10, 11, 5, 12, 3, ...
. As we know it is a javascript file using deno, usually it should be in the format of import library
or import { method } from library
. From the result pattern we can conclude the first line is import { ?pp?i??t?o...
. After checking some examples of deno, we can deduce the first line of code should be import { Application, Router } from "https://deno.land/x/oak@v?.?.?/mod.ts";
with the unknown numeric version. With the updated PoC below, we were able to recover part of (only the char in the group mp
below) the main.js
and with some guessing manually, whole main.js
“theoretically”.
|
|
During the competition, Mystiz implemented another PoC with request.session and binary search in order to improve the efficiency of recovering the content. For the real solution, please wait for the writeup from Mystiz and I will update the link here later.
In particular, there is a GET request for getting the flag ( btw we were very excited after retrieving the first line of this code segment but then received the message go away
) and basically we tried to crack all combination of the possible hashes to obtain the pass e7552d9b7c9a01fad1c37e452af4ac95 md5 gibflag
|
|
Thanks to Mystiz, harrier and cdemirer for brainstorming together during midnight :P
FLAG : ALLES!{is_it_encryption_if_there’s_no_key?also_a_bit_too_lossy_for_high_entropy_secrets:MRPPASQHX3b0QrMWH0WF}