Help us understand the problem. What is going on with this article?

脆弱性を攻撃してみよう (1) OSコマンドインジェクション (OGNL式インジェクション)

More than 1 year has passed since last update.

はじめに

脆弱性を理解するには、実際に脆弱性を攻撃してみるのが一番です。

といっても、脆弱性のありそうなサイトを見つけて攻撃してみよう!と言っているわけではありません。自分だけが使っている仮想マシンの上で、脆弱性のあるアプリケーションを動かし、攻撃してみることをお勧めします。

脆弱性は、以前紹介したバグだらけのWebアプリケーションにたくさん実装してあります。Webアプリケーションはここからダウンロードして、次のコマンドで起動します。

java -jar easybuggy.jar

※実行するにはJavaが必要です。詳細については、こちらのページを参照して下さい。

このWebアプリケーションは、現時点で以下の脆弱性を実装しています。

  • XSS (クロスサイトスクリプティング)
  • SQLインジェクション
  • LDAPインジェクション
  • コードインジェクション
  • OSコマンドインジェクション
  • メールヘッダーインジェクション
  • Nullバイトインジェクション
  • サイズ制限の無いファイルアップロード
  • 拡張子制限の無いファイルアップロード
  • オープンリダイレクト可能なログイン画面
  • ブルートフォース攻撃可能なログイン画面
  • セッション固定攻撃可能なログイン画面
  • 親切過ぎる認証エラーメッセージ
  • 危険なファイルインクルード
  • パストラバーサル
  • 意図しないファイル公開
  • CSRF (クロスサイトリクエストフォージェリ)
  • クリックジャッキング
  • XEE (XMLエンティティ拡張)
  • XXE (XML外部エンティティ)

今回はこの中から「OSコマンドインジェクション」(より正確に言うと、「OGNL式インジェクション」)を攻撃して、サーバーに致命的なダメージを与えたいたいと思います:smiley:

OSコマンドインジェクションとは

OSコマンドインジェクションとは、Webアプリケーションなどを経由してサーバーのOSコマンドを実行する攻撃を指します。

oscmdinjct.png

例えば、Webアプリケーションが特定のリクエストパラメータを未検証のままOSのコマンドの引数に渡し、実行していたとします。一般のユーザーは通常の使い方しかしないので問題ないかもしれませんが、悪意のあるユーザーは「;」(セミコロン)などの区切り文字とともに「rm -fr /」のような文字列を付加したパラメータを送信して、サーバーのファイルを全て削除してしまうかもしれません。

攻撃してみよう

では、さっそくOSコマンドインジェクションの脆弱性を攻撃してみましょう。

Webアプリケーションをここからダウンロードして、java -jar easybuggy.jarで起動します。なお、今回はSELinuxを無効にしたCentOS 6に、コマンドの実行権限があるrootユーザーでログインしています。

※この記事では、バージョン1.3.2を使用しています。今後のリリースで動作は変わる可能性があります。

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

main.png

この中の上から5番目に「OSコマンドインジェクション」のリンクがあるので、クリックします。

クリックすると、次のような画面が表示されます。

ognl1.png

数式を入力すると、その答えを表示するという単純な機能です。数式にはjava.lang.Mathを使用することができます。

まずは1 + 2Math.sqrt(5)などの簡単な数式を入力してみて下さい。正しい答えが返ってくるはずです。次にabcなどの数式として不正な値を入力して下さい。エラーメッセージが表示されます。

err1.png

この機能は一見正常に動作するように見えます。

しかし、画面下部のinfo.jpgに記載されているように@Runtime@getRuntime().exec('rm -fr /your-important-dir/')を入力し、計算ボタンをクリックすると、サーバー上のディレクトリ(/your-important-dir/)が削除されてしまいます。

※注意:/your-important-dir/は、サーバー上の削除してもいいディレクトリへの絶対パスにするか、削除する前にバックアップを取得して下さい。また、Windowsの場合は、rm -frコマンドの代わりにrmdir /s /qマンドを使って下さい。

では、実際にやってみます。対象のディレクトリは/etcにします。ll(ls -la)コマンドで現在の/配下のディレクトリ構成を確認すると、以下のようになっています。

$ ll /
合計 102
dr-xr-xr-x.   2 root root  4096  4月 23 03:46 2017 bin
dr-xr-xr-x.   5 root root  5120  4月 26 21:53 2017 boot
drwxr-xr-x.  20 root root  3820  6月  1 22:57 2017 dev
drwxr-xr-x. 119 root root 12288  6月  1 22:57 2017 etc
drwxr-xr-x.   4 root root  4096  4月  2 00:41 2016 home
dr-xr-xr-x.  11 root root  4096  4月 19 21:20 2017 lib
dr-xr-xr-x.  10 root root 12288  4月 23 03:46 2017 lib64
drwx------.   2 root root 16384  8月  8 01:21 2015 lost+found
drwxr-xr-x.   2 root root  4096  3月 18 20:24 2017 media
drwxr-xr-x.   4 root root  4096 11月 15 18:00 2015 mnt
drwxr-xr-x.   8 root root  4096  4月  1 21:49 2017 opt
dr-xr-xr-x. 277 root root     0  6月  1 22:56 2017 proc
dr-xr-x---.  78 root root  4096  6月  2 07:39 2017 root
dr-xr-xr-x.   2 root root 12288  4月 20 03:27 2017 sbin
drwxr-xr-x.   7 root root     0  6月  1 22:56 2017 selinux
drwxr-xr-x.   2 root root  4096  9月 23 20:50 2011 srv
drwxr-xr-x   13 root root     0  6月  1 22:56 2017 sys
drwxrwxrwx.   3 root root  4096  6月  2 07:41 2017 tmp
drwxr-xr-x.  14 root root  4096  9月 23 08:32 2016 usr
drwxr-xr-x.  23 root root  4096 10月  5 17:12 2016 var

実行する前にバックアップを取っておきましょう。

$ cp -pr /etc etc_bk

@Runtime@getRuntime().exec('rm -fr /etc')を入力し、計算ボタンをクリックします。

rmetc1.png

そして、もう一度llコマンドを実行します。

$ ll /
合計 90
dr-xr-xr-x.   2 0 0  4096  4月 22 18:46 2017 bin
dr-xr-xr-x.   5 0 0  5120  4月 26 12:53 2017 boot
drwxr-xr-x.  20 0 0  3820  6月  1 13:57 2017 dev
drwxr-xr-x.   4 0 0  4096  4月  1 15:41 2016 home
dr-xr-xr-x.  11 0 0  4096  4月 19 12:20 2017 lib
dr-xr-xr-x.  10 0 0 12288  4月 22 18:46 2017 lib64
drwx------.   2 0 0 16384  8月  7 16:21 2015 lost+found
drwxr-xr-x.   2 0 0  4096  3月 18 11:24 2017 media
drwxr-xr-x.   4 0 0  4096 11月 15 09:00 2015 mnt
drwxr-xr-x.   8 0 0  4096  4月  1 12:49 2017 opt
dr-xr-xr-x. 296 0 0     0  6月  1 13:56 2017 proc
dr-xr-x---.  78 0 0  4096  6月  9 14:09 2017 root
dr-xr-xr-x.   2 0 0 12288  4月 19 18:27 2017 sbin
drwxr-xr-x.   7 0 0     0  6月  1 13:56 2017 selinux
drwxr-xr-x.   2 0 0  4096  9月 23 11:50 2011 srv
drwxr-xr-x   13 0 0     0  6月  1 13:56 2017 sys
drwxrwxrwx.   8 0 0  4096  6月  8 14:31 2017 tmp
drwxr-xr-x.  14 0 0  4096  9月 22 23:32 2016 usr
drwxr-xr-x.  23 0 0  4096 10月  5 08:12 2016 var

/etcというディレクトリは無くなりました。これがOSコマンドインジェクションです。ここでは/etcを削除しましたが、もちろん他のディレクトリも削除できますし、rmコマンド以外のコマンドも使えます。OSのコマンドを操れるので、いろいろな攻撃が考えられます。

攻撃者はどのように脆弱性を見つけるか

「とはいえ、問題を起こす文字列(@Runtime@...)を攻撃者は知ることができないのでは??」と思った方もいるかと思います。このアプリケーションでは、あえて脆弱性の攻撃方法を画面上に記載していますが、通常のアプリケーションには当然そのようなものはありません。攻撃者はどのように脆弱性を見つけるのでしょうか?

ヒントはエラーメッセージにあります。エラーメッセージは攻撃者にアプリケーションの実装を推測させる材料にもなりえます。

いろいろな種類の不正な入力値でエラーメッセージを表示させてみましょう。例えば、この画面でMath.sqrt(5x)と入力して計算ボタンをクリックしてみると、次のようなエラーが表示されます。

sqrt5x.png

「不正な数式です」の後に英語のメッセージが続いています。アプリケーションの開発経験がある人であれば、「不正な数式です」の後に、何らかのライブラリが返したエラーメッセージをそのまま連結しているのではないか、と推測するのではないでしょうか。

次は、Mathを完全修飾クラス名にして、java.lang.Math.sqrt(5)と入力し、計算ボタンをクリックしてみます。

javalangMathsqrt5.png

入力していないはずの「@」(アットマーク)が入力されたことを示すようなエラーメッセージが出力されました。「@」には何か意味がありそうです。

さらに、Math.を付けずにsqrt(5)と入力して計算ボタンをクリックしてみましょう。次のようなエラーが表示されます。

sqrt5.png

今度は、入力していない「ognl.OgnlContext」というキーワードが現れました。これを見て、知識のある人であれば、この機能を実現しているのが「OGNL」という技術であると推測できます。OGNLはJavaに似た文法の式言語のライブラリです。

OGNLの仕様を見てみると、以下のような記述があります。

Calling Static Methods
You can call a static method using the syntax @class@method(args).

静的なメソッド呼び出しは@class@method(args)で実現できるようです。

「ということは...アプリケーションの内部では、次のように入力値の中にあるMath.@Math@に置換して、OGNL式として実行しているだけかもしれない」という推測ができます。

Math.sqrt(5)@Math@sqrt(5)に置換 → OGNL式として実行

この推測が正しいか確認するために、@System@currentTimeMillis()を入力してみましょう。

@System@currentTimeMillis()@System@currentTimeMillis()のまま(置換されず) → OGNL式として実行

正しければ、1970年1月1日0時からの経過時刻をミリ秒で取得できるはずです。

sct.png

結果が示す通り、推測が正しい可能性が高いと判断できます。

ここまできたら、「@System@exit(0)」と入力してWebアプリケーションのプロセスをシャットダウンさせるか、それともその前に...:sunglasses:といろいろなことが想像できます。

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

実際にどのような実装になっているのでしょうか。ソースコードを見てみましょう。重要な部分は以下です。

try {
    Object expr = Ognl.parseExpression(expression.replaceAll("Math\\.", "@Math@"));
    value = Ognl.getValue(expr, ctx);
} catch (OgnlException e) {
    isValid = false;
    if (e.getReason() != null) {
        errMessage = e.getReason().getMessage();
    }

推測の通り、Math.@Math@に置換して、OGNL式として実行しているだけです。

ちなみに、今回実行したのはOSコマンドインジェクションですが、Javaのコードも実行できるため、「コードインジェクション」と言うこともできます。さらに、より詳細に分類すると「OGNL式インジェクション」とも言えます。

OGNL式インジェクションについて

OGNL式インジェクションの名前を広めたのは、Webアプリケーションのフレームワークとして有名な「Struts 2」です。今年の3月にも、OGNL式インジェクションの脆弱性がStruts 2に見つかり、複数のウェブサイトにおいて情報漏洩等の被害が発生しました。日本のサーバーにも、海外から大量の不正リクエストが送信されてきたという噂です。私もこの脆弱性は実際に試してみましたが(もちろんローカルのStrutsアプリで)、非常に簡単で危険性の高い攻撃ができるものでした。

Struts 2の脆弱性として報告されるもののうち、かなりの割合を占めるのがこの脆弱性です。Struts 2のいろいろな箇所でOGNL式が利用されており、各箇所でOGNL式インジェクションが起きないように正規表現を使って、式の妥当性チェックを行っています。しかし、脆弱性を対策した正規表現が公開されても、それをかいくぐるような新しい文字列での攻撃方法が見つかり、なかなか根本解決に至っていないのが現状です。

OSコマンド/コードインジェクションの対策

では、どのような対策をすればいいでしょうか?対策として思いつくのは、以下のようなことです。

  • そもそもそのような危険性を含む機能はつくらない(設計の段階で却下)
  • 別の実装方法を検討する
  • 実装を特定されるようなエラーメッセージをユーザーに返さない
  • 入力値の妥当性チェックを実施する
  • Webアプリケーションを適切なユーザー権限で実行する(Tomcatユーザー権限など)
  • OSの機能で保護する(Unix chroot jail、AppArmor、SELinuxなど)
  • JVMの機能で保護する(SecurityManager)

この中のいくつかを実際にやってみましょう。

適切なユーザー権限で実行する

rootユーザー以外でWebアプリケーションを実行していたら、どうなるでしょうか?tomcatユーザーに変更して、確認してみましょう。

$ su tomcat

Webアプリケーションを起動したら、@Runtime@getRuntime().exec('rm -fr /etc')と入力して、計算ボタンをクリックします。エラーは表示されませんでした。どうなったのでしょうか?

$ ll /
合計 102
dr-xr-xr-x.   2 root root  4096  4月 23 03:46 2017 bin
dr-xr-xr-x.   5 root root  5120  4月 26 21:53 2017 boot
drwxr-xr-x.  20 root root  3820  6月  1 22:57 2017 dev
drwxr-xr-x. 119 root root 12288  6月  1 22:57 2017 etc
drwxr-xr-x.   4 root root  4096  4月  2 00:41 2016 home
dr-xr-xr-x.  11 root root  4096  4月 19 21:20 2017 lib
dr-xr-xr-x.  10 root root 12288  4月 23 03:46 2017 lib64
drwx------.   2 root root 16384  8月  8 01:21 2015 lost+found
drwxr-xr-x.   2 root root  4096  3月 18 20:24 2017 media
drwxr-xr-x.   4 root root  4096 11月 15 18:00 2015 mnt
drwxr-xr-x.   8 root root  4096  4月  1 21:49 2017 opt
dr-xr-xr-x. 296 root root     0  6月  1 22:56 2017 proc
dr-xr-x---.  78 root root  4096  6月  9 23:09 2017 root
dr-xr-xr-x.   2 root root 12288  4月 20 03:27 2017 sbin
drwxr-xr-x.   7 root root     0  6月  1 22:56 2017 selinux
drwxr-xr-x.   2 root root  4096  9月 23 20:50 2011 srv
drwxr-xr-x   13 root root     0  6月  1 22:56 2017 sys
drwxrwxrwx.   8 root root  4096  6月  9 23:11 2017 tmp
drwxr-xr-x.  14 root root  4096  9月 23 08:32 2016 usr
drwxr-xr-x.  23 root root  4096 10月  5 17:12 2016 var

この通り、/etcは削除されていません。とはいっても、Webアプリケーションを動かすユーザーが実行できるコマンド(rm -fr /home/tomcat/anydirなど)は、この攻撃でも実行できてしまいます。しかし、それだけでもかなり有効な対策であると言えます。

OSの機能で保護する(SELinux)

SELinuxを有効にしていたら、OSコマンドインジェクションを抑止してくれるのでしょうか?もう一度rootユーザーに変更して、SELinuxを有効にします。

$ vi /etc/selinux/config
# SELINUX=disabled
SELINUX=enforcing

変更したら、CentOSを再起動します。Webアプリケーションを起動したら、@Runtime@getRuntime().exec('rm -fr /etc')と入力して、計算ボタンをクリックします。

$ ll /
合計 90
dr-xr-xr-x.   2 0 0  4096  4月 22 18:46 2017 bin
dr-xr-xr-x.   5 0 0  5120  4月 26 12:53 2017 boot
drwxr-xr-x.  20 0 0  3820  6月  1 13:57 2017 dev
drwxr-xr-x.   4 0 0  4096  4月  1 15:41 2016 home
dr-xr-xr-x.  11 0 0  4096  4月 19 12:20 2017 lib
dr-xr-xr-x.  10 0 0 12288  4月 22 18:46 2017 lib64
drwx------.   2 0 0 16384  8月  7 16:21 2015 lost+found
drwxr-xr-x.   2 0 0  4096  3月 18 11:24 2017 media
drwxr-xr-x.   4 0 0  4096 11月 15 09:00 2015 mnt
drwxr-xr-x.   8 0 0  4096  4月  1 12:49 2017 opt
dr-xr-xr-x. 296 0 0     0  6月  1 13:56 2017 proc
dr-xr-x---.  78 0 0  4096  6月  9 14:09 2017 root
dr-xr-xr-x.   2 0 0 12288  4月 19 18:27 2017 sbin
drwxr-xr-x.   7 0 0     0  6月  1 13:56 2017 selinux
drwxr-xr-x.   2 0 0  4096  9月 23 11:50 2011 srv
drwxr-xr-x   13 0 0     0  6月  1 13:56 2017 sys
drwxrwxrwx.   8 0 0  4096  6月  8 14:31 2017 tmp
drwxr-xr-x.  14 0 0  4096  9月 22 23:32 2016 usr
drwxr-xr-x.  23 0 0  4096 10月  5 08:12 2016 var

消えてしまいました...デフォルトでSELinuxが保護するディレクトリ(例えば、/selinux)以外は、保護してくれないようです。少なくともデフォルト設定のSELinuxを有効にしたところで、OSコマンドインジェクションに効果は無いと言えそうです。

JVMの機能で保護する(SecurityManager)

Javaには悪意のあるユーザーからのJVMの操作を保護する「SecurityManager」という機能があります。これを有効にしていたら、どうなるのでしょうか?

SecurityManagerを有効にするには、easybuggy.jar起動時のオプションに以下を付加します。

-Djava.security.manager -Djava.security.policy=catalina.policy

mvnコマンドでEasyBuggyを起動する場合は、pom.xmlの以下の行のコメントを解除して下さい。
<!-- <argument>-Djava.security.manager</argument>
<argument>-Djava.security.policy=catalina.policy</argument> -->

catalina.policyここからダウンロードして下さい。このファイルは、デフォルトではOSコマンドインジェクションを防ぐようになっていませんが、以下のように修正を行うことでそれを抑止できます。

・ Linux (CentOS 6.3)の場合

grant {
    // 全てのファイルへの全ての操作を許可せず、必要なものだけ許可する
    // permission java.io.FilePermission "<<ALL FILES>>", "write, read, execute, delete";
    permission java.io.FilePermission "-", "write, read, execute, delete";
    permission java.io.FilePermission "/usr/java/-", "write, read, execute";
    permission java.io.FilePermission "/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.79.x86_64/-", "write, read, execute";
  ・・・

Windowsの場合

grant {
    // 全てのファイルへの全ての操作を許可せず、必要なものだけ許可する
    // permission java.io.FilePermission "<<ALL FILES>>", "write, read, execute, delete";
    permission java.io.FilePermission "-", "write, read, execute, delete";
    permission java.io.FilePermission "C:/Program Files/Java/-", "write, read, execute";
    permission java.io.FilePermission "C:/Windows/Sun/Java/-", "write, read, execute";
  ・・・

(※設定は、環境により異なります)

変更が完了したら、SecurityManagerを有効にするオプションを付加してWebアプリケーションを起動します。そして、@Runtime@getRuntime().exec('rm -fr /etc')と入力して、計算ボタンをクリックします。

smrmetc.png

SecurityManagerにより、OSコマンドインジェクションは失敗しました。

$ ll /
合計 102
dr-xr-xr-x.   2 root root  4096  4月 23 03:46 2017 bin
dr-xr-xr-x.   5 root root  5120  4月 26 21:53 2017 boot
drwxr-xr-x.  20 root root  3820  6月  1 22:57 2017 dev
drwxr-xr-x. 119 root root 12288  6月  1 22:57 2017 etc
drwxr-xr-x.   4 root root  4096  4月  2 00:41 2016 home
dr-xr-xr-x.  11 root root  4096  4月 19 21:20 2017 lib
dr-xr-xr-x.  10 root root 12288  4月 23 03:46 2017 lib64
drwx------.   2 root root 16384  8月  8 01:21 2015 lost+found
drwxr-xr-x.   2 root root  4096  3月 18 20:24 2017 media
drwxr-xr-x.   4 root root  4096 11月 15 18:00 2015 mnt
drwxr-xr-x.   8 root root  4096  4月  1 21:49 2017 opt
dr-xr-xr-x. 296 root root     0  6月  1 22:56 2017 proc
dr-xr-x---.  78 root root  4096  6月  9 23:09 2017 root
dr-xr-xr-x.   2 root root 12288  4月 20 03:27 2017 sbin
drwxr-xr-x.   7 root root     0  6月  1 22:56 2017 selinux
drwxr-xr-x.   2 root root  4096  9月 23 20:50 2011 srv
drwxr-xr-x   13 root root     0  6月  1 22:56 2017 sys
drwxrwxrwx.   8 root root  4096  6月  9 23:11 2017 tmp
drwxr-xr-x.  14 root root  4096  9月 23 08:32 2016 usr
drwxr-xr-x.  23 root root  4096 10月  5 17:12 2016 var

さらにcatalina.policyの次の行をコメントアウトすると、System.exit(n)も拒否できます。

permission java.lang.RuntimePermission "exitVM";

コメントアウトして、EasyBuggyを再起動したら、@System@exit(0)と入力して、計算ボタンをクリックします。

exitvm.png

このようにJVMのシャットダウンも防ぐことができます。SecurityManagerは、設定が直感的に分かりやすいわけではないので、しっかり理解して使用しなければなりませんが、OSコマンドインジェクションやコードインジェクションには効果的な対策と言えます。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした