代码审计 - PHP - 某网盘1day分析
前言
本篇文章首发在先知社区(为先知打Call) 作者Zjacky(本人) 先知社区名称: Zjacky
原文链接为https://xz.aliyun.com/t/13868
代码审计篇章都是自己跟几个师傅们一起审计的1day或者0day(当然都是小公司较为简单),禁止未经允许进行转载,发布到博客的用意主要是想跟师傅们能够交流下审计的思路,毕竟审计的思路也是有说法的,或者是相互源码共享也OK,本次审计的目标是某开源网盘的审计,大部分是一些带颜色的网站和奇怪的东东使用,有师傅分析过了但没详细审计思路于是有了这篇博客来交流交流
审计
路由分析
首先这里明眼人一看就知道在application
这个目录下,可以抓登录的接口或者注册的接口或者进去后的接口来判断对应的代码之后登录进去寻找文件
路由很少,所以很明显看出我们登录的/user/login/
对应的是index
目录下的User.php
的login
方法
后台SQL注入
emm本来想看前台的,但是大部分是存在鉴权代码的
$this->userInfo['id']
由于是TP的框架,所以先来回顾下TP的where
语句
在TP框架中,大部分的SQL查询都是做了参数绑定的,如下图所示
这里的$code
就是做了参数绑定,但是一开始去试着访问的时候发现是 404
并且返回非法请求
然后参考了下这篇文章 发现原来可能存在路由定义的问题
https://www.kancloud.cn/z8859346/thinkphp/1747811
找到/route/route.php
发现对index
这个模块进行了定义,所以要根据他的规则来进行访问,只要访问了/s:code
就会去访问index/share
方法
但因为:code
进行了占位符 所以相当于绑定了参数无法进行注入
所以这些点都不存在SQL注入
于是转向了后台
跟踪到index的file文件,发现了list()
方法这里接受几个可控的 参数foloder.id
、search
,进入到FileManager#listFile()
方法中
主要是因为listfile()
这个命名还是很大可能会跟数据库有关联的,所以跟进下
这listFile
这个方法里面,他会先执行self
类里面的getfolderPid
这个方法,并且传入$folder_id
再次跟进
我们传入的内容不为空,所以传了啥返回啥,接下来往下走就是整个SQL的一个坑点了
坑点
接着就是数组的形式进行赋值
那么现在问题来了,他是以数组的形式去进行sql查询,那么可能存在sql的点吗?我们来进行mysql的监控
代码如下,先是单数组正常使用select
查询
1 |
|
发现正常转义了,那么双数组跟三数组也是一样到效果,那么这里就得到一个结论,并不是因为数组的拼接问题所导致的sql注入,那么细心的话可以发现,我的代码其实跟原始代码是有一定的区别的,就是并没有加入一个关键代码
1 |
|
这里来解释一下fetchSql
方法
fetchSql 方法在 ThinkPHP 框架中(以及其他可能支持该功能的框架或数据库操作类)的主要作用是获取即将执行的 SQL 查询语句,而不是真正执行这个查询。当你调用 fetchSql(true) 时,框架会生成对应的 SQL 语句并返回,但并不会执行该 SQL
啥意思呢,来看数据库监控
可以发现数据库监控仅仅只是输出了一条 展示字段的SQL语句
1 |
|
那其实就是因为他压根就没有进行查询我们的where
条件 —> 这其实就也印证了我之前一直说 并没有在数据库监控中看到这些查询语句,是因为使用了fetchSql(true)
那么接下来既然是没有进行SQL语句的执行的,那么也就是我们现在的$files_sql
是一条尚未执行的SQL语句,我们来打印一下看看
1 |
|
正是因为没有带入数据库查询,所以里面的特殊符号比如'
就并没有参数绑定或者预编译或者转义,而最终产生SQL注入的万恶之源,正是下方的union
1 |
|
所以最终的结果就是因为通过了union
去拼接起来了,所以才导致了SQL注入的产生,破案了
最终报文如下
1 |
|
文件上传
首先全局搜索move_uploaded_file
函数,发现public/server/index.php
文件引用该函数
之后便发现这个方法是一个protected
权限来修饰,所以这个方法直接调用不成功
之后就要去观看这个文件被谁来调用 发现是\public\server\index.php#start()
调用了,刚好是符合protected
属性的
但是往上看发现存在验签操作
发现是存在两个传参,$command
$sign
并且是需要验签的,那我们跟进sign_verify
看看
继续跟进sign_params
如果不理解我们可以写个demo本地测试下
首先流程如下
1 |
|
他会把我们传入的$sign
不作为$params
去传参
那么逻辑就清楚了就是我们GET
传参的内容跟我们token
传入的内容进行拼接且MD5的值跟我们$sign
要相同
如果验签成功,就能够进入到upload
的逻辑中
之后就调用upload_file
这个函数,这个函数我也说了,他是调用move_uplaoded_file
函数进行上传的,他接受一个参数uid
的值
那么在这里我们就尝试进行复现,当刚打开BP我就发现问题了,emmm在上述的demo中我是写死了拼接的字符串的,但是远程是$this->config['token']
拼接这个东西啊,所以跟进一下这个$config
的内容,发现在最下面就定义了token的内容为asdasfasfasfasfasfa
那么就可以直接生成sign
了
1 |
|
那么继续复现
1 |
|
上传后返回如下
解码后为 同步错误
来看看源码
发现已经上传了,但是咱不知道上传路径啊。。。找一下本地可以发现在这里
但是如何找路径呢,可以关注到后续还有一串代码
1 |
|
我将$info
打印了下发现已经存入了上传的路径了
跟进下upload_notify
发现跟SSRF一样可以向外请求并且带上我们的$info
——–> 这感觉写的就怕我找不到路径似的,这里的验签压根就不需要管他,因为文件都传上去了,无所谓了,只需要路径,而他也并没有判断验签对不对(如果校验了,就没办法上传了,因为$info
是未知的)
于是带好参数写好验签即可成功触发
1 |
|
1 |
|
总结
- 多扣代码多调试才行,一步一步来
- 有些时候框架忘记或者不熟悉还是要多仔细看看才行