Webアプリエンジニアができるセキュリティ対策のはじめの一歩

### お品書き
- フロントエンド（クライアントサイド）でできるセキュリティ対策
- サーバーサイドでできるセキュリティ対策

**注意**
- この記事・勉強会では、インフラ周りで解決できる事象等については言及しません。
- この資料は、[株式会社VISIONARY JAPAN](https://qiita.com/organizations/visionary-jp)の勉強会資料となります。
- JavaScript, PHPを用いたサンプルコードを展開していますが、あくまでサンプルのためベストプラクティスにならない可能性があります。

---

## 1. クライアントサイドでできるセキュリティ対策について

### はじめに

Webアプリケーションにおいて、セキュリティは非常に重要な要素の一つであります。特に、フロントエンド（クライアントサイド）でできるXSSなどのセキュリティ対策は、重要かつ必須のものとなっています。ここでは、フロントエンドでできるXSSなどのセキュリティ対策について、より詳しく解説していきます。

## クロスサイトスクリプティング（XSS）に対する対策

クロスサイトスクリプティング（XSS）は、攻撃者がWebサイト上に悪意のあるスクリプトを埋め込み、ユーザーのブラウザ上で実行させる攻撃です。これにより、機密情報が盗まれたり、不正な操作が行われたりする可能性があります。フロントエンド（クライアントサイド）でできるXSSに対する対策は、以下のようになります。

1. エスケープ処理の実施： ユーザーからの入力値をそのまま出力しないように、HTMLエンコードなどのエスケープ処理を実施することが大切です。エスケープ処理をすることで、不正なスクリプトを埋め込まれても、ブラウザがHTMLタグと認識し、そのまま出力することができます。
2. クッキーのsecure属性に設定する： secure属性を設定することで、クッキー情報が暗号化されて送信されるため、HTTP通信を傍受されることで情報が漏れることを防止できます。暗号化されたクッキー情報を傍受されても、復号するための鍵が必要であり、攻撃者にとって情報を盗むことが困難になります。
3. Content Security Policy（CSP）を実施する： CSPを実施することで、Webサイト内で許可されたコンテンツしかブラウザ上で実行されなくなります。これにより、攻撃者からの不正なスクリプトの実行を防止することができます。

### エスケープ処理の実施

エスケープ処理は、ユーザーからの入力値をそのまま出力しないように、HTMLエンコードなどのエスケープ処理を実施することで、XSS攻撃を防止することができます。JavaScriptでエスケープ処理を実施するサンプルコードを以下に示します。

```jsx
function escapeHTML(str) {
  return str.replace(/[&'`"<>]/g, function(match) {
    return {
      '&': '&amp;',
      "'": '&#x27;',
      '`': '&#x60;',
      '"': '&quot;',
      '<': '&lt;',
      '>': '&gt;',
    }[match];
  });
}
```

上記のコードでは、`&`, `'`, ```, `"` , `<`, `>` 等の特殊文字を、HTMLエンコードすることで、XSS攻撃を防止することができます。また、エスケープ処理をすることで、攻撃者が不正なスクリプトを埋め込んでも、そのスクリプトが実行されず、ユーザーが危険にさらされることを防止することができます。

### cookieのsecure属性に指定する
以下は、クッキーのsecure属性を設定するサンプルコードです。

```js
// クッキーのsecure属性を設定する
setcookie('name', 'value', time() + 3600, '/', 'example.com', true, true);

```

上記のコードでは、`setcookie`関数の6番目の引数に`true`を設定することで、secure属性を設定することができます。secure属性が設定されると、HTTPS経由での通信のみでcookieが送信されるため、通信の暗号化が行われ、盗聴や改ざんなどの攻撃から保護することができます。

また、クッキーには、ユーザーの認証情報などの機密情報が含まれることがあります。そのため、クッキーのセキュリティを強化するためには、以下のような対策が必要です。

- secure属性を設定することで、HTTPS接続時のみクッキーを送信するようにする。
- HttpOnly属性を設定することで、JavaScriptからクッキーにアクセスできなくする。

以下は、クッキーにHttpOnly属性を設定するサンプルコードです。

