shinobe179の日記

パケットの気持ちになって考えるのはもうやめだ

【CTF】wargame writeup

概要

wargameを始めたので、writeupを書いていきます。随時更新。

wargame.kr

already got

開発者ツールを開いてページを更新すると、FLAGというレスポンスヘッダーがついています。

free button

マウスの動きに合わせてボタンが逃げ回る。開発者ツールを確認すると、ボタンにonclick="window.location='?key=6cdf;"という属性がついていることが分かります。http://wargame.kr:8080/flee_button/?=6cdfにアクセスするとflagがあります。開発者ツールのコンソールからdocument.getElementsByTagName("input")[0].click();を実行して、擬似的にボタンを押すのでもOKです。ボタンにはid属性がなかったので、定番?のdocument.getElementById().click();できなかったのが大変でした。

QR CODE PUZZLE

バラバラのQRコードを揃えます。開発者ツールを開くとsytle属性にurl("./img/qr.png")とあるので、http://wargame.kr:8080/qr_code_puzzle/img/qr.pngにアクセスすると完全なQRコードが現れます。後は読みこむだけ。http://qrcode.red/

login filtering

ログインフォームの突破。以下のアカウントが存在しており、いずれもログインを拒否されていることが問題文やソースに記載されています。

<!--

you have blocked accounts.

guest / guest
blueh4g / blueh4g1234ps

-->

IDとパスワードの代入からSQL組み立て、アカウント判定までのロジックは以下の通り。

$id = mysql_real_escape_string(trim($_POST['id']));
  $ps = mysql_real_escape_string(trim($_POST['ps']));

  $row=mysql_fetch_array(mysql_query("select * from user where id='$id' and ps=md5('$ps')"));

  if(isset($row['id'])){
   if($id=='guest' || $id=='blueh4g'){
    echo "your account is blocked";
   }else{
    echo "login ok"."<br />";
    echo "Password : ".$key;
   }
  }else{
   echo "wrong..";
  }
 }

MySQLは文字列の検索時に大文字と小文字を区別しないらしい。それを利用して、idをGuest、パスワードをguestとすると、id=guestのレコードが返され、if($id=='guest' || $id=='blueh4g'){の判定は(こっちは大文字小文字区別するので)すり抜けます。id=guest、ps=Guestでもいいのでは?と思ったのですが、psのほうはクエリの時点でMD5でハッシュ化しており、大文字小文字が違うと別の結果が出るので通りません。

WTF CODE

ソースコードをダウンロードすると空白文字がたくさん。Writeupを見て「Whitespace」なるプログラミング言語の存在を知りました。

https://ja.wikipedia.org/wiki/Whitespace

ブラウザ上でWhitespaceを実行できるサイトを使いました。はじめエラーが出たのですが、最終行に改行を入れたらflagが出力されました。 https://naokikp.github.io/wsi/whitespace.html

fly me to the moon

激ムズな避けゲー。時間経過でscoreが上がっていき、側壁に激突したらゲームオーバー。31337点以上取るとflagが表示されます。Writeupや開発者ツールを見て、以下のような挙動であることを確認しました。

  • 約10秒毎にtoken.phpをGETしにいく
    • 最大4ケタ?の数字が返される
  • ゲームオーバーと同時にhigh-scores.phpをPOSTする
    • リクエストパラメータとして、scoreのほかに直前にGETしたtoken.phpの値を送信する
      • サーバーは、リクエストのtokenと、直前にレスポンスしたtoken.phpの値とを比較し、異なる場合はチートと判断する

high-scores.phpをローカルプロキシで止めてscoreをノルマ以上に書き換え、約10秒間隔で更新されるtokenの値をタイミングよく更新して送信する必要があます。華麗にJSを書いて捌くなんてことはまだできないので、以下のようにBurp Proxyとtsharkを使って泥臭くflagをとりました。

  • Burp proxyでhigh-scores.phpへのリクエストのみを止めるように設定する。
  • ターミナルでtshark -Tjson src host 175.207.12.40 and port 8080 | grep http.file_dataを実行する。
  • ゲームスタート。すぐに自爆。
  • Burpがhigh-scores.phpへのリクエストを止めているので、scoreを十分大きい値に書き換える。
  • ターミナルに表示されるhttp.file_dataの値が更新されたら、すぐさまBurpのtokenを書き換えてForwardする。

後から「ゲームオーバーした後のtoken.phpをBurpで止めておけば、high-scores.phpをリクエストする直前のtoken.phpの値をゆっくり書き写して送信できるのでは?」と思って試したんですがダメでした。サーバーが直前にレスポンスした値と同じでありさえすればよいのだと思っていたのですが……。

md5 password

一見、入力したパスワードのハッシュがテーブル内にあれば成功です。

$row=@mysql_fetch_array(mysql_query("select * from admin_password where password='".md5($ps,true)."'"));

説明文によると、PHPMD5()の第2引数は、trueならバイナリで、falseなら16進数で出力するということのようです。だから、Writeup曰く、もしもハッシュが'or'を含んでいると、SQLインジェクションが成立します。これには感動しました。

select * from admin_password where password='' or ''

そして、以下の値のハッシュはそのような形式になります。

129581926211651571912466741651878684928

# 検証
$ echo -n 129581926211651571912466741651878684928 | md5sum | xxd -r -p
�T0D�#��'or'8

送信すると、「8」の部分がtrue判定され、SQLインジェクションが成立します。