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

Webアプリケーションのメジャーな脆弱性とその対策

社内勉強会用にWebアプリケーションにおけるメジャーな脆弱性についてです。
セブンペイ・PayPayから学ぶ認証セキュリティでは認証セキュリティについて触れていますので興味があればどうぞ。

SQLインジェクション

SQLインジェクションは、アプリケーションが想定しているSQLとは別のSQLを実行させるように操作する攻撃です。

掲示板機能を例にします。

DBの内容
mysql> select * from posts;
+----+---------------+----------+---------------------+---------------------+
| id | email         | body     | created_at          | deleted_at          |
+----+---------------+----------+---------------------+---------------------+
|  1 | test@test.com | testtest | NULL                | NULL                |
|  3 | test@test.com | aa       | 2019-10-05 00:00:00 | 2019-10-08 22:00:00 |
|  4 | test@test.com | bb       | 2019-10-05 00:00:00 | NULL                |
+----+---------------+----------+---------------------+---------------------+
3 rows in set (0.00 sec)

このデータを表示する画面はこのようになっています。

markdown-mark.png

postsテーブルのdeleted_atカラムに値が入っている場合は、該当のレコードが削除されたと判断し表示しないようなSQLをアプリケーション側で組み込んでいます。

アプリケーション側で発行されるSQL
select
    id, email, body, created_at
from
    posts
where
    deleted_at is null;

SQLインジェクションを試す

この掲示板画面には投稿を検索する機能を設けています。
投稿を検索のボックス内に、検索したい投稿内容の文字列を入力し検索ボタンをクリックすると検索結果が出るようになっています。

この時のSQL発行の実装はこちらです。
※例はJavaになっています。

アプリケーション実装
StringBuilder sql = new StringBuilder();
sql.append("select id, email, body, created_at ");
sql.append("from posts ");
sql.append("where ");
sql.append("deleted_at is null ");
sql.append("and body like '%").append(queryBody).append("%'");

// SQLインジェクションのテスト
List<Post> posts = new ArrayList<>();
jdbcTemplate.query(sql.toString(),
    new BeanPropertyRowMapper<Post>(Post.class))
        .stream().forEach(n -> {
            Post post = new Post(n.getId(), n.getEmail(),
                                 n.getBody(), n.getDeletedAt(),
                                 n.getCreatedAt());
            posts.add(post);
        });

こちらはSQLをStringBuilderで生成して、jdbcTemplate.queryでSQLを実行。そのままposts変数に結果を詰め替えているというような実装になっています。
StringBuilderを生成している途中で、queryBodyという変数があると思うのですが、この中には投稿を検索する際の検索文字列が入力されています。

では検索してみます。
検索ボックス内に、' OR 'A' like 'Aを入力します。

markdown-mark.png

以下は検索ボタンをクリックした結果です。

markdown-mark.png

delete_atに日付が入っている aa という投稿内容は、本来では表示されるべきではないのですが、表示されてしまいました。
これがSQLインジェクションになります。

SQLインジェクションで実行されたSQL
select
    id, email, body, created_at
from
    posts
where
    deleted_at is null
    and body like '%'
    or 'A' like 'A%';

今回の例は掲示板なのであまり影響ないだろうと思うかもしれませんが、1つでもSQLインジェクションが可能な状態の箇所があると、そこから他のテーブルの情報を取得することも可能なのでリスクがあります。
どこまで攻撃手法を教えていいものか判断に困るので、今回はお伝えしませんが、攻撃方法を変更することで、ユーザー情報が格納されたテーブルを取得されてしまう等のリスクがありますので、十分に気を付けてください。

以下はパスワードをSQLインジェクションで取得されてしまった例になります。

markdown-mark.png

対策

ではSQLインジェクションを防ぐにはどうすればよいかですが、バインド変数を利用します。
バインド変数を利用して実行されたSQLでは、渡されたパラメータがそのまま文字列として認識されるため、SQLインジェクションを防ぐことが可能です。

バインド変数を利用したアプリケーション実装
StringBuilder sql = new StringBuilder();
sql.append("select id, email, body, created_at ");
sql.append("from posts ");
sql.append("where ");
sql.append("deleted_at is null ");
sql.append("and body like ?");

// SQLインジェクションのテスト
List<Post> posts = new ArrayList<>();
jdbcTemplate.query(sql.toString(),
    new BeanPropertyRowMapper<Post>(Post.class),
    "%" + queryBody + "%")
        .stream().forEach(n -> {
            Post post = new Post(n.getId(), n.getEmail(),
                                 n.getBody(), n.getDeletedAt(),
                                 n.getCreatedAt());
                posts.add(post);
        });
バインド変数を利用したSQL
select
    id, email, body, created_at
from
    posts
where
    deleted_at is null
    and body like ?;

XSS

XSSはクロスサイトスクリプティングの略で、攻撃目的のスクリプトを他人の脆弱性のあるサイトに埋め込みを行う攻撃のことです。

さっそくイメージ図をどうぞ。

XSS説明.png

今回の例では、掲示板にJavaScriptのコードを投稿することで、次回以降該当の掲示板を開いた人に投稿したJavaScriptが実行されてしまうような状況を作り出しています。

具体的に見ていきます。

まずは掲示板投稿フォームに<script>alert('XSS!');</script>を入力して投稿します。

投稿.png

するとDBにはこのような内容が登録されます。

