ShowDoc代码审计(失败分析)
前置知识
2020年ShowDoc修复了一个高危漏洞
复现过程可以看
可以看到的是,上传数据库的文件后缀为.<>php
,这是绕过了黑名单的关键
为什么要这个样子写呢?
很明显的可以看到可以绕过黑名单的判断
那为什么<>
不见了呢?
原因在于,这套代码基于ThinkPHP 3.2.3
的内核
在底层的上传逻辑中,在生成文件名之前,存在一个过滤标签的操作
那有没有其他办法绕过这里的黑名单呢?
完全可以~!
- 第一个关注点在,程序验证后缀的文件只是通过
editormd-image-file
参数传入的 - 第二个关注点,在调用上传方法的时候代码是
$upload->upload()
,这里为进行传参
那么在底层逻辑中
当传入的file
参数为空的时候,是会重新获取参数的
也就是说我们只需要传入任意非检测参数即可
比如传参file
至于文件名字,暂不做讨论
代码审计(失败)
前台的上传点,直接返回了false
,并且限制了上传后缀
那么需要登陆的呢?
发现这里没有设置白名单
根进Attachment
的upload
方法
没有办法,这里向底层的upload
方法传了参数
而且接受文件的参数固定,无法更改editormd-image-file
就是在这里没有关注uploadOne
和upload
方法的区别,从一开始方向就错了
总结发现
常规情况下无法继续上传了
未发现直接调用底层
upload
方法而不进行传参的地方传进底层的是一个参数名为
editormd-image-file
的$_FILES
数组这套代码是
ThinkPHP 3.2.3
抛出问题
ThinkPHP 3.2.3
和5
版本的对比,一个很容易被忽略的小知识点,就是3
未设置error_reporting
,这就导致,我们可以出现很多警告,只要不是致命错误即可- 接下来就是就是
$_FILES数组了
,可能很少人去搞他,多文件上传也是不同的名字即可,那我就想用同一个参数名字传多个文件可不可以做到呢?
对于上面的问题,其实有答案
我们平时用的$_FILES
基本都是$_FILES['file']['name']
来获取一个上传文件的文件名字
但它其实还有一个维度,那就是当file
为数组的时候
写法为:$_FILES['file']['name'][$i]
,$i
就是区分第几个文件
代码测试
测试代码
1 |
|
当不传入数组的时候
传入数组的时候
可以看到这就是代表,第一个文件是1.php
,第二个文件是2.php
为什么要提这个,因为ThinkPHP
中对这种模式的传参,有着自己的处理逻辑,直接上代码
继续代码分析
upload.class.php
的dealFiles
方法
正常情况下是没问题,但是当传参为一个具体的文件信息时会出问题
文件的处理逻辑
- 比如
.net
,$_FILES[0]['name']
,代表上传数组的第一个文件名字 - 但是
php
中上面说了,$_FILES['name'][0]
才代表第一个文件名字
经过这段代码处理后
比如$_FILES
有以下几个键名
name
type
tmp_name
error
size
直接就变成了五个文件
测试代码
1 |
|
结果
那么0
和1
的键值可控吗
自然可控
进行如下构造,发现我们可以在type文件出构造一个完整的文件数组
为什么不在name中构造呢
因为filename
中不能出现目录,只会取文件名
那么tmp_name
的目录可控吗,其实默认配置下
Windows
为C:\windows\php71E2.tmp
,一般不会修改,我这是纯属第一次没看到缓存文件才改的linux
默认自查
那怎么操作呢
输入一个固定的缓存文件,总情况是确定的,肯定在上传文件的时候会出现一个和我们输入的缓存名一样的缓存
为什么非要这样呢,因为php
底层对move_uploaded_file
的缓存文件有签名认证的地方,所以必须是上传时生成的才行
测试代码
1 |
|
运气有点好
瞬间跑出来了
然后激动的去测试,发现了一点
uploadOne
调用upload
的时候,又加了一层array
,
就是说不能直接调用upload
$upload->upload($_FILES['file'])
这种在正常情况下,也不能上传,执着了执着了
失败了,暂时这样吧
总结
写给自己看
这就像极了小时候的考试不审题
出发点的角度错了,终点的偏差会更加的大
当然也不能全然说一点收获没有
比如通过upload
方法上传的文件,黑名单是限制不住的,可以通过多文件来绕过