宝塔面板未授权访问漏洞分析
次访问
宝塔面板是一款使用方便、功能强大且终身免费的服务器管理软件,支持Linux与Windows系统。支持一键配置:LAMP/LNMP、网站、数据库、FTP、SSL,让用户可以通过Web端轻松管理服务器。
宝塔Linux面板7.4.2/Windows面板6.8版本中引入了一个未授权访问漏洞。攻击者可以通过面板开放的特定端口和url,无需认证直接访问到面板的phpmyadmin数据库管理界面,进而获得数据库的全部权限。
漏洞复现
漏洞影响版本
- 宝塔Linux面板7.4.2
- 宝塔Windows面板6.8
受影响版本离线包
漏洞环境
由于宝塔官方只提供了在线安装一种方式,无法安装历史版本。可以考虑安装最新版之后,再用7.4.2版本的离线包进行替换,但是这种方法比较麻烦,而且可能遇到各种报错和问题。在此提供一个存在漏洞的宝塔7.4.2的docker镜像:
1 | docker pull sayers3/baota_unauthorized |
漏洞复现
漏洞分析
代码分析
根据漏洞通告内容和时间节点及版本,遍历ThinkPHP源代码commit记录,将漏洞修复代码定位到commit 1bbe75019ce6c8e0101a6ef73706217e406439f2:
由上述代码看出,针对漏洞的修复主要是对sessionId的值添加了限制,要求id中的所有字符必须为字母或数字。结合漏洞描述,推测漏洞成因可能是sessionId中的特殊字符导致session存储时产生了目录遍历等情况。因此重点关注session的存储实现。
首先,通过代码看到session name为PHPSESSID:
查看实现session存储的save()
方法代码如下:
在save()
方法中,当$data
变量不为空时,将$data
内容序列化后调用了$this->handler->write()
函数;当$data
为空时,调用了$this->handler->delete()
函数。跟踪write()
和delete()
函数,跟踪到vendor/topthink/framework/src/think/session/driver/File.php中:
可以看到在write()
和delete()
函数中,都调用getFileName()
函数,根据sessionId获取文件名,write()
函数紧接着根据参数配置判断是否对数据进行gz压缩,最后调用writeFile()
将数据写入指定路径文件。而delete()
函数直接调用unlink()
函数删除文件。继续跟踪getFileName()
函数,发现其实现内容就是以sessionId作为文件名,拼接上配置中的存储路径,形成写入文件的路径:
而writeFile()
函数,则直接调用了file_put_contents()
函数执行写文件操作:
从上述调用链条来看,当存储session文件时,写入文件名由cookie中PHPSESSID的值控制,而代码中对PHPSESSID值的校验,只判断其满足长度为32的字符串即可。因此,可以使用../等字符,构造长度为32的文件名,实现目录穿越,当$data
不为空时,可以实现任意文件创建/写入,当$data
为空时,可以实现任意文件删除。
从代码来看,$data
的值默认为空,可以通过setData()
或set()
函数进行赋值:

在框架代码中进行搜索,发现原始代码中并没有对session进行赋值的操作。因此,默认环境下,只能利用此漏洞进行文件删除操作。如果要想实现任意文件创建写入,需要配合实际业务开发代码中有session赋值操作才能实现。
利用复现
启用session
ThinkPHP 6.0默认不启用session,如果想要触发此漏洞,需要首先启用session。修改app/middleware.php,将
\think\middleware\SessionInit::class
前的注释去掉即可。启用session后,session默认存储路径为runtime/session
下文件删除
如上述分析所说,文件删除可以在默认配置环境下触发,只需要在32位长度的PHPSESSID值中,利用
../
等特殊字符,将目标文件路径从runtime/session
穿越到指定目录下即可。利用效果如下:文件上传/GetShell
触发文件上传/GetShell,需要ThinkPHP业务开发代码中,有session赋值操作。为了复现漏洞,这里简单在
app/controller/Index.php
中添加一个Session:set()
操作,然后再启动ThinkPHP服务:构造请求如下:
1
2
3
4GET /?username=%3C?php%20phpinfo()%20?%3E HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Cookie: PHPSESSID=../../../../public/sayers123.php;提交请求后,即可看到ThinkPHP路径下
public
路径中被创建了sayers123.php
文件,其内容如下:1
a:1:{s:4:"user";s:18:"<?php phpinfo() ?>";}
访问
http://127.0.0.1:8000/sayers123.php
,可以看到phpinfo()被执行:
同理,将phpinfo()替换为一句话木马,即可实现GetShell。