电子签约管理控制台
定时任务RCE
契约锁管理控制台的后台存在一个定时任务功能,可通过上传java
代码执行。
上传的接口为/api/code/upload
,对应控制器为com.qiyuesuo.ucenter.CustomCodeController
的upload
方法。
在处理上传的过程中,调用到com.qiyuesuo.code.CustomCodeServiceImpl
的create
方法时,会检测是否存在Runtime
、Process
、ProcessBuilder
三个关键字,这个可以通过加载字节码的方式很容易绕过。
后续会调用到com.qiyuesuo.core.compiling.javac.JavaDynamicCompiler
的compileFile
方法对传入的java
源码进行编译,会调用loadClass
加载类,但并未调用。
通过CustomCode
对象保存包名
、类名
等信息。
最后返回CustomCode
对象的id
配置循环执行的次数和延期的时间或者定时表达式来执行上传的代码。
点击确定以后会请求/api/utask/create
接口,该接口对应com.qiyuesuo.utask.UserTaskController
的create
方法,调用this.timerService.addTask(record)
添加到定时任务服务中。
等待一段时间后,会自动调用到com.qiyuesuo.code.CustomCodeStrategyManager
的loadCustomCode
方法,加载并调用上传的java代码。
权限绕过
每次请求会经过的Filter
,顺序从上自下。
1 | 0 = {ApplicationFilterConfig@34795} "ApplicationFilterConfig[name=corsFilter, filterClass=org.springframework.web.filter.CorsFilter]" |
对用户鉴权方法在AccessControlFilter
的doFilterInternal
方法中,调用了this.isAllow(request, response)
方法处理路径鉴权逻辑。
在isAllow
方法中,匹配请求路径,如果在白名单中,则放行,如果不在白名单中,则验证cookie
,最终如果返回true
则执行doFilter
,返回false
则返回登录失效,并跳转到登录页面。
调用this.securityProperties.getAllowedPatterns()
方法获取无需登录即可访问的路径。
1 | 0 = "/api/setup*" |
在matchesRequestURI
方法中,调用request.getRequestURI()
获取请求的路径,遍历allowedPatterns
中的路径,调用this.pathMatcher.matches(pattern, requestUri)
方法逐个验证。
当匹配规则为/api/setup*
时,只能匹配到/api/setupxxx
这类url,不能匹配/api/setup/a
当匹配规则为/api/setup/**
时,可以匹配到/api/setup/aaaa/aaaa/aaa
这类路由
这里使用request.getRequestURI
获取请求路径,这种方式存在一个安全隐患,当路径中存在..
危险关键字并不会将其..
剔除掉,所以在路径中添加/api/setup/../../api/code/upload
也会被成功匹配到。
请求经过Filter
过滤器之后,会调用到DispatcherServlet
类的doService
方法,它是Spring MVC的核心,负责接收HTTP请求,并根据请求信息分发到相应的Controller进行处理
,会对URL
进行路径归一化处理。
调用栈
1 | getRequestUri:382, UrlPathHelper (org.springframework.web.util) |
在调用到Controller
之前,会调用this.decodeRequestString(request, uri)
对URI进行一次解码。
然后通过request.getServletPath()
获取请求路径
通过request.getServletPath()
获取到的路径路径获取bean
但是当通过/api/setup/../../api/code/upload
进行访问时,会被拦截掉。
这是因为在RiskUrlPreventFilter
的doFilterInternal
方法中,会对请求的url进行检测,如果url中包含RISK_URLS
变量中的只,则不允许访问,其中就包括/..
。
1 | static { |
由于会对路径做归一化处理,会进行url解码,而这里,我们可以将..
进行URL编码即可绕过。
漏洞复现
TestTimerTask1.java
1 | package com.qiyuesuo.utask.java; |
上传,并配置执行方式
电子签约签署平台
表达式注入
在接口/api/template/html/add
,该接口对应com.qiyuesuo.api.TemplateHtmlController
的addHtmlTemplate
方法,该方法接收一个TemplateBean
类型的bean
,其中包含title
、params
等变量,首先会校验title
不能为空并且不能超过100个字符长度。后续会获取该对象的params
参数进行遍历,该参数是一个List
对象,保存的是TemplateParam
类型的对象,该对象存在一个属性值extensionParam
,在160
行会调用param.getExtensionParam()
获取,并调用this.objectMapper.readValue
将其转换成Map
,然后会判断是否包含expression
键,存在则获取该键,并调用this.checkExpression(newExpression)
方法。
在checkExpression
方法中,传入newExpression
参数会被当做js
代码执行。
TemplateBean
对象的参数值完全可控,所以可以构造TemplateBean
参数的params
参数,将expression
键值指定JS
代码实现代码执行。
权限绕过
签约平台
所经过的Filter
过滤器
1 | 0 = "ApplicationFilterConfig[name=corsFilter, filterClass=org.springframework.web.filter.CorsFilter]" |
同样用户鉴权方法在AccessControlFilter
的doFilterInternal
方法中,调用了this.isAllow(request, response)
方法处理路径鉴权逻辑。
访问路径白名单
1 | 0 = "/api/captcha/**" |
绕过原理跟管理控制一致。
漏洞复现
执行命令延时判断漏洞是否存在
1 | VChqYXZhLmxhbmcuVGhyZWFkKS5zbGVlcCg1MDAwKQ== |
1 | POST /login/%2e%2e/api/template/html/add HTTP/1.1 |
参考文章
https://www.cnblogs.com/nice0e3/p/14801884.html#%E7%BB%95%E8%BF%87%E6%96%B9%E5%BC%8F