前言
我本来没打算发这里,从前因后果看,作者明显是知道漏洞但不打算修了,所以给大家提个醒吧,赶紧卸载了吧,免费这么多年的好心可以领了,但如此草台且极度不安全的“防火墙”还是别留着了。除非你打算把把服务器的隐私数据规规矩矩地供奉给宝塔。
1. 宝塔免费Nginx防火墙8.3/8.4/8.5静默上传用户文件
# free_waf_main.py 第 154-158 行
result = {}
result[file] = r
self.__webshell_data.append(result)
# 云端查杀。需要上传到云端进行研判 # <--- 8.5新增了这行注释,但用户界面上依然没有任何文字提示过用户,文件会被上传到云端
self.send_baota2(file) # <--- 直接把文件发走
# free_waf_main.py 第 304-309 行
def send_baota2(self, filename):
cloudUrl = 'http://www.bt.cn/api/panel/btwaf_submit' # <--- 8.4之前版本是http明文上传,8.5这里改https了。
pdata = {
'codetxt': public.ReadFile(filename), # <--- 文件全文
'md5': self.read_file_md5(filename),
'type': '0',
'host_ip': public.GetLocalIp(),
'size': os.path.getsize(filename),
}
ret = public.httpPost(cloudUrl, pdata)
return True # <--- 永不检查结果,绝对是true,不会产生报错的日志
- 触发条件: 在面板点过一次「扫描木马」,且站点里有任意
.php 文件命中规则。
- 问题是查杀规则写得极其宽泛:
"str_rot13"、base64_decode\($_、"eval(" 这些都能命中。随便找个现代 PHP 程序、WordPress 插件、甚至任何涉及加解密的程序,有几个能逃过这些规则?只要命中规则,文件就会在用户不知情的情况下(除非你自己去看了他的8.5的源码里的那行注释,不然面板上你是看不到任何地方提醒过你,你的文件会被上传给宝塔)

具体文档如下就是下边这个图里的《第三方木马查杀接口说明》 (链接|网页存档)

来,你是用户,你凭这几行字你能看出,你点了「扫描木马」后,防火墙会静默上传你的文件给宝塔的服务器接口吗???
2. 宝塔免费Nginx防火墙8.3/8.4/8.5:特定参数即可绕过POST检测
-- 4.7.lua 第1668-1678行
function continue_key(key)
if method ~='POST' then return true end
key = tostring(key)
if string.len(key) > 64 then return false end; -- <--- 参数名的长度超过 64 个字符
local keys = {"content","contents","body","msg","file","files","img","newcontent","message","subject","kw","srchtxt",""} -- <--- 参数名叫这些名字的话
for _,k in ipairs(keys)
do
if k == key then return false end; -- <--- 直接返回 false
end
return true;
end
处理 POST 请求时,只要参数名的长度超过 64 个字符,或者参数名叫 kw(搜索关键字)、body(正文)、content(内容)、msg(留言)、file 之类的名字就可以直接绕过POST检测
实机跑了几个测试:
- 对照组(常规参数):POST
id=<script>alert(1)</script> → 拦截(拦截了XSS,正常工作)
- 实测用例:POST
[65个字符的长参数名]=union select 1,2,3 → 放行(绕过成功!)
- 实测用例:POST
kw=union select 1,2,3 → 放行(绕过成功!)
- 实测用例:POST
body=<script>alert(1)</script> → 放行(绕过成功!)
3. 走http链路拉取云端脚本并不检查就以root权限直接执行
# install.sh 第7-10行
public_file=/www/server/panel/install/public.sh
if [ ! -f $public_file ];then
wget -O $public_file http://download.bt.cn/install/public.sh -T 5; # <--- http 下载脚本
fi
. $public_file
# free_waf_main.py:1185行-1188行
public.writeFile('/root/install_cjson.sh', c) # <--- 脚本内容里含 http 下载
public.ExecShell('cd /root && bash install_cjson.sh') # <--- 不校检直接root权限运行了
return True
只要一些本地文件缺失就会执行的逻辑。用 HTTP 拉取云端文件,然后看都不看就写入本地文件,直接用 root 权限编译并执行,但凡链路有个中间人劫持,呵呵。
4. 硬编码wenshell免杀后门 hackcnm
# free_waf_main.py 第 133-140 行
def getdir_list(self, get):
path = get.path
if os.path.exists(path):
pc = 'hackcnm' # <--- 不是,这啥玩意?
rs = self.getdir(path, pc)
return rs
# 递归查询站点根目录搜索文件的逻辑
elif str(l.lower())[-4:] == '.php' and str(dir + '/' + l).find(pc) == -1:
lis.append(dir + '/' + l)
只要把 Webshell 命名为 hackcnm.php,或者扔在 hackcnm/ 目录下,在木马查杀里就能享受到永久免检了。
5. 人人都可以用假 PTR 绕过防护
代码就不贴了,涉及多个文件的相互逻辑,核心是4.7.lua:1953-2026, 2095-2103这部分代码。
宝塔 Nginx 免费防火墙为了不误伤搜索引擎蜘蛛,设计了 PTR 反查机制, 但是在匹配域名时,使用的是无头无尾的模糊匹配而不是从最后开始匹配,比如对百度的:string.find(ptrdname, "baidu")。只要含有baidu就行。比如对360的:string.find(ptrdname, "0")。只要 PTR 有数字0就行。类似的挂着谷歌bot UA的请求,只要 PTR 含有google就行(这随便买台谷歌云都会带着的吧)
一旦请求被判定为搜索引擎蜘蛛,防火墙为了防止误判,就会把完整18个检测步骤里的 14 个直接跳过:drop()(黑名单)、drop_abroad()(海外阻断)、cc() / cc2()(CC 防护)、user_agent()(UA检测)、url()(GET防护)、cookie()(cookie检测)、ThinkPHP_RCE5_0_23()、ThinkPHP_3_log()、scan_black(),以及站点级 URL 检测
只留下 args()、post()、post_data()、post_data_chekc() 四个参数级检测。可别忘了前边有个 特定参数即可绕过POST检测 的问题呢。 攻击者只要给自己的一台 VPS 配个类似 baidu.攻击者自己的域名.com 的 PTR 记录,再带上百度蜘蛛的 UA 发起请求,这套防火墙的防护模块就只剩args()对他有效了。
6. 只要能访问 http://127.0.0.1/就能清空IP黑名单
-- 4.7.lua 第1777-1786行
function min_route()
if ngx.var.remote_addr ~= '127.0.0.1' then return false end
if uri == '/get_btwaf_drop_ip' then
return_message(200,get_btwaf_drop_ip())
elseif uri == '/remove_btwaf_drop_ip' then
return_message(200,remove_btwaf_drop_ip())
elseif uri == '/clean_btwaf_drop_ip' then
return_message(200,clean_btwaf_drop_ip())
end
end
服务器上任何一个能发 HTTP 请求的东西,什么PHP 的 curl、图片代理、webhook 回调、被拿下的低权限进程、默认配置的本机反代(如果反代在防火墙前边,错误的默认配置,会让一切请求的remote_addr都是127.0.0.1),只要能访问 http://127.0.0.1/,就能把IP黑名单清空。
7. 神一样的判断 Nginx 成功启动的方式
判断 Nginx 成功居然是检测 netstat 输出中有没有出现字符串 80(netstat -nltp | grep nginx | grep 80 | wc -l),HTTP在非80端口的主机怎么办?再说呢netstat 输出的也不只有端口号啊,还有PID呢,PID里含有80也算Nginx启动成功?
为什么我说“作者明显是知道存在静默上传和漏洞但不打算修了”
我们看下时间线
- 10月22日:宝塔论坛有人提了这个问题(帖子链接|网页存档)
- 10月26日:10点半发的博客文章,拉着4家AI,审核了一下8.4的代码并在周末实机测试了AI的说法:结论是「虽然排除了部分AI的夸大描述,但依然存在漏洞 P0 级 13 个、P1 级 16 个、P2 级 12 个,涉及用户文件被静默上传,用户凭据被明文上传,root权限执行未经验证云端文档,云端配置可被中间人拦截篡改,文件权限过高,多个故意设下的绕过措施,蜜汁正则匹配导致的可绕过、并行写入,畸形正则,路径穿越等等一堆问题」(文章链接|网页存档)
- 10月26日:18点30,宝塔免费 Nginx 防火墙火速更新了8.5版本,8.4 到 8.5,一共改了7个地方:①、6个
http被加了个字母s。②在静默上传用户文件的代码self.send_baota2(file)前加了一行注释# 云端查杀。需要上传到云端进行研判。你们说:他这行注释是写给谁看的?! 自己的良心?还是python解释器?总不可能是给用户吧,宝塔面板的用户有几个能看到的,明文静默上传代码都明晃晃的挂了这么多年了,不也没人看到嘛?!(能主动看的人,说明有技术能力,大概率不会去用,去用的人,大概率没技术能力,大概率看不到注释)
宝塔论坛内发布这个防火墙,并表示防火墙有问题请联系他的人,账号是论坛的管理员「宝塔技术-小强」我只想问问宝塔面板 @btpanel ,你们的论坛的技术对“防火墙”的安全性就是这样定义的???8.4升级到8.5的代码改动,表示他明确知道了问题(不然都 http 明文了这么多年,偏偏在有人发了帖子和文章后做了更新),他知道他明文外传源码,所以他特地改了 HTTPS,还煞有介事地加了行注释解释了一下。但明显压根就不在乎用户的安全死活,更不想花哪怕半小时去修修那些烂到骨子里的底层逻辑。官方的技术人员就这样修漏洞和对待用户的???