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>
そして、ビルドします。
ビルドしたら、再度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:
can be represented as:
簡単に訳すと、長いヘッダフィールドは改行と半角スペースまたはタブで複数行に分割できるということです。したがって、半角スペース付きのBcc:
は件名の2行目として扱われることになります。
実際にメールが送られなくなるかどうかは、SMTPサーバーを前述のOutlook.comやGoogleのSMTPサーバーに変更すると分かります。
Java以外の言語でメールヘッダーインジェクションは起きるのか?
Pythonでも標準ライブラリであるsmtplibの古いバージョンを使用していると、メールヘッダーインジェクションを攻撃される可能性があります。こちらの記事で紹介した「EasyBuggy Django」で、3.1.3以前のバージョンのPython 3.xを使うと、メールヘッダーインジェクションを再現できます。また、最新のPythonを使ったとしても、件名に改行+bcc
+半角スペース+:
+メールアドレスを付加することができます(改行+bcc:
+半角スペース+メールアドレスを付加することはできませんが)。
このあたりについては、私のブログに書きました。興味がある方は読んでみて下さい。
参考