脆弱性を攻撃してみよう (2) オープンリダイレクト

## はじめに

Webアプリケーションに**オープンリダイレクト**の脆弱性があると、どのような攻撃を受けるでしょうか？実際にオープンリダイレクトを攻撃してみましょう。

<table width="100%"><tr><td>※この記事は、[バグだらけのWebアプリケーション](http://qiita.com/tamura__246/items/7275c7406706fb0057e8)を使って、いろいろな脆弱性とそれを攻撃、防御する方法を紹介する記事の2回目です。1回目の記事は[ここ](http://qiita.com/tamura__246/items/6307889936d6e7c98403)にあります。</td></tr><table>

## 攻撃してみよう

オープンリダイレクトについて詳しい説明する前に、オープンリダイレクトの脆弱性を攻撃してみましょう。

Webアプリケーションを[ここ](https://github.com/k-tamura/easybuggy/releases)からダウンロードして、次のコマンドで起動します。

```bash
java -jar easybuggy.jar
```

※実行するにはJavaが必要です。詳細については、[こちらのページ](https://t246osslab.wordpress.com/2017/03/14/easybuggy-%e3%81%ae%e3%83%88%e3%83%aa%e3%82%bb%e3%83%84/)を参照して下さい。

<table width="100%"><tr><td>※この記事では、バージョン1.3.6を使用しています。今後のリリースで動作は変わる可能性があります。</td></tr><table>

起動したら、[http://localhost:8080](http://localhost:8080)にアクセスして下さい。以下のような画面の真ん中に「脆弱性」と書かれたセクションがあります。

![main.png](https://qiita-image-store.s3.amazonaws.com/0/43869/c3eae6bc-7580-9917-72fb-990c3965452a.png)

この中の上から10番目に「オープンリダイレクト可能なログイン画面」のリンクがあります。クリックすると、次のような画面が表示されます。

![Screenshot from 2017-09-21 02-26-59.png](https://qiita-image-store.s3.amazonaws.com/0/43869/6d442e95-f4fd-c105-b843-8f152598ef78.png)

ユーザーIDに「`admin`」、パスワードに「`password`」を入力し、ログインボタンをクリックすると、次のような画面が表示されます。

![Screenshot from 2017-09-21 02-23-27.png](https://qiita-image-store.s3.amazonaws.com/0/43869/755ff511-61a7-b781-0b26-487b1b65697e.png)

これだけの機能ですが、オープンリダイレクトの脆弱性があります。注目するのはURLです。最初にアクセスしたログイン画面のURLは`http://localhost:8080/openredirect/login?goto=/uid/serverinfo.jsp`で、ログイン後のURLは`http://localhost:8080/uid/serverinfo.jsp`となっています。`goto=/uid/serverinfo.jsp`というクエリストリングで、ログイン後の遷移先を指定しているのが推測できるかと思います。

いったんログアウトして、今度は以下のリンクをクリックして下さい。

http://localhost:8080/openredirect/login?goto=%2f%2f%6b%2d%74%61%6d%75%72%61%2e%67%69%74%68%75%62%2e%69%6f%2f%6f%70%65%6e%72%65%64

先ほどと同じログイン画面が表示されます。再度「`admin`」、「`password`」でログインします。

![Screenshot from 2017-09-20 20-42-46.png](https://qiita-image-store.s3.amazonaws.com/0/43869/290a5ef4-1979-ed7d-bafa-a46e7dee4ab5.png)

ログインできたでしょうか？ログインできたとは思いますが、その前に以下のようなログインエラー画面が表示されたはずです。

![Screenshot from 2017-09-20 20-42-57.png](https://qiita-image-store.s3.amazonaws.com/0/43869/a58ec7ce-9bf4-3971-5176-b9c07e3795de.png)

この時、再入力したユーザーIDとパスワードは外部のサイトに送信されていたことに気づきましたか？

ログインエラー画面のブラウザのURLを見て下さい。外部のサイトのURL(`https://k-tamura.github.io/openred`)になっています。ログインエラー画面が表示されましたが、実際にはログインは成功しており、このWebアプリケーションの画面に似せたエラー画面を持つ外部サイトに遷移していたわけです。ユーザーIDとパスワードを再入力してログインボタンをクリックした後、本来の遷移先にリダイレクトされているので、外部サイトに遷移したことに気づかなかった人もいたのではないでしょうか。

## オープンリダイレクトとは

このように、オープンリダイレクトとは、Webアプリケーションのリダイレクト機能を悪用して、ユーザーを悪意のあるサイトに誘導する攻撃のことを言います。

攻撃者は、先ほどのようなリンクをユーザーにクリックさせて、気付かないうちにユーザーを攻撃者のサイトに誘導させます。さらに今回のように、精巧に偽せたログインエラー画面を外部サイトに用意して、ユーザーIDとパスワードを入力させようとするかもしれません(「フィッシング」)。今回はお試しアプリから悪意の無い私のサイト(`https://k-tamura.github.io/`)へのリダイレクトでしたが、これがオンラインバンキングから悪意のあるサイトへのリダイレクトであれば大変なことです。

![open_redirect.png](https://qiita-image-store.s3.amazonaws.com/0/43869/70f76e72-143b-d20a-dffc-487415f29eb8.png)

このWebアプリケーション(EasyBuggy)のように、リクエストパラメータなどで指定したURLにユーザーをリダイレクトする機能を持っているWebアプリケーションには、オープンリダイレクトの脆弱性が存在する可能性があります。

ただし、Webアプリケーションが想定外のURLにユーザーをリダイレクトしないように、チェックしていれば問題はありません。チェックせずに任意のURLへリダイレクトしているのであれば、攻撃者にリクエストパラメータを書き換えられて、ユーザーを危険なサイトへと誘導されてしまいます。

## どのような実装になっているか

今回試したログイン機能は、どのような実装になっているのでしょうか。[ソースコード](https://github.com/k-tamura/easybuggy/blob/master/src/main/java/org/t246osslab/easybuggy/vulnerabilities/OpenRedirectServlet.java)を見てみましょう。重要な部分は以下です。

```java
    String gotoUrl = request.getParameter("goto");
    if (gotoUrl != null) {
        response.sendRedirect(gotoUrl);
    } else {
```

リクエストパラメータ`goto`の値を検証せずに、`sendRedirect`しています。

## オープンリダイレクトの対策

では、どのような対策をすればいいでしょうか？

対策はシンプルで、**リダイレクト先が遷移先として適切なURLであるかをチェックすること**です。ただし、気をつけないといけないのは、**チェック処理の実装方法**です。

外部サイトへのリダイレクトを禁止する目的で「`http`」で始まるURLを拒否したとしても、前述の例のように「`//k-tamura.github.io/openred`」をほとんどのブラウザが「`http://k-tamura.github.io/openred`」と解釈するので、適切な対策とはいえません。許可できるリダイレクト先が明確にリストアップされているのであれば、それだけを完全一致でチェックするような実装をした方が、部分一致や正規表現でチェックするような実装よりも安全と考えられます。

例えば、リダイレクト先が`/uid/serverinfo.jsp`と`/uid/clientinfo.jsp`しかないのであれば、以下のような実装に修正するのが一番安全です。

```java
    String gotoUrl = request.getParameter("goto");
    if ("/uid/serverinfo.jsp".equals(gotoUrl) || "/uid/clientinfo.jsp".equals(gotoUrl)) {
        res.sendRedirect(gotoUrl);
    } else {
```

しかし、そもそもこういった機能が本当に必要なのかを検討して、必要なければ別の方式を考えた方がいいかもしれません。

## 参考

 - [OWASP - Unvalidated Redirects and Forwards Cheat Sheet](https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet)
 - [CWE-601: URL Redirection to Untrusted Site ('Open Redirect')](https://cwe.mitre.org/data/definitions/601.html)
 - [@IT - これなら合格！ 正しいリダイレクターの作り方](http://www.atmarkit.co.jp/ait/articles/1406/20/news007.html)