ザッとわかる！PHPで実践するIPA「安全なWebサイトの作り方」11の脆弱性と対策

「セキュリティって大事」って聞くけど、**実際なにが危なくて、どう対策すればいいのか分からない...​。** そんな人向けに、IPAの「[安全なWebサイトの作り方（第7版）](https://www.ipa.go.jp/security/vuln/websecurity.html)」をベースに、**よくある11の脆弱性と、その防ぎ方**をザッと解説します。

## 本記事の目的

- 時間がない方でも**どんな実装が危ないか**を、コード例でザッと理解できる
- 危険なコードとその対策をセットで理解できる
- **初学者でも実践できるセキュリティ対策**をざっくり把握する


## 脆弱性①：SQLインジェクション

### どんな脆弱性？
ユーザーの入力をSQL文にそのまま埋め込むと、**意図しない命令が実行されてしまう**。

### ダメな例
```php
$name = $_GET['name'];
$sql = "SELECT * FROM users WHERE name = '$name'";
```

### 何がダメ？
`$name` に `a' OR '1'='1` と入力されると、全件取得される。

### 対策
```php
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->bindParam(':name', $name);
$stmt->execute();
```

- SQLは**文字列の連結で作らない**
- **プレースホルダ＋バインド**を使う
- エラー画面に**SQL文を表示しない**
- DBユーザーには**読み取り専用など最低限の権限を与える**


## 脆弱性②：OSコマンドインジェクション

### どんな脆弱性？
`exec()` や `system()`などの関数にユーザー入力を渡すと、**悪意あるコマンドがそのまま実行されてしまう**。

### ダメな例
```php
$filename = $_GET['file'];
exec("ls $filename");
```

### 何がダメ？
`$filename`に`; rm -rf /`を渡すと、

```bash
ls ; rm -rf /
```

と解釈されてしまい、**システムが破壊される危険**がある。

### 対策
```php
$allowedDirs = ['images', 'docs'];
$dir = $_GET['dir'];
if (in_array($dir, $allowedDirs)) {
    exec("ls " . escapeshellarg($dir));
}
```

- **ユーザー入力をOSコマンドに直接渡さない**
- `escapeshellarg()` / `escapeshellcmd()` を使用する
- 可能なら`exec()`を使わず、PHP関数（例：`scandir()`）で代替
- 処理対象をホワイトリストで制限する



## 脆弱性③：ディレクトリ・トラバーサル

### どんな脆弱性？
ファイル名に`../`を含めることで、**上の階層へ移動し、任意のファイルにアクセスされる**。

### ダメな例
```php
$file = $_GET['file'];
readfile("/uploads/" . $file);
```

### 何がダメ？
`file=../../../../etc/passwd`のように指定されると、
システムの機密ファイルが読まれてしまう。

### 対策
```php
$filename = basename($_GET['file']);
$filepath = "/uploads/" . $filename;
if (file_exists($filepath)) {
    readfile($filepath);
}
```

- `basename()`でファイル名に`../`を含めないようにする
- ファイル名に使える文字列は**ホワイトリストで管理**
- 絶対パスと`realpath()`で**パスの正当性を確認**


## 脆弱性④：セッション管理の不備

### どんな脆弱性？
セッションIDを盗まれたり、ログイン後に再生成されないと、**なりすましログインが可能になる**。

### ダメな例
```php
session_start();
// ログイン後もセッションIDを再生成しない
```

### 何がダメ？
攻撃者にセッションIDが漏れたら、そのまま乗っ取られる。
また、IDを固定したままだと「セッション固定攻撃」にも弱い。

### 対策
```php
session_start();
if ($isLoginSuccessful) {
    session_regenerate_id(true); // IDを再生成
}
```

- ログイン成功時に`session_regenerate_id(true)` 
- Cookieに**Secure / HttpOnly / SameSite を設定**
- **HTTPSを常時使用**（セッション盗聴防止）



## 脆弱性⑤：**クロスサイトスクリプティング**（XSS）

### どんな脆弱性？
HTMLにユーザー入力をそのまま表示すると、**悪意あるJavaScriptが実行されてしまう**。

### ダメな例
```php
echo $_GET['comment'];
```

### 何がダメ？
攻撃者が次のような入力をすると…

```php-template
<script>alert(document.cookie)</script>
```
→ そのスクリプトが**訪問者のブラウザで実行されてしまう**

### 対策
```php
echo htmlspecialchars($_GET['comment'], ENT_QUOTES, 'UTF-8');
```

- HTMLに出力するすべてのユーザー入力は**エスケープ**
- JavaScript内で使う場合は、JS向けエスケープを検討
- 属性値、URLなどの**用途に応じたエスケープ方法**を使い分ける


## 脆弱性⑥：クロスサイトリクエストフォージェリ（CSRF）

### どんな脆弱性？
ユーザーがログイン状態のまま、**別の悪意あるサイトから勝手にリクエストを送られてしまう**。

### ダメな例
攻撃者が以下のようなHTMLを用意：

```html
<img src="https://example.com/delete_account.php">
```

→ 被害者がこのページを開くと、自動で delete_account.php にアクセスされてしまう。

### 何がダメ？
ユーザーがこのHTMLを含むページを開くと、セッションIDが格納されたCookieが自動的に送信され、**意図しない操作が実行されてしまう。**

### 対策

```php
//トークン発行（初回のみ）
session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
```

```html
<!-- フォームにトークンを埋め込む -->
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
```

```php
//トークン検証
session_start();
if (
    !isset($_POST['csrf_token'], $_SESSION['csrf_token']) ||
    $_POST['csrf_token'] !== $_SESSION['csrf_token']
) {
    die('Invalid CSRF token');
}

```

- フォーム送信に**CSRFトークン**を埋め込んで、サーバー側で検証
- トークンはsessionで管理。`random_bytes()`で安全に生成
- Cookieには`SameSite=Lax`を指定して外部サイトからの送信をブロック


## 脆弱性⑦：HTTPヘッダインジェクション

### どんな脆弱性？
ユーザー入力を`header()`関数などで使う際に、**改行文字を含めると任意のヘッダを追加できる**。

### ダメな例
```php
header("Location: ".$_GET['url']);
```
→ url=nextpage.php%0d%0aSet-Cookie:admin=true

### 何がダメ？
ヘッダの改行により、**本来意図していないヘッダが注入される**。

### 対策
```php
$url = filter_var($_GET['url'], FILTER_SANITIZE_URL);
if (!preg_match('/^https?:\/\/yourdomain\.com/', $url)) {
    $url = '/default';
}
header("Location: " . $url);
```

- 改行文字（\r, \n）は除去する
- リダイレクトURLはホワイトリスト制御
- フレームワークに頼るとより安全（例：redirect()->to()）

## 脆弱性⑧：メールヘッダインジェクション

### どんな脆弱性？
`mail()`関数などでメールヘッダにユーザー入力をそのまま渡すと、**Bcc挿入などの攻撃が可能**。

### ダメな例
```php
$to = $_POST['email']; // 入力: victim@example.com\r\nBcc:attacker@example.com
mail($to, "Subject", "Body");
```

### 何がダメ？
意図しない相手にもメールが送信される・本文の改ざんなどが可能。

### 対策
```php
$email = str_replace(["\r", "\n"], '', $_POST['email']);
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
    mail($email, "Subject", "Body");
}
```

- メールヘッダに改行文字が含まれないかチェック
- `filter_var(..., FILTER_VALIDATE_EMAIL)`でバリデーション
- 外部ライブラリ（PHPMailer など）を使うのも有効



## 脆弱性⑨：クリックジャッキング

### どんな脆弱性？
悪意あるサイトが透明な`iframe`で正規サイトを覆い隠し、ユーザーが意図せずクリックしてしまうことで、意図しない操作を実行させる。

### ダメな例
```php
// ヘッダに何も設定されていない
```

### 何がダメ？
以下のような攻撃ページを用意されると…
```html
<iframe src="https://example.com/important-action.php" style="opacity:0; position:absolute; top:0; left:0; width:100%; height:100%;"></iframe>
```
ユーザーがボタンをクリックしたつもりでも、**実際は透明なiframe内の危険な操作を実行してしまう。**

### 対策
```php
// クリックジャッキング対策ヘッダを追加
header('X-Frame-Options: DENY');
```
または同等のContent Security Policy（CSP）での対策も有効

```php
header("Content-Security-Policy: frame-ancestors 'none';");
```

- `X-Frame-Options: DENY`や CSPでiframe埋め込みを防止
- フレームを使う必要がある場合でも、**許可するドメインを明示的に制限する**



## 脆弱性⑩：バッファオーバーフロー

### どんな脆弱性？
もともとC/C++で多い脆弱性。PHPでは原則発生しないが、**ユーザーの意図しない大量入力を処理する際の不備**が原因で、メモリ不足や異常動作を招くことがある。**。

### ダメな例
```php
$input = $_POST['memo'];
file_put_contents('/tmp/memo.txt', $input);
```

### 何がダメ？
攻撃者が数百MB以上の文字列を送信すると、**サーバーの負荷が急増**し、DoS的な攻撃になる可能性がある。

### 対策
```php
$input = $_POST['memo'];
if (strlen($input) > 1000) {
    die('入力が長すぎます');
}
file_put_contents('/tmp/memo.txt', $input);
```

- **ユーザー入力の長さに制限を設ける**
- PHPの`post_max_size`や`max_input_vars`などの設定も確認
- ファイルアップロードサイズも必要に応じて制限する

## 脆弱性⑪：認可制御の欠如

### どんな脆弱性？
**ログイン後の操作に対して、ユーザーの権限を確認しないまま処理してしまう**ことで、管理者機能などを不正に使われる。

### ダメな例
```php
// 管理者ページ（認証は通っているが、権限チェックがない）
if (isset($_SESSION['user_id'])) {
    // 管理者限定の処理
    deleteAllUsers(); 
}
```
### 何がダメ？
一般ユーザーがログインしていれば、URLを直接入力するなどで**管理機能を実行できてしまう。**

### 対策
```php
session_start();

if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
    die('許可されていない操作です');
}

// 管理者限定処理
deleteAllUsers();
```

- **認証（ログイン）と認可（権限チェック）は別物として実装**
- ロールや権限に応じた機能制御をサーバー側で行う
- 画面側の非表示だけでは不十分（直接URLアクセスに備える）



## まとめ

セキュリティ対策は一見むずかしく思えるかもしれませんが、「どんな脆弱性があるのか」「どう直すのか」をセットで知るだけで、十分に防げるようになります。「何が危なくて、どう直せばいいのか？」が分かれば、今日からでも実践可能です。実際、セキュリティ対策はたった1行のコード変更で防げるケースも少なくありません。
**”知っていれば防げる。知らなければ狙われる。”**
このガイドが、あなたのプロダクトを安全に一歩でも近づける助けになれば幸いです。



## 参考リンク

- [IPA「安全なWebサイトの作り方」（第7版）](https://www.ipa.go.jp/security/vuln/websecurity.html)