DBの内容
mysql> select * from posts;
+----+---------------+---------------------------------------+----------------------------+---------------------+
| id | email         | body                                  | created_at                 | deleted_at          |
+----+---------------+---------------------------------------+----------------------------+---------------------+
|  1 | test@test.com | testtest                              | NULL                       | NULL                |
|  3 | test@test.com | aa                                    | 2019-10-05 00:00:00        | 2019-10-08 22:00:00 |
|  4 | test@test.com | bb                                    | 2019-10-05 00:00:00        | NULL                |
|  5 | test@test.com | <script>alert('XSS!');</script>       | 2019-10-05 00:00:00        | NULL                |
+----+---------------+---------------------------------------+----------------------------+---------------------+
3 rows in set (0.00 sec)

次に該当のページを開いてみます。
すると、先ほど投稿したスクリプトが実行されて、アラートが表示されるようになりました。
これがXSSになります。

ぺージ表示.png

XSSの具体的な攻撃方法

XSSを利用した攻撃方法としては、セッション情報の取得があります。
以下が具体的な攻撃方法です。

XSSの目的.png
  1. 最初に攻撃者は、攻撃対象のサイトにJavaScriptを埋め込みます。
  2. JavaScriptが埋め込まれたぺージに利用者は遷移してきます。
  3. すると埋め込まれていたJavaScriptによりCookieの情報が攻撃者が用意したサーバに送信されます。
  4. 最後に攻撃者は送信されてきたCookieを利用してなりすましでログインが可能となります。

XSSの対策

XSSの対策は、ユーザーからの入力をそのまま表示するのではなく、エスケープ処理を行うことです。

今回の場合は、<script>alert('XSS!');</script>という内容をそのまま表示するようになっていましたが、&lt;script&gt;alert(&#039;XSS!&#039;);&lt;/script&gt;のようにHTMLの特殊文字を変換するようにしてあげることで、Scriptとして認識させずにそのまま画面に表示させることができます。

エスケープ.png

エスケープ処理はほとんどのフレームワークの中でやってくれると思うので、自分が利用するフレームワークでエスケープする方法(もしくはエスケープがされない場合)を調べて、XSS出来ないことを確認するテストを実施すればよいと思います。

CSRF

最後にCSRFになります。
CSRFはクロスサイトリクエストフォージェリの略で、攻撃者のサーバを閲覧した際に、同時に攻撃対象のアプリケーションへFormを実行させることです。

分かりにくいのでWikipediaを引用します。

CSRF脆弱性とは以下のような攻撃(CSRF攻撃)を可能にする脆弱性を指す:攻撃者はブラウザなどのユーザ・クライアントを騙し、意図しないリクエスト(たとえばHTTPリクエスト)をWebサーバに送信させる。Webアプリケーションがユーザ・クライアントからのリクエストを十分検証しないで受け取るよう設計されている場合、このリクエストを正規のものとして扱ってしまい、被害が発生する。CSRF攻撃はURL、画像の読み込み、XMLHttpRequestなどを利用して実行される。

引用 Wikipedia クロスサイトリクエストフォージェリ

CSRF攻撃のイメージ図になります。

CSRF説明.png

攻撃者が用意した画面のHTMLを見てみます。

攻撃者が用意した画面(メイン)
<body>
  <h1>Hello Nginx</h1>
  <iframe src="csrf.html"></iframe>
</body>
csrf.html
<body>
  <p>csrf iframe</p>
  <form action="http://localhost:8080/account/detail">
  </form>
  <script type="text/javascript">
    document.addEventListener("DOMContentLoaded", function() {
      document.forms[0].submit();
    });
  </script>
</body>

このようにiframe内に外部サーバ(今回はlocalhost:8080)にリクエストを投げるようなJavaScriptを用意しておきます。
今回のケースでは、EventListenerにDOMContentLoadedが指定されているので、この画面を起動した直後に実行されます。

今回はアカウント詳細画面を表示するだけですが、本来であればパスワード変更のリクエストを実行するようにします。

またiframeの内容が見えてしまうと、利用者に気付かれてしまうので、iframe自体を本来は隠すようにしているはずです。

対策方法

CSRFの対策方法としては、利用者ごとにトークンを発行して、POSTの度にトークンをユーザー側から送信するようにし、トークンの内容を検証することです。

リクエスト例:

リクエストURI: http://localhost:8080/account/edit
リクエストパラメータ
_csrf: 095278d0-8749-4ac7-a14b-7b2c1e7b7b51
password: password
passwordConfirmation: password

このようにcsrfに関するトークンをPOST時に含めて検証することで、CSRFの対策となる。

もう一つ、iframeはCSRFなどの攻撃に利用されることが多いため、外部サイトからフレームが呼び出されないように設定することも効果的です。
レスポンスヘッダーにX-Frame-Optionsというものがあるのですが、このヘッダーがDENYになっているとiframeからのぺージ表示が拒否されます。

これも対策をしておくと良いと思います。

Springについて

最後に、今回の内容はどのWebアプリケーションにおいても発生しうる内容ではありますが、自分への備忘も含めてSpringでの上記対策について記載しておきます。

SQLインジェクション

ORMによる。
DOMA2であれば、シングルコーテーションを含むSQL生成を拒否するので安全。

XSS

thymeleafでは、出力内容をエスケープしてくれる方法があります。
適切な使い分けをしてください。

エスケープしてくれる
<td th:text="${post.body}"></td>
エスケープしない
<td th:utext="${post.body}"></td>

詳細はこちら thymeleaf->XSS対策1

CSRF

Spring Secutiryを利用していれば、デフォルトでCSRF対策をしてくれます。

もしCSRF対策を無効にする場合には、以下のような記載が必要です。

Configファイル
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
}

こちらが分かりやすいかもしれません。 TERASOLUNA->CSRF対策

参考文献


  1. 少し情報が古いです 

Why do not you register as a user and use Qiita more conveniently?
  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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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