All Articles

N1CTF2021 Writeup

2021/11/20から開催されていたn1CTFに参加してきました。

残念ながら解ける問題がなく、チャレンジだけで終了しました。

今回は、他のチャレンジャーのWriteUpを参考に、解けなかった問題について振り返っていこうと思います。

もくじ

Signin (Web)

以下のようなPHPスクリプトで稼働しているWEBサイトにアクセスできます。

<?php 
//flag is /flag
$path=$_POST['path'];
$time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");
$name="/var/www/tmp/".time().rand().'.txt';
$black="f|ht|ba|z|ro|;|,|=|c|g|da|_";
$blist=explode("|",$black);
foreach($blist as $b){
    if(strpos($path,$b) !== false){
        die('111');
    }
}
if(file_put_contents($name, $time)){
	echo "<pre class='language-html'><code class='language-html'>logpath:$name</code></pre>";
}
$check=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($path));
if(is_file($check)){
	echo "<pre class='language-html'><code class='language-html'>".file_get_contents($check)."</code></pre>";
}

どうやら/flagというパスにフラグが存在するものの、POSTで送られてきたpathのデータはブラックリストでフィルタリングされてしまうようです。

この問題については、当初はpathの値に、ブラックリストには引っかからない文字列を使ったeval文などを用いて、最終的に$check/flagが格納されている状態を作る問題と想定していましたが、残念ながら誤りでした。

この問題のFlagを取るためには、$time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");の行に着目します。

isset($_GET[‘time’])

この記法は、変数の存在確認を行います。

つまり、GETクエリにtimeが含まれているかどうかを判断しているというわけです。

参考:PHP: isset - Manual

filegetcontents(‘php://input’)

GETクエリにtimeが含まれているとき、このスクリプトが呼び出されます。

file_get_contents('php://input')は、HTTPヘッダ以下の生データを取得することができます。

参考:ajax - PHP “php://input” vs $_POST - Stack Overflow

参考:PHP filegetcontents()とは - Qiita

date()のバイパス

上記のとおりfile_get_contents('php://input')で文字列として取得したリクエストデータをdate()関数に渡しています。

ここで、公式ドキュメントを読むと、date()のフォーマットのサンプルが出てきました。

<?php
// Assuming today is March 10th, 2001, 5:16:18 pm, and that we are in the
// Mountain Standard Time (MST) Time Zone

$today = date("F j, Y, g:i a");                 // March 10, 2001, 5:16 pm
$today = date("m.d.y");                         // 03.10.01
$today = date("j, n, Y");                       // 10, 3, 2001
$today = date("Ymd");                           // 20010310
$today = date('h-i-s, j-m-y, it is w Day');     // 05-16-18, 10-03-01, 1631 1618 6 Satpm01
$today = date('\i\t \i\s \t\h\e jS \d\a\y.');   // it is the 10th day.
$today = date("D M j G:i:s T Y");               // Sat Mar 10 17:16:18 MST 2001
$today = date('H:m:s \m \i\s\ \m\o\n\t\h');     // 17:03:18 m is month
$today = date("H:i:s");                         // 17:16:18
$today = date("Y-m-d H:i:s");                   // 2001-03-10 17:16:18 (the MySQL DATETIME format)
?>

これを見ると分かるようにdate('\i\t \i\s \t\h\e jS \d\a\y.');の形式でバックスラッシュを付けることで、date()関数による日付への変換がバイパスされる仕様のようです。

この仕様を利用して、以下のようにPOSTデータに\/\f/l\/a/gを送信すると、最終的に$time/flagが格納され、Flagを取得できます。

まとめ

ほんとはRevの問題の復習をしたかったのだけど、WriteUpが見つかりませんでした。

見つかったら更新しようと思います。