宝塔面板phpMyAdmin未授权访问安全漏洞是个低级错误吗?

发布时间:2024-02-17 点击:117
周日晚,某群里突然发布了一则消息,宝塔面板的phpmyadmin存在未授权访问漏洞的紧急漏洞预警,并给出了一大批存在漏洞的url:
随便点开其中一个,赫然就是一个大大的phpmyadmin后台管理页面,无需任何认证与登录。当然,随后各种神图神事也都刷爆了社交网络,作为一个冷静安全研究者,我对此当然是一笑置之,但是这个漏洞的原因我还是颇感兴趣的,所以本文我们就来考证一下整件事情的缘由。
一、我们的问题究竟是什么?
首先,我先给出一个结论:这件事情绝对不是简简单单地有一个pma目录忘记删除了,或者宝塔面板疏忽大意进行了错误地配置,更不是像某些人阴谋论中说到的官方刻意留的后门。
我为什么这么说?首先,根据官方的说法,这个漏洞只影响如下版本:
linux正式版7.4.2
linux测试版7.5.13
windows正式版6.8
这个版本就是最新版(漏洞修复版)的前一个版本。也就是说,这个确定的小版本之前的版本面板是不受影响的。我们试想一下,如果是“后门”或者官方忘记删除的目录,为什么只影响这一个版本呢?况且宝塔面板发展了这么久,积累了400万用户,体系安全性也相对比较成熟,如果存在这么低劣的错误或“后门”,也应该早就被发现了。
经过实际查看互联网上的案例和询问使用了宝塔面板的朋友,我发现在7.4.2以前的版本中没有pma这个目录,并且phpmyadmin默认情况下认证方法是需要输入账号密码的。所以,宝塔出现这个漏洞,一定是做过了下面这两件事:
新增了一个pma目录,内容phpmyadmin
phpmyadmin的配置文件被修改了认证方式
那么,我们的问题就变成了,官方为什么要做这两处修改,目的究竟是什么?
为了研究这个问题,我们需要先安装一个宝塔7.4.2版本。但是,宝塔的安装是一个傻瓜化的一键化脚本:
yum install -y wget && wget -o install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh并没有给到用户一个可以选择版本号的选项,官方的git也许久没更新了,我们如何才能安装到一个合适的版本(7.4.2)呢?
二、安装一个合适的版本
这当然难不倒我。首先,我安装了最新版的宝塔面板,用的就是上述一键化脚本。
安装的过程自然没什么问题,安装完成后,系统显示的版本号是最新版7.4.3,因为在爆出这个漏洞以后,官方迅速进行了修复升级。不过没关系,我们仍然可以找到离线升级包:
http://download.bt.cn/install/update/linuxpanel-7.4.0.ziphttp://download.bt.cn/install/update/linuxpanel-7.4.2.ziphttp://download.bt.cn/install/update/linuxpanel-7.4.3.zip分别是7.4.0/7.4.2/7.4.3的版本,我们分别下载并解压,并尝试将自己的服务器版本恢复成漏洞版本7.4.2。
在恢复代码之前,我们先将服务器断网,或者将宝塔设置成离线模式:
这么做的目的是防止宝塔进行自动版本更新,避免好不容易恢复的代码又自动升级了。
宝塔系统代码默认安装完是在/www/server/panel,接着我们直接将将压缩包内的panel目录上传到这里来,覆盖掉已有的文件。重启下宝塔,即可发现系统版本号已经恢复成7.4.2了:
还没完,我们使用beyond compare打开7.4.2和7.4.3的压缩包代码,先看看官方是怎么修复的漏洞:
比较粗暴,直接判断目录/www/server/phpmyadmin/pma是否存在,如果存在就直接删掉。所以,我们虽然恢复了系统版本代码,但删掉的pma已经不在了,我们还需要恢复一下这个目录。
方法也很简单,/www/server/phpmyadmin下本身存在一个phpmyadmin目录,我们直接复制一下这个目录即可:
三、漏洞究竟是怎么回事
有了环境,我们仍需看看代码。
首先,由于7.4.2是引入漏洞的版本,我们看看官方对7.4.2的更新日志:
用beyond compare打开7.4.0和7.4.2的压缩包代码,看看具体增加了哪些代码:
可见,在7.4.2版本中增加了两个视图,分别对应着phpmyadmin和adminer。视图中用到了panelphp#start方法,这个方法其实也是新加的:
def start(self,puri,document_root,last_path = ''): ''' @name 开始处理php请求 @author hwliang<2020-07-11> @param puri string(uri地址) @return socket or response ''' ... #如果是php文件 if puri[-4:] == '.php': if request.path.find('/phpmyadmin/') != -1: ... if request.method == 'post': #登录phpmyadmin if puri in ['index.php','/index.php']: content = public.url_encode(request.form.to_dict()) if not isinstance(content,bytes): content = content.encode() self.re_io = stringio(content) username = request.form.get('pma_username') if password = request.form.get('pma_password') if not self.write_pma_passwd(username,password): return resp('未安装phpmyadmin') if puri in ['logout.php','/logout.php']: self.write_pma_passwd(none,none) else: ... #如果是静态文件 return send_file(filename)
代码太长,我们不展开分析,只我写出来的部分。在请求的路径是/phpmyadmin/index.php且存在pma_username、pma_password时,则执行self.write_pma_passwd(username,password)。
跟进self.write_pma_passwd:
def write_pma_passwd(self,username,password): ''' @name 写入mysql帐号密码到配置文件 @author hwliang<2020-07-13> @param username string(用户名) @param password string(密码) @return bool ''' self.check_phpmyadmin_phpversion() pconfig = 'cookie' if pconfig = 'config' pma_path = '/www/server/phpmyadmin/' pma_config_file = os.path.join(pm

阿里云服务器数据库收费
云服务器搭建网站绑定域名
git常用的六个命令是什么
MySQL删除数据库的命令是什么?
怎么在云服务器上办公
等保三级要求
R如何读取win7_64位.xlsx文件
开机进入bios无法进入系统怎么办_电脑开机就进入bios的解决方法