Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Twig template injection #401

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker/tanner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ RUN sed -i 's/dl-cdn/dl-2/g' /etc/apk/repositories && \
musl-dev \
python3-dev && \
# Setup Tanner
git clone --depth=1 https://github.com/mushorg/tanner -b develop /opt/tanner && \
git clone --depth=1 https://github.com/mushorg/tanner /opt/tanner && \
cp /root/dist/config.yaml /opt/tanner/tanner/data/ && \
cd /opt/tanner/ && \
pip3 install --no-cache-dir setuptools && \
Expand Down
7 changes: 6 additions & 1 deletion tanner/data/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ EMULATOR_ENABLED:
lfi: True
xss: True
cmd_exec: True
php_code_injection: True
php_code_injection: True
twig_template_injection: True
php_object_injection: True
crlf: True
xxe_injection: True
Expand Down Expand Up @@ -100,3 +101,7 @@ REMOTE_DOCKERFILE:
SESSIONS:
delete_timeout: 300
analyze_timeout: 300

TWIG_PATH:
autoloader: "path/of/Autoloader.php"
stringloader: "path/of/StringLoader.php"
13 changes: 8 additions & 5 deletions tanner/emulators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from tanner import __version__ as tanner_version
from tanner.config import TannerConfig
from tanner.emulators import lfi, rfi, sqli, xss, cmd_exec, php_code_injection, php_object_injection, crlf, \
xxe_injection, template_injection # noqa
xxe_injection, template_injection, twig_template_injection # noqa
from tanner.utils import patterns


Expand All @@ -22,7 +22,8 @@ def __init__(self, base_dir, db_name, loop=None):
'php_object_injection': TannerConfig.get('EMULATOR_ENABLED', 'php_object_injection'),
'crlf': TannerConfig.get('EMULATOR_ENABLED', 'crlf'),
'xxe_injection': TannerConfig.get('EMULATOR_ENABLED', 'xxe_injection'),
'template_injection': TannerConfig.get('EMULATOR_ENABLED', 'template_injection')
'template_injection': TannerConfig.get('EMULATOR_ENABLED', 'template_injection'),
'twig_template_injection': TannerConfig.get('EMULATOR_ENABLED', 'twig_template_injection')
}

self.emulators = {
Expand All @@ -39,13 +40,15 @@ def __init__(self, base_dir, db_name, loop=None):
'crlf': crlf.CRLFEmulator() if self.emulator_enabled['crlf'] else None,
'xxe_injection': xxe_injection.XXEInjection(loop) if self.emulator_enabled['xxe_injection'] else None,
'template_injection': template_injection.TemplateInjection(loop) if
self.emulator_enabled['template_injection'] else None
self.emulator_enabled['template_injection'] else None,
'twig_template_injection': twig_template_injection.TwigTemplateInjection(loop) if
self.emulator_enabled['twig_template_injection'] else None
}

self.get_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'php_code_injection', 'php_object_injection',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add emulator here in order to enable it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POST too.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible_get_detection = await self.get_emulation_result(session, get_data, self.get_emulators)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh okay

'cmd_exec', 'crlf', 'xxe_injection', 'template_injection']
'cmd_exec', 'crlf', 'xxe_injection', 'template_injection', 'twig_template_injection']
self.post_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'php_code_injection', 'php_object_injection',
'cmd_exec', 'crlf', 'xxe_injection', 'template_injection']
'cmd_exec', 'crlf', 'xxe_injection', 'template_injection', 'twig_template_injection']
self.cookie_emulators = ['sqli', 'php_object_injection']

def extract_get_data(self, path):
Expand Down
66 changes: 66 additions & 0 deletions tanner/emulators/twig_template_injection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import asyncio
import logging

from tanner.config import TannerConfig
from tanner.utils.php_sandbox_helper import PHPSandboxHelper
from tanner.utils import patterns


class TwigTemplateInjection:
def __init__(self, loop=None):
self._loop = loop if loop is not None else asyncio.get_event_loop()
self.logger = logging.getLogger("tanner.twig_template_injection")
self.helper = PHPSandboxHelper(self._loop)
self.autoloader = TannerConfig.get("TWIG_PATH", "autoloader")
self.stringloader = TannerConfig.get("TWIG_PATH", "stringloader")

async def get_injection_result(self, code):
"""
Injects the code from attacker to vulnerable code and get emulation results from php sandbox.
:param code (str): Input payload from attacker
:return: twig_injection_result (dict): file_md5 (md5 hash), stdout (injection result) as keys.
"""

vul_code = """
<?php

require '%s';
require '%s';

Twig_Autoloader::register();
$loader = new Twig_Loader_String();
$twig = new Twig_Environment($loader);
$twig->addExtension(new \\Twig\\Extension\\StringLoaderExtension());
$payload = "%s";
$result = $twig->render($payload);
echo $result;
?>
""" % (
self.autoloader,
self.stringloader,
code,
)

self.logger.debug(
"Getting the twig injection results of %s from php sandbox", code
)
twig_injection_result = await self.helper.get_result(vul_code)

return twig_injection_result

def scan(self, value):
"""
Scans the input payload to detect attack using regex
:param value (str): code from attacker
:return: detection (dict): name (attack name), order (attack order) as keys
"""

detection = None
if patterns.TEMPLATE_INJECTION_TORNADO.match(value):
detection = dict(name="twig_template_injection", order=3)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with super simple string /foobar?b={{3*%272%27}} There are 2 possible detections:

{'name': 'template_injection', 'order': 4}
{'name': 'twig_template_injection', 'order': 3}

Since template_injection is order 4, tanner chooses it and execute.

Any ideas how to distinguish? @mzfr @rjt-gupta

Copy link
Collaborator

@rjt-gupta rjt-gupta Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For twig this case should give 6 and for tornado 222. (right?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need something that distinguishes these two..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah the regex is almost similar so it' hard to distinguish.

Also afeena can you please tell me how did you tested it? I mean the setup I want to know what I am doing wrong.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mzfr scan works perfectly fine, if you print possible detection you can see it. the only problem in the signature of the handle function.

return detection

async def handle(self, attack_params, session=None):
attack_params[0]['value'] = unquote(attack_params[0]['value'])
result = await self.get_injection_result(attack_params[0]['value'])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tornado is getting its results from the custom docker image, and here its phpox. the returned format is different..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjt-gupta do you have an example of the format?

return result