お品書き
- フロントエンド(クライアントサイド)でできるセキュリティ対策
- サーバーサイドでできるセキュリティ対策
注意
- この記事・勉強会では、インフラ周りで解決できる事象等については言及しません。
- この資料は、株式会社VISIONARY JAPANの勉強会資料となります。
- JavaScript, PHPを用いたサンプルコードを展開していますが、あくまでサンプルのためベストプラクティスにならない可能性があります。
1. クライアントサイドでできるセキュリティ対策について
はじめに
Webアプリケーションにおいて、セキュリティは非常に重要な要素の一つであります。特に、フロントエンド(クライアントサイド)でできるXSSなどのセキュリティ対策は、重要かつ必須のものとなっています。ここでは、フロントエンドでできるXSSなどのセキュリティ対策について、より詳しく解説していきます。
クロスサイトスクリプティング(XSS)に対する対策
クロスサイトスクリプティング(XSS)は、攻撃者がWebサイト上に悪意のあるスクリプトを埋め込み、ユーザーのブラウザ上で実行させる攻撃です。これにより、機密情報が盗まれたり、不正な操作が行われたりする可能性があります。フロントエンド(クライアントサイド)でできるXSSに対する対策は、以下のようになります。
- エスケープ処理の実施: ユーザーからの入力値をそのまま出力しないように、HTMLエンコードなどのエスケープ処理を実施することが大切です。エスケープ処理をすることで、不正なスクリプトを埋め込まれても、ブラウザがHTMLタグと認識し、そのまま出力することができます。
- クッキーのsecure属性に設定する: secure属性を設定することで、クッキー情報が暗号化されて送信されるため、HTTP通信を傍受されることで情報が漏れることを防止できます。暗号化されたクッキー情報を傍受されても、復号するための鍵が必要であり、攻撃者にとって情報を盗むことが困難になります。
- Content Security Policy(CSP)を実施する: CSPを実施することで、Webサイト内で許可されたコンテンツしかブラウザ上で実行されなくなります。これにより、攻撃者からの不正なスクリプトの実行を防止することができます。
エスケープ処理の実施
エスケープ処理は、ユーザーからの入力値をそのまま出力しないように、HTMLエンコードなどのエスケープ処理を実施することで、XSS攻撃を防止することができます。JavaScriptでエスケープ処理を実施するサンプルコードを以下に示します。
function escapeHTML(str) {
return str.replace(/[&'`"<>]/g, function(match) {
return {
'&': '&',
"'": ''',
'`': '`',
'"': '"',
'<': '<',
'>': '>',
}[match];
});
}
上記のコードでは、&
, '
, ```, "
, `<`, `>` 等の特殊文字を、HTMLエンコードすることで、XSS攻撃を防止することができます。また、エスケープ処理をすることで、攻撃者が不正なスクリプトを埋め込んでも、そのスクリプトが実行されず、ユーザーが危険にさらされることを防止することができます。
cookieのsecure属性に指定する
以下は、クッキーのsecure属性を設定するサンプルコードです。
// クッキーのsecure属性を設定する
setcookie('name', 'value', time() + 3600, '/', 'example.com', true, true);
上記のコードでは、setcookie
関数の6番目の引数にtrue
を設定することで、secure属性を設定することができます。secure属性が設定されると、HTTPS経由での通信のみでcookieが送信されるため、通信の暗号化が行われ、盗聴や改ざんなどの攻撃から保護することができます。
また、クッキーには、ユーザーの認証情報などの機密情報が含まれることがあります。そのため、クッキーのセキュリティを強化するためには、以下のような対策が必要です。
- secure属性を設定することで、HTTPS接続時のみクッキーを送信するようにする。
- HttpOnly属性を設定することで、JavaScriptからクッキーにアクセスできなくする。
以下は、クッキーにHttpOnly属性を設定するサンプルコードです。
// クッキーに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属性を設定するサンプルコードです。
// クッキーに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を実施するサンプルコードを記載します。
<!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サイトを提供することができます。
2. サーバーサイドでできるセキュリティ対策について
バックエンドでできるセキュリティ対策には、以下のようなものがあります。
SQLインジェクション対策の強化
SQLインジェクション対策として、プリペアドステートメントを使用することができます。プリペアドステートメントは、SQL文をあらかじめ用意しておき、実行時にパラメータを指定することで、SQLインジェクション攻撃を防止するための仕組みです。プリペアドステートメントを使用することで、不正なデータがSQL文に含まれることを防止することができます。
// データベースに接続する
$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
メソッドを使用して結果を取得しています。
また、データベースのユーザー権限を制限することで、攻撃者がデータベースを操作することを防止することも重要です。
// ユーザー権限を制限する
GRANT SELECT, INSERT, UPDATE, DELETE ON mydatabase.* TO 'myusername'@'localhost' IDENTIFIED BY 'mypassword';
HTTPSの使用
HTTPSを使用することで、通信の暗号化が行われ、盗聴や改ざんなどの攻撃から保護することができます。証明書を取得して、Webサーバーにインストールすることで、HTTPSを導入することができます。
// 証明書の取得
$ sudo certbot --nginx -d example.com
上記のように、バックエンドでもセキュリティ対策を行うことで、Webアプリケーション全体のセキュリティを向上させることができます。
エラーの詳細を非表示にする
バックエンドで発生したエラーの詳細をユーザーに表示しないようにすることで、攻撃者が情報を収集することを防止することができます。PHPでは、以下のようにすることで display_errors
をオフにすることができます。
ini_set('display_errors', 0);
サニタイズ
サニタイズとは、ユーザーからの入力値を適切に処理し、安全に利用できるようにすることです。具体的には、入力値から不正な文字列やコードを除去したり、エスケープ処理を行うことで、XSSやSQLインジェクションなどの攻撃から保護することができます。例えば、PHPでは、htmlspecialchars
関数を使用して、HTMLエンコードを行うことができます。
$username = htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
クッキーのセキュリティ強化
クッキーには、ユーザーの認証情報などの機密情報が含まれることがあります。クッキーのセキュリティを強化するためには、以下のような対策が必要です。
- secure属性を設定することで、HTTPS接続時のみクッキーを送信するようにする。
- HttpOnly属性を設定することで、JavaScriptからクッキーにアクセスできなくする。
// クッキーにsecure属性を設定する
setcookie('mycookie', 'myvalue', time() + 3600, '/', 'example.com', true, true);
// クッキーにHttpOnly属性を設定する
ini_set('session.cookie_httponly', 1);
ファイルアップロードの制限
ファイルアップロード機能がある場合は、アップロードされたファイルの種類やサイズを制限することができます。ファイルの種類は、MIMEタイプをチェックすることで判定できます。ファイルサイズは、許容範囲内であることをチェックすることができます。
// アップロードされたファイルの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
関数を使用して、ログを出力することができます。
// エラーログを出力する
error_log('An error occurred');
脆弱性のスキャン
脆弱性スキャンツールを使用して、システムの脆弱性を定期的にスキャンすることで、早期に脆弱性を発見し、修正することができます。
ファイアウォールの設定
ファイアウォールを使用することで、不正なアクセスや攻撃を防止することができます。ファイアウォールの設定には、以下のようなものがあります。
入力フィルタリング
入力フィルタリングを行うことで、不正な入力をブロックし、セキュリティを強化することができます。以下は、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で出力フィルタリングを行うサンプルコードです。
// $output に対して出力フィルタリングを行う
echo htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
上記のコードでは、htmlspecialchars
関数を使用して、$output
変数を安全な形式に変換しています。htmlspecialchars
関数は、HTMLタグやJavaScriptコードを無効化することができます。
アクセス制御
アクセス制御を行うことで、不正なアクセスをブロックし、セキュリティを強化することができます。以下は、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を実装するには、以下の手順が必要です。
- ロールと権限の定義: システム内で使用するロールと権限を定義します。
- ユーザーとロールの紐付け: 各ユーザーとロールを紐付けます。
- ロールと権限の紐付け: 各ロールと権限を紐付けます。
- 認可処理の実装: ロールと権限の紐付けを使用して、認可処理を実装します。
RBACを実装することで、システム内でのアクセス制御を効率的に行うことができます。また、セキュリティ対策にもなります。しかし、RBACを実装する場合、ロールと権限の設計には細心の注意が必要であり、誤った設計が行われると、セキュリティ上のリスクが生じる可能性があります。
セキュリティのアップデート
バックエンドに使用しているライブラリやフレームワークのセキュリティアップデートを定期的に行うことで、最新の脆弱性からシステムを守ることができます。
認証情報の管理
認証情報を適切に管理することで、不正なアクセスを防止することができます。認証情報の管理には、以下のような方法があります。
- パスワードのハッシュ化: パスワードをハッシュ化して、認証情報の漏洩を防止する。
- セキュアな認証情報の保存: 認証情報を暗号化して、不正なアクセスから保護する。
上記のように、バックエンドでもセキュリティ対策を行うことで、Webアプリケーション全体のセキュリティを向上させることができます。ただし、常に最新の脆弱性情報を収集し、適切な対策を実施することが重要です。