[b00t2root '19] Writeup (EasyPHP)

# はじめに
[b00t2root'19](https://ctftime.org/event/780)のWriteupです。

# EasyPHP

<img width="305" alt="キャプチャ1.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/248555/35869ac7-a021-f8ca-ba5f-82d73846e336.png">

## 問題内容

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

<img width="305" alt="キャプチャ1.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/248555/7026fd8d-89fd-9a4c-d2d6-592d1fab9d27.png">

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

## 第一段階

```PHP
$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 + (数値)`の形の文字列を探します。
こちらの[記事](https://graneed.hatenablog.com/entry/2019/03/11/122020)から、`0e215962017`が該当するようです。

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

## 第二段階

```PHP
$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`が表示されました。

## 第三段階

```PHP
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`について調べると、参照型があるようです。([参考](http://www.1x1.jp/blog/2010/12/php_serialize_data_type.html))
例えば、`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}`が表示され、フラグを獲得できました。