LoginSignup
1
0

More than 5 years have passed since last update.

PHPから学ぶ、脆弱性の潰し方

Last updated at Posted at 2017-12-13

これは 日本情報クリエイト Engineer's Advent Calendar 2017 の14日目の記事です。

はじめに

皆さんは『脆弱性』についてどの程度意識してますか?
有効な対応はできていますか?
今回はPHPの脆弱性とその修正内容から何に気をつけるべきなのか、その一例を見てみたいと思います。

今回見ていくPHPの脆弱性

CVE-2017-11142

対象Ver

  • ~ 5.6.31
  • ~ 7.0.17
  • 7.1.x ~ 7.1.3

内容
長い変数を使用するとCPUを消費させDosを引き起こす可能性があります。

脆弱なコード

今回の脆弱なコードは main/php_variables.c 内に定義されている static zend_bool add_post_var() という関数です。

実際のコードは

php_variables.c

typedef struct post_var_data {
    smart_str str;
    char *ptr;
    char *end;
    uint64_t cnt;
} post_var_data_t;

static zend_bool add_post_var(zval *arr, post_var_data_t *var, zend_bool eof TSRMLS_DC)
{
    char *ksep, *vsep, *val;
    size_t klen, vlen;
    /* FIXME: string-size_t */
    unsigned int new_vlen;

    if (var->ptr >= var->end) {
        return 0;
    }

    vsep = memchr(var->ptr, '&', var->end - var->ptr);
    if (!vsep) {
        if (!eof) {
            return 0;
        } else {
            vsep = var->end;
        }
    }

    /* 中略 */

    var->ptr = vsep + (vsep != var->end);
    return 1;
}

こちらです。
そしてこの脆弱性の修正コードは


typedef struct post_var_data {
    smart_str str;
    char *ptr;
    char *end;
    uint64_t cnt;
+
+   /* Bytes in ptr that have already been scanned for '&' */
+   size_t already_scanned;
} post_var_data_t;

static zend_bool add_post_var(zval *arr, post_var_data_t *var, zend_bool eof TSRMLS_DC)
{
-   char *ksep, *vsep, *val;
+   char *start, *ksep, *vsep, *val;
    size_t klen, vlen;
    /* FIXME: string-size_t */
    unsigned int new_vlen;

    if (var->ptr >= var->end) {
        return 0;
    }

-   vsep = memchr(var->ptr, '&', var->end - var->ptr);
+   start = var->ptr + var->already_scanned;
+   vsep = memchr(start, '&', var->end - start);
    if (!vsep) {
        if (!eof) {
+           var->already_scanned = var->end - var->ptr;
            return 0;
        } else {
            vsep = var->end;
        }
    }

    /* 中略 */

    var->ptr = vsep + (vsep != var->end);
+   var->already_scanned = 0;
    return 1;
}

たったのコレだけです。
では、何がいけなくて何を解決したのか見ていきましょう。

なぜ攻撃が成立するのか?

修正の要点は

-   vsep = memchr(var->ptr, '&', var->end - var->ptr);
+   start = var->ptr + var->already_scanned;
+   vsep = memchr(start, '&', var->end - start);

コレですね。
修正前は、POSTデータ全体に対して1文字検索しているのに対して後者は既にスキャン済みの領域に対しては検索対象から外しています。
単純な文字検索だけでDosが成立してしまうとは2・・・恐ろしい。

まとめ

当たり前のことですが、入力値はどんな値が渡されるか予め知ることはできません。
ですので、コードを書くときには予期しないデータが渡された時にどんな影響があるか今一度考えてみましょう。
思わぬところに落とし穴があるかもしれません。


  1. 厳密には全データを一度に検索するわけではなさそうですが、どのくらいのデータをまとめて検索するかは調べきれませんでした。 

  2. dockerphp:5.6.30-apachephp:5.6.31-apache のイメージを使って実験してみましたが、残念ながら再現できませんでした。 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0