Webセキュリティについて
Webサイトやアプリケーションを安全に保つための技術や対策
脆弱性とは
プログラムのミス等が原因となって発生するサイバーセキュリティ上の欠陥
セキュリティホールとも言います。
代表的なWebの脆弱性の例
- SQLインジェクション攻撃
 - OSコマンド・インジェクション攻撃
 - XSS(クロスサイト・スクリプティング)攻撃
 - CSRF(クロスサイト・リクエスト・フォージェリー)攻撃
 - ディレクトリ・トラバーサル攻撃
 
POSTとGETについて
GET
お気に入り登録や検索機能を利用する際に使用
リクエストパラメータがURLにそのまま表示されてしまうので以下のように送信内容が丸見えになります。
例)  http://localhost:3000/index.php?id=1
そのため、機密情報等を送信する場合はPOSTを利用すると良いです。
POST
サーバー上のデータ新規作成や更新を行う際に利用
GETとは異なり、リクエストパラメータがBody部に格納されます。
<ポイント>
GET : 内容をWebサーバから取り出す
POST : 内容をWebサーバに送る
今回は上記のWeb脆弱性の例で紹介した中の3つ(OSコマンドインジェクション、SQLインジェクション、XSS)について紹介したいと思います。
SQLインジェクションについて
webアプリケーションの脆弱性を利用し、SQL文を悪用した攻撃
データベースに不正にアクセスし、情報の搾取や改ざんを行います。
では脆弱なログイン画面を作ってみます。
- index.php(ログイン画面)
 
<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>ログイン</title>
</head>
<body>
    <h1>ログイン</h1><br>
    <form action = "send.php" method = "post">
        <label for = "user">ユーザー名</label>
        <input type = "text" name = "user" id = "user"><br>
        <label for = "pass">パスワード</label>
        <input type = "password" name = "pass" id = "pass"><br>
        <input type = "submit" value = "ログイン">
    </form>
</body>
- 
send.php(ログインの処理)
※データベースはphpMyAdminを利用しています。 
<?php
//phpMyAdminを使用するのに必要な情報
$servername = "localhost";
$username = "aaaa"; //phpMyAdminのログインユーザー名
$password = "aaaa"; //phpMyAdminのログインパスワード
$dbname = "aaaa"; //自分の作成したデータベース名
$conn = new mysqli($servername, $username, $password, $dbname);
//postリクエストからユーザー名とパスワードを取得
$userID = $_POST['user'];
$password = $_POST['pass'];
//脆弱性のあるコード
$query = "SELECT * FROM users WHERE ID = '$userID' AND PWD = '$password'";
$result = $conn->query($query);
// 結果をチェック
if ($result->num_rows == 0) {
    echo "ユーザ名または、パスワードに誤りがあります。";
  } else {
    // ログイン成功時の処理
    echo "ログイン成功";
  }
?>
<作成手順>
- index.phpの作成
 - データベースの作成
 - send.phpの作成
 
