CTFサイト
問題
原文
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 でログインすると、フラグ :* が表示されます。
試行錯誤
まずは、サイトにアクセスする。
問題文を見るにUsername
、Password
ともにadminでログインするとFlagが取れるらしいので、試してみる。
ダメみたいですね。adminでのログインはローカルホストからしか許可されていないと。
一旦コードを見てみる。
<?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の方のコードを見てみる。
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パラメータを追加、変更、または複製することによって行われます。これらの操作の影響はユーザーに直接は見えませんが、サーバーサイドでアプリケーションの機能を大幅に変更し、クライアントサイドにも影響を与える可能性があります。
今回の場合は、まさにこれ。
パラメータ解析: Flask vs. PHP
Web技術が重複するHTTPパラメータを処理する方法は異なり、HPP攻撃への脆弱性に影響します。
Flask: クエリ文字列a=1&a=2で最初に遭遇したパラメータ値を採用し、初期のインスタンスを後続の重複より優先します。
PHP(Apache HTTP Server上): 逆に、与えられた例では a=2 を選択し、最後のパラメータ値を優先します。この動作は、攻撃者の操作されたパラメータを元のパラメータよりも優先することで、HPPの悪用を誤って容易にする可能性があります。
つまり、username=admin&username=test&password=admin&login-submit=
このようなリクエストを送った場合、php側ではusername=test&password=admin&login-submit=
を処理してFlask側ではusername=admin&password=admin&login-submit=
の値で処理する。
フラグ取得
Burpで先ほどのパラメータを送信する。
無事Flag取得できました。
めでたしめでたし。