0xL4ugh CTF 2024 - Micro Writeup

# CTFサイト

https://ctf24.0xl4ugh.com/

# 問題

原文

>Remember Bruh 1,2 ? This is bruh 3 : D
login with admin:admin and you will get the flag :*

日本語

> Bruh 1,2 を覚えていますか?これはブル 3 :D
admin:admin でログインすると、フラグ :* が表示されます。

# 試行錯誤

まずは、サイトにアクセスする。

![スクリーンショット 2024-02-12 192122.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/e4175e65-d77a-525e-cd48-1ee7ec7b96b7.png)

問題文を見るに`Username`、`Password`ともにadminでログインするとFlagが取れるらしいので、試してみる。

![スクリーンショット 2024-02-13 040221.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/33beac2b-f941-929c-1c91-9f57ba4fee52.png)

ダメみたいですね。adminでのログインはローカルホストからしか許可されていないと。

一旦コードを見てみる。

```index.php
<?php

error_reporting(0);
function Check_Admin($input)
{
    $input=iconv('UTF-8', 'US-ASCII//TRANSLIT', $input);   // Just to Normalize the string to UTF-8
    if(preg_match("/admin/i",$input))
    {
        return true;
    }
    else
    {
        return false;
    }
}

function send_to_api($data)
{
    $api_url = 'http://127.0.0.1:5000/login';
    $options = [
        'http' => [
            'method' => 'POST',
            'header' => 'Content-Type: application/x-www-form-urlencoded',
            'content' => $data,
        ],
    ];
    $context = stream_context_create($options);
    $result = file_get_contents($api_url, false, $context);
    
    if ($result !== false) 
    {
        echo "Response from Flask app: $result";
    } 
    else 
    {
        echo "Failed to communicate with Flask app.";
    }
}

if(isset($_POST['login-submit']))
{
	if(!empty($_POST['username'])&&!empty($_POST['password']))
	{
        $username=$_POST['username'];
		$password=md5($_POST['password']);
        if(Check_Admin($username) && $_SERVER['REMOTE_ADDR']!=="127.0.0.1")
        {
            die("Admin Login allowed from localhost only : )");
        }
        else
        {
            send_to_api(file_get_contents("php://input"));
        }   

	}
	else
	{
		echo "<script>alert('Please Fill All Fields')</script>";
	}
}
?>
```

`Check_Admin`関数があるので確認してみると、`Username`が`admin`だと正規表現にマッチしてはじかれることが確認できた。
それ以外の場合はFlaskにPOSTリクエストが送信される。
`Username`を`admin`以外にしてみる。

![flask_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/75e52369-753c-673b-b98d-278669a96e70.png)

これもダメみたいですね。

Flaskの方のコードを見てみる。

```app.py
from flask import Flask, request
import mysql.connector, hashlib

app = Flask(__name__)

# MySQL connection configuration
mysql_host = "127.0.0.1"
mysql_user = "ctf"
mysql_password = "ctf123"
mysql_db = "CTF"

def authenticate_user(username, password):
    try:
        conn = mysql.connector.connect(
            host=mysql_host,
            user=mysql_user,
            password=mysql_password,
            database=mysql_db
        )

        cursor = conn.cursor()

        query = "SELECT * FROM users WHERE username = %s AND password = %s"
        cursor.execute(query, (username, password))

        result = cursor.fetchone()

        cursor.close()
        conn.close()

        return result  
    except mysql.connector.Error as error:
        print("Error while connecting to MySQL", error)
        return None

@app.route('/login', methods=['POST'])
def handle_request():
    try:
        username = request.form.get('username')
        password = hashlib.md5(request.form.get('password').encode()).hexdigest()
        # Authenticate user
        user_data = authenticate_user(username, password)

        if user_data:
            return "0xL4ugh{Test_Flag}"  
        else:
            return "Invalid credentials"  
    except:
        return "internal error happened"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
```

`authenticate_user`関数でadmin:adminの認証が成功した場合にFlagが取れるみたいです。
php側では`Username`が`admin`以外でないとダメで、Flask側では`admin:admin`でないとダメ。
そこで今回使用する攻撃は`HTTP パラメータ汚染`!

## HTTP パラメータ汚染とは

>HTTPパラメータ汚染（HPP）は、攻撃者がHTTPパラメータを操作してWebアプリケーションの動作を意図しない方法で変更する技術です。この操作は、HTTPパラメータを追加、変更、または複製することによって行われます。これらの操作の影響はユーザーに直接は見えませんが、サーバーサイドでアプリケーションの機能を大幅に変更し、クライアントサイドにも影響を与える可能性があります。
>>[HackTrics-Parameter Pollution-より](https://book.hacktricks.xyz/v/jp/pentesting-web/parameter-pollution)

今回の場合は、まさにこれ。

>パラメータ解析: Flask vs. PHP
Web技術が重複するHTTPパラメータを処理する方法は異なり、HPP攻撃への脆弱性に影響します。
Flask: クエリ文字列a=1&a=2で最初に遭遇したパラメータ値を採用し、初期のインスタンスを後続の重複より優先します。
PHP（Apache HTTP Server上）: 逆に、与えられた例では a=2 を選択し、最後のパラメータ値を優先します。この動作は、攻撃者の操作されたパラメータを元のパラメータよりも優先することで、HPPの悪用を誤って容易にする可能性があります。
>>[HackTrics-Parameter Pollution-より](https://book.hacktricks.xyz/v/jp/pentesting-web/parameter-pollution)

つまり、`username=admin&username=test&password=admin&login-submit=`このようなリクエストを送った場合、php側では`username=test&password=admin&login-submit=`を処理してFlask側では`username=admin&password=admin&login-submit=`の値で処理する。

# フラグ取得

Burpで先ほどのパラメータを送信する。

![param inject.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/66153d5a-e218-48d6-2214-6515d7b1aa44.png)

無事Flag取得できました。
めでたしめでたし。

![flag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/e003d49c-d6e6-e715-5c2e-5755f862d46b.png)