脆弱性箇所について
//脆弱性のあるコード
$query = "SELECT * FROM users WHERE ID = '$userID' AND PWD = '$password'";
下記項目でログインしてみる
ユーザー名 : test
パスワード : 'or'a'='a
※ユーザー名は適当な値です
すると以下のようなクエリが実行されます。
$query = "SELECT * FROM users WHERE ID = 'test' AND PWD = ''or'a'='a'";
これをわかりやすくくくってみます。
AND演算子はOR演算子よりも優先されるので以下のように表せます。
$query = "SELECT * FROM users WHERE ((ID = 'test' AND PWD = ''))or('a'='a'");
つまり、ID = test + PWD = 空白 もしくは 'a' = 'a'(常にTrue)ということになります。そのため、パスワードが間違っていてユーザー名が適当であったり空白でもログインできてしまうのです。
しかし、このコードは先ほどのユーザー名とパスワードの値を逆にしてSQLiを仕掛けても成功しません。なぜなら以下のようになるためです。
$query = SELECT * FROM users WHERE (ID = '') OR (('a' = 'a' AND PWD = '$password'));
ここでは、ORの右辺がa = aで常にTrueとなっているもののパスワードの値を検証しているためパスワードの値を正確に入力しなくてはいけないからです。
では、今度は1' or '1' = '1';-- こちらをユーザー名の部分に入れてログインしてみましょう!
するとSELECT * FROM users WHERE ID = '1' or '1' = '1';-- ' AND PWD = ''このようなクエリが実行されます。これは--(コメントアウト)の部分で以降のSQL文を無効化しています。そのため、パスワードの入力なしでログインが可能となります。
XSS(クロスサイトスクリプティングについて)
脆弱性のある入力フォーム等に悪意のあるスクリプトを仕掛け、そこにアクセスしたユーザーの個人情報等を盗む攻撃手法
では脆弱なショッピングサイトを作ってみます。
$host = 'localhost';
$dbname = 'aaaa';//自分の作ったデータベース名
$username = 'root'; //ログインの際のユーザー名
$password = 'aaaa';//ログインの際に使用するパスワード
// MySQLデータベース接続
try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("データベース接続に失敗しました: " . $e->getMessage());
}
// ユーザーが入力した検索キーワードを取得
$search = isset($_GET['search']) ? $_GET['search'] : '';
// 商品を取得
$sql = "SELECT * FROM products";
if (!empty($search)) {
    $sql .= " WHERE name LIKE :search";
}
$stmt = $pdo->prepare($sql);
if (!empty($search)) {
    $stmt->bindValue(':search', '%' . $search . '%', PDO::PARAM_STR);
}
$stmt->execute();
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ショッピングサイト</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #f4f4f4; }
        .container { width: 80%; margin: 0 auto; }
        .search-bar { margin: 20px 0; }
        .product { border: 1px solid #ddd; border-radius: 5px; margin: 10px 0; padding: 15px; background: #fff; }
        .product img { max-width: 150px; }
    </style>
</head>
<body>
<div class="container">
    <h1>ショッピングサイト</h1>
    <form class="search-bar" method="GET" action="">
        <input type="text" name="search" placeholder="検索" style="width: 300px; padding: 10px;">
        <button type="submit" style="padding: 10px;">検索</button>
    </form>
    <?php if (!empty($search)): ?>
        <p>検索結果: <strong><?= $search ?></strong></p>
    <?php endif; ?>
    <?php foreach ($products as $product): ?>
        <div class="product">
            <img src="<?= $product['image_url'] ?>" alt="<?= $product['name'] ?>">
            <h2>商品名: <?= $product['name'] ?></h2>
            <p>価格: <?= $product['price'] ?>円</p>
        </div>
    <?php endforeach; ?>
</div>
</body>
</html>
脆弱性箇所について
① $search = isset($_GET['search']) ? $_GET['search'] : '';
②   <?php if (!empty($search)): ?>
     <p>検索結果: <strong><?= $search ?></strong></p>
  <?php endif; ?>
①は$searchという変数にユーザーが入力した値を格納しています。
②では$searchをそのまま出力しています。
ここで問題なのが、ユーザーの入力した値がそのまま出力されてしまっている点です。この脆弱性を利用して次のような攻撃ができてしまいます。
では以下の2つのスクリプトを実行します。
①  
<script>alert("XSS")</script>
②  
<script>alert(documrnt.cookie)</script>
すると、以下のようなアラートが出てきました。
②
これらの攻撃が成功するとユーザーのcookie情報や機密情報等の流出が懸念されます。
この問題を解決するためには、ユーザーの入力した値をそのまま出力するのではなくエスケープ処理をする必要があります。
OSコマンドインジェクションについて
WebサーバーのOSコマンドを不正に実行し、ファイルの消去や改ざん等のサーバー側に意図しない挙動をさせる攻撃
Ubuntuを利用して、脆弱な問い合わせフォームを作ってみました。
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    // フォームから入力されたデータを取得
    $input = $_POST['message'];
    // コマンドを実行
    $output = shell_exec("echo " . $input);
    echo "<pre>" . "こちらの内容で送信しました : " . $output . "</pre>";
}
?>
<form method="POST">
    <h1>問い合わせ</h1>
    <label for="command">名前:</label>
    <input type="text" name="name" id="name"><br>
    <label for="message">メッセージ:</label>
    <input type="text" name="message" id="message"><br>
    <button type="submit">実行</button>
</form>
<作成手順>
- UbuntuにApacheを入れ、PHPをインストール
 - var/www/htmlにphpファイルを格納する
 - ブラウザで表示!
 
脆弱性箇所について
    $input = $_POST['message'];
    // コマンドを実行
    $output = shell_exec("echo " . $input);
    echo "<pre>" . "以下の内容で送信しました" . $output . "
ここで問題なのがユーザーの入力値をそのままshellコマンドとして実行しているところです。
まず、こちらの$input = $_POST['message'];でユーザーの入力値を一度$inputという変数に格納しています。その後shell_exec("echo " . $input);でshellコマンドとして実行するように定義したものを$outputに格納、出力しています。そのため、不正なコマンドを入力された際に意図しない挙動をしてしまいます。
以下のような内容を送信してみました。
名前 : test
メッセージ : ;ls
すると以下のようにhtmlファイルの中身が表示されてしまいました。

今度はメッセ-ジの部分を;ls -laにしてみます。すると...

ディレクトリ内の権限情報や所有者、更新日等が表示されてしまいました。
このようにならないためには入力値をそのままシェルに受け渡す設計をしないようにすることが大切です。
以上で今回の記事は終わります。
次回は対策についての記事を書きます。
<参考資料>
- https://cybersecurity-jp.com/column/98823#Web
 - https://www.soumu.go.jp/main_sosiki/cybersecurity/kokumin/basic/risk/11/
 - https://medium-company.com/http-get-post-%e9%81%95%e3%81%84/
 - https://wa3.i-3-i.info/diff7method.html
 - https://apidog.com/jp/blog/introduction-to-post-method/
 - https://www.nttpc.co.jp/column/security/cross_site_scripting.html
 - https://www.jbsvc.co.jp/useful/security/sql-injection.html
 - https://www.pentasecurity.co.jp/pentapro/entry/Web-Security-Vulnerabilities
 - https://www.ipa.go.jp/security/vuln/websecurity/os-command.html
 - https://www.nttpc.co.jp/column/security/os-command_injection.html
 - https://www.shadan-kun.com/blog/measure/2873/
 

