周日晚上,有群突然发布消息,对宝塔面板存在未授权访问漏洞的紧急漏洞预警,并给出了大量存在漏洞的网址:
只需点击其中一个,您就会看到一个大的后端管理页面,无需任何身份验证或登录。当然,各种神奇的图片和神话后来在社交网络上流行起来。作为一个冷静的安全研究人员,我当然一笑置之,但我对这个漏洞的成因还是蛮感兴趣的,所以在这篇文章中我们将考察整个过程。事件发生的原因。
我们的问题到底是什么?
首先我先给出一个结论:这件事绝对不是单纯的pma目录我忘了删除,或者宝塔面板不小心配置错误,更不是某些人阴谋论中所说的官方的。故意留后门。
我为什么这么说呢?首先,根据官方的说法,该漏洞仅影响以下版本:
该版本是最新版本(bug修复版本)的前一个版本。也就是说,该小版本之前的版本面板不受影响。我们想一下,如果是“后门”或者是官方忘记删除的目录,为什么只影响这个版本呢?而且宝塔面板发展了这么久,已经积累了400万用户,系统安全性也比较成熟。如果真有这种低劣的错误或者“后门”,早就应该被发现了。
经过在网上实际查看案例并询问使用过宝塔面板的朋友,发现7.4.2之前的版本没有pma目录,并且认证方式默认需要输入账号密码。因此,如果这个漏洞出现在宝塔中,它一定做了以下两件事:
那么,我们的问题就变成了,官方为什么要做出这两项改变,目的是什么?
为了研究这个问题,我们需要先安装一个版本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,因为漏洞曝光后,官方很快修复并升级。不过没关系,我们还是可以找到离线升级包的:
它们分别是版本7.4.0/7.4.2/7.4.3。我们分别下载并解压,并尝试将我们的服务器版本恢复到存在漏洞的版本7.4.2。
在恢复代码之前,我们首先断开服务器与互联网的连接,或者将宝塔设置为离线模式:
这样做的目的是为了防止宝塔进行自动版本更新,防止最后恢复的代码自动升级。
宝塔系统代码默认安装在/www//panel中。那么我们这里直接上传压缩包中的面板目录,覆盖已有的文件。重启宝塔,你会发现系统版本号已经恢复到7.4.2:
事情还没有结束。我们使用压缩包代码打开7.4.2和7.4.3看看官方是如何修复漏洞的:
这是比较粗糙的。直接判断/www///pma目录是否存在。如果存在则直接删除。因此,虽然我们恢复了系统版本码,但是删除的pma已经不存在了,我们还是需要恢复这个目录。
方法也很简单。 /www//下有一个目录。我们可以直接复制这个目录:
漏洞到底是什么?
环境搭建好了,我们还是要看代码。
首先,由于7.4.2是引入漏洞的版本,我们先看一下7.4.2官方的更新日志:
打开7.4.0和7.4.2的压缩包代码,看看具体添加了哪些代码:
可以看到7.4.2版本中增加了两个视图,分别对应 和 。 #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 username:
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)
代码太长我们就不分析了,只分析我写的部分。当请求的路径为//index.php且存在时,执行self.(,)。
自我跟进:
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 username:
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)
if not conf: return False
rep = r"/\* Authentication type \*/(.|\n)+/\* Server parameters \*/"
rstr = '''/* Authentication type */
$cfg['Servers'][$i]['auth_type'] = '{}';
$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['port'] = '{}';
$cfg['Servers'][$i]['user'] = '{}';
$cfg['Servers'][$i]['password'] = '{}';
/* Server parameters */'''.format(pconfig,self.get_mysql_port(),username,password)
conf = re.sub(rep,rstr,conf)
public.writeFile(pma_config_file,conf)
return True
这段代码也很容易理解。如果传入 和 ,宝塔会重写配置文件.inc.php,更改认证方式,并记下账号密码。
这就是为什么pma在7.4.2版本可以直接访问的原因。
补个课:
支持多种身份验证方法。默认是身份验证。这时需要输入账户密码;用户还可以将认证方式更改为身份验证。此时将使用配置文件中的账户密码来连接MySQL数据库,即不需要输入账户密码。 。
采取这些行动的官方原因
事实上,读者看到这里一定会感到困惑。这些代码是什么意思?官方为何将认证方式改为mode?
这是很多漏洞分析文章中常见的问题。文章后面是漏洞代码,找到漏洞发生点和利用方法后结束。他们没有深入研究为什么开发要这样写。那么下次你仍然无法挖出这个漏洞。
那么,思考到这里,我们现在至少有以下几个问题:
对于第一个问题,其实我们可以简单地找到答案。正常安装最新版本的宝塔7.4.3时,当我们点击宝塔后台的链接时,会访问这样一个路径:
为了修复这个漏洞,7.4.3版本回滚了部分代码,所以这个方法其实就是之前版本7.4.2的访问方式:通过888端口开头的文件夹直接访问。
在这种旧的访问方式中,888端口是一个单独的Nginx或服务器。整个过程是安全的,访问需要输入帐户和密码。
不过这种访问方式有点麻烦。它需要额外开放888端口,并且每次登录时都需要重新输入密码。因此,官方开发人员提出了一种新的方法,在宝塔后端级别将用户请求转发到php-fpm。这具有三个优点:
这就是为什么宝塔在7.4.2中添加了相关视图。这个视图是一个代理,它的工作就是将用户的请求转发给php-fpm。
当用户第一次使用这种方式登录时,系统会自动发送一个包含Mysql账户密码的数据包。此时宝塔后端会捕获账号密码,填写到配置文件中,并将认证方式改为。对于用户来说,体验是不再需要输入任何Mysql密码即可使用。
这确实给用户带来了更好的体验。
漏洞重现
说到这里我们应该还有一个疑问:既然官方的目的是“直接在层级上做用户认证,与宝塔的用户认证统一”,那么还有认证吗?为什么会出现未授权访问漏洞?
我们可以重现这个漏洞。首先我们以系统管理员身份登录宝塔后台,进入数据库页面,点击“”按钮,会弹出如下模式框:
这里面有两种访问方式。 “通过Nginx//OIs访问”是老版本的访问方式,“通过面板安全访问”是7.4.2中新增加的代理模式。
我们点击“通过面板安全访问”并捕获数据包。我们将捕获这样一个数据包:
宝塔前端填写了我们的Mysql账号密码,直接发送。而且因为我们前面分析的代码,是在后台直接将账号和密码写入到配置文件中,实现免认证逻辑。
如果未认证的用户直接访问:8888//index.php怎么办?将直接重定向到登录页面:
如果只是这样的话,这个过程就不会有任何漏洞。然而,官方开发者犯了一个错误。他将pma应用程序放在/www//目录下,该目录原本是旧访问方式使用的Web根目录。
这意味着我可以通过旧的888端口+pma目录访问新的,而新的修改了官方的配置文件,最终导致了未授权访问漏洞:
那么,如何解决这个问题呢?也很简单,把pma移到其他目录即可。
总结
我们来做个总结吧。
首先,宝塔面板绝对不是弱智。此漏洞不仅仅是将未经授权的 pma 留在外面并忘记删除它的问题。这实际上会打很多人的脸,因为大多数人认为这只是一个简单的未授权访问漏洞,并diss宝塔,但没想到背后其实存在复杂的逻辑错误。
其次,用户体验和安全绝对不冲突。我真的不喜欢为了确保安全而削弱用户体验的做法。所以希望宝塔官方不要因为这个漏洞事件而彻底回滚代码(据说7.4.3更新只是临时解决方案),需要改进的地方还是需要改进。
我已经好几年没有使用 Linux 面板了。这次重新体验了2020年的Linux面板,个人感觉宝塔其实是一个从外部更注重安全的系统,比如自动生成用户密码、用户名和密码。策略、默认的Php安全配置、自动版本更新等肯定比国内很多其他商业系统要好。但看代码,还是有很多需要改进的地方。稍后有机会我会详细阐述这一点。
你喜欢这篇文章吗?关注我,走之前点击阅读~
扫一扫在手机端查看
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。