ニシキヘビってかわいいよね、実際みたことないけど。

無職がいよかん国でプログラミングとかの備忘録を書いてます。 一日一食たまごかけごはん。

DVWAでOSコマンドインジェクション

注入つながりで、SQLインジェクションの次はOSコマンドインジェクションを試す。

DVWAの左メニューより「Command Injection」を選択

下図のようなping機能が実装されている。
出力を見た感じ、内部でコマンドを呼び出し、結果を直接出力してる模様。
f:id:nyanmao:20171112135006p:plain

別のコマンドを実行させるような値を渡す。
'; ls', '| ls', '127.0.0.1 && ls', どれも動かん.....
そして成功したのがこれ。
f:id:nyanmao:20171112135442p:plain

実際のコードを見てみる
まずは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アドレスっぽい形であるか確認している。 コーディングミスで機能が使えないことがあっても、コマンドが実行させるような事態はめったに起きないと思われる。 念の為、シェル用のエスケープ関数と合わせておけば、保険になってよろしいかな。