```js
// クッキーにHttpOnly属性を設定する
ini_set('session.cookie_httponly', 1);

```

上記のコードでは、`ini_set`関数を使用して、`session.cookie_httponly`オプションの値を`1`に設定することで、HttpOnly属性を設定することができます。HttpOnly属性が設定されると、JavaScriptからクッキーにアクセスできなくなるため、XSS攻撃などの攻撃から保護することができます。

さらに、クッキーのセキュリティを強化するためには、以下のような対策も必要です。

- SameSite属性の設定： SameSite属性を設定することで、クロスサイトリクエストフォージェリ（CSRF）攻撃から保護することができます。SameSite属性には、「Strict」、「Lax」、「None」の3種類があります。
- セッションIDの管理： セッションIDを適切に管理することで、不正なアクセスを防止することができます。セッションIDの管理には、以下のような方法があります。
    - セッションIDの長さを増やす。
    - セッションIDをランダムに生成する。
    - セッションIDを定期的に再生成する。

以下は、クッキーにSameSite属性を設定するサンプルコードです。

```js
// クッキーにSameSite属性を設定する
setcookie('name', 'value', [
  'expires' => time() + 3600,
  'path' => '/',
  'domain' => 'example.com',
  'secure' => true,
  'httponly' => true,
  'samesite' => 'Lax',
]);

```

上記のコードでは、`setcookie`関数の3つ目の引数に配列を渡すことで、各属性の設定が可能です。`samesite`属性には、「Strict」、「Lax」、「None」の3種類があり、上記の例では、`Lax`を指定しています。

`samesite`属性は、クッキーがどのような場合に送信されるかを制御する属性です。`samesite`属性には、以下の3つの値があります。

- `Strict`: クッキーは同一オリジン内の場合のみ送信されます。
- `Lax`: クッキーは、同一オリジン内か、外部サイトからのリンクの場合に限り送信されます。
- `None`: クッキーは常に送信されます。

`Lax`がデフォルトの値として使用されます。`samesite`属性を使用することで、クロスサイトリクエストフォージェリ（CSRF）攻撃を防止することができます。ただし、`None`を使用する場合は、secure属性を使用することが必要です。また、`SameSite`属性は、古いブラウザではサポートされていない場合があるため、注意が必要です。

以上のように、クッキーのsecure属性やHttpOnly属性、SameSite属性を設定することで、クッキーのセキュリティを強化することができます。ただし、常に最新の脆弱性情報を収集し、適切な対策を実施することが重要です。

### Content Security Policy（CSP）の実施

Content Security Policy（CSP）は、Webサイト内で許可されたコンテンツしかブラウザ上で実行されなくなります。以下に、JavaScriptでCSPを実施するサンプルコードを記載します。

```html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' <https://example.com>">
  <title>Example</title>
</head>
<body>
  ...
</body>
</html>
```

上記のコードでは、`default-src`は、どのリソースを読み込むかを指定し、`script-src`は、JavaScriptファイルを読み込むURLを指定しています。 `default-src` には、`'self'`を指定することで、同じオリジン内のリソースのみを読み込むように設定することができます。`script-src`には、`https://example.com`を指定することで、 `https://example.com` からのみJavaScriptファイルを読み込むように設定することができます。

## まとめ

Webアプリケーションにおいて、セキュリティは非常に重要な要素の一つであります。フロントエンド（クライアントサイド）でできるXSSなどのセキュリティ対策について、いくつか紹介しました。セキュリティを考慮したWebアプリケーションを開発することで、ユーザーの機密情報を守り、安心して利用できるWebサイトを提供することができます。

---

<br>

## 2. サーバーサイドでできるセキュリティ対策について

バックエンドでできるセキュリティ対策には、以下のようなものがあります。

### SQLインジェクション対策の強化

SQLインジェクション対策として、プリペアドステートメントを使用することができます。プリペアドステートメントは、SQL文をあらかじめ用意しておき、実行時にパラメータを指定することで、SQLインジェクション攻撃を防止するための仕組みです。プリペアドステートメントを使用することで、不正なデータがSQL文に含まれることを防止することができます。

