[PHP-dev 658] hiddenフィールドの検証コードの実装

Hiroyuki Toda php-dev@php.gr.jp
Fri, 06 Dec 2002 00:39:18 +0900


どうもこんにちわ 戸田@日本ノーベルです

webアプリケーションを作成する上で、http sessionを
またいでデータの受け渡しを行う際によくhiddenフィールドを
用いますが、この方法は皆様ご承知のとおり改ざんが簡単に
できてしまうという問題点を抱えています。

httpの性質上、hiddenフィルードの改ざんを阻止することは不可能です。
そこで次善の策として改ざんが行われたことを検知する為の検証コードを
生成する手段を提供することが考えられます。


…という訳でその様な組み込み関数verify_code_md5を作成してみました。
(最初はユーザ定義関数で実装しようとしたのですが、処理速度上の観点から
組み込み関数による実装に切り替えました)

中身は検証範囲となるデータと"鍵"それぞれにMD5を施し、それらのXORを
とって、さらにMD5を施したものを検証コードとしています。


書式:
verify_code_md5(鍵, 検証データ#1, 検証データ#2, .....);

鍵には文字列。検証データには数値、文字列又は配列が指定可能です。
配列を指定した場合、配列の各要素のデータが検証データとなります。
ただし、要素のキーが文字列である場合は、それをデータの頭に付与して評価します。
(数値の場合はデータだけが検証データとして扱われます。)

ex) 以下は全て等価です。
    verify_code_md5(鍵, array("key1" => "value1", 0 => "value2"));
    verify_code_md5(鍵, "key1value1", array(0 => "value2"));
    verify_code_md5(鍵, "key1value1", "value2");

配列の配列は指定できません。

検証データの評価順序は、XORをとっていますので問われません。
(これは配列の要素の順番が保証されていないことによる措置です)


ユーザには検証データからverify_code_md5によって生成された検証コードを
渡します。ユーザからのそのレスポンスから検証データと検証コードを取り出し、
同じ処理を施します。検証コードが一致しない場合は検証データの検証コードの
改ざんが(事実上)検知できます。


以下コードです。(各ファイルの適切な位置に追加してください)
php-4.2.2で動作確認しましたが、外部関数等との依存性は少ないと
思いますのでその他のバージョンでも動作することが期待できます。


ext/standard/basic_functions.c:
PHP_NAMED_FE(verify_code_md5, php_if_verify_code_md5, NULL);

ext/standard/md5.h:
PHP_NAMED_FUNCTION(php_if_verify_code_md5);

ext/standard/md5.c:
/* {{{ proto string verify_code_md5(string secret, mixed ...)
   Calculate the md5 hash of a strings and arrays */
PHP_NAMED_FUNCTION(php_if_verify_code_md5)
{
        zval ***args, **arg, **data;
        char md5str[33];
        PHP_MD5_CTX context;
        unsigned char digest[16];
        unsigned char validate_code[16];
        int i, j, argc;
        char *s_key;
        unsigned long n_key;
        char *str;

        memset(validate_code, 0, 16);
        md5str[0] = '\0';

        argc = ZEND_NUM_ARGS();
        if (argc == 0) {
                WRONG_PARAM_COUNT;
        }

        args = (zval***)emalloc(argc * sizeof(zval**));
        if (zend_get_parameters_array_ex(argc, args) == FAILURE) {
                efree(args);
                WRONG_PARAM_COUNT;
        }

        if ((*args[0])->type != IS_STRING) {
                efree(args);
                WRONG_PARAM_COUNT;
        }


        for(i = 0; i < argc; i++){
                arg = args[i];

                (*arg)->refcount++;
                switch((*arg)->type){
                case IS_LONG:
                case IS_STRING:
                case IS_DOUBLE:
                        convert_to_string_ex(arg);
                        PHP_MD5Init(&context);
                        PHP_MD5Update(&context, Z_STRVAL_PP(arg), Z_STRLEN_PP(arg));
                        PHP_MD5Final(digest, &context);
                        for(j = 0; j < 16; j++)
                                validate_code[j] ^= digest[j];

                        break;

                case IS_ARRAY:
                        zend_hash_internal_pointer_reset((*arg)->value.ht);
                        while (zend_hash_get_current_data((*arg)->value.ht, (void**)&data) == SUCCESS) {

                                switch ((*data)->type) {
                                case IS_LONG:
                                case IS_STRING:
                                case IS_DOUBLE:
                                        convert_to_string_ex(data);
                                        break;

                                default:
                                        efree(args);
                                        WRONG_PARAM_COUNT;
                                        break;
                                }

                                switch (zend_hash_get_current_key((*arg)->value.ht, &s_key, &n_key, 0)){
                                case HASH_KEY_IS_STRING:
                                        break;

                                case HASH_KEY_IS_LONG:
                                        s_key = "";
                                        n_key = 0;
                                        break;

                                default:
                                        efree(args);
                                        WRONG_PARAM_COUNT;
                                        break;
                                }

                                str = emalloc((Z_STRLEN_PP(data) + strlen(s_key)) * sizeof(char) + 1);
                                sprintf(str, "%s%s", s_key, Z_STRVAL_PP(data));

                                PHP_MD5Init(&context);
                                PHP_MD5Update(&context, str, strlen(str));
                                PHP_MD5Final(digest, &context);
                                for(j = 0; j < 16; j++)
                                        validate_code[j] ^= digest[j];

                                efree(str);

                                zend_hash_move_forward((*arg)->value.ht);
                        }
                        break;

                default:
                        efree(args);
                        WRONG_PARAM_COUNT;
                        break;
                }
        }

        efree(args);

        PHP_MD5Init(&context);
        PHP_MD5Update(&context, validate_code, 16);
        PHP_MD5Final(validate_code, &context);

        make_digest(md5str, validate_code);
        RETVAL_STRING(md5str, 1);
}
/* }}} */

少々コードが汚く恥ずかしいですが、何かの役に立てばと投稿しました。
皆様からのご意見・アドバイスをお待ちしています。




それでは戸田@日本ノーベルでした。