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


はじめに

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

※この記事は、バグだらけのWebアプリケーション(EasyBuggy)を使って、いろいろな脆弱性とそれを攻撃、防御する方法を紹介する連載の3回目です。第1回の記事はここにあります。

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

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

攻撃してみよう

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

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

$ java -jar fakeSMTP-2.0.jar

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

次にEasyBuggyをここからダウンロードして、以下のコマンドで起動します。

$ java -jar easybuggy.jar

※実行するにはJava(JRE) 6以上が必要です。詳細については、こちらのページを参照して下さい。また、この記事ではバージョン1.3.9を使用しています。今後のリリースで動作は変わる可能性があります。

EasyBuggyが使用するSMTPサーバーの設定は、application.propertiesに定義してありますが、この定義は起動時の-Dオプションで上書きすることができます。SMTPサーバーのポート番号を変更している場合は、それに合わせて以下のようなパラメータを追加し、EasyBuggyを起動します。

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

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

$ java -jar \
 -Dmail.smtp.host=smtp-mail.outlook.com \
 -Dmail.smtp.port=587 \
 -Dmail.smtp.auth=true \
 -Dmail.smtp.starttls.enable=true \
 -Dmail.user=[email protected] \
 -Dmail.password=password \
 -Dmail.admin.address=[email protected] \
 easybuggy.jar 

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

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

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

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

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

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: [email protected]<br><br>=E6=9C=AC=E6=96=87: test!!<br>
------=_Part_2_1213124274.1528982083161--

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

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

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

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: [email protected]
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: [email protected]<br><br>=E6=9C=AC=E6=96=87: test!!<br>
------=_Part_4_350452366.1528983957931--

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

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

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

では、どのような実装になっているのでしょうか。ソースコードを見てみましょう。問題となっているサーブレットEmailUtilsというクラスのsendEmailWithAttachment()メソッドを呼び出しているだけです。このメソッドの実装も以下のように、Javaの標準ライブラリーであるJavaMailを使用したごく一般的なメール送信の実装です。

// 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で、ちょっと古いです。

<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つです。

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

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

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

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

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

そして、ビルドします。

$ mvn clean package

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

Sat, 16 Jun 2018 10:30:55 +0900 (JST)
Date: Sat, 16 Jun 2018 10:30:54 +0900 (JST)
To: root1@localhost
Message-ID: <[email protected]>
Subject: hhhhhhh9uuuuuuuuuuu999
 Bcc: [email protected]
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」を見てみましょう。

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の古いバージョンを使用していると、メールヘッダーインジェクションを攻撃される可能性があります。こちらの記事で紹介した「EasyBuggy Django」で、3.1.3以前のバージョンのPython 3.xを使うと、メールヘッダーインジェクションを再現できます。また、最新のPythonを使ったとしても、件名に改行+bcc+半角スペース+:+メールアドレスを付加することができます(改行+bcc:+半角スペース+メールアドレスを付加することはできませんが)。

  • bcc :[email protected] -> メールヘッダーインジェクションの攻撃可能(※ただし、RFCに準拠していない形式だが、一部のSMTPサーバはこれをエラーとせずに、送信する)
  • bcc: [email protected] -> メールヘッダーインジェクションの攻撃不可能(エラーとなる)

このあたりについては、私のブログに書きました。興味がある方は読んでみて下さい。

参考