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

周日晚,某群里突然发布了一则消息,宝塔面板phpmyadmin存在未授权访问漏洞的紧急漏洞预警,并给出了一大批存在漏洞的URL:

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

随便点开其中一个,赫然就是一个大大的phpmyadmin后台管理页面,无需任何认证与登录。当然,随后各种神e * f N #图神事也都刷爆了社交网络,作为一个冷静安v T ! a R全研究者,我对此当然是一笑置之l ; I `,但是这个漏4 g ^ 9洞的原因我还是颇感兴趣的,所以本文我们就来考证一下整件事情的缘由。

一、我们- 3 ] b %的问题究竟是什么?

首先,我先给出一B K 8 m 0 |个结论:这件事情绝对不是简简单单地有Y t = o F一个pma目录忘记删除了,或J 3 I v ^ B ` p宝塔面板疏忽大意进行了错误地配置,更不是像某些人阴谋论中说到的官方刻意留的后门。

我为什/ ( d么这么说?首先,根据官方的说法,这个漏洞只影响如下版本:

  • Linux正式版7.4.2

  • Linux测试版7.5.13

  • Windows正式版6.8

这个版本就是最新版(漏洞修复版)的前一个版本。也就是说,这个确0 2 u } %定的小版本之前的版本面板是不受影响的。我们试想一下,如果是“后门”或者官方忘记删除的目录,为什么只影响这一个版本呢?况A g i且宝塔面板发展了这么久,积累了400万用户,体系安全性也相对比较成熟,如果存在这么低劣的错误或“后门”,也应该早就被发现k 9 a i p 7 Z | I了。

经过实际查看互联网上的案例和询问使用了宝塔面板的朋友,我发现在7.4.^ - h z ? 42以前的版本中没有pma这个目录,并且phpmyadmin默认情况下认证方法是需要输入账号密码的。所以,宝塔出现这个漏洞,一定是做过了下面这两件事:

  • 新增了一个pma目录,内容phpmyad( 7 F . Amin

  • phpmyadmin的配置文件被修改了认证方式

那么,我们的问题就变成了,官方为什么要做这两处修改,目的究竟是什么?

为了研究这个问题,我们需要先安装k 6 7 M . I 2 .一个宝塔7.4.2版本。但是,宝塔的安装是一个傻瓜化的一键化脚本

yum ins| F _ W c d `tall -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh

并没有给到用户一个可以选择版本号的选项,官方的Git也许久没更新了,我们如何G ; .才能安装到一个合适的版] 6 _ 1 4 C v $本(7.4.2)呢?

二、安装一个合适的版本

这当然难不倒我。首先,我安装了最新版的宝塔面板,用的就是上述一键化脚本

安装的过程o ( K 9 n x % H %自然没什么问题,安装完成后,系统显示的版本号是最新版7.4.3,因为在爆出这个漏洞以后,官方迅L * = M [ N速进行了修复升级。不过没关系,我们仍然可以找到离线升级包:

http://download.bt.cn/install/update/LinuxPaneQ y W @l-7.4.0.zi] } d Op
http://download.bt.cn/insta^ % Nll/update/LinuxPanel-7.4.2.zip
http://download.* # i 5 N { ^bt.cn/inf A { ostall/update/LinuxPanel-7.2 . ; Q B t4.3.zip

分别是7.4.0/7.m C a ~ h E4.2/7.4.3的版本,我们d y n $ v m ? o W分别下载并解压,并尝试将自己的服务器版本恢复成漏% } ! R ] o B洞版本7.4.2。

在恢复代码之前,我们先将服务器断网,或者将宝塔设置成离线模式:

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

这么做的目的是防止宝塔进行自动版本更新,避r y 3免好不容易恢复的代码又自动升级了。

宝塔系统代码默认安装完是在/www/server/panel,接着我们直接将将压缩包内的panel目录上传到这里来,覆盖掉已有的文件。重启下宝z p 4塔,即可发现系7 S K G ] N [ r统版本号已经恢复成7.4.2了:

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

还没完,我们使用beyond compare打开7.4.2和7.4.3的压缩包代码,先看看官方是怎么修复的漏洞:

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

比较粗暴,直接判断目录/www/server/phpmyadmin/pma是否存在,如果存在就直接删掉。所以,我们虽然恢复了系统版? ] u f C本代码,但删] e 1 U X K / ^ 7掉的pma已经不在了,我们还需要4 . p恢复一下这个目录。

方法也很简单,/www/server/phpmyadmin下本身存在一个ph% q l @ n d 1 =pmyadmin目录,我们直接复制一下这个目录即可:

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

三、漏洞究竟是怎么回事

有了T L f v K - M .环境,我们仍需看看a { * Y S 2 X代码。

首先,由于i O _ * t X7.4.2是引入漏洞的版本,我们看看官方对7.4.2的更新日志:

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

用beyond compaG l p ere打开7.! 8 I 2 n4.0和7.4.2的压缩包代码,看看具体增加了哪些代码:

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

可见,在7.4.2版本中增加了两个视图,分别对应着phpmyadmin和? 9 U 2 & 4 gadminer。视图中用到了panelPHP#start方法,这个方法其实也是新加的:

    def start(self,purm e t ; =i,documt z ; + q d } sent_root,last_path = ''):
