WatchBird是一个非常强大的AWD神器,以至于Readme都要写上禁止在AWD使用🤣
但是最近Ciscn出现了针对性WatchBird探测,理论上来说,在AWD使用Waf是很难实现效果很好的针对性检测的,但是WatchBird有非常多个明显的强特征,我们今天来魔改去除WatchBird的强特征,并且顺便来学习一下它的功能都是怎么实现的。
0x00 安装与使用
Watchbird安装非常简单,只需要三行命令就能完成安装
pyhton3 pack.py
gcc waf.c -shared -o waf.so
php watchbird.php --install [Web目录]
安装完成后可以发现,它是通过把Web目录下的每一个PHP文件前都加了一句包含来实现Waf功能的。
0x01 特征分析
设置完初始密码后进入到控制台,抓包后发现,它会每秒钟定时请求最新的日志,其中有一个参数为watchbird=log 而进入控制台的参数为watchbird=ui 可以猜测,这个参数表明的是执行何种操作,这个参数名是一个特征,需要去除
尝试触发SQLi保护,这个页面也是一个特征,需要去除
整个watchbird只有一个文件,很好索引分析。先全文搜索一下watchbird试试
发现有55个匹配项
可以将所有请求参数包括html页面中参数的watchbird换成cappuccin,其他都不动
再次打开控制台试试,参数已经被成功修改了
再删掉logo
如此,在黑盒测试下的watchbird强特征已经被去除了,接下来可以通过分析源码进一步挖掘其他特征。
0x02 源码分析
为了分析方便,本节展示的代码均为原始watchbird-source.php
首先,跳过前面的各种定义,程序流程是从1815行开始的。
在这一部分,可以看到各种操作定义,我们今天的重点是寻找是否还有其他特征以及分析WAF相关功能的实现
前一部分是命令行安装卸载代码,而后是通过序列化来实现保存配置。
注意看,上图中1850行注释写着“高级虚假flag”,我们来看看get_fake_flag()和get_preg_flag()是怎样实现的
get_fake_flag()大概的意思是根据真实flag的长度生成了一个随机虚假的flag。这里的69行貌似是一个多余的操作
get_preg_flag()大概意思是将flag分成了三部分,中间添加了*|,返回的结果看起来就像这样
flag\{523af53*|7946b79c4f8369ed39ba78605*|9ed39ba78605\}*|
/*
* 这里我不太能理解作者生成一个这样的Reg的目的是什么😥
*/
先跳过对于操作参数的响应处理部分,最后一行实例化了一个watchbird对象
$watchbird = new watchbird();
整个php文件有watchbird, ui, configmanager三个类,其中watchbird类是整个WAF的核心类。
在watchbird类的构造方法里,有各种攻击检测的流程,根据注释可以知道,它会检测HTTP头、GET参数、POST参数、上传的文件等。
首先来看看它是怎么检测头部内容的。
这里我们看见了sql注入检测和RCE检测,它是通过正则黑名单实现的。
public $sql_blacklist = "/drop |dumpfile\b|INTO FILE|union select|outfile\b|load_file\b|multipoint\(/i";
public $rce_blacklist = "/`|var_dump|str_rot13|serialize|base64_encode|base64_decode|strrev|eval\(|assert|file_put_contents|fwrite|curl_exec\(|dl\(|readlink|popepassthru|preg_replace|preg_filter|mb_ereg_replace|register_shutdown_function|register_tick_function|create_function|array_map|array_reduce|uasort|uksort|array_udiff|array_walk|call_user_func|array_filter|usort|stream_socket_server|pcntl_exec|passthru|exec\(|system\(|chroot\(|scandir\(|chgrp\(|chown|shell_exec|proc_open|proc_get_status|popen\(|ini_alter|ini_restore|ini_set|LD_PRELOAD|ini_alter|ini_restore|ini_set|base64 -d/i";
接下来,我们来看看文件上传检测,它只要检测到有文件上传,就会把写入攻击日志,并且会删除后缀不在白名单和内容含有<?php的文件。
接着,我们来看看GET、POST参数内容检测,首先它会先检测是否有特殊字符,发现请求内容中含有非ASCII字符或一些符号就会记录攻击日志,并且拦截此次请求;如此大范围的拦截势必会影响一些业务系统的运行,但是这也杜绝了通过非ASCII字符如取反等方法绕过前面正则黑名单的方法。
然后,会将请求参数内容依次检测,我们可以看到SQLi检测、RCE检测、反序列化检测和flag请求检测都是通过黑名单正则实现的。
而464行开始的文件包含检测首先检查$str中是否包含多个自身路径,若有多个自身路径则直接拦截,否则将$str中自身路径去除,用正则判断是否有类似 ./../../../xxx.php 的内容,如果是则直接拦截。
这里的$_SERVER[‘PHP_SELF’]变量代表的值官方文档的解释是
‘PHP_SELF’当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://example.com/foo/bar.php 的脚本中使用 $_SERVER[‘PHP_SELF’] 将得到 /foo/bar.php。__FILE__ 常量包含当前(例如包含)文件的完整路径和文件名。 如果 PHP 以命令行模式运行,这个变量将包含脚本名。
这里有点奇怪的是,全文调用watch_attack_keyword()这个函数的只有检测GET和POST参数的时候,也就是说$str只是请求参数,按理不需要判断$_SERVER[‘PHP_SELF’]数量,猜测之前这个函数用于检测HTTP头,而不是参数
// watch_attack_keyword仅仅在这两处地方调用
foreach ($_GET as $keywords){ // 监测GET参数,出现问题则记录
$this->watch_attack_keyword($this->watch_special_char($keywords));
}
if ($this->request_data != '')
{
foreach ($_POST as $keywords){ // 监测POST参数,出现问题则记录
$this->watch_attack_keyword($this->watch_special_char($keywords));
}
}
接下来就是“深度检测响应包”,在看检测流程之前,先看看getcont()这个函数,它给出的注释是“将流量发送到本地服务器进行自检”,这个函数写得比较长,乍一看也看不出个所以然。注意看,在函数最后几行有一个debug模式,它会输出处理好的$out以及$response_content
这里请求的index.php文件内容是
<?php include_once './watchbird.php'; ?>
<?php echo "666";
打开debug模式请求一次试试
观察响应包可以发现,在我们发起一个请求之前,watchbird首先会向自己发送一个相同的请求并且会检测这个请求对应的响应包
我们回到深度检测响应包的部分,拿到响应包之后首先检查一下有没有自己真实的flag,$conten_disallow存储的是自己真实的flag。这里我想吐槽的一点是,发现响应flag后直接die()是不是太草率了,应该将响应的真flag修改为假flag才对的上高级虚假flag的名号呐
之后else语句的内容就是将响应包返回给用户,至此就是全部的深度检测响应包实现辣!
接下来再寻找一下是否还有其他特征,还记得__constructor()开头有一段接收头含有watchbird关键字吗?
if(isset($_SERVER['HTTP_WATCHBIRDTOKEN']) && file_exists($this->tokendir . $_SERVER['HTTP_WATCHBIRDTOKEN'])){
unlink($this->tokendir . $_SERVER['HTTP_WATCHBIRDTOKEN']);
putenv("php_timestamp=".$_SERVER['HTTP_WATCHBIRDTIMESTAMP']);
return 0;
}
这里就是处理自检响应包的部分,这个头部不会对外请求,因此不会暴露特征。
稍微总结一下,将请求参数以及LOGO修改后,WatchBird不再具有强特征了,WAF部分通过正则黑名单+过滤非ASCII字符请求杜绝了大部分攻击形式,在AWD如此问题不会太大,但是在现实应用Waf中给予的借鉴就比较有限了。