📝

SQLインジェクション脆弱性をPHPで試す

2021/04/21に公開

はじめに

エンジニアの皆様、脆弱性を正しく対策をしながらWEBアプリケーションを開発してますでしょうか? 作成したコードに「SQLインジェクション脆弱性」が含まれていると「データベースが外部に盗まれる」「データベースの内容を書き換えられる」「データベースを破壊される」「データベースを経由してプログラムの実行」などが行われます。「SQLインジェクション脆弱性」は、他の脆弱性と比べて「情報漏洩」に直結する悪用されると被害の大きい脆弱性となります。

今回は、この「SQLインジェクション脆弱性」をPHPのコードを使用して試したいと思います。
※コードを試す際はデータベースも必要となります。

本記事の確認する環境について

本記事の確認は 下記をローカルに作成して行いました。

  • Windows10/WSL2(Ubuntu 20.04.2 LTS)
  • Apache 2.4.41
  • PHP 7.4.3
  • MariaDB 10.3.25

WSL2のLAMP環境の作り方は、下記の記事を参照いただけたらと思います。

https://zenn.dev/o2z/articles/f68d7aea7c075f

SQLインジェクション脆弱性とは?

SQLインジェクション脆弱性は「SQLを呼び出している箇所」に発生する脆弱性です。プログラム上のデータアクセスに不備があり意図しないSQLが実行される事により、攻撃者がデータベースにアクセスを行う事が可能となります。

コード作成の際に人為的ミスで発生する脆弱性でもありますが、フレームワークを利用している場合は、フレームワーク側に脆弱性が潜んでいる場合もありますので利用しているフレームワークにバージョンアップが公開されたら、なるべく早いタイミングで適用をすると共にフレームワークは正しく理解して利用する事を推奨します。

脆弱性を試すPHPコード

データベースは接続ユーザーが作られているモノとして 下記のデータが入っている状態を想定しています。

login_user テーブル

id name passwd flg
1 Aさん password1 1
2 Bさん password2 0
3 Cさん password3 0

menu テーブル

id menu_name page flg
1 ショップ shop 0
2 入庫 stock 0
3 出庫 move 0
4 管理 management 1

※カラム名など設計が悪いと思いますがサンプルのためご容赦ください。

SQLインジェクション脆弱性があるコード

// SQL組み立て
$sql  = "";
if(isset($_GET['flg'])){
    $sql .= "SELECT id,menu_name,page FROM menu WHERE flg = ".$_GET['flg'].";";
}
// SQL実行
if ($sql != "") {
    try {
        $db = new PDO("mysql:host=localhost;dbname=$database", $user, $password);
        foreach($db->query($sql) as $rows) {
            $records[] = $rows;
        }
    } catch (PDOException $e) {
        echo "<div>Error:" . $e->getMessage() . "</div>";
        //die();
    }
}
// 表示
foreach($records as $recoed) {
    echo $recoed['menu_name'];
    echo ":";
    echo $recoed['page'];
    echo "\n";
}
?>

$_GET['flg'] をそのまま利用しSQLを組み立てています。

このコードにSQLインジェクションをしてみたいと思います。

flg="2 UNION SELECT null,null,(select CONCAT(id,"$",name,"$",passwd) from login_user limit 0,1)";

実行されるSQLはこうなります。

SELECT id,menu_name,page 
  FROM menu WHERE flg = 2
 UNION SELECT null,null,(SELECT
              CONCAT(id,"$",name,"$",passwd)
         FROM login_user limit 0,1)

UNIONを利用してSQLをつなぎ、3カラム目にサブクエリでlogin_userテーブルにSQLを発行します。さらにCONCATでセパレート文字に「$」を挟みながら結合してレコードを抜き出しています。

「INFORMATION_SCHEMA」に注意する

SQLインジェクション脆弱性があったとしても、これはだけでは「データベース名」や「テーブル名」や「カラム名」が分からないので大丈夫。と考えるかもしれませんが Mysql(MariaDB) は「INFORMATION_SCHEMA」を確認する事でデータベースの構造を知る事が可能です。
※「SHOW tables」でもテーブル名の一覧は取得可能です。

さきほどのSQLインジェクションのコードを「INFORMATION_SCHEMA」を確認するように修正してみます。

SELECT id,menu_name,page
  FROM menu WHERE flg = 2
 UNION SELECT null,null,(SELECT 
              CONCAT(table_schema,"$",table_name,"$",engine)
	 FROM information_schema.tables limit 0,1)

参考
MYSQLリファレンス:INFORMATION_SCHEMA

対策について

SQLを組み立てる際に GET(POST)のパラメータをそのままSQL文に渡している箇所があるとSQLインジェクションを引き起こす可能性が高まり危険なコードとなります。入力値のバリデーションを強化すると共にSQLに渡される前に文字列をエスケープする必要があります。
SQLを投げている箇所は全てPDO(プレースホルダ)を利用した形に置き換えるか、SQLインジェクションに対応しているフレームワークを利用する事で、SQLインジェクションをが紛れ込まないようにする対策が必要となります。

まとめ

SQLインジェクション脆弱性によりデータベースは壊滅的なダメージを受けます。
「プログラムは思った通りに動けば良い」「外部に公開していない社内システムだから脆弱性があっても問題ない」などは非常に安易で危険な考えと思います。
データベースが漏洩すればサイトやシステムだけではなく会社全体の信用が揺らぐ事になります。そして、信用を取り戻すためにかかる費用は開発を大きく上回る事でしょう。
SQLインジェクション脆弱性を人為的なミスにより引き起こさないために個人ではなく、組織として脆弱性に目を向ける事からスタートしていく事が良いのではないかと感じます。

以上、PHPで試す「SQLインジェクション」脆弱性でした。いかがでしたでしょうか。脆弱性は正しく理解して対策していきたいと思います。

GitHubで編集を提案

Discussion