'''
@name 开始处理PHP请求
@author h 0 J c rwliang<2020-07-11>
@param p7 u I , q Z t } Wuri string(URI地* X i址)
@return socket or Resp: B y * : [onse
'''
...
#如果是PHP文件
if pura 3 ? . m /i[-4:] == '.php':
if  request.path.find('$ C X  | w V c/phpmyadmin/') != -1:
...
if request.method == 'POST':
#登录php[ i  i gmyadmin
if puri in ['index.php','/index.php']:
content =w ~  . + i a { ` public.url_encode(request.form.to_dict())
if not isinstance(content,bytes):
content = content.encode()
selfZ X 4 B ! $ H 6 i.re_io = StringIO(contc D W lent)
username = request.form.get('pma_username')
if username:
password = request.form.get('pma_password')
if not self.write_pma_passwd(usernam~ T C he,password):
return Resp('未安装phE ( _ K b  N | 6pmyadmin')
if puri in ['logout.php','/logout.php']:
self. a . P 9 6 p ,write_pma_passwd(None,None)
else:
...
#如果是静态文件
return send_file(filename)

代码太长,我们不展开分析0 C z a,只} Q l 9 = d ` ~ A我写出来的部分。在请求的路径是/phpmyadmin/index.php且存在pma_usernamepma_password时,则e P a , K 1 3 : ?执行self.write_pma_passwd(usernamez } G,password)

跟进self.write_pma_passwd:

    def write_pma_pW  , k d $assZ [ _ 8wd(self,usernd R 0 y 0ame,passws ^ Qord):
'''
@name 写入mysql帐号密码到配置文件
@author hwliang<2020-07-13>
@param username string(用户名)
@param password string(密码)
@return bool
'''
self.check_phpmyadmin_phpversion()
pconfig = 'cookie'
if username:
pconfig = 'config'
pma_path = '/www/server/phpmyadmk I k 9 } # in/'
p~ e ! a L E qma_config_file = os.path.join(pma_path,'pma/config.inc.php')
conf = public.readFile(pma_config_file)
if not conf: return F. 7 galse
rep = r"/\* AuI C Y  %thentication type \*/(.0 } R $ C r|\n)+Z / U/\* Server parameters \*/"
rstr =c  / 9 & '''/* Authentication type */
$cfg['Servers'][, { C +$i]['auth_type'] = '{}';
$cfg['Servers'][x : f v x$i]['host'] = 'localhost';
$cfg['Servers'][$i]['port'] = '{}';
$cfg['SeH ; - W % X Nrvers'][$i]['user'] = '{}';
$cfg['Servers'][$i]['passwor: 8 xd'] = '{}';
/*S 3 } 0 Y . I 6 Server par4 8 a { t m M K .ameters */'''.format(pconfig,self.get_mysql_W i U + O =port(),username,password)
conf = re.sub(rep,rstr,conf)
pu- 9 ) 6blic.writeFile(pma_config_file5 } d H [,conf)
return True

这个代码也很好理解了,如果传入了usernac M ] ` me和password的情况下,宝塔会改写phpmyadmin的配置文件config.inc.php,将认证方式改成config,并写死账号密码。

这就是为什么7.4.2版本中pma可以直接访问的原因。

补个课:

phpmya| ^ j hdm} C t f xin支持数种/ | G * { S T , t认证方法,默认情况下是Cookie认证,此时需要输入账号密码;用户也可以将认证方式修改成Config认证,此时phpmyadmin会使用配置文件中的账H 6 f B 4 L D a号密码来连接mysql数据库,即不用再输入账号密码。1 E 2 F = 1 F i

四、官方做这些动作的原因

其实各位看官看到这里肯定脑子里还是一团浆h x C ) N糊,这些代码究竟意味着什么呢?为什么官方要将认证模式改成con c ? 1 S N A A Onfig模式?

是很多漏洞分析文章的通病,这些文章在出现漏洞后跟一遍漏洞代码,找到漏洞发生点和d 1 A x A利用方法就结束了,并没有g R 7 z & R深入研究开发为什么会这么写,那么下次你还是挖不出漏洞。

所以,这里思考一下,我们现, ! 4 c Q z在起码还有下列疑问:

  • 在7.4.2版本以前,用户是如何使用phpmyadmin的?

  • 宝塔为什么要在7.4.2版本增加phpmyadmin有关的视图?

  • 宝塔为什么要将phpmyadmin认证模式改成config?

我们如何复现这个漏洞?

第一个问题,我们其实可以简单找到答案。在正常安装宝塔最新版7.4.3时,我们点击宝塔后台的phpmyF T 8 ) &admin链接,会访问到这样一个路径:

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

7.4.3版本为了修复这个漏洞,回t z 9 } 1 I O滚了部分代码,所以这种方式其实就是7.4.2以前版本的phpmyadmin的访问方式:通过888端口下的一个以phpmyadmin_开头的文件夹直接访问phpmyadminw ) d 3 v ] + n 0

这种老的访问方法中,888端口是一个单独的Nginx或Apache服务器,整个东西是安全的,访问也需要输入账号密码C W U 6 B g 6

但是这种访问方法有些麻烦,需要额外开放888端@ @ 4 P口,而~ v y O B ! L .且每次登陆都要重新输入: ~ 1密码。所以,官方开发人员提出了一种新的做法,在宝塔后端的pythonN + ~ q x W层面转发用户对phpM : & 7myadmin的请求给php-fpm。这样有三个好处:

  • 直接在python层面做用户认证,和宝塔的用户认证& + r { I o进行统一,不需要多次输入mysql密码

  • 也不需要再对外开放888端口了Q ! / $

  • 使用phpmyadmin也不再依赖于Nginx? c 8 U 5/Apache等服务器中间件了

这就是为什么宝塔要在m E r | V + { + p7.4.2增加phpmyadmin有关的视图的原因,这个6 h q视图就是一个phpmyadmin的代理,做的事情就是转发用户的请求给php-fpm。

用户在第一次使用这种方式登~ s B录时,系统会自动发送包含了Mysql账号密码的数据包,宝塔后端会捕捉到此时的账号密码,填入phpmyadmin的配置文件,并将认证方式改成config。对于用户来说,感受到的体验就是,不再需要输入任何Mysql密码即可使用phpmyadmin了。

这的确给用户的使用带来了更好的体验。

五、漏洞复现

此时M z J ~ m我们应该还有个疑问:既然^ R s T . W官方目的是^ } z G _ ; R“直接在python层面做& , 6 :用户认证,和宝5 - f塔的用户认证进行统一”,那么仍然是有认证的呀?为什么会出现未授权访问漏洞呢?

我们可以来复现一下这个e Z a d漏洞。首先,我们R A G ) g W z ^以系统管理员的身份登录宝塔后台,来到数据库页面,点) - b /击“phpMyAdmib 0 z $ q Qn”按钮% ; ;,会弹出如下模态框:j , 7 4 e s ; @

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

这个里面有两种访问模式,“通过Nginx/Apach? F 4 Q N ue/OIs访问”是老版本的访问方式,“通过面板安全访问”就是7.4F B T l c 4 M.2新增加的代理模式。n ` v ; ` 8 C

我们N , E v点击“通过面板安全访问”,并抓包,会抓到这样一个数据包~ 1 V Z L G ; c :

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

宝塔前端将我们的Mysql账号密码填好了直接发给phpmyadmin。又因为我S & K r _ s们前面分析L Z Z X ? b B M y过的那段代码,后台将账号密码直接写入了phpX B v . b c / _myadmin配置文件,来做到免认证的逻辑。

如果一个未认证的用户,直接访问http://ip:8888/phpmyadZ ] n $ l w 7 dmin/index.php呢?会被直接重定向到登录/ Y g页面:

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

如果仅仅是这样,这个过程是不存k 5 ( 9 n l L c F在漏洞的。但是,官( [ +方开发人员犯了一个错误,他将pma应用放在了/www/server/phpmyadmin目录下,而这个目录原本是老的phpmyadmin访问方式所使用的WQ 6 W 7 - d L feb根目录。

这意味着,我通过老的888端口+pma目录,可以访问到新的phpmyadmin,e m %而新& + Q t c的phpmyadmin又被官方修改了配置文件,最终导致了e X 5 0未授权访问漏洞:

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

所以,如何解决这个问& i ` S Z 4题呢b i t 7 d .?也很简单,只需要将pma移到其他目录去即可。

六、总结

我们来做个总结。

首先,宝塔面板绝对不是弱智,这个漏洞不是简简单单的放了一个未授权的pma在外面忘记删。这其实会打很多人脸,因为大部分# X & I z !人认为这只是个简单的phpmyadmin未授权访问漏洞,并1 w o 9 h 4 V 4 )对宝塔进行了一顿diss,没有想到这后面其实是B e @ t一个复杂的逻辑错误。

其次,用户体验和安全绝对是不冲突的,我十分不喜C Z | X l ? x j欢为了保障安全而阉割用户体验的做法。所以希望宝塔官方不会因为这次的漏洞事件而彻底将代码回滚(据说7.4.3的更新只是临2 z 6 2 l时解决方案),该改进的地方还是要改进。

我有数年不再使用Linuxt k H w面板了,这次也算重新体验了一下2020年的Linux面板,个` H & ? , i人感觉宝塔看外在其实是一个比较注重安全的系统,比如自动生成的用户密码、用户名和密码的策略、默认的J ? L J S X =Php安全配置、自动的版本更q T W 8 U / R ~新等等,相比于很多国内其他的商业系统( y - ! 7 } J Q,绝对属于有过之而无不及了。但是看代码其实需要改进的地方还有很多,这个以后有机会再细3 N s F B说吧。

本文来自公众号:https://mp.weixin.qq.com/s/3ZjwFo5gWlJACSkeYWQLXA

以上就是宝塔面板phpMyAdmin未授权访问安全漏洞是个低级错误吗?的详细内容。

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