일요일 밤 갑자기 특정 그룹에서 Pagoda 패널의 phpmyadmin에 무단 접근 취약점에 대한 긴급 취약점 경고 메시지가 게시되었으며, 취약점이 있는 URL이 대량으로 제공되었습니다:
방금. 클릭하세요. 하나는 인증이나 로그인이 없는 대규모 phpmyadmin 백엔드 관리 페이지입니다. 물론 온갖 종류의 마법 그림과 신화가 나중에 소셜 네트워크에서 인기를 끌게 되었습니다. 차분한 보안 연구자로서 물론 웃어넘겼지만, 여전히 이 취약점의 원인에 대해서는 상당한 관심이 있으므로 이 기사에서는 살펴보겠습니다. 사건의 전체 과정.
1. 우리의 문제는 무엇입니까?
먼저 결론을 내리겠습니다. 이 문제는 단순히 삭제하는 것을 잊어버린 pma 디렉토리나 파고다 패널이 부주의하게 잘못 구성되었거나 일부 사람들이 음모론에서 말하는 것처럼 그런 것이 아닙니다. 공식 백도어 일부러 열어두었습니다.
내가 왜 그런 말을 하는 걸까요? 우선 공식 성명에 따르면 이 취약점은 다음 버전에만 영향을 미칩니다.
Linux 공식 버전 7.4.2
Linux 베타 버전 7.5.13
Windows 공식 버전 6.8
이 버전은 최신 버전(버그 수정 버전)입니다. 즉, 이 특정 마이너 버전 이전의 버전 패널은 영향을 받지 않습니다. 생각해 봅시다. "백도어"이거나 관계자가 삭제하는 것을 잊어버린 디렉토리라면 왜 이 버전에만 영향을 미치나요? 게다가 Pagoda Panel은 오랫동안 개발되어 400만 명의 사용자를 보유하고 있으며, 시스템 보안이 비교적 성숙되어 있습니다. 이러한 열악한 오류나 "백도어"가 있었다면 오래 전에 발견되었을 것입니다.
실제로 인터넷에서 사례를 확인하고 파고다 패널을 사용하는 친구들에게 물어본 결과 7.4.2 이전 버전에는 pma 디렉토리가 없으며 phpmyadmin의 인증 방법은 기본적으로 계정 비밀번호를 입력해야 한다는 것을 알게 되었습니다. 따라서 이 취약점이 파고다에 나타날 때 다음 두 가지 작업이 수행되어야 합니다.
새로운 pma 디렉터리가 추가되었고, phpmyadmin
phpmyadmin의 구성 파일 내용이
그래서 우리의 질문은 공무원이 왜 이 두 가지 변경을 했으며 그 목적은 무엇입니까?
이 문제를 연구하려면 먼저 Pagoda 7.4.2 버전을 설치해야 합니다. 그러나 Pagoda 설치는 완벽한 원클릭 스크립트입니다.
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
는 사용자에게 버전 번호를 선택할 수 있는 옵션을 제공하지 않습니다. 공식 Git이 오랫동안 업데이트되지 않았을 수 있습니다. 버전(7.4.2)?
둘째, 적합한 버전을 설치하세요
물론 저에게는 문제가 되지 않습니다. 먼저 위에서 언급한 원클릭 스크립트를 사용하여 최신 버전의 Pagoda Panel을 설치했습니다.
설치 과정은 당연히 문제가 없습니다. 설치가 완료된 후 시스템에 표시되는 버전 번호는 최신 버전인 7.4.3입니다. 취약점이 노출된 후 공식적으로 신속하게 복구하고 업그레이드했기 때문입니다. 하지만 문제가 되지 않습니다. 오프라인 업그레이드 패키지를 찾을 수 있습니다:
http://download.bt.cn/install/update/LinuxPanel-7.4.0.zip http://download.bt.cn/install/update/LinuxPanel-7.4.2.zip http://download.bt.cn/install/update/LinuxPanel-7.4.3.zip
각각 버전 7.4.0/7.4.2/7.4.3입니다. 각각 다운로드하고 압축을 푼 다음 서버 버전을 다음으로 복원하려고 했습니다. 취약한 버전 7.4.2.
코드를 복원하기 전에 먼저 인터넷에서 서버 연결을 끊거나 Pagoda를 오프라인 모드로 설정합니다.
이 목적은 Pagoda가 자동으로 버전을 업데이트하는 것을 방지하고 자동 업그레이드를 방지하는 것입니다. 드디어 복원된 코드입니다.
Pagoda 시스템 코드는 기본적으로 /www/server/panel
에 설치됩니다. 그런 다음 여기에 압축된 패키지의 패널 디렉터리를 직접 업로드하여 기존 파일을 덮어씁니다. 탑을 다시 시작하면 시스템 버전 번호가 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
아직 끝나지 않았습니다. Beyond Compare를 사용하여 7.4.2 및 7.4.3의 압축 패키지 코드를 열고 먼저 공식 수정 방법을 확인합니다. 취약점:
< img src="https://img.php.cn/upload/image/652/684/240/1598248841349251.png" title="1598248841349251.png" alt="Pagoda 패널에 있는 phpMyAdmin의 무단 액세스 보안 취약점은 낮은 수준의 실수인가요?" /> 대체로 /www/server/phpmyadmin/pma
디렉토리가 존재하는지 직접 확인하세요. 존재한다면 직접 삭제하세요. 따라서 시스템 버전 코드를 복원했지만 삭제된 pma는 더 이상 존재하지 않으며 여전히 이 디렉터리를 복원해야 합니다.
방법도 매우 간단합니다. /www/server/phpmyadmin
아래에 이 디렉터리를 직접 복사할 수 있습니다.
用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 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)
代码太长,我们不展开分析,只我写出来的部分。在请求的路径是/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 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
这个代码也很好理解了,如果传入了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/OI를 통한 액세스"는 이전 버전의 액세스 방법이고 "패널을 통한 보안 액세스"는 7.4.2에 새로 추가된 프록시 모드입니다.
"패널을 통한 보안 액세스"를 클릭하고 패킷을 캡처합니다.
Pagoda 프런트 엔드는 Mysql 계정 비밀번호를 입력하고 phpmyadmin으로 직접 보냅니다. 그리고 앞서 분석한 코드로 인해 계정과 비밀번호가 백그라운드에서 phpmyadmin 구성 파일에 직접 기록되어 인증이 필요 없는 논리를 구현합니다.
인증되지 않은 사용자가 http://ip:8888/phpmyadmin/index.php
에 직접 접속하면 어떻게 되나요? 로그인 페이지로 직접 리디렉션됩니다: http://ip:8888/phpmyadmin/index.php
呢?会被直接重定向到登录页面:
如果仅仅是这样,这个过程是不存在漏洞的。但是,官方开发人员犯了一个错误,他将pma应用放在了/www/server/phpmyadmin
/www/server/phpmyadmin
디렉토리에 pma 애플리케이션을 배치했습니다. 이것은 이전 888 포트 + pma 디렉터리를 통해 새 phpmyadmin에 액세스할 수 있으며 새 phpmyadmin이 공식적으로 구성 파일을 수정하여 궁극적으로 무단 액세스 취약점으로 이어지는 것을 의미합니다.
그래서 해결 방법 이 문제? 또한 매우 간단합니다. pma를 다른 디렉토리로 옮기면 됩니다.
6. 요약
요약을 만들어 보겠습니다. 우선 Pagoda 패널은 확실히 정신지체 패널이 아닙니다. 이 취약점은 단순히 승인되지 않은 PMA를 외부에 두고 삭제하는 것을 잊어버리는 문제가 아닙니다. 이것은 실제로 많은 사람들의 뺨을 때릴 것입니다. 왜냐하면 대부분의 사람들은 이것이 단지 phpmyadmin의 단순한 무단 액세스 취약점이라고 생각하고 탑을 비난하지만 실제로 그 뒤에 복잡한 논리 오류가 있을 것이라고는 예상하지 않았기 때문입니다. 둘째, 사용자 경험과 보안은 절대 충돌하지 않습니다. 보안을 보장하기 위해 사용자 경험을 훼손하는 관행은 정말 마음에 들지 않습니다. 따라서 이번 취약점 사건으로 인해 파고다 관계자가 코드를 완전히 롤백하지 않기를 바라며(7.4.3 업데이트는 일시적인 해결책일 뿐이라고 합니다) 아직 개선이 필요한 부분은 남아있습니다. 몇년간 리눅스 패널을 사용하지 않았는데 이번에 2020년 리눅스 패널을 다시 경험하게 되었습니다. 개인적으로 파고다는 사용자 자동생성 등 보안에 좀 더 신경을 쓴 시스템처럼 느껴집니다. 비밀번호, 사용자 이름 및 비밀번호 정책, 기본 Php 보안 구성, 자동 버전 업데이트 등은 다른 많은 국내 상용 시스템보다 확실히 뛰어납니다. 하지만 코드를 살펴보면 아직 개선이 필요한 부분이 많습니다. 이에 대해서는 나중에 기회가 되면 자세히 설명하겠습니다. 🎜🎜이 기사는 공개 계정에서 가져온 것입니다: https://mp.weixin.qq.com/s/3ZjwFo5gWlJACSkeYWQLXA🎜위 내용은 Pagoda 패널에 있는 phpMyAdmin의 무단 액세스 보안 취약점은 낮은 수준의 실수인가요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!