脆弱性を攻撃してみよう (3) メールヘッダーインジェクション

## はじめに

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

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

## メールヘッダーインジェクションとは

**メールヘッダーインジェクションとは、メールの件名などの入力値を改ざんすることで、`Bcc: `などのメールヘッダーを付加する攻撃のことをいいます。**問い合わせフォームのようなメール送信画面にメールヘッダーインジェクションに対する脆弱性があると、迷惑メールの送信に悪用されてしまう可能性があります。

![mail_header_injection.png](https://qiita-image-store.s3.amazonaws.com/0/43869/213f5461-7d6f-3128-eede-2350fc561ff5.png)

## 攻撃してみよう

言葉で説明するよりも試した方が理解しやすいと思いますので、実際にメールヘッダーインジェクションの脆弱性を攻撃してみましょう。

メールヘッダーインジェクションの攻撃をするには、メールを送信するためのSMTPサーバーが必要です。GoogleなどのSMTPサーバーを利用しても構いませんが、今回はFakeSMTPというテスト用のダミーSMTPサーバーをローカルで動作させます。FakeSMTPを[このページ](http://nilhcem.com/FakeSMTP/download.html)からダウンロードし、解凍したら、次のコマンドで起動します。

```bash
$ java -jar fakeSMTP-2.0.jar
```

起動すると、次のような画面が表示されます。ポート番号は25のままで、「サーバー起動」ボタンをクリックします（競合などにより、ポート番号に25が使用できない場合は、25以外に変更して下さい）。

![2018-06-14 22.13.50 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/ddca6753-b8a9-ca4c-f2e2-d34ee1b6cdba.png)

次にEasyBuggyを[ここ](https://github.com/k-tamura/easybuggy/releases)からダウンロードして、以下のコマンドで起動します。

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

<table width="100%"><tr><td>※実行するにはJava(JRE) 6以上が必要です。詳細については、[こちらのページ](https://t246osslab.wordpress.com/2017/03/14/easybuggy-%e3%81%ae%e3%83%88%e3%83%aa%e3%82%bb%e3%83%84/)を参照して下さい。また、この記事ではバージョン1.3.9を使用しています。今後のリリースで動作は変わる可能性があります。</td></tr><table>

EasyBuggyが使用するSMTPサーバーの設定は、[`application.properties`](https://github.com/k-tamura/easybuggy/blob/master/src/main/resources/application.properties)に定義してありますが、この定義は起動時の`-D`オプションで上書きすることができます。SMTPサーバーのポート番号を変更している場合は、それに合わせて以下のようなパラメータを追加し、EasyBuggyを起動します。

```bash
$ java -jar -Dmail.smtp.port=9925 easybuggy.jar
```

ちなみにOutlook.comのSMTPサーバーを利用する場合は、以下のように起動します。

```bash
$ java -jar \
 -Dmail.smtp.host=smtp-mail.outlook.com \
 -Dmail.smtp.port=587 \
 -Dmail.smtp.auth=true \
 -Dmail.smtp.starttls.enable=true \
 -Dmail.user=xxxxx@hotmail.com \
 -Dmail.password=password \
 -Dmail.admin.address=xxxxx@gmail.com \
 easybuggy.jar 
```

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

![2018-06-14 22.09.58 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/b228a053-b9cd-95bd-2673-10b3d0fa590e.png)

この中の上から6番目に「メールヘッダーインジェクション」のリンクがあるので、クリックすると、次のような問い合わせフォームが表示されます。

![2018-06-14 22.07.45 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/ec8c034e-971d-d6ef-fb05-04fe1060e2a9.png)

適当な値を入力して、送信ボタンをクリックしてみましょう。

![2018-06-14 22.32.25 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/884d66c2-766d-0f11-ccb2-02436f8f6e1f.png)

すると、FakeSMTPはメールを1件受信するはずです。

![2018-06-14 22.15.07 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/f87eaab8-3726-1841-1c98-cc4bc8e857b7.png)

このメールをクリックして、内容を確認してみましょう。

```text
Thu, 14 Jun 2018 22:14:43 +0900 (JST)
Date: Thu, 14 Jun 2018 22:14:43 +0900 (JST)
To: root@localhost
Message-ID: <219170872.3.1528982083164.JavaMail.tamura@tamura-virtual-machine>
Subject: test
MIME-Version: 1.0
Content-Type: multipart/mixed; 
	boundary="----=_Part_2_1213124274.1528982083161"

------=_Part_2_1213124274.1528982083161
Content-Type: text/html;charset=UTF-8
Content-Transfer-Encoding: quoted-printable

=E5=90=8D=E5=89=8D: Kohei<br>=E3=83=A1=E3=83=BC=E3=83=AB=E3=82=A2=E3=83=89=
=E3=83=AC=E3=82=B9: kohei@gmail.com<br><br>=E6=9C=AC=E6=96=87: test!!<br>
------=_Part_2_1213124274.1528982083161--
```

メールヘッダーを見て分かるように、件名（`Subject: `）には画面で入力した件名（`test`）がセットされています。宛先（`To: `）は入力とは無関係な固定値（`root@localhost`）で、ボディ部分に件名以外のすべての入力値が含まれるようになっています。したがって、通常の使い方の範囲内であれば、画面の入力値を変更しても`root@localhost`宛にしかメールは送信されないはずです。

しかし、実際には`root@localhost`以外の宛先にメールを送信するよう入力値を改ざんすることができます。実際にやってみるので、下の画像をよく見て下さい（画像をクリックすると、改ざん操作を最初から見ることができます）。

![mov_ml_inj.gif](https://qiita-image-store.s3.amazonaws.com/0/43869/eb66739a-f9ae-e969-a14f-edb2135aa3b2.gif)

分かりましたか？件名の入力欄を`textarea`に変更し、改行してから「Bcc: 」で始まる１行を追加したわけです。その後送信ボタンをクリックすると、FakeSMTPは次のようなメールを受信します。

```text
Thu, 14 Jun 2018 22:45:57 +0900 (JST)
Date: Thu, 14 Jun 2018 22:45:57 +0900 (JST)
To: root@localhost
Message-ID: <645809008.5.1528983957933.JavaMail.tamura@tamura-virtual-machine>
Subject: test
Bcc: ml@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; 
	boundary="----=_Part_4_350452366.1528983957931"

------=_Part_4_350452366.1528983957931
Content-Type: text/html;charset=UTF-8
Content-Transfer-Encoding: quoted-printable

=E5=90=8D=E5=89=8D: Kohei<br>=E3=83=A1=E3=83=BC=E3=83=AB=E3=82=A2=E3=83=89=
=E3=83=AC=E3=82=B9: kohei@gmail.com<br><br>=E6=9C=AC=E6=96=87: test!!<br>
------=_Part_4_350452366.1528983957931--
```

`Subject: test`の後に`Bcc: ml@example.com`のメールヘッダーが追加されたのが確認できます。`Bcc: `で指定したので、管理者に気づかれることなく、`ml@example.com`というアドレスにメールを送信できました。`ml@example.com`が多数のユーザーを含むメーリングリストのアドレスであれば、そのすべてのユーザーにこのアプリケーションから大量のスパムメールを送信できるわけです。もちろん複数のメールアドレスを`Bcc: `に追加することもできます。

ちなみに、今回はブラウザの開発者モードで件名の入力欄を`textarea`に変更しましたが、ブラウザを介さずにcURLコマンドでHTTPリクエストを送ることもできます。このように、クライアントサイドではいくらでも改ざんができるので、ブラウザの入力桁数の制限やJavaScriptでの入力チェックを追加してもそれだけは不十分で、サーバーサイドでのチェックが必要となります。

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

では、どのような実装になっているのでしょうか。ソースコードを見てみましょう。問題となっている[サーブレット](https://github.com/k-tamura/easybuggy/blob/master/src/main/java/org/t246osslab/easybuggy/vulnerabilities/OGNLExpressionInjectionServlet.java)は`EmailUtils`というクラスの`sendEmailWithAttachment()`メソッドを呼び出しているだけです。[このメソッドの実装](https://github.com/k-tamura/easybuggy/blob/master/src/main/java/org/t246osslab/easybuggy/core/utils/EmailUtils.java#L46)も以下のように、Javaの標準ライブラリーであるJavaMailを使用したごく一般的なメール送信の実装です。

```java
// creates a new e-mail message
Message msg = new MimeMessage(session);
if (!StringUtils.isBlank(ApplicationUtils.getSmtpUser())){
    msg.setFrom(new InternetAddress(ApplicationUtils.getSmtpUser()));
}
InternetAddress[] toAddresses = { new InternetAddress(ApplicationUtils.getAdminAddress()) };
msg.setRecipients(Message.RecipientType.TO, toAddresses);
((MimeMessage)msg).setSubject(subject,"UTF-8");
msg.setSentDate(new Date());
msg.setHeader("Content-Transfer-Encoding", "7bit"); 

// creates message part
MimeBodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setContent(message, "text/html;charset=UTF-8");

// creates multi-part
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);

// adds attachments
if (attachedFiles != null && !attachedFiles.isEmpty()) {
    for (File aFile : attachedFiles) {
        MimeBodyPart attachPart = new MimeBodyPart();
         try {
            attachPart.attachFile(aFile);
        } catch (IOException e) {
            log.error("IOException occurs: ", e);
        }
         multipart.addBodyPart(attachPart);
    }
}

// sets the multi-part as e-mail's content
msg.setContent(multipart);

// sends the e-mail
Transport.send(msg);
```

では、なぜこの問題が起きたのでしょうか？

実はこのライブラリーのバージョンが関係しています。`pom.xml`を見るとわかりますが、バージョンが1.5.1で、ちょっと古いです。

```xml
<dependency>
	<groupId>com.sun.mail</groupId>
	<artifactId>javax.mail</artifactId>
	<version>1.5.1</version>
</dependency>
```

現在(2018年6月時点)のJavaMailの最新バージョンは1.6.1です。1.5.5以前のバージョンのJavaMailは添付ファイル付きメールを送信する際に、件名に改行含まれているかどうかをチェックしないため、メールヘッダーインジェクションができてしまいます。

## メールヘッダーインジェクションの対策

したがって、この場合の対策は単純で、1.5.6以上のメールライブラリーを使用するということです。一般的なメールライブラリーは、そういった脆弱性が起きないようにすでに対策されているはずですが、何らかの理由で古いバージョンを使用しなければならない場合や、自作する必要があるような場合は注意が必要です。

一般的なメールヘッダーインジェクションの対策は以下の2つです。

 - 対策済みのバージョンのメールライブラリーを使用する
 - メールヘッダーに改行が含まれていないことをチェックするロジックを実装する（古いバージョンのライブラリーを使用したり、メール送信機能を自作する場合）

では、前者の対策を試してみましょう。次のコマンドでソースコードを取得し、

```bash
$ git clone https://github.com/k-tamura/easybuggy.git
```

`pom.xml`のJavaMailのバージョンを対策済みの1.5.6に書き換えます。

```xml
<dependency>
	<groupId>com.sun.mail</groupId>
	<artifactId>javax.mail</artifactId>
	<version>1.5.6</version>
</dependency>
```

そして、ビルドします。

```bash
$ mvn clean package
```

ビルドしたら、再度EasyBuggyを起動します。そしてもう一度先ほどの問い合わせフォームで件名を改ざんしたメールを送信し、FakeSMTPが受け取ったメールを見てみましょう。

```text
Sat, 16 Jun 2018 10:30:55 +0900 (JST)
Date: Sat, 16 Jun 2018 10:30:54 +0900 (JST)
To: root1@localhost
Message-ID: <1745459456.1.1529112655003@openam01.example.co.jp>
Subject: hhhhhhh9uuuuuuuuuuu999
 Bcc: test@test.com
MIME-Version: 1.0
Content-Type: multipart/mixed; 
	boundary="----=_Part_0_1979439733.1529112654887"

------=_Part_0_1979439733.1529112654887
Content-Type: text/html;charset=UTF-8
Content-Transfer-Encoding: 7bit

Name: yyy<br>Mail Address: yyyyyyyyyyyyy<br><br>Content: sssssssssssssss<br>
------=_Part_0_1979439733.1529112654887--
```

今回も`Bcc: `が入っていますが、よくよく見ると、その前に半角スペースが入っています。これで対策ができたことになるのでしょうか？メールの仕様を規定している[RFC 2822「Internet Message Format」](https://www.ietf.org/rfc/rfc2822.txt)を見てみましょう。

> 2.2.3. Long Header Fields
> 
>    Each header field is logically a single line of characters comprising
>    the field name, the colon, and the field body.  For convenience
>    however, and to deal with the 998/78 character limitations per line,
>    the field body portion of a header field can be split into a multiple
>    line representation; this is called "folding".  The general rule is
>    that wherever this standard allows for folding white space (not
>    simply WSP characters), a CRLF may be inserted before any WSP.  For
>    example, the header field:
> 
>            Subject: This is a test
> 
>    can be represented as:
> 
>            Subject: This
>             is a test

簡単に訳すと、長いヘッダフィールドは改行と半角スペースまたはタブで複数行に分割できるということです。したがって、半角スペース付きの` Bcc: `は件名の2行目として扱われることになります。

実際にメールが送られなくなるかどうかは、SMTPサーバーを前述のOutlook.comやGoogleのSMTPサーバーに変更すると分かります。

## Java以外の言語でメールヘッダーインジェクションは起きるのか？

Pythonでも標準ライブラリである_smtplib_の古いバージョンを使用していると、メールヘッダーインジェクションを攻撃される可能性があります。[こちらの記事](https://qiita.com/tamura__246/items/0c0df9f1a774ced07b0f)で紹介した「EasyBuggy Django」で、3.1.3以前のバージョンのPython 3.xを使うと、メールヘッダーインジェクションを再現できます。また、最新のPythonを使ったとしても、件名に改行＋`bcc`＋半角スペース＋`:`＋メールアドレスを付加することができます（改行＋`bcc:`＋半角スペース＋メールアドレスを付加することはできませんが）。

 - `bcc :ml@example.com` -> メールヘッダーインジェクションの攻撃可能（※ただし、RFCに準拠していない形式だが、一部のSMTPサーバはこれをエラーとせずに、送信する）
 - `bcc: ml@example.com` -> メールヘッダーインジェクションの攻撃不可能（エラーとなる）

このあたりについては、[私のブログ](https://t246osslab.wordpress.com/2018/05/09/python%e3%81%a7%e3%81%ae%e3%83%a1%e3%83%bc%e3%83%ab%e3%83%98%e3%83%83%e3%83%80%e3%83%bc%e3%82%a4%e3%83%b3%e3%82%b8%e3%82%a7%e3%82%af%e3%82%b7%e3%83%a7%e3%83%b3%e5%af%be%e7%ad%96%e3%81%ab%e3%81%a4/)に書きました。興味がある方は読んでみて下さい。

## 参考

 - [Fortify ソフトウェア セキュリティ エラー Header Manipulation: SMTP](https://vulncat.hpefod.com/ja/detail?id=desc.dataflow.java.header_manipulation_smtp#Java%2fJSP)
 - [COMPASS SECURITY ADVISORY - CSNC-2014-001](https://www.compass-security.com/fileadmin/Datein/Research/Advisories/CSNC-2014-001_javamail_advisory_public.txt)

　[![mark.png](https://github.com/k-tamura/cocha-icons/raw/master/designed_by_cocha_icons.gif)](https://github.com/k-tamura/cocha-icons)