2、传递参数的正则匹配绕过:URL-WAF往往存在一些缺点
(1). HPP参数污染。 当某些WAF检测重复参数时,它们通常只检测第一个参数。 我们可以重复传递参数,如 /?password=admin&&pasword=' order by 1—+ 但需要注意的是,只有解析 PHP 的中间件才会通过 最后一个参数覆盖前面的参数(重复参数,如前面的反例) 。
(2)。 截断旁路。
①长度截断:部分WAF在检测URL参数时,为了节省资源,往往会截取一定粗细的参数进行安全检查,而忽略其旁边的参数。
②终止符被截断。 有些WAF会判断参数读取完成,只检测部分内容。
数据分割被绕过。
(3)。 URL-WAF通常单独检测每个请求,或者仅检测连续但分割的请求中的第一次。
①通过块编码传输进行旁路。 当消息体头部存在Transfer-Encoding:chunked时,表示采用chunked编码传输,可以合并多个请求。
消息体由数量未定的块组成,每个非空块以该块包含数据的字节数(十六进制的字节)开头,后跟 CRLF(回车换行),然后是数据本身,最后一个块CRLF结束。 最后一个块是一行,由块大小 (0)、一些可选的填充空白和 CRLF 组成。 最后一个块不再包含任何数据,但可以发送可选的尾部,包括消息头数组。 消息以 CRLF 结尾。
②使用管道绕过。 当消息体的头部包含Connection:keep-alive时,表示本次请求构造的连接不会中断,直到Connection的值改为close。 注意关闭burpsuit的repeater模块的Content-Length手动更新。
3、传递参数的数据类型匹配绕过:传入的变量类型不符合预期
对于$_GET['num_value'](和$_POST['num_value']是一样的)来说,不仅仅是/?num_value=xxx作为合法有效的参数传递格式。 PHP接受参数时,会对获取到的参数名称进行一定的转换。
输入内容(传入时会进行url编码) PHP解析的变量名
空间数值
数值
num[value(这里必须是left,right会报错)
数值
数值
数值
数字值(区分大小写)
报告错误
数值
数值
数值
数值
输入/?num_value[]=xxx也是合法的,但是数据类型和上面的字符串不同,传入的是链表。这个在ctf中经常使用,因为md5(数组)==0。
4、传递参数时的编码问题
(1). 当源码中有文件操作函数时,会对url进行两次解码,此时可以对urlencode进行两次编码。 (例如 %27 变为 %25%27)
(2)。 解码Url时,如果遇到%+字母,会手动过滤%。 如果传入了 sel%ect ,则解码得到 select。
(3)。 解码Base64时,如果字符数不是3的倍数,将难以解码并抛出错误。
Part2 变量生成
参数传入后php截取字符串,PHP会按照一定的规则生成变量。
(1). 服务器使用REQUEST获取参数,通过POST和GET同时分包可以绕过部分WAF。
(2)。 服务器使用extract()函数根据获取到的变量中的键和值生成对应的变量,这可能会造成变量覆盖,导致安全问题。 Ctf经常被用来覆盖白名单。
(3)。 变量名添加[]传入字段,绕过一些关于md5函数的检测。
如md5(aaa[])===md5(bbb[])
(4)。 反序列化。 服务器使用unserialize()函数来处理参数并将它们实例化为对象。 这是 PHP 关于变量生成的一个特殊功能。
Var_dump(“x66x6cx61x67”==”flag”); // 输出是bool(ture)
同样的,反序列化
O:5”Guess”:1:{s:3:”key”;s:16:”x66x6cx61x67”;}
与反序列化
O:5”Guess”:1:{s:3:”key”;s:16:”flag”;}
没有区别
x66是字符串的ascii值加上前面的x的十六进制格式,可以用下面的脚本生成
$string = 'flag';
//在这里输入要处理的字符串
$arr = str_split(bin2hex($string), 2);
foreach ($arr as $value) {
print('x'.$value);
}
//结果是 x66x6cx61x67
(5)。 和4的原理有相似之处,md5(xxx,ture)会输出一个16位的二进制数据,这个二进制数据也可以被php解码。 所以当xxx为ffifdyop时,PHP会认为它类似于通用密码'或1=1
(其实有一点区别,后者不是1=1,但也是TURE)
Part3 变量处理
生成变量后,PHP无非就是三种处理——变量比较、正则匹配、反序列化。 我们来一一分析。 (本文不会提及反序列化,稍后再讨论)
1. 变量比较
PHP 的弱类型自诞生以来就一直受到批评。 PHP 有两个比较相等的符号,即“==”和“===”。 前者只比较值是否相等。 当不同类型相互比较时,将手动更改它们。 这就是安全问题发生的地方。 后者比较第一个类型,然后比较值,对于不同类型的比较返回 false。
如下:
var_dump("abcd"==0); //true
var_dump("1abcd"==1);//true
var_dump("abcd1"==1) //false 字符串和数字比较,比较前面的同类型部分
var_dump(abdc1==0) //true 但是同时会报错
var_dump(abdc1==1) //false 但是同时会报错
var_dump(False==0) //true
var_dump("abcd1"==0) //true
var_dump("0e123456789"=="0e888888") //true php把0e开头解释为科学计数法,为0
不过,字符串和布尔值不能比较
2、正则匹配
(1).异或绕过
PHP有一个神奇的功能,XOR.XOR本身并不神奇,但是PHP可以异或ascii编码的字符串
异或的简单规则:如果a和b的两个值不相同,那么异或结果为1。如果a和b的两个值相同,那么异或结果为0 .当比较的两侧只有一侧为true时,返回true,否则返回false。 字母和数字(类似于int整数的实数)异或结果是原始数字,不带逗号的字母将被视为字符串。
3 xor 2==1
2 xor 2==0
'`'^'*'=='J' (ascii编码异或)
a^2==2 (但会报错)
附上一个python脚本
def xor():
for x in range(0,127):
for y in range(0,127):
z=x^y
print(" "+chr(x)+"ascii:"+str(x)+' xor '+chr(y)+" ascii:"+str(y)+' == '+chr(z)+" ascii:"+str(z))
//复制粘贴要注意这里和上一行是同一行,不然报错
if __name__ == "__main__":
xor()
这是一个简单的反例。
(2)。 PCRE回溯次数被绕过
在PHP的正则表达式中,如果匹配模式被转义(例如,or?),则可能会发生回溯。 通配符前后还有其他匹配要求,容易造成回溯。 正则表达式的每个符号都会匹配整个字符串,匹配的临时结果将允许下一个正则匹配符号再次匹配整个字符串。
例如/^/,它将匹配html标签上方的内容。 当我们输入bcdefg进行匹配时,开始匹配,发现行尾之前没有字符串,开始回溯,匹配g,发现错误,将g从临时结果中去掉,继续回溯,匹配f,错误,回溯,如此反复得到>,匹配最终结果。 当bcdefg达到一百万时,PHP将不再继续回溯,并跳过匹配并返回false,从而绕过正则性。 为了防止这些问题,PHP提出了新的句子规范。 如果正则匹配没有匹配到某个字符,则返回0php截取字符串,如果回溯次数过多,则返回false。 使用===来比较结果不会绕过if判断。
Part4 变量存储
变量有时还有处理后的最后一步,存储(嵌入)。 存储后仍然会有WAF检测是否存在威胁(欺骗)。 但不管怎样,目前的存储检测都是静态检测,所以绕过它并不困难。 (即使有 D 盾)
1.静态旁路
(1). 命名空间的使用
贴一段PHP中文手册内容
静态检测存储变量(比如Pony),回调函数加个命名空间通常可以绕过,手册内容太多,一般面对90%的WAF,在回调函数上加个就大功告成了。
(2).自定义函数
使用自定义函数来拼接、删除和替换字符串或函数名称。 除了绕过WAF之外,还有一些优秀的危险代码可以绕过人,比如将代码前面的空格数转换成字符。
这里附上一个简单的自定义函数,和归一化类似。
array($_POST[‘a’]));//甚至对于assert这个关键字也可以用变量再次拼接 $y=’a’+’ssert’; function x($a,$b){call_user_func_array($a,$b);}x(‘assert’,
除了调用两次保留函数外,还可以创建一个自建的加密函数来达到类似的效果,只要将静态改为动态即可避免扫描。