Webサイト開発・構築時のセキュリティチェック事項

Webサイトを開発するときやサーバー構築時に気をつけないといけない点を一覧化しました。

# 開発時の留意点
## SQLインジェクション (重要度: ★★★)
ユーザーから受け取ったパラメーターを元にSQLを組み立てて実行する処理があった場合に、パラメーターを工夫することで、**意図しないSQLが実行されてしまう**可能性がある問題。

```php
$sql = "SELECT 会員ID FROM 会員一覧 WHERE 会員ID = {$_POST['id']}";
```

例えば、上記のような処理を入れていると、id値に`1; DELETE FROM 会員一覧` というパラメーターが送られたとき、会員一覧が全部消えてしまう。

#### 対策

プレースホルダー機構に対応したSQL実行の仕組みを採用する。以下、一例。

```php
$sth = $dbh->prepare("SELECT 会員ID FROM 会員一覧 WHERE 会員ID = :id");
$sth->execute(['id' => $id]);
```

上記のような仕組みを採用したとしても、例えば**プレースホルダーの数が可変の処理がある**などの理由で、コーダーによって手動でSQLが組み立てられてしまうことがよくあるので、**ソースレビュー**等、他の対策も必要になってくる。

なお、`LIKE`検索は通常の`SQLエスケープ`だけでは不十分で、`\` を `\\`　に、`%`を`\%`に、`_`を`\_`に置換する必要がある。

```php
$str = strtr($str, ['\\' => '\\\\', '%' => '\%', '_' => '\_']);
```

## XSS - クロスサイトスクリプティング (重要度: ★★★)
ユーザーからの投稿を元に、HTMLに投稿内容をそのまま反映してしまうことで、**任意のHTMLタグを埋め込むことができ、サイトが改竄されてしまう**問題。

```html
<div><?php echo $row['ユーザーが入力した本文']; ?></div>
```

こんな処理を入れていると、`<script type="text/javascript">alert('このサイトは乗っ取った');</script>`といった本文を投稿されたら、ページを表示しただけでアラートダイアログが表示されてしまう。
単にサイトが改竄できてしまうだけでなく、**アクセスしてきたユーザーのセッションIDを盗んで攻撃者に送る**みたいなこともできてしまうので危険。

#### 対策
ユーザーの入力を元にしたパラメーターは全て`HTMLエスケープ`を行う。と言うか、ユーザーの入力でなくても**一律**`HTMLエスケープ`しておくぐらいの方が良い。
テンプレートエンジンによっては**デフォルトでHTMLエスケープし、エスケープしない場合に逆に特殊な記述をする必要がある**ものがあるので、活用できる。
ユーザーの入力した改行を反映させたい場合は、改行を`<br>`タグに変換する処理も必要。

```html
<div><?php echo nl2br(htmlspecialchars($row['ユーザーが入力した本文'])); ?></div>
```

また、`JavaScript`でも同様の問題が起こり得るので注意する。
どうしても**HTMLタグの入力を受け付ける**必要がある場合は、

* バリデーションでHTMLタグの構文チェックをする。
* 特定のタグや特定の属性のみを許容する。
* マークダウン記法など、別の方式で入力してもらうようにする。

などを検討する。(どれも難しいのでできるだけ避ける)

また、以下の設定を入れることも有効。(古いブラウザで、画像などのファイルにJavaScriptを埋め込んでも働かないようにする設定)
`Apache`

```Apache
Header always set X-Content-Type-Options nosniff
```

`nginx`

```nginx
add_header X-Content-Type-Options nosniff;
```

## CSRF - クロスサイトリクエストフォージェリー (重要度: ★★★)

画面遷移のチェックを正しく行わないことにより、**入力・確認画面を経ずに直接投稿完了・購入完了などの処理が行えてしまう**問題。
別のユーザーから悪意のあるリンクを設置されると、それを踏んだユーザーに意図しない投稿・購入を行わせることができてしまう。例えば、完了画面のURLを `/item/finish` とし、

```html
<form method="POST" action="https://ドメイン/item/finish">
   <input type="hidden" name="商品ID" value="買わせたい品" />
   <input type="submit" value="詳しくはこちら" />
</form>
```

というタグを外部に設置されてしまうと、「詳しくはこちら」を押したユーザーに意図しない品を買わせることができてしまう。

#### 対策
`セッション`や`Cookie`を活用し、正規の画面遷移で進んだ場合のみ正しいセッション情報などを記録するようにする。完了画面では確認画面を経て遷移されたことを確認する。

## パスワードのハッシュ化 (重要度: ★★★)
ユーザーのパスワードを**平文**でDBに保存してしまうと、万が一DBの情報が流出したときに**ユーザーのパスワードも漏れてしまう**。その結果、同じパスワードを使っている**他サイトにも被害が拡大**してしまう。また、流出しなかったとしても**内部犯**にパスワードを使われてしまう。

#### 対策
ユーザーのパスワードを保存するときは、必ず`一方向ハッシュ化関数`を使って、ハッシュ化した値を保存する。単にハッシュ化するだけでなく、サイト独自の `Salt値` も使って保存した方が、流出時にハッシュ値を照合されるリスクを減らせる。
`PHP`の場合は、`md5`や`sha1`などの関数はパスワード保存には向かず、`Salt値`の自動生成まで含めてやってくれる `password_hash`関数が推奨されるとのこと。ただし、この関数を使った場合は、パスワードの認証時に `password_verify`関数を使う必要がある。

## メール送信ミス (重要度: ★★★)
**メールを誤った人に送ったり**、**宛先と本文内に記載の氏名が一致しなかったり**、**メール内にパスワードなどの重要情報を含めてしまう**、等のトラブルを起こしてしまう。

#### 対策
完全な対策は無いが、

* メール本文内に**個人を特定できる情報**はできるだけ含めない。理想は、個人情報は一切メールに含まず、Webサイトに行かないと確認できないこと。(「確認メール」等の用途では現実的に難しい場合もあるが)
* メール本文に**パスワード**の情報を含めない。
* 一連の処理内で複数通のメールを送る際は、**前に送った宛先や本文が必ずクリアされた状態**で次のメールを送る。
* ユーザーからのアクションをトリガーにしないような、こちらから一方的に送るメールは送信時間に気をつける (深夜に送らない)


## GETメソッドでの更新処理 (重要度: ★★)
データの更新が伴う処理を`GET`メソッドでも受け付けてしまうと

* うっかりユーザーが他人にURLを共有したときにユーザーの入力内容がバレてしまう。
* アクセスログにユーザーの個人情報などの投稿内容が残ってしまう。
* CSRF攻撃がより行いやすくなる。

#### 対策

データの更新が伴う処理は、`POST` (場合によっては`PUT`、`DELETE`)のメソッドでのみ受け付ける。

## メールアドレス認証 (重要度: ★★)
ユーザーにメールアドレスを入力してもらうサイトで、

* **本人でないメールアドレス**で会員登録される、もしくは**ダミーのメールアドレス**で会員登録される。
* **いたずら目的**または、**営業メールを他人に送りつける目的**で、**他人のメールアドレス**を入力される。

例えば、問い合わせフォームに自動返信機能をつけた場合、

```
差出人: メールを送りつけたい相手
本文: Tシャツが50%オフ http://～～～/
```

と入力することで、他人にメールを送り付けることができる。

#### 対策
* 会員登録は一度認証メールを送り、踏まれたときに正式に登録する。
* `ソーシャルログイン`機能を使う。
* 問い合わせフォームなど、1回限りの処理については現実問題許容せざるを得ないことが多い。(「**このメールに心当たりが無い場合は**」のような文言を入れるのは有効)

## 二重送信 (重要度: ★★)
投稿画面や購入画面で、ボタンを**二度押し**したり、完了画面で**F5キー**を押すことで、投稿・購入が二重に行われてしまう。

#### 対策
* 一度ボタンを押されたら、二度目の押下を`JavaScript`で無効化する。(100%の対策ではない)
* 完了時に別URLにリダイレクトさせる。(戻るボタンで戻られたときに再処理が行われてしまう問題は残る)
* 投稿・購入完了後、セッションを終了させる。

## ディレクトリトラバーサル (重要度: ★★)
ユーザーの入力を元に読み込むファイルを決定する処理があった場合に、**親ディレクトリを辿られる**など意図しないファイルを参照される問題。親ディレクトリにあるサーバー設定ファイルやパスワードファイルなどを参照されてしまう可能性がある。

```php
echo file_get_contents("/var/www/hoge.jp/img/{$_GET['filename']}");
```

この場合、filenameに`../../../../etc/passwd` と指定されるとアカウント情報を参照されてしまう。

#### 対策
**ユーザーの入力を元にファイルパスを決定するような処理を行わない**。どうしても必要な場合、**必要な文字種を限定する**などのバリデーションを行う。


## OSコマンドインジェクション (重要度: ★★)
ユーザーの入力を元にOSのコマンドを組み立てて実行することにより、任意のコマンドを実行できてしまう問題。

```php
exec("mail -s '{$_POST['title']}' sitemaster@hoge.co.jp");
```

上記のような処理を組んでしまうと、titleが`'; rm -rf /;`だったときに、全消去コマンドが実行されてしまう。

#### 対策
そもそも、**OSコマンドを実行するような処理は組まない**。大抵の処理は、プログラミング言語内に類似の処理をする関数が存在する。
何らかの理由でどうしても必要な場合はバリデーションをして、特定の文字列のみ受け付ける。

## パラメーター偽装 (重要度: ★★)
**JavaScriptで入力値のチェックをしている**サイトで、ブラウザの開発機能を使って**直接パラメーターを改竄して**、**JavaScriptのチェックをすり抜ける**問題。

#### 対策
JavaScriptでバリデーションをしていたとしても、**サーバー側でもバリデーションを実施しなければならない**。
画面に見えていない `hidden`パラメーターも同様。

## メールヘッダーインジェクション (重要度: ★)
メール送信時に、ユーザーの入力値を利用してメールを組み立てることで、任意のメールヘッダーが挿入されてしまう問題。

```php
$mail = "Subject: {$_POST['subject']}";
```

という処理を入れると subject値が `a\r\nCc: hogehoge@hoge.jp`だった場合に、勝手にCcヘッダーが付加されてしまう。

#### 対策
自力でメールを組み立てるような原始的な実装はしない。ただし、既存のメール関数にも脆弱性があるものがあるので注意する。

## HTTPヘッダーインジェクション (重要度: ★)
ユーザーから受け取った情報を元に、`HTTPレスポンスヘッダー`を組み立てることで、**任意のHTTPヘッダーが挿入されてしまう**問題。特にHTTPヘッダーの組み立てまで自力で実装する`Perl`のような言語では注意が必要。

```perl
$cgi = new CGI;
$url = $cgi->param('url');
print "Location: $url\n\n";
```

urlが`/\nSet-Cookie: hoge` などとなっていると`Set-Cookie`ヘッダーを埋め込めてしまう。

#### 対策
できるだけヘッダーの生成にユーザーの入力値を利用しない。パラメーターのバリデーションを正しく行う。

## HTTPヘッダー偽装 (重要度: ★)
ユーザーから受け取る `Referer` や `User-Agent` などのヘッダーは、ユーザーによっていくらでも偽装可能。

#### 対策
基本的には問題は特に無いが、偽装値が入れられる場合があることは留意する。`Referer`を元に戻り先URLを決めるなどの処理は避けた方が無難。また異常値が入力されていたとしてもバグらないように気をつける。

## リダイレクト踏み台化 (重要度: ★)
前にいた画面のURLを受け取り、処理完了後にそのURLにリダイレクトする、といった処理を実装した場合に、任意のリダイレクト先URLを埋め込まれてしまい、サイトを**踏み台として利用されてしまう**問題。

```php
header('Location: {$_GET['url']}');
```
といった処理をしていると、`/register?url=http%3A%2F%2F～～` というパラメーターにより任意のサイトに飛べてしまう。

#### 対策
**「/」で始まり、かつ「//」では始まらない**URLのみ受け入れるようにすれば、サイト内のURLに限定できる。

# Webサーバー構築時の留意点

## nginxキャッシュ (重要度: ★★★)

`nginx`には一定期間生成された**HTMLなどをキャッシュ**する機能があるが、誤って**マイページ**等をキャッシュすると、ユーザーに別人のマイページが表示されてしまい、**個人情報の漏洩**になってしまうので、絶対に設定を誤ってはならない。

#### 対策
ユーザーに応じた情報が表示されるページはキャッシュしないようにする。

```nginx
set $do_not_cache 0;
if ($request_uri ~ "^/(mypage|contact)(\?|/|$)") {
	set $do_not_cache 1;
}

proxy_no_cache     $do_not_cache;
proxy_cache_bypass $do_not_cache;
```

## HTTPS化 (重要度: ★★★)

Webサイトを`TLS` (`HTTPS`)に対応しないことにより、個人情報等の重要な情報が平文のままネットワーク上を流れてしまう。

#### 対策

`SEO`の観点からも、**サイト全体をHTTPS化**するのが良い。現在は`Let's Encrypt`等の無料で導入できるSSL証明書もあるし、`無料CDN`を活用することでもHTTPS化が可能。
尚、HTTPS化する際は、`SSL 2.0`、`SSL 3.0`、`TLS 1.0`などの古い方式は避ける。また、証明書も**鍵長**や**暗号方式**の推奨値が頻繁に変わっているので注意。

## ディレクトリリスティング (重要度: ★★)

`Apache`の設定により、ディレクトリにあたる`URL`にアクセスしたときに、**ディレクトリ内のファイル一覧が表示されてしまう**問題。不必要な情報を与えてしまう。

#### 対策

ディレクトリの中身一覧の表示を無効化する。

```
Options -Indexes
```

## クリックジャッキング攻撃 (重要度: ★★)

他サイト内の `iframe` に勝手に自分のWebサイトを埋め込まれてしまう問題。サイト内に巧妙に被せることにより、ユーザーに意図しない操作をさせることができてしまう。

#### 対策
他サイトに埋め込まれることを禁止する。
`Apache`の設定

```apache
Header always set X-Frame-Options DENY
```

`nginx`の設定

```nginx
add_header X-Frame-Options DENY;
```

## Webサーバー情報の隠蔽 (重要度: ★)
出す必要の無いWebサーバーのバージョン情報などが晒されてしまう問題。

#### 対策

`Apache`の設定

```apache
ServerSignature Off
```

`nginx`の設定

```nginx
server_tokens off;
```

## HTTPメソッド制限 (重要度: ★)

`TRACE`などの`HTTPメソッド`のリクエストを受け付けることにより、`クロスサイトトレーシング` (`XST`)などの脆弱性を生んでしまう問題。

#### 対策

通常使う`HTTPメソッド`は、`GET`, `POST`, `PUT`, `DELETE`, `HEAD`だけなので、それ以外の不要なHTTPは無効化する。
`Apache`の設定

```
TraceEnable Off
```

```apache
<LimitExcept HEAD GET POST PUT DELETE>
	Order deny,allow
	Deny from all
</LimitExcept>
```

`nginx`は`TRACE`はデフォルトで無効になっているとのこと。

## その他 (重要度: ★)

* `Apache`では、`.htaccess`は無効化した方が良い。(何らかの原因でファイルを外部から置かれた場合に誤動作の原因になる。
* `PHP`の場合 `open_basedir`を指定して、PHPでアクセスできる範囲を最小化する。
* `chown`や`chmod`でパーミッションを適切に設定する。

# サーバー構築時の留意事項

## パッケージの最新化 (重要度: ★★★)

サーバー内のパッケージが古いことにより、セキュリティホールができるため、定期的にパッケージをアップデートする。

## WAFの導入 (重要度: ★★★)

`SQLインジェクション`等を狙った攻撃をブロックする。

## FW/iptablesの設定 (重要度: ★★★)
必要最小限のポート以外は塞ぐ。主に必要なことが多いポートは以下の通り。

| ポート番号 | プロトコル | 方向　 | 用途 |
| ---- | ---- | ---- | ---- |
| 80 | TCP | IN | HTTPアクセスの受け入れ |
| 443 | TCP | IN | HTTPSアクセスの受け入れ |
| 25 | TCP | OUT | メール送信 |
| 53 | UDP | OUT | DNS問い合わせ |
| 80 | TCP | OUT | 外部へのHTTP通信 (外部APIとのやり取り、パッケージのアップデートなど) |
| 123 | UDP | OUT | NTPでの時刻同期 |
| 443 | TCP | OUT | 外部へのHTTPS通信 |

`CDN`を導入している場合、`CDN`の`IPアドレス`以外からの`HTTP/HTTPS`アクセスを受け入れないと尚良い。

## セキュリティ診断サービスの利用 (重要度: ★★★)
他社に`セキュリティ診断サービス`でサイトの脆弱性をチェックしてもらう。

## IDS/IPSの導入 (重要度: ★★)

侵入検知、侵入防止

## ウイルススキャンの実施 (重要度: ★★)

`ClamAV`などを使って定期的にチェックを実施する。

## ログ分析 (重要度: ★★)

`logwatch`や商用のサービスなどを使ってログを集約して傾向を把握する。