```php
// データベースに接続する
$pdo = new PDO('mysql:host=localhost;dbname=mydatabase', 'myusername', 'mypassword');

// プリペアドステートメントを作成する
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username');

// パラメータを指定する
$stmt->bindParam(':username', $username);

// クエリを実行する
$stmt->execute();

// 結果を取得する
$result = $stmt->fetchAll();

```

上記のコードでは、`PDO`クラスを使用してデータベースに接続し、`prepare`メソッドを使用してプリペアドステートメントを作成しています。`bindParam`メソッドを使用してパラメータをバインドし、`execute`メソッドを使用してクエリを実行しています。また、`fetchAll`メソッドを使用して結果を取得しています。

また、データベースのユーザー権限を制限することで、攻撃者がデータベースを操作することを防止することも重要です。

```php
// ユーザー権限を制限する
GRANT SELECT, INSERT, UPDATE, DELETE ON mydatabase.* TO 'myusername'@'localhost' IDENTIFIED BY 'mypassword';
```

### HTTPSの使用

HTTPSを使用することで、通信の暗号化が行われ、盗聴や改ざんなどの攻撃から保護することができます。証明書を取得して、Webサーバーにインストールすることで、HTTPSを導入することができます。

```yaml
// 証明書の取得
$ sudo certbot --nginx -d example.com
```

上記のように、バックエンドでもセキュリティ対策を行うことで、Webアプリケーション全体のセキュリティを向上させることができます。

### エラーの詳細を非表示にする

バックエンドで発生したエラーの詳細をユーザーに表示しないようにすることで、攻撃者が情報を収集することを防止することができます。PHPでは、以下のようにすることで `display_errors` をオフにすることができます。

```php
ini_set('display_errors', 0);
```

### サニタイズ

サニタイズとは、ユーザーからの入力値を適切に処理し、安全に利用できるようにすることです。具体的には、入力値から不正な文字列やコードを除去したり、エスケープ処理を行うことで、XSSやSQLインジェクションなどの攻撃から保護することができます。例えば、PHPでは、`htmlspecialchars`関数を使用して、HTMLエンコードを行うことができます。

```php
$username = htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
```

### クッキーのセキュリティ強化

クッキーには、ユーザーの認証情報などの機密情報が含まれることがあります。クッキーのセキュリティを強化するためには、以下のような対策が必要です。

- secure属性を設定することで、HTTPS接続時のみクッキーを送信するようにする。
- HttpOnly属性を設定することで、JavaScriptからクッキーにアクセスできなくする。

```php
// クッキーにsecure属性を設定する
setcookie('mycookie', 'myvalue', time() + 3600, '/', 'example.com', true, true);

// クッキーにHttpOnly属性を設定する
ini_set('session.cookie_httponly', 1);
```

### ファイルアップロードの制限

ファイルアップロード機能がある場合は、アップロードされたファイルの種類やサイズを制限することができます。ファイルの種類は、MIMEタイプをチェックすることで判定できます。ファイルサイズは、許容範囲内であることをチェックすることができます。

```php
// アップロードされたファイルのMIMEタイプをチェックする
if ($_FILES['file']['type'] !== 'image/jpeg') {
  die('Invalid file type');
}

// アップロードされたファイルのサイズをチェックする
if ($_FILES['file']['size'] > 1024 * 1024) {
  die('File size is too large');
}
```

### ログの監視

システムのログを監視することで、不正なアクセスや攻撃を早期に発見することができます。PHPでは、 `error_log` 関数を使用して、ログを出力することができます。

```php
// エラーログを出力する
error_log('An error occurred');
```

### `脆弱性のスキャン`

脆弱性スキャンツールを使用して、システムの脆弱性を定期的にスキャンすることで、早期に脆弱性を発見し、修正することができます。

### `ファイアウォールの設定`

ファイアウォールを使用することで、不正なアクセスや攻撃を防止することができます。ファイアウォールの設定には、以下のようなものがあります。

### 入力フィルタリング

入力フィルタリングを行うことで、不正な入力をブロックし、セキュリティを強化することができます。以下は、PHPで入力フィルタリングを行うサンプルコードです。

```php
// $_POST['username'] に対して入力フィルタリングを行う
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);

// $username を使用して処理を行う
```

上記のコードでは、`filter_input`関数を使用して、`$_POST`変数から`'username'`キーに対応する値を取得し、`FILTER_SANITIZE_STRING`フィルターを適用しています。`FILTER_SANITIZE_STRING`フィルターは、文字列を安全な形式に変換するフィルターで、HTMLタグやJavaScriptコードを除去することができます。

### 出力フィルタリング

出力フィルタリングを行うことで、出力に含まれる機密情報をブロックし、セキュリティを強化することができます。以下は、PHPで出力フィルタリングを行うサンプルコードです。

```php
// $output に対して出力フィルタリングを行う
echo htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
```

上記のコードでは、`htmlspecialchars`関数を使用して、`$output`変数を安全な形式に変換しています。`htmlspecialchars`関数は、HTMLタグやJavaScriptコードを無効化することができます。

### アクセス制御

アクセス制御を行うことで、不正なアクセスをブロックし、セキュリティを強化することができます。以下は、PHPでアクセス制御を行うサンプルコードです。

```php
// ログイン済みでない場合は、ログインページにリダイレクトする
if (!isset($_SESSION['user_id'])) {
  header('Location: /login.php');
  exit;
}

// 管理者以外はアクセスできないページ
if ($_SESSION['role'] !== 'admin') {
  die('Access denied');
}
```

上記のコードでは、`$_SESSION`変数を使用して、ログイン済みであることや、ユーザーの役割などを判定しています。また、`header`関数を使用して、リダイレクトを行っています。

### `認証と認可`

ユーザーの認証と認可を実装することで、不正なアクセスを防止することができます。認証と認可には、以下のような方法があります。

- パスワード認証： ユーザー名とパスワードを使用して、認証を行う。
- トークン認証： トークンを使用して、認証を行う。
- OAuth認証： OAuthを使用して、外部サービスとの認証を行う。

認可は、認証されたユーザーが、どのような操作を許可され、どのような操作を拒否されるかを決定することです。認証がユーザーが誰であるかを確認するのに対して、認可はユーザーが何を行えるかを制御するための処理です。

認可には、ロールベースアクセス制御（RBAC）が使用されます。RBACでは、ユーザーにロールを割り当て、各ロールには権限が割り当てられます。ユーザーは、ロールに基づいて権限を継承することができ、必要な権限のみを持つことができます。

例えば、Webアプリケーションには、一般ユーザー、管理者、開発者の3つのロールが存在するとします。一般ユーザーは、記事を閲覧することができますが、記事の編集や削除はできません。管理者は、記事の編集や削除ができますが、ユーザーの登録や削除はできません。開発者は、全ての操作が許可されます。これらのロールに対して、それぞれの権限を割り当てることで、認可を実現することができます。

RBACを実装するには、以下の手順が必要です。

1. ロールと権限の定義： システム内で使用するロールと権限を定義します。
2. ユーザーとロールの紐付け： 各ユーザーとロールを紐付けます。
3. ロールと権限の紐付け： 各ロールと権限を紐付けます。
4. 認可処理の実装： ロールと権限の紐付けを使用して、認可処理を実装します。

RBACを実装することで、システム内でのアクセス制御を効率的に行うことができます。また、セキュリティ対策にもなります。しかし、RBACを実装する場合、ロールと権限の設計には細心の注意が必要であり、誤った設計が行われると、セキュリティ上のリスクが生じる可能性があります。

### `セキュリティのアップデート`

バックエンドに使用しているライブラリやフレームワークのセキュリティアップデートを定期的に行うことで、最新の脆弱性からシステムを守ることができます。

### `認証情報の管理`

 認証情報を適切に管理することで、不正なアクセスを防止することができます。認証情報の管理には、以下のような方法があります。

- パスワードのハッシュ化： パスワードをハッシュ化して、認証情報の漏洩を防止する。
- セキュアな認証情報の保存： 認証情報を暗号化して、不正なアクセスから保護する。

上記のように、バックエンドでもセキュリティ対策を行うことで、Webアプリケーション全体のセキュリティを向上させることができます。ただし、常に最新の脆弱性情報を収集し、適切な対策を実施することが重要です。