Help us understand the problem. What is going on with this article?

[b00t2root '19] Writeup (EasyPHP)

More than 1 year has passed since last update.

はじめに

b00t2root'19のWriteupです。

EasyPHP

キャプチャ1.PNG

問題内容

書かれているURLに飛ぶとそのページのソースコードが表示されます。

キャプチャ1.PNG

フラグ表示まで3段階になっています。順番に見ていきましょう。

第一段階

$str1 = $_GET['1']; 

if(isset($_GET['1'])){ 
    if($str1 == md5($str1)){ 
        echo $flag1; 
    } 
    else{ 
        die(); 
    } 
} 
else{ 
    die();    
} 

入力した文字列と、その文字列のMD5ハッシュ値が一致すればフラグの1つ目が表示されます。
しかし、そのような文字列を探すのは至難の業です。

そこでコードをよく見てみると、比較している箇所のイコールが3つではなく、2つになっています。
イコール2つの比較演算子は「緩やかな比較」と呼ばれ、PHP側が適宜キャストを行ってから比較を行います。
例えば、'3'=='3.0'はTrueになります。('3'==='3.0'は文字列のまま比較をするのでFalseです)
つまり、この性質を利用すれば文字列とMD5ハッシュ値が厳密に一致しなくても突破できそうです。

では、どのようなMD5ハッシュ値を探せばよいでしょうか。
PHPでの数値の表し方はいくつかあり、その1つに浮動小数点数の指数表記があります。
これは1.2e3 (=1.2*10^3)というような表し方です。
この指数表記では0e + (数値)はすべて0になります。
つまり、MD5ハッシュ値が0e + (数値)となるような0e + (数値)の形の文字列を探します。
こちらの記事から、0e215962017が該当するようです。

URLに?1=0e215962017を追加すると、b00t2root{wh4t3v3r_が表示されました。

第二段階

$str2 = $_GET['2']; 
$str3 = $_GET['3']; 

if(isset($_GET['2']) && isset($_GET['3'])){ 
    if($str2 !== $str3){ 
        if(hash('md5', $salt . $str2) == hash('md5', $salt . $str3)){ 
            echo $flag2; 
        } 
        else{ 
            die(); 
        } 
    } 
    else{ 
        die(); 
    } 
} 
else{ 
    die();    
} 

入力した2つの文字列の先頭に文字列$saltを結合した後ハッシュ値が計算され、それらが一致するとフラグの2つ目が表示されます。
しかし、入力する2つの文字列は異なる必要があります。

初めはハッシュ伸長攻撃か?と思いましたが、ハッシュ値が表示されないのでこれではなさそうです。

ここで、$str2,$str3が文字列以外になる可能性を考えてみましょう。
例えば配列だった場合、$salt . $strはどうなるでしょうか。
試してみると'test' . array('a','b')'testArray'となります。
つまり、$str2,$str3が異なる配列となれば、フラグが表示されます。

では、どうすれば配列になるでしょうか。
PHPではパラメータ送信時にparam[]にすると$_GET['param']で配列として受け取ります。
2[]='a',3[]='b'とすれば$str2 = array(1) { [0]=> string(1) "a"},$str3 = array(1) { [0]=> string(1) "b"}となり、異なる配列になります。

URLに&2[]=a&3[]=bを追加すると、b00t2root{wh4t3v3r_17_74k3sが表示されました。

第三段階

class Secrets { 
    var $temp; 
    var $flag; 
} 

if (isset($_GET['4'])) { 
    $str4 = $_GET['4']; 

    if(get_magic_quotes_gpc()){ 
        $str4=stripslashes($str4); 
    } 

    $res = unserialize($str4); 

    if ($res) { 
    $res->flag=$flag3; 
        if ($res->flag === $res->temp) 
            echo $res->flag; 
        else 
            die(); 
    } 
    else die(); 
} 

unserializeでSecretsクラスのオブジェクトを作った後に$flagプロパティにフラグが代入され、それが$tempプロパティと等しければフラグが表示されます。
厳密な比較なので第一段階のようなことはできません。ではどうすればいいでしょうか。

真っ先に思いつくのは片方のプロパティにもう片方のプロパティを参照させることです。
今回は$flag$tempを参照させてみましょう。
しかし、そのようなことはできるのでしょうか。

unserializeについて調べると、参照型があるようです。(参考)
例えば、a:2:{s:3:"1st";i:100;s:3:"2nd";R:2;}$2nd$1stを参照します。
いろいろ試したところ、a:2:{s:3:"1st";R:3:100;s:3:"2nd";i:100;}のように後ろのプロパティの参照はできないようです。
以上を踏まえると、$flag$tempを参照するようなシリアライズデータはO:7:"Secrets":2:{s:4:"temp";N;s:4:"flag";R:2;}となります。

URLに4=O:7:%22Secrets%22:2:{s:4:%22temp%22;N;s:4:%22flag%22;R:2;}を追加すると、b00t2root{wh4t3v3r_17_74k3s_cuz_1_l0v3_th3_4dren4l1n3_1n_my_v31ns_932b315}が表示され、フラグを獲得できました。

LorseKudos
競プロ / CTF
techtrain
プロのエンジニアを目指すU30(30歳以下)の方に現役エンジニアにメンタリングもらえるコミュニティです。
https://techbowl.co.jp/techtrain/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away