随便点开个中一个,赫然便是一个大大的phpmyadmin后台管理页面,无需任何认证与登录。当然,随后各种神图神事也都刷爆了社交网络,作为一个镇静安全研究者,我对此当然是一笑置之,但是这个漏洞的缘故原由我还是颇感兴趣的,以是本文我们就来考证一下整件事情的缘由。
我们的问题究竟是什么?首先,我先给出一个结论:这件事情绝对不是简大略单地有一个pma目录忘却删除了,或者宝塔面板轻忽大意进行了缺点地配置,更不是像某些人阴谋论中说到的官方刻意留的后门。
我为什么这么说?首先,根据官方的说法,这个漏洞只影响如下版本:

这个版本便是最新版(漏洞修复版)的前一个版本。也便是说,这个确定的小版本之前的版本面板是不受影响的。我们试想一下,如果是“后门”或者官方忘却删除的目录,为什么只影响这一个版本呢?况且宝塔面板发展了这么久,积累了400万用户,体系安全性也相比拟较成熟,如果存在这么低劣的缺点或“后门”,也该当早就被创造了。
经由实际查看互联网上的案例和讯问利用了宝塔面板的朋友,我创造在7.4.2以前的版本中没有pma这个目录,并且phpmyadmin默认情形下认证方法是须要输入账号密码的。以是,宝塔涌现这个漏洞,一定是做过了下面这两件事:
新增了一个pma目录,内容phpmyadminphpmyadmin的配置文件被修正了认证办法那么,我们的问题就变成了,官方为什么要做这两处修正,目的究竟是什么?
为了研究这个问题,我们须要先安装一个宝塔7.4.2版本。但是,宝塔的安装是一个傻瓜化的一键化脚本:
yuminstall-ywget&&wget-Oinstall.shhttp://download.bt.cn/install/install_6.0.sh&&shinstall.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方法,这个方法实在也是新加的:
defstart(self,puri,document_root,last_path=''):'''@name开始处理PHP要求@authorhwliang<2020-07-11>@parampuristring(URI地址)@returnsocketorResponse'''...#如果是PHP文件ifpuri[-4:]=='.php':ifrequest.path.find('/phpmyadmin/')!=-1:...ifrequest.method=='POST':#登录phpmyadminifpuriin['index.php','/index.php']:content=public.url_encode(request.form.to_dict())ifnotisinstance(content,bytes):content=content.encode()self.re_io=StringIO(content)username=request.form.get('pma_username')ifusername:password=request.form.get('pma_password')ifnotself.write_pma_passwd(username,password):returnResp('未安装phpmyadmin')ifpuriin['logout.php','/logout.php']:self.write_pma_passwd(None,None)else:...#如果是静态文件returnsend_file(filename)
代码太长,我们不展开剖析,只我写出来的部分。在要求的路径是/phpmyadmin/index.php且存在pma_username、pma_password时,则实行self.write_pma_passwd(username,password)。
跟进self.write_pma_passwd:
defwrite_pma_passwd(self,username,password):'''@name写入mysql帐号密码到配置文件@authorhwliang<2020-07-13>@paramusernamestring(用户名)@parampasswordstring(密码)@returnbool'''self.check_phpmyadmin_phpversion()pconfig='cookie'ifusername:pconfig='config'pma_path='/www/server/phpmyadmin/'pma_config_file=os.path.join(pma_path,'pma/config.inc.php')conf=public.readFile(pma_config_file)ifnotconf:returnFalserep=r"/\Authenticationtype\/(.|\n)+/\Serverparameters\/"rstr='''/Authenticationtype/$cfg['Servers'][$i]['auth_type']='{}';$cfg['Servers'][$i]['host']='localhost';$cfg['Servers'][$i]['port']='{}';$cfg['Servers'][$i]['user']='{}';$cfg['Servers'][$i]['password']='{}';/Serverparameters/'''.format(pconfig,self.get_mysql_port(),username,password)conf=re.sub(rep,rstr,conf)public.writeFile(pma_config_file,conf)returnTrue
这个代码也很好理解了,如果传入了username和password的情形下,宝塔会改写phpmyadmin的配置文件config.inc.php,将认证办法改成config,并写去世账号密码。
这便是为什么7.4.2版本中pma可以直接访问的缘故原由。
补个课:
phpmyadmin支持数种认证方法,默认情形下是Cookie认证,此时须要输入账号密码;用户也可以将认证办法修正成Config认证,此时phpmyadmin会利用配置文件中的账号密码来连接mysql数据库,即不用再输入账号密码。
官方做这些动作的缘故原由实在各位看官看到这里肯定脑筋里还是一团浆糊,这些代码究竟意味着什么呢?为什么官方要将认证模式改成config模式?
是很多漏洞剖析文章的通病,这些文章在涌现漏洞后跟一遍漏洞代码,找到漏洞发生点和利用方法就结束了,并没有深入研究开拓为什么会这么写,那么下次你还是挖不出漏洞。
以是,这里思考一下,我们现在最少还有下列疑问:
在7.4.2版本以前,用户是如何利用phpmyadmin的?宝塔为什么要在7.4.2版本增加phpmyadmin有关的视图?宝塔为什么要将phpmyadmin认证模式改成config?我们如何复现这个漏洞?第一个问题,我们实在可以大略找到答案。在正常安装宝塔最新版7.4.3时,我们点击宝塔后台的phpmyadmin链接,会访问到这样一个路径:
7.4.3版本为了修复这个漏洞,回滚了部分代码,以是这种办法实在便是7.4.2以前版本的phpmyadmin的访问办法:通过888端口下的一个以phpmyadmin_开头的文件夹直接访问phpmyadmin。
这种老的访问方法中,888端口是一个单独的Nginx或Apache做事器,全体东西是安全的,访问也须要输入账号密码。
但是这种访问方法有些麻烦,须要额外开放888端口,而且每次上岸都要重新输入密码。以是,官方开拓职员提出了一种新的做法,在宝塔后真个python层面转发用户对phpmyadmin的要求给php-fpm。这样有三个好处:
直接在python层面做用户认证,和宝塔的用户认证进行统一,不须要多次输入mysql密码也不须要再对外开放888端口了利用phpmyadmin也不再依赖于Nginx/Apache等做事器中间件了这便是为什么宝塔要在7.4.2增加phpmyadmin有关的视图的缘故原由,这个视图便是一个phpmyadmin的代理,做的事情便是转发用户的要求给php-fpm。
用户在第一次利用这种办法登录时,系统会自动发送包含了Mysql账号密码的数据包,宝塔后端会捕捉到此时的账号密码,填入phpmyadmin的配置文件,并将认证办法改成config。对付用户来说,感想熏染到的体验便是,不再须要输入任何Mysql密码即可利用phpmyadmin了。
这的确给用户的利用带来了更好的体验。
漏洞复现此时我们该当还有个疑问:既然官方目的是“直接在python层面做用户认证,和宝塔的用户认证进行统一”,那么仍旧是有认证的呀?为什么会涌现未授权访问漏洞呢?
我们可以来复现一下这个漏洞。首先,我们以系统管理员的身份登录宝塔后台,来到数据库页面,点击“phpMyAdmin”按钮,会弹出如下模态框:
这个里面有两种访问模式,“通过Nginx/Apache/OIs访问”是老版本的访问办法,“通过面板安全访问”便是7.4.2新增加的代理模式。
我们点击“通过面板安全访问”,并抓包,会抓到这样一个数据包:
宝塔前端将我们的Mysql账号密码填好了直接发给phpmyadmin。又由于我们前面剖析过的那段代码,后台将账号密码直接写入了phpmyadmin配置文件,来做到免认证的逻辑。
如果一个未认证的用户,直接访问http://ip:8888/phpmyadmin/index.php呢?会被直接重定向到登录页面:
如果仅仅是这样,这个过程是不存在漏洞的。但是,官方开拓职员犯了一个缺点,他将pma运用放在了/www/server/phpmyadmin目录下,而这个目录原来是老的phpmyadmin访问办法所利用的Web根目录。
这意味着,我通过老的888端口+pma目录,可以访问到新的phpmyadmin,而新的phpmyadmin又被官方修正了配置文件,终极导致了未授权访问漏洞:
以是,如何办理这个问题呢?也很大略,只须要将pma移到其他目录去即可。
总结我们来做个总结。
首先,宝塔面板绝对不是傻瓜,这个漏洞不是简大略单的放了一个未授权的pma在表面忘却删。这实在会打很多人脸,由于大部分人认为这只是个大略的phpmyadmin未授权访问漏洞,并对宝塔进行了一顿diss,没有想到这后面实在是一个繁芜的逻辑缺点。
其次,用户体验和安全绝对是不冲突的,我十分不喜好为了保障安全而阉割用户体验的做法。以是希望宝塔官方不会由于这次的漏洞事宜而彻底将代码回滚(听说7.4.3的更新只是临时办理方案),该改进的地方还是要改进。
我有数年不再利用Linux面板了,这次也算重新体验了一下2020年的Linux面板,个人觉得宝塔看外在实在是一个比较看重安全的系统,比如自动天生的用户密码、用户名和密码的策略、默认的Php安全配置、自动的版本更新等等,比较于很多海内其他的商业系统,绝对属于有过之而无不及了。但是看代码实在须要改进的地方还有很多,这个往后有机会再细说吧。