最新のフレームワークであるSpring Bootをベースにしたアプリケーションにも、脆弱性やリソースリークをつくり込むことはできるでしょうか?また、どのような実装をすると問題となるのでしょうか?
実際に検証してみました。
検証方法
レガシーな技術でつくった、バグだらけのWebアプリケーション「EasyBuggy」のクローンをSpring Bootベースでつくれるか、全てのバグを同様に実装できるどうかで検証しました。
結果(実装の可否)
結果から言うと、Spring Bootベースで開発しても、全てのバグをつくり込むことができました。
※このアプリは[「EasyBuggy Boot」](https://github.com/k-tamura/easybuggy4sb/blob/master/README.jp.md)という名前でGitHubに公開しました。詳細については、[こちらのページ](http://qiita.com/tamura__246/items/c8229d68ef199890827b)を参照して下さい。 |
バグ | 可否 | 特記事項 |
---|---|---|
デッドロック (Java) | ○ | |
デッドロック (SQL) | ○ | 明示的にトランザクション管理することで実現。 |
完了しないプロセスの待機 | ○ | |
無限ループ | ○ | |
メモリリーク (Javaヒープ領域) | ○ | |
メモリリーク (パーマネント領域/MetaSpace) | ○ | |
メモリリーク (Cヒープ領域) | ○ | |
ネットワークソケットリーク | ○ | |
データベースコネクションリーク | ○ | Spring JDBCにはプールからコネクションを取得するメソッドがあるので、それを利用。 |
ファイルディスクリプタリーク | ○ | |
スレッドリーク | ○ | |
文字化け | ○ | デフォルトで、文字コードに関する煩わしい問題が発生しないような仕組みになっている。そのため、文字化けは発生しにくいが、文字コードを変更する方法は提供されている。 |
整数オーバーフロー | ○ | |
丸め誤差 | ○ | |
打ち切り誤差 | ○ | |
情報落ち | ○ | |
XSS (クロスサイトスクリプティング) | ○ | Thymeleafを使っていれば、XSSは簡単に実現可能。 |
SQLインジェクション | ○ | Spring JDBCを使っていても、SQLの文字列にユーザーの入力値を連結すれば実現できる。 |
LDAPインジェクション | ○ | Spring LDAPのメソッドが、LDAPインジェクションの対象となる文字をエスケープしていなかったので、それを利用。 |
コードインジェクション | ○ | |
OSコマンドインジェクション | ○ | |
メールヘッダーインジェクション | △ | 古いバージョンのJavaMailでSpring Mailをオーバーライドして実現。 |
Nullバイトインジェクション | △ | バージョン1.7.0_40より前のJavaを使用することで実現可能。 |
サイズ制限の無いファイルアップロード | △ | application.propertiesで明示的にサイズ制限を無効(またはそれと同等に大きな値)にできる。 |
拡張子制限の無いファイルアップロード | ○ | |
オープンリダイレクト可能なログイン画面 | ○ | |
ブルートフォース攻撃可能なログイン画面 | ○ | |
セッション固定攻撃可能なログイン画面 | ○ | Spring Scurityのセッション固定攻撃の抑止機能を正しく実装しなければ、実現可能。 |
親切過ぎる認証エラーメッセージ | ○ | |
危険なファイルインクルード | △ | ThymeleafではなくをJSPを使用することで実現。 |
パストラバーサル | △ | ThymeleafではなくをJSPを使用することで実現。 |
意図しないファイル公開 | △ | ディレクリリスティングを有効にすれば可能。 |
CSRF (クロスサイトリクエストフォージェリ) | ○ | Spring ScurityのCSRF攻撃の抑止機能を正しく実装しなければ、実現可能。 |
クリックジャッキング | ○ | Spring Scurityのクリックジャッキングの抑止機能を正しく実装しなければ、実現可能。 |
XEE (XMLエンティティ拡張) | ○ | |
XXE (XML外部エンティティ) | ○ | |
正規表現解析による遅延 | ○ | |
プラス演算子による文字列結合の遅延 | ○ | |
不必要なオブジェクト生成による遅延 | ○ |
EasyBuggyでは、NullPointerException
などの例外やOutOfMemoryError
などのエラーもワンクリックで発生するように実装していますが、それらについても、Javaのロジック上の問題のため、Spring Bootを使うかどうかに関わらず、つくり込むことができます。
解説
少し詳細に説明します。どのような実装になっているかは、実際にソースコードを参照いただいた方がいいかと思います。
デッドロック (SQL)
PlatformTransactionManager
かアノテーション@Transactional
で、明示的にトランザクションを管理するように実装し、複数スレッドが逆順でレコードを更新すれば、デッドロックを実現できます。
データベースコネクションリーク
Spring JDBCを使う場合、基本的に直接コネクションプールからコネクションを取得する必要は無いと思います。ただし、それができなくなったわけではく、コネクションを取得するメソッドも存在するので、そのメソッドを利用してコネクションリークをつくり込むことは可能です。
文字化け
意外と実装しづらかったのが文字化けの問題です。サーブレットフィルターで文字コードを適当なものに変更すれば、文字化けすると思っていたのですが、実際にやってみると文字化けしませんでした。OrderedCharacterEncodingFilter
を継承し、super.setEncoding("[適当な文字コード]");
することで文字コードをUTF-8以外に変更でき、文字化けを発生させるようにしました。
XSS (クロスサイトスクリプティング)
Thymeleaf のth:utext
を使用すれば、サニタイズ処理は行われないので、XSSは簡単に実装できます。
SQLインジェクション
SQLの文字列は連結してつくれるので、Spring JDBCを使っても、SQLインジェクションの回避は不可能です。
LDAPインジェクション
Spring LDAPを使いましたが、LDAPフィルターの文字列をエスケープしていないメソッドがありました。エスケープ処理の実装漏れのバグかと思ったのですが、LdapQuery.filter(String)
のJavadocを読むと、エスケープしない旨が記載されていました。
/**
* Specify a hardcoded filter. Please note that using this method, the filter string will not be
* validated or escaped in any way. <b>Never</b> use direct user input and use it concatenating strings
* to use as LDAP filters. Doing so opens up for "LDAP injection", where malicious user
* may inject specifically constructed data to form filters at their convenience. When user input is used
* consider using {@link #where(String)} or {@link #filter(String, Object...)} instead.
*
* @param hardcodedFilter The hardcoded filter string to use in the search.
* @return this instance.
* @throws IllegalStateException if a filter has already been specified.
*/
public LdapQuery filter(String hardcodedFilter) {
initRootContainer();
rootContainer.append(new HardcodedFilter(hardcodedFilter));
return this;
}
メールヘッダーインジェクション
pom.xml
のdependency
の<artifactId>javax.mail</artifactId>version>1.5.1</version>
で下位のバージョンに上書きしなければ実現できなかったので、最新のSpring Bootを利用している場合は、この脆弱性を気にしなくてもいいかと思います。
Nullバイトインジェクション
バージョン1.7.0_40より前のJavaの脆弱性なので、Spring Bootの推奨する前提バージョン1.8では再現できません。ただし、Spring Bootはバージョン1.7のJavaでも動かすことができるので、その場合は再現が可能です。
サイズ制限の無いファイルアップロード
application.properties
に明示的にspring.http.multipart.max-file-size=-1
を定義することで再現できます。
セッション固定攻撃
このアプリではSpring Scurityは実装しておらず、対策もしていません。以下のようにSpring Scurityのセッション管理機能を使用すると、セッション固定攻撃対策が有効になります。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionFixation().migrateSession();
}
}
なお、この例では、.sessionFixation().migrateSession()
を呼び出しているので、ログイン前に使用していたセッションを破棄し、新たにセッションを生成します。
危険なファイルインクルード
Spring Bootでは非推奨となっているJSP/JSTLを使うことで実現しました。ThymeleafにはJSTLのc:import
の代替が無いので、リモートファイルインクルードが実現できませんが、th:include
をすることでローカルファイルインクルードは実現できます。
パストラバーサル
Spring Bootでは非推奨となっているJSP/JSTLを使うことで実現しました。Thymeleafを使用する場合は、「../
」でディレクトリをさかのぼれないため、パストラバーサルが実現できませんでした。
意図しないファイル公開
次のような実装で実現しましたが、これを行うと、ThymeleafやJSPを使用した動的なページ生成ができなくなります。同じバージョンの非Spring Boot組み込みのTomcatのディレクリリスティングとは動作が異なっているので、Spring Bootのバグかもしれません(この実装が正しければ)。
@Bean
public ServletRegistrationBean servletRegistrationBean() {
/* Enable directory listing under /uid/ */
final DefaultServlet servlet = new DefaultServlet();
final ServletRegistrationBean bean = new ServletRegistrationBean(servlet, "/uid/*");
bean.setEnabled(true);
bean.addInitParameter("listings", "true");
bean.setLoadOnStartup(1);
return bean;
}
CSRF (クロスサイトリクエストフォージェリ)
CSRF攻撃の抑止機能はSpring Security 4.0からデフォルトで有効となっています。Spring Security を導入していたとしても、以下のように明示的に無効にしていれば、問題になります。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
クリックジャッキング
Spring Scurityにクリックジャッキングの抑止機能がありますが、正しく実装しなければ脆弱性をつくり込むことになります。正しい実装例は以下のようになります。
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin();
}
}
まとめ
以上の通り、最新のJavaやフレームワークを使うことで、つくり込みを防止できる問題はあります。しかし、それはごく一部であり、プログラマーはこれらの問題について理解し、正しい実装をする必要があるといえます。