DVWAでOSコマンドインジェクション
注入つながりで、SQLインジェクションの次はOSコマンドインジェクションを試す。
DVWAの左メニューより「Command Injection」を選択
下図のようなping機能が実装されている。
出力を見た感じ、内部でコマンドを呼び出し、結果を直接出力してる模様。
別のコマンドを実行させるような値を渡す。
'; ls', '| ls', '127.0.0.1 && ls', どれも動かん.....
そして成功したのがこれ。
実際のコードを見てみる
まずはhigh
<?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $target = trim($_REQUEST[ 'ip' ]); // Set blacklist $substitutions = array( '&' => '', ';' => '', '| ' => '', '-' => '', '$' => '', '(' => '', ')' => '', '`' => '', '||' => '', ); // Remove any of the charactars in the array (blacklist). $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); // Determine OS and execute the ping command. if( stristr( php_uname( 's' ), 'Windows NT' ) ) { // Windows $cmd = shell_exec( 'ping ' . $target ); } else { // *nix $cmd = shell_exec( 'ping -c 4 ' . $target ); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } ?>
ブラックリスト方式で、メタ文字のエスケープを行っている。
最初見た時「え、インジェクションできるのこれ」と思ったが、$substitutionsの2番要素をよーく見ると、垂直ライン文字の後ろに空白が付いている。
さらに、第一引数にarrayを渡した時のstr_replaceの仕様で、arrayの先頭から順番に文字列置換を適用するので、
アプリから渡した文字列'|| ls'
はまず$substitutionsの2番要素'| '
とマッチ・置換され'|ls'
になる。
その後の'||'
とはマッチしないので、最終的にはping -c 4 |ls
がコマンドになり、lsが実行できる。
ついでにいうと、|ls
のように、パイプライン直後に空白を入れないようにしてもメタ文字が置換されないのでlsが実行できる。
lsのところを書き換えたら任意のコマンドが実行できるので、後はお察し。
メタ文字をエスケープする戦略は正しいです。が、コーディングミスして満足にテストを行ってないと、ありえる状況ですね。 ブラックリスト方式だと抜けた部分が直接セキュリティホールになるので、OSインジェクションに限らず、セキュリティ対策は可能な限り、プロによって十分テストされた外部ライブラリを使いたいですね。
PHPだとescapeshellcmd()
でメタ文字と\x0aなど別コマンド実行の恐れがある文字のエスケープができたり、escapeshellarg()
では文字列をシングルクォートで囲む形でメタ文字を無効化できる。
Pythonでもshlex
モジュールのquote()
でエスケープできたり、スクリプト言語ではおそらく標準で提供されている?
ただ、今回のように入力値が限られている場合は、ホワイトリスト方式で実装するほうが確実。
実際、impossibleではそのようにコーディングされている。
<?php if( isset( $_POST[ 'Submit' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Get input $target = $_REQUEST[ 'ip' ]; $target = stripslashes( $target ); // Split the IP into 4 octects $octet = explode( ".", $target ); // Check IF each octet is an integer if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) { // If all 4 octets are int's put the IP back together. $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3]; // Determine OS and execute the ping command. if( stristr( php_uname( 's' ), 'Windows NT' ) ) { // Windows $cmd = shell_exec( 'ping ' . $target ); } else { // *nix $cmd = shell_exec( 'ping -c 4 ' . $target ); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } else { // Ops. Let the user name theres a mistake echo '<pre>ERROR: You have entered an invalid IP.</pre>'; } } // Generate Anti-CSRF token generateSessionToken(); ?>
簡易的ではあるものの、入力値がIPv4アドレスっぽい形であるか確認している。 コーディングミスで機能が使えないことがあっても、コマンドが実行させるような事態はめったに起きないと思われる。 念の為、シェル用のエスケープ関数と合わせておけば、保険になってよろしいかな。