From 9a7653718a9a0faebb5ed7e488985350517facfb Mon Sep 17 00:00:00 2001 From: yagamiL Date: Fri, 23 Feb 2024 19:13:03 -0500 Subject: [PATCH 1/3] Update for docker support + Added ssl/tls verification flag --- .gitignore | 14 + .travis.yml | 7 - .vscode/launch.json | 16 + Dockerfile | 12 + README.md | 22 +- burp_extension.py | 1 - burp_extension/README.md | 53 ---- burp_extension/__init__.py | 0 burp_extension/burp_extender.py | 12 - burp_extension/channel.py | 35 --- burp_extension/config_tab.py | 113 ------- burp_extension/scan_issue.py | 146 --------- burp_extension/scanner_check.py | 28 -- core/channel.py | 9 +- core/checks.py | 2 - docker-envs/Dockerfile.java | 19 -- docker-envs/Dockerfile.node | 15 - docker-envs/Dockerfile.php | 16 - docker-envs/Dockerfile.python2 | 13 - docker-envs/Dockerfile.python3 | 14 - docker-envs/Dockerfile.ruby | 12 - docker-envs/README.md | 16 - docker-envs/docker-compose.yml | 45 --- requirements.txt | 1 - tests/basetest.py | 202 ------------ tests/env_java_tests/spark-app/build.gradle | 25 -- .../gradle/wrapper/gradle-wrapper.properties | 6 - .../env_java_tests/spark-app/settings.gradle | 2 - .../webframeworks/SparkApplication.java | 135 -------- .../src/main/resources/templates/hello.html | 10 - tests/env_node_tests/connect-app.js | 289 ------------------ tests/env_php_tests/eval.php | 31 -- tests/env_php_tests/smarty-3.1.32-secured.php | 32 -- .../env_php_tests/smarty-3.1.32-unsecured.php | 38 --- tests/env_php_tests/twig-1.19.0-unsecured.php | 40 --- tests/env_php_tests/twig-1.20.0-secured.php | 40 --- tests/env_py_tests/webserver.py | 194 ------------ tests/env_ruby_tests/config.ru | 3 - tests/env_ruby_tests/webserver.rb | 60 ---- tests/run_channel_test.sh | 33 -- tests/run_java_tests.sh | 30 -- tests/run_node_tests.sh | 39 --- tests/run_php_tests.sh | 31 -- tests/run_python2_tests.sh | 33 -- tests/run_python3_tests.sh | 34 --- tests/run_ruby_tests.sh | 31 -- tests/test_channel.py | 225 -------------- tests/test_java_freemarker.py | 65 ---- tests/test_java_velocity.py | 88 ------ tests/test_node_dot.py | 56 ---- tests/test_node_dust.py | 64 ---- tests/test_node_ejs.py | 67 ---- tests/test_node_javascript.py | 79 ----- tests/test_node_marko.py | 58 ---- tests/test_node_nunjucks.py | 68 ----- tests/test_node_pug.py | 80 ----- tests/test_php_php.py | 88 ------ tests/test_php_smarty_secured.py | 81 ----- tests/test_php_smarty_unsecured.py | 113 ------- tests/test_php_twig_secured.py | 56 ---- tests/test_php_twig_unsecured.py | 64 ---- tests/test_py_jinja2.py | 159 ---------- tests/test_py_mako.py | 177 ----------- tests/test_py_python.py | 56 ---- tests/test_py_tornado.py | 104 ------- tests/test_ruby_erb.py | 57 ---- tests/test_ruby_ruby.py | 56 ---- tests/test_ruby_slim.py | 57 ---- tests/tests.sh | 12 - utils/cliparser.py | 10 +- utils/config.py | 1 - utils/strings.py | 1 - 72 files changed, 70 insertions(+), 3831 deletions(-) delete mode 100644 .travis.yml create mode 100644 .vscode/launch.json create mode 100644 Dockerfile delete mode 100644 burp_extension.py delete mode 100644 burp_extension/README.md delete mode 100644 burp_extension/__init__.py delete mode 100644 burp_extension/burp_extender.py delete mode 100644 burp_extension/channel.py delete mode 100644 burp_extension/config_tab.py delete mode 100644 burp_extension/scan_issue.py delete mode 100644 burp_extension/scanner_check.py delete mode 100644 docker-envs/Dockerfile.java delete mode 100644 docker-envs/Dockerfile.node delete mode 100644 docker-envs/Dockerfile.php delete mode 100644 docker-envs/Dockerfile.python2 delete mode 100644 docker-envs/Dockerfile.python3 delete mode 100644 docker-envs/Dockerfile.ruby delete mode 100644 docker-envs/README.md delete mode 100644 docker-envs/docker-compose.yml delete mode 100644 tests/basetest.py delete mode 100644 tests/env_java_tests/spark-app/build.gradle delete mode 100644 tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties delete mode 100644 tests/env_java_tests/spark-app/settings.gradle delete mode 100644 tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java delete mode 100644 tests/env_java_tests/spark-app/src/main/resources/templates/hello.html delete mode 100644 tests/env_node_tests/connect-app.js delete mode 100644 tests/env_php_tests/eval.php delete mode 100644 tests/env_php_tests/smarty-3.1.32-secured.php delete mode 100644 tests/env_php_tests/smarty-3.1.32-unsecured.php delete mode 100644 tests/env_php_tests/twig-1.19.0-unsecured.php delete mode 100644 tests/env_php_tests/twig-1.20.0-secured.php delete mode 100644 tests/env_py_tests/webserver.py delete mode 100644 tests/env_ruby_tests/config.ru delete mode 100644 tests/env_ruby_tests/webserver.rb delete mode 100755 tests/run_channel_test.sh delete mode 100755 tests/run_java_tests.sh delete mode 100755 tests/run_node_tests.sh delete mode 100755 tests/run_php_tests.sh delete mode 100755 tests/run_python2_tests.sh delete mode 100755 tests/run_python3_tests.sh delete mode 100755 tests/run_ruby_tests.sh delete mode 100644 tests/test_channel.py delete mode 100644 tests/test_java_freemarker.py delete mode 100644 tests/test_java_velocity.py delete mode 100644 tests/test_node_dot.py delete mode 100644 tests/test_node_dust.py delete mode 100644 tests/test_node_ejs.py delete mode 100644 tests/test_node_javascript.py delete mode 100644 tests/test_node_marko.py delete mode 100644 tests/test_node_nunjucks.py delete mode 100644 tests/test_node_pug.py delete mode 100644 tests/test_php_php.py delete mode 100644 tests/test_php_smarty_secured.py delete mode 100644 tests/test_php_smarty_unsecured.py delete mode 100644 tests/test_php_twig_secured.py delete mode 100644 tests/test_php_twig_unsecured.py delete mode 100644 tests/test_py_jinja2.py delete mode 100644 tests/test_py_mako.py delete mode 100644 tests/test_py_python.py delete mode 100644 tests/test_py_tornado.py delete mode 100644 tests/test_ruby_erb.py delete mode 100644 tests/test_ruby_ruby.py delete mode 100644 tests/test_ruby_slim.py delete mode 100755 tests/tests.sh diff --git a/.gitignore b/.gitignore index d28288c..daa30dd 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,20 @@ node_modules # Jython *.class +# VS-code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + # Vim # swap .sw[a-p] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9a1b5d2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -sudo: required - -services: - - docker - -script: - ./tests/tests.sh diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dc5005e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File with Arguments", + "type": "debugpy", + "request": "launch", + "program": "tplmap.py", + "console": "integratedTerminal", + "args": "-u http://localhost/?name=so -k" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e8be6d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.9 + +WORKDIR /app +COPY . /app + +RUN python -m pip install --upgrade pip +RUN pip install --no-cache-dir -r requirements.txt +# Running the script when the container launches +ENTRYPOINT ["python", "tplmap.py"] +# Default cmd +CMD ["-h"] + diff --git a/README.md b/README.md index 075ae79..e14d6fd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ Tplmap ====== -> 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). - 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. 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. @@ -98,7 +96,7 @@ Use `--os-shell` option to launch a pseudo-terminal on the target. ``` $ ./tplmap.py --os-shell -u 'http://www.target.com/page?name=John' -[+] Tplmap 0.5 +[+] Tplmap 0.3 Automatic Server-Side Template Injection Detection and Exploitation Tool [+] Run commands on the operating system. @@ -110,14 +108,21 @@ root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh ``` +### Using docker +You can use docker instead, if you encounter any issue with the script (mostly caused by python2 dependencies e.g yaml). +```console +$ docker build -t tplmap . +$ docker run --rm tplmap:latest -h +$ docker run --rm tplmap:latest -u 'http://www.target.com/page?name=jhon' +``` Supported template engines -------------------------- Tplmap supports over 15 template engines, unsandboxed template engines and generic _eval()_-like injections. -| Engine | Remote Command Execution | Blind | Code evaluation | File read | File write | -|------------------------|---------------|-------------------|-----------------|-----------|------------| +| Template engine | Remote Command Execution | Blind | Code evaluation | File read | File write | +|------------------------|-------|-------------------|-----------------|-----------|------------| | Mako | ✓ | ✓ | Python | ✓ | ✓ | | Jinja2 | ✓ | ✓ | Python | ✓ | ✓ | | Python (code eval) | ✓ | ✓ | Python | ✓ | ✓ | @@ -134,10 +139,9 @@ Tplmap supports over 15 template engines, unsandboxed template engines and gener | ERB | ✓ | ✓ | Ruby | ✓ | ✓ | | Smarty (unsecured) | ✓ | ✓ | PHP | ✓ | ✓ | | PHP (code eval) | ✓ | ✓ | PHP | ✓ | ✓ | -| Twig (<=1.19) | ✓ | ✓ | PHP | ✓ | ✓ | -| Freemarker | ✓ | ✓ | Java | ✓ | ✓ | -| Velocity | ✓ | ✓ | Java | ✓ | ✓ | -| Twig (>1.19) | × | × | × | × | × | +| Freemarker | ✓ | ✓ | × | ✓ | ✓ | +| Velocity | ✓ | ✓ | × | ✓ | ✓ | +| Twig | × | × | × | × | × | | Smarty (secured) | × | × | × | × | × | | Dust (> dustjs-helpers@1.5.0) | × | × | × | × | × | diff --git a/burp_extension.py b/burp_extension.py deleted file mode 100644 index 5aadc42..0000000 --- a/burp_extension.py +++ /dev/null @@ -1 +0,0 @@ -from burp_extension.burp_extender import BurpExtender diff --git a/burp_extension/README.md b/burp_extension/README.md deleted file mode 100644 index d1c5d60..0000000 --- a/burp_extension/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Burp Suite Plugin - -Tplmap is able to run as a Burp Suite Extension. - -### Install - -Load burp_extension.py with following conditions. - -* Burp Suite edition: Professional -* The Python modules required for Tplmap are installed. - * PyYaml - * requests -* Extension type: Python - -An example of a simple setup procedure: - -1. Install Jython by installer -```sh -$ wget 'https://repo1.maven.org/maven2/org/python/jython-installer/2.7.2/jython-installer-2.7.2.jar' -O jython_installer.jar -$ mkdir "$HOME"/jython -$ java -jar jython_installer.jar -s -d "$HOME"/jython -t standard -$ rm jython_installer.jar -``` -2. Install additional Python modules -```sh -$ curl -sL 'https://github.com/yaml/pyyaml/archive/refs/tags/5.1.2.tar.gz' | tar xzf - -$ cd pyyaml-5.1.2 -$ "$HOME"/jython/bin/jython setup.py install -$ cd .. -$ curl -sL 'https://github.com/psf/requests/archive/refs/tags/v2.22.0.tar.gz' | tar xzf - -$ cd requests-2.22.0 -$ "$HOME"/jython/bin/jython setup.py install -$ cd .. -$ rm -rf pyyaml-5.1.2 requests-2.22.0 -``` -3. Run your Burp Suite -4. Open Jython file chooser dialog -[Extender] - [Options] - [Python Environment] - [Location of the Jython standalone JAR file] -5. Choose the file `$HOME/jython/jython.jar` -6. Load `burp_extender.py` as Python type burp extension - -### Scanning - -Configure scanning option from 'Tplmap' tab, and do an active scan. - -### Limitation - -Only the detection feature of Tplmap is available. -Exploitation feature is not implemented, use Tplmap CLI. - -The `--injection-tag` option is also not available, because this extension follows Burp's Insertion Point setting. - -If you need the `--injection-tag` option, you can use [Scan manual insertion point](https://github.com/ClementNotin/burp-scan-manual-insertion-point) extension. diff --git a/burp_extension/__init__.py b/burp_extension/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/burp_extension/burp_extender.py b/burp_extension/burp_extender.py deleted file mode 100644 index 46e5b61..0000000 --- a/burp_extension/burp_extender.py +++ /dev/null @@ -1,12 +0,0 @@ -from burp import IBurpExtender -from config_tab import ConfigTab -from scanner_check import ScannerCheck - -class BurpExtender( IBurpExtender ): - - def registerExtenderCallbacks( self, callbacks ): - configTab = ConfigTab( callbacks ) - callbacks.setExtensionName( 'Tplmap' ) - callbacks.addSuiteTab( configTab ) - callbacks.registerScannerCheck( ScannerCheck( callbacks, configTab ) ) - diff --git a/burp_extension/channel.py b/burp_extension/channel.py deleted file mode 100644 index 7b1af58..0000000 --- a/burp_extension/channel.py +++ /dev/null @@ -1,35 +0,0 @@ -class Channel: - - def __init__( self, callbacks, configTab, baseRequestResponse, insertionPoint, payloadPosition ): - self._callbacks = callbacks - self._helpers = callbacks.getHelpers() - self._configTab = configTab - self._baseRequestResponse = baseRequestResponse - self._insertionPoint = insertionPoint - self._payloadPosition = payloadPosition - self._request = self._helpers.analyzeRequest( baseRequestResponse ) - - self.url = self._request.getUrl() - self.args = { - 'level': self._configTab.getLevel(), - 'technique': self._configTab.getTechniques() } - self.data = {} - self.detect = False - self.messages = [] - - def req( self, injection ): - payload = injection if self._payloadPosition == 'replace' else self._insertionPoint.getBaseValue() + injection - checkRequest = self._insertionPoint.buildRequest( self._helpers.stringToBytes( payload ) ) - checkRequestResponse = self._callbacks.makeHttpRequest( self._baseRequestResponse.getHttpService(), checkRequest ) - self.messages.append( { - 'injection': injection, - 'requestResponse': checkRequestResponse - } ) - return self._helpers.bytesToString( checkRequestResponse.getResponse() ) - - def detected( self, technique, detail ): - self.detect = True - self.technique = technique - self.detail = detail - self.detect_offset = len( self.messages ) - diff --git a/burp_extension/config_tab.py b/burp_extension/config_tab.py deleted file mode 100644 index eb09f33..0000000 --- a/burp_extension/config_tab.py +++ /dev/null @@ -1,113 +0,0 @@ -from burp import ITab - -from javax.swing import JPanel, GroupLayout, JLabel, JComboBox, JCheckBox -from java.awt import Dimension - -from core.checks import plugins - -class ConfigTab( ITab, JPanel ): - - def __init__( self, callbacks ): - self._callbacks = callbacks - self._helpers = callbacks.getHelpers() - self.__initLayout__() - - def __initLayout__( self ): - self._levelComboBox = JComboBox() - levelComboBoxSize = Dimension( 300, 30 ) - self._levelComboBox.setPreferredSize( levelComboBoxSize ) - self._levelComboBox.setMaximumSize( levelComboBoxSize ) - for level in range( 0, 6 ): - self._levelComboBox.addItem( str( level ) ) - - self._techRenderedCheckBox = JCheckBox( 'Rendered', True ) - self._techTimebasedCheckBox = JCheckBox( 'Time-based', True ) - - self._plugin_groups = {} - for plugin in plugins: - parent = plugin.__base__.__name__ - if not self._plugin_groups.has_key( parent ): - self._plugin_groups[ parent ] = [] - self._plugin_groups[ parent ].append( plugin ) - self._pluginCheckBoxes = [] - for pluginGroup in self._plugin_groups.values(): - for plugin in pluginGroup: - self._pluginCheckBoxes.append( PluginCheckBox( plugin ) ) - - self._positionReplaceCheckBox = JCheckBox( 'Replace', True ) - self._positionAppendCheckBox = JCheckBox( 'Append', False ) - - displayItems = ( - { - 'label': 'Level', - 'components': ( self._levelComboBox, ), - 'description': 'Level of code context escape to perform (1-5, Default:0).' - }, - { - 'label': 'Techniques', - 'components': ( self._techRenderedCheckBox, self._techTimebasedCheckBox, ), - 'description': 'Techniques R(endered) T(ime-based blind). Default: RT.' - }, - { - 'label': 'Template Engines', - 'components': self._pluginCheckBoxes, - 'description': 'Force back-end template engine to this value(s).' - }, - { - 'label': 'Payload position', - 'components': ( self._positionReplaceCheckBox, self._positionAppendCheckBox, ), - 'description': 'Scan payload position. This feature only appears in BurpExtension.' - } - ) - - layout = GroupLayout( self ) - self.setLayout( layout ) - layout.setAutoCreateGaps( True ) - layout.setAutoCreateContainerGaps( True ) - - labelWidth = 200 - hgroup = layout.createParallelGroup( GroupLayout.Alignment.LEADING ) - vgroup = layout.createSequentialGroup() - for displayItem in displayItems: - label = JLabel( displayItem.get( 'label' ) ) - label.setToolTipText( displayItem.get( 'description' ) ) - _hgroup = layout.createSequentialGroup().addComponent( label, labelWidth, labelWidth, labelWidth ) - _vgroup = layout.createParallelGroup( GroupLayout.Alignment.BASELINE ).addComponent( label ) - for component in displayItem.get( 'components' ): - _hgroup.addComponent( component ) - _vgroup.addComponent( component ) - hgroup.addGroup( _hgroup ) - vgroup.addGroup( _vgroup ) - - layout.setHorizontalGroup( hgroup ) - layout.setVerticalGroup( vgroup ) - - def getTabCaption( self ): - return 'Tplmap' - - def getUiComponent( self ): - return self - - def getLevel( self ): - return self._levelComboBox.getSelectedIndex() - - def getTechniques( self ): - return '%s%s' % ( 'R' if self._techRenderedCheckBox.isSelected() else '', 'T' if self._techTimebasedCheckBox.isSelected() else '' ) - - def getEngines( self ): - return [ checkbox.getPlugin() for checkbox in self._pluginCheckBoxes if checkbox.isSelected() ] - - def getPayloadPosition( self ): - return { 'replace': self._positionReplaceCheckBox.isSelected(), 'append': self._positionAppendCheckBox.isSelected() } - -class PluginCheckBox( JCheckBox ): - - def __init__( self, plugin ): - JCheckBox.__init__( self, plugin.__name__, True ) - self._plugin = plugin - parent = plugin.__base__.__name__ - tooltip = parent if( parent != 'Plugin' ) else 'eval' - self.setToolTipText( tooltip ) - - def getPlugin( self ): - return self._plugin diff --git a/burp_extension/scan_issue.py b/burp_extension/scan_issue.py deleted file mode 100644 index 01ceb4a..0000000 --- a/burp_extension/scan_issue.py +++ /dev/null @@ -1,146 +0,0 @@ -from burp import IScanIssue - -from array import array -import cgi - -class ScanIssue( IScanIssue ): - - def __init__( self, callbacks, baseRequestResponse, insertionPoint, channel ): - self._callbacks = callbacks - self._helpers = callbacks.getHelpers() - self._baseRequestResponse = baseRequestResponse - self._insertionPoint = insertionPoint - self._channel = channel - - def getUrl( self ): - return self._helpers.analyzeRequest( self._baseRequestResponse ).getUrl() - - def getIssueName( self ): - return 'Server-side template injection' - - def getIssueType( self ): - return 0x08000000 - - def getSeverity( self ): - return 'High' - - def getConfidence( self ): - return 'Certain' - - def getIssueBackground( self ): - return None - - def getRemediationBackground( self ): - return None - - def getIssueDetail( self ): - prologue_template = """ - The {parameter} parameter appears to be vulnerable to server-side template injection attacks. - The template engine appears to be {template}.

- """ - - render_template = """ - The payload {payload} was submitted in the {parameter} parameter. - This payload contains an {template} template statement.

- The server response contained the string {rendered}. - This indicates that the payload is being interpreted by a server-side template engine.

- """ - - blind_template = """ - The time-based blind payload {payload} was submitted in the {parameter} parameter. - 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.

- """ - - info_template = """ - Identified Informations:
- - Capabilities: - - """ - - data = self._channel.data - parameter = cgi.escape( self._insertionPoint.getInsertionPointName() ) - template = data.get( 'engine' ) - language = data.get( 'language' ) - prologue = prologue_template.format( parameter=parameter, template=template, language=language ) - - if self._channel.technique == 'render': - payload = self._channel.messages[ self._channel.detect_offset - 1 ].get( 'injection' ) - technique_part = render_template.format( - parameter = parameter, - template = template, - payload = cgi.escape( payload ), - rendered = cgi.escape( self._channel.detail.get( 'expected' ) ) ) - execute_method = 'execute' - elif self._channel.technique == 'blind': - blind_true_payload = self._channel.messages[ self._channel.detect_offset - 2 ].get( 'injection' ) - detail = self._channel.detail - blind_true_detail = detail.get( 'blind_true' ) - average = ( detail.get( 'average' ) / 1000.0 ) - delta_true = blind_true_detail.get( 'end' ) - blind_true_detail.get( 'start' ) - delta_true_milliseconds = ( delta_true.seconds * 1000000.0 + delta_true.microseconds ) / 1000.0 - technique_part = blind_template.format( - payload = cgi.escape( blind_true_payload ), - parameter = parameter, - average = average, - delta = delta_true_milliseconds ) - execute_method = 'execute_blind' - - _okng = lambda f: 'OK' if f else 'NG' - info_part = info_template.format( - template = template, - language = language, - technique = self._channel.technique, - os = data.get( 'os', 'undetected' ), - execute_method = execute_method, - execute = _okng( data.get( execute_method ) ), - read = _okng( data.get( 'read' ) ), - write = _okng( data.get( 'write' ) ), - bind_shell = _okng( data.get( 'bind_shell' ) ), - reverse_shell = _okng( data.get( 'reverse_shell' ) ) ) - return prologue + technique_part + info_part - - def getRemediationDetail( self ): - return None - - def getHttpMessages( self ): - if self._channel.technique == 'render': - responseMarkString = self._channel.detail.get( 'expected' ) - detectedMessage = self._channel.messages[ self._channel.detect_offset - 1 ] - messages = [ self._markHttpMessage( detectedMessage.get( 'requestResponse' ), detectedMessage.get( 'injection' ), responseMarkString ) ] - elif self._channel.technique == 'blind': - blind_true_message = self._channel.messages[ self._channel.detect_offset - 2 ] - blind_false_message = self._channel.messages[ self._channel.detect_offset - 1 ] - messages = [ - self._markHttpMessage( blind_true_message.get( 'requestResponse' ), blind_true_message.get( 'injection' ), None ), - self._markHttpMessage( blind_false_message.get( 'requestResponse' ), blind_false_message.get( 'injection' ), None ) ] - for evaluate in self._channel.messages[ self._channel.detect_offset: ]: - messages.append( self._markHttpMessage( evaluate.get( 'requestResponse' ), evaluate.get( 'injection' ), None ) ) - return messages - - def getHttpService( self ): - return self._baseRequestResponse.getHttpService() - - def _markHttpMessage( self, requestResponse, injection, responseMarkString ): - responseMarkers = None - if responseMarkString: - response = requestResponse.getResponse() - responseMarkBytes = self._helpers.stringToBytes( responseMarkString ) - start = self._helpers.indexOf( response, responseMarkBytes, False, 0, len( response ) ) - if -1 < start: - responseMarkers = [ array( 'i',[ start, start + len( responseMarkBytes ) ] ) ] - - requestHighlights = [ self._insertionPoint.getPayloadOffsets( self._helpers.stringToBytes( injection ) ) ] - return self._callbacks.applyMarkers( requestResponse, requestHighlights, responseMarkers ) - diff --git a/burp_extension/scanner_check.py b/burp_extension/scanner_check.py deleted file mode 100644 index 9eca516..0000000 --- a/burp_extension/scanner_check.py +++ /dev/null @@ -1,28 +0,0 @@ -from burp import IScannerCheck - -from channel import Channel -from scan_issue import ScanIssue - -class ScannerCheck( IScannerCheck ): - - def __init__( self, callbacks, configTab ): - self._callbacks = callbacks - self._helpers = callbacks.getHelpers() - self._configTab = configTab - - def doPassiveScan( self, baseRequestResponse ): - return None - - def doActiveScan( self, baseRequestResponse, insertionPoint ): - for position in [ position for ( position, selected ) in self._configTab.getPayloadPosition().items() if selected ]: - channel = Channel( self._callbacks, self._configTab, baseRequestResponse, insertionPoint, position ) - for engineClass in self._configTab.getEngines(): - engine = engineClass( channel ) - engine.detect() - if channel.detect: - return [ ScanIssue( self._callbacks, baseRequestResponse, insertionPoint, channel ) ] - return None - - def consolidateDuplicateIssues( self, existingIssue, newIssue ): - return 0 - diff --git a/core/channel.py b/core/channel.py index 70d8289..5d75fdd 100644 --- a/core/channel.py +++ b/core/channel.py @@ -28,6 +28,7 @@ def __init__(self, args): self.injs = [] self.inj_idx = 0 + self.ssl_verf = self.args.get('ssl_verf') proxy = self.args.get('proxy') if proxy: @@ -201,6 +202,7 @@ def req(self, injection): post_params = deepcopy(self.post_params) header_params = deepcopy(self.header_params) url_params = self.base_url + ssl_verfication = self.ssl_verf # Pick current injection by index inj = deepcopy(self.injs[self.inj_idx]) @@ -298,9 +300,10 @@ def req(self, injection): data = post_params, headers = header_params, proxies = self.proxies, - # By default, SSL check is skipped. - # TODO: add a -k curl-like option to set this. - verify = False + # By default, SSL check is not skipped. + # WARNING : Using this option makes the transfer insecure. + # TODO: add a -k curl-like option to set this. (Done) + verify = ssl_verfication ).text except requests.exceptions.ConnectionError as e: if e and e[0] and e[0][0] == 'Connection aborted.': diff --git a/core/checks.py b/core/checks.py index 0d7f9e8..3bface0 100644 --- a/core/checks.py +++ b/core/checks.py @@ -17,11 +17,9 @@ from plugins.languages.php import Php from plugins.languages.python import Python from plugins.languages.ruby import Ruby -from core.channel import Channel from utils.loggers import log from core.clis import Shell, MultilineShell from core.tcpserver import TcpServer -import time import telnetlib import sys diff --git a/docker-envs/Dockerfile.java b/docker-envs/Dockerfile.java deleted file mode 100644 index 2243feb..0000000 --- a/docker-envs/Dockerfile.java +++ /dev/null @@ -1,19 +0,0 @@ -FROM gradle:4.10.2-jdk8 - -USER root - -RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y -RUN pip install requests PyYAML - -COPY tests/env_java_tests/spark-app/ /apps/tests/env_java_tests/spark-app/ -WORKDIR /apps/tests/ - -# install dependencies -RUN cd env_java_tests/spark-app/ && sed -ie 's/id "com\.github\.johnrengelman\.shadow".*//' build.gradle && \ - gradle classes - -COPY . /apps/ - -EXPOSE 15003 - -CMD cd env_java_tests/spark-app/ && gradle run diff --git a/docker-envs/Dockerfile.node b/docker-envs/Dockerfile.node deleted file mode 100644 index 98e2bd1..0000000 --- a/docker-envs/Dockerfile.node +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:10.12.0 - -RUN apt-get update && apt-get install --upgrade dnsutils python-pip libpython-dev -y -RUN pip install requests PyYAML - -COPY tests/env_node_tests/ /apps/tests/env_node_tests/ - -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 - -EXPOSE 15004 - -COPY . /apps/ -WORKDIR /apps/tests/ - -CMD cd /apps/tests/env_node_tests/ && node connect-app.js diff --git a/docker-envs/Dockerfile.php b/docker-envs/Dockerfile.php deleted file mode 100644 index 0454279..0000000 --- a/docker-envs/Dockerfile.php +++ /dev/null @@ -1,16 +0,0 @@ -FROM php:7.2.10-apache - -RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y -RUN pip install requests PyYAML - -RUN sed -i '0,/Listen [0-9]*/s//Listen 15002/' /etc/apache2/ports.conf - -RUN mkdir /var/www/html/lib/ && cd /var/www/html/lib && \ - curl -sL 'https://github.com/smarty-php/smarty/archive/v3.1.32.tar.gz' | tar xzf - && \ - curl -sL 'https://github.com/twigphp/Twig/archive/v1.20.0.tar.gz' | tar xzf - && \ - curl -sL 'https://github.com/twigphp/Twig/archive/v1.19.0.tar.gz' | tar xzf - - -COPY . /apps/ -COPY tests/env_php_tests/* /var/www/html/ - -WORKDIR /apps/tests/ diff --git a/docker-envs/Dockerfile.python2 b/docker-envs/Dockerfile.python2 deleted file mode 100644 index 5326af8..0000000 --- a/docker-envs/Dockerfile.python2 +++ /dev/null @@ -1,13 +0,0 @@ -FROM python:2.7.15 - -RUN pip install mako jinja2 flask tornado PyYAML requests -RUN apt-get update && apt-get install dnsutils -y - -COPY . /apps/ -WORKDIR /apps/tests/ - -RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py - -EXPOSE 15001 - -CMD python env_py_tests/webserver.py diff --git a/docker-envs/Dockerfile.python3 b/docker-envs/Dockerfile.python3 deleted file mode 100644 index f9a56cd..0000000 --- a/docker-envs/Dockerfile.python3 +++ /dev/null @@ -1,14 +0,0 @@ -FROM python:2.7.15 - -RUN apt-get update && apt-get install dnsutils python3-pip -y -RUN pip3 install mako jinja2 flask tornado -RUN pip install PyYAML requests - -COPY . /apps/ -WORKDIR /apps/tests/ - -RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py - -EXPOSE 15001 - -CMD python3 env_py_tests/webserver.py diff --git a/docker-envs/Dockerfile.ruby b/docker-envs/Dockerfile.ruby deleted file mode 100644 index 51ae139..0000000 --- a/docker-envs/Dockerfile.ruby +++ /dev/null @@ -1,12 +0,0 @@ -FROM ruby:2.5.1 - -RUN gem install slim tilt cuba rack -RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y -RUN pip install requests PyYAML - -COPY . /apps/ -WORKDIR /apps/tests/ - -EXPOSE 15005 - -CMD cd env_ruby_tests && rackup --host 0.0.0.0 --port 15005 diff --git a/docker-envs/README.md b/docker-envs/README.md deleted file mode 100644 index aad9b2b..0000000 --- a/docker-envs/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Running vulnerable test environment in Docker - -To setup vulnerable environments for your test, you can use tplmap's test environment with Docker. - -The following command starts all test environments: - -```sh -$ docker-compose up -``` - -Starts specified test environments: - -```sh -$ docker-compose up tplmap_test_python tplmap_test_php -``` - diff --git a/docker-envs/docker-compose.yml b/docker-envs/docker-compose.yml deleted file mode 100644 index a805ca7..0000000 --- a/docker-envs/docker-compose.yml +++ /dev/null @@ -1,45 +0,0 @@ -version: '2' - -services: - tplmap_test_python: - build: - context: ../ - dockerfile: docker-envs/Dockerfile.python2 - restart: always - ports: - - "15001:15001" - tplmap_test_python3: - build: - context: ../ - dockerfile: docker-envs/Dockerfile.python3 - restart: always - ports: - - "15006:15001" - tplmap_test_php: - build: - context: ../ - dockerfile: docker-envs/Dockerfile.php - restart: always - ports: - - "15002:15002" - tplmap_test_java: - build: - context: ../ - dockerfile: docker-envs/Dockerfile.java - restart: always - ports: - - "15003:15003" - tplmap_test_node: - build: - context: ../ - dockerfile: docker-envs/Dockerfile.node - restart: always - ports: - - "15004:15004" - tplmap_test_ruby: - build: - context: ../ - dockerfile: docker-envs/Dockerfile.ruby - restart: always - ports: - - "15005:15005" diff --git a/requirements.txt b/requirements.txt index 031fa4b..2a8be62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,3 @@ chardet==3.0.4 idna==2.8 requests==2.22.0 urllib3==1.24.1 -wsgiref==0.1.2 diff --git a/tests/basetest.py b/tests/basetest.py deleted file mode 100644 index cbff766..0000000 --- a/tests/basetest.py +++ /dev/null @@ -1,202 +0,0 @@ -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from core.channel import Channel -from core.checks import check_template_injection -from core.checks import detect_template_injection -from utils import rand -from utils import strings -import utils.loggers -import logging - -utils.loggers.stream_handler.setLevel(logging.FATAL) - -# Test levels limits -LEVEL_LIMIT = 0 -CLEVEL_LIMIT = 0 - -# Extra tests -EXTRA_UPLOAD = False -EXTRA_DOWNLOAD = False -EXTRA_UPLOAD_BLIND = False - -class BaseTest(object): - - def _get_detection_obj_data(self, url, level = 0, closure_level = 0, technique = 'RT'): - - channel = Channel({ - 'url' : url, - 'force_level': [ level, closure_level ], - 'injection_tag': '*', - 'technique': technique - }) - obj = detect_template_injection(channel, [ self.plugin ]) - - # Delete OS to make the tests portable - if 'os' in channel.data: - del channel.data['os'] - - return obj, channel.data - - - def test_detection(self): - - channel = Channel({ - 'url' : self.url, - 'level': 5, - 'injection_tag': '*', - 'technique': 'RT' - }) - check_template_injection(channel) - - # Delete OS to make the tests portable - if 'os' in channel.data: - del channel.data['os'] - - # Delete any unreliable engine detected - if 'unreliable' in channel.data: - del channel.data['unreliable'] - - self.assertEqual( - channel.data, - self.expected_data, - ) - - def test_reflection(self): - - for reflection_test in self.reflection_tests: - - level, clevel, template, channel_updates = reflection_test - - # Honour global level limit - if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT: - continue - - expected_data = self.expected_data.copy() - expected_data.update(channel_updates) - - obj, data = self._get_detection_obj_data(self.url % template, level, clevel, technique = 'R') - - self.assertEqual( - data, - expected_data, - msg = '\nreflection\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data)) - ) - - def test_blind(self): - - for blind_test in self.blind_tests: - - level, clevel, template, channel_updates = blind_test - - # Honour global level limit - if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT: - continue - - expected_data = self.expected_data_blind.copy() - expected_data.update(channel_updates) - - obj, data = self._get_detection_obj_data(self.url_blind % template, level, clevel, technique = 'T') - - self.assertEqual( - data, - expected_data, - msg = '\nblind\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data)) - ) - - def test_download(self): - - obj, data = self._get_detection_obj_data(self.url) - self.assertEqual(data, self.expected_data) - - if not EXTRA_DOWNLOAD: - return - - # Normal ASCII file - readable_file = '/etc/resolv.conf' - content = open(readable_file, 'r').read() - self.assertEqual(content, obj.read(readable_file)) - - # Long binary file - readable_file = '/bin/ls' - content = open(readable_file, 'rb').read() - self.assertEqual(content, obj.read(readable_file)) - - # Non existant file - self.assertEqual(None, obj.read('/nonexistant')) - # Unpermitted file - self.assertEqual(None, obj.read('/etc/shadow')) - # Empty file - self.assertEqual('', obj.read('/dev/null')) - - def test_upload(self): - - obj, data = self._get_detection_obj_data(self.url) - self.assertEqual(data, self.expected_data) - - if not EXTRA_UPLOAD: - return - - remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) - # Send long binary - data = open('/bin/ls', 'rb').read() - obj.write(data, remote_temp_path) - self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) - obj.execute('rm %s' % (remote_temp_path)) - - remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) - # Send short ASCII data, without removing it - data = 'SHORT ASCII DATA' - obj.write(data, remote_temp_path) - self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) - - # Try to append data without --force-overwrite and re-check the previous md5 - obj.write('APPENDED DATA', remote_temp_path) - self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) - - # Now set --force-overwrite and rewrite new data on the same file - obj.channel.args['force_overwrite'] = True - data = 'NEW DATA' - obj.write(data, remote_temp_path) - self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) - obj.execute('rm %s' % (remote_temp_path)) - - def test_upload_blind(self): - - obj, data = self._get_detection_obj_data( - self.url_blind, - technique = 'T' - ) - self.assertEqual(data, self.expected_data_blind) - - if not EXTRA_UPLOAD_BLIND: - return - - # Send file without --force-overwrite, should fail - remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) - obj.write('AAAA', remote_temp_path) - self.assertFalse(os.path.exists(remote_temp_path)) - - # Now set --force-overwrite and retry - obj.channel.args['force_overwrite'] = True - - # Send long binary - data = open('/bin/ls', 'rb').read() - obj.write(data, remote_temp_path) - - # Since it's blind, read md5 from disk - checkdata = open(remote_temp_path, 'rb').read() - self.assertEqual(strings.md5(checkdata), strings.md5(data)) - os.unlink(remote_temp_path) - - remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) - # Send short ASCII data - data = 'SHORT ASCII DATA' - obj.write(data, remote_temp_path) - - checkdata = open(remote_temp_path, 'rb').read() - self.assertEqual(strings.md5(checkdata), strings.md5(data)) - os.unlink(remote_temp_path) \ No newline at end of file diff --git a/tests/env_java_tests/spark-app/build.gradle b/tests/env_java_tests/spark-app/build.gradle deleted file mode 100644 index 6dbba88..0000000 --- a/tests/env_java_tests/spark-app/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id "java" - id "application" - id "com.github.johnrengelman.shadow" version "1.2.3" -} - -group 'org.tplmap.webframeworks' -version '1.0-SNAPSHOT' - - -sourceCompatibility = 1.8 -mainClassName = 'org.tplmap.webframeworks.SparkApplication' - - -repositories { - jcenter() - mavenCentral() -} - -dependencies { - compile 'com.sparkjava:spark-core:2.3' - compile group: 'org.freemarker', name: 'freemarker', version: '2.3.14' - compile group: 'org.apache.velocity', name: 'velocity', version: '1.6.2' - testCompile group: 'junit', name: 'junit', version: '4.11' -} diff --git a/tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties b/tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 0d03d87..0000000 --- a/tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Wed Feb 17 14:28:33 EET 2016 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip diff --git a/tests/env_java_tests/spark-app/settings.gradle b/tests/env_java_tests/spark-app/settings.gradle deleted file mode 100644 index de499d6..0000000 --- a/tests/env_java_tests/spark-app/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'spark-app' - diff --git a/tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java b/tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java deleted file mode 100644 index 80c2d50..0000000 --- a/tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.tplmap.webframeworks; -import spark.Request; -import spark.Response; -import spark.Route; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import java.io.StringReader; -import java.io.IOException; -import java.io.StringWriter; -import java.util.HashMap; -import org.apache.velocity.VelocityContext ; -import org.apache.velocity.app.VelocityEngine ; -import org.apache.velocity.exception.MethodInvocationException ; -import org.apache.velocity.exception.ParseErrorException ; -import org.apache.velocity.exception.ResourceNotFoundException ; -import org.apache.velocity.runtime.RuntimeConstants ; -import org.apache.velocity.runtime.log.LogChute ; -import org.apache.velocity.runtime.log.NullLogChute ; -import java.util.UUID; - -import static spark.Spark.*; - -public class SparkApplication { - -public static void main(String[] args) { - port(15003); - get("/freemarker", SparkApplication::freemarker); - get("/velocity", SparkApplication::velocity); -} - -public static Object velocity(Request request, Response response) { - - - // Get inj parameter, exit if none - String inj = request.queryParams("inj"); - if(inj == null) { - return ""; - } - - // Get tpl parameter - String tpl = request.queryParams("tpl"); - - // If tpl exists - if(tpl != null && !tpl.isEmpty()) { - // Keep the formatting a-la-python - tpl = tpl.replace("%s", inj); - } - else { - tpl = inj; - } - - String blind = request.queryParams("blind"); - - LogChute velocityLogChute = new NullLogChute() ; - VelocityEngine velocity; - StringWriter w; - try{ - velocity = new VelocityEngine() ; - // Turn off logging - catch exceptions and log ourselves - velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, velocityLogChute) ; - velocity.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8") ; - velocity.init() ; - - VelocityContext context = new VelocityContext(); - w = new StringWriter(); - - velocity.evaluate( context, w, "mystring", tpl ); - - - }catch(Exception e){ - e.printStackTrace(); - return ""; - } - - // Return out string if not blind - if(blind == null){ - return UUID.randomUUID().toString() + w.toString() + UUID.randomUUID().toString(); - } - else { - return UUID.randomUUID().toString(); - } -} - -public static Object freemarker(Request request, Response response) { - - // Get inj parameter, exit if none - String inj = request.queryParams("inj"); - if(inj == null) { - return ""; - } - - // Get tpl parameter - String tpl = request.queryParams("tpl"); - - // If tpl exists - if(tpl != null && !tpl.isEmpty()) { - // Keep the formatting a-la-python - tpl = tpl.replace("%s", inj); - } - else { - tpl = inj; - } - - // Get blind parameter - String blind = request.queryParams("blind"); - - // Generate template from "inj" - Template template; - try{ - template = new Template("name", new StringReader(tpl), new Configuration()); - }catch(IOException e){ - e.printStackTrace(); - return ""; - } - - // Write processed template to out - HashMap data = new HashMap(); - StringWriter out = new StringWriter(); - try{ - template.process(data, out); - }catch(TemplateException | IOException e){ - e.printStackTrace(); - return ""; - } - - // Return out string if not blind - if(blind == null){ - return UUID.randomUUID().toString() + out.toString() + UUID.randomUUID().toString(); - } - else { - return UUID.randomUUID().toString(); - } -} -} diff --git a/tests/env_java_tests/spark-app/src/main/resources/templates/hello.html b/tests/env_java_tests/spark-app/src/main/resources/templates/hello.html deleted file mode 100644 index c9a6020..0000000 --- a/tests/env_java_tests/spark-app/src/main/resources/templates/hello.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Hello world - - -

Hello, [[${name}]]!

- - \ No newline at end of file diff --git a/tests/env_node_tests/connect-app.js b/tests/env_node_tests/connect-app.js deleted file mode 100644 index a965c1c..0000000 --- a/tests/env_node_tests/connect-app.js +++ /dev/null @@ -1,289 +0,0 @@ -var connect = require('connect'); -var http = require('http'); -var url = require('url'); -var pug = require('pug'); -var nunjucks = require('nunjucks'); -var dust = require('dustjs-linkedin'); -var dusthelpers = require('dustjs-helpers'); -var randomstring = require("randomstring"); -var doT=require('dot'); -var marko=require('marko'); -var ejs=require('ejs'); - -var app = connect(); - -// Pug -app.use('/pug', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - res.end(randomstring.generate() + pug.render(tpl) + randomstring.generate()); - } -}); - -// Pug blind endpoint -app.use('/blind/pug', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - console.log('PAYLOAD: ' + tpl); - pug.render(tpl) - res.end(randomstring.generate()); - } -}); - -// Nunjucks -app.use('/nunjucks', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - res.end(randomstring.generate() + nunjucks.renderString(tpl) + randomstring.generate()); - } -}); - -// Nunjucks blind endpoint -app.use('/blind/nunjucks', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - nunjucks.renderString(tpl); - res.end(randomstring.generate()); - } -}); - -// Javascript -app.use('/javascript', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - res.end(randomstring.generate() + String(eval(tpl)) + randomstring.generate()); - } -}); - -// Javascript blind endpoint -app.use('/blind/javascript', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - eval(tpl); - res.end(randomstring.generate()); - } -}); - -// Dust -app.use('/dust', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - - console.log('PAYLOAD: ' + tpl); - dust.debugLevel = "DEBUG" - output = ''; - var compiled = dust.compile(tpl, "compiled"); - dust.loadSource(compiled); - dust.render("compiled", {}, function(err, outp) { output = outp }) - res.end(randomstring.generate() + output + randomstring.generate()); - } -}); - -// Dust blind endpoint -app.use('/blind/dust', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - - console.log('PAYLOAD: ' + tpl); - dust.debugLevel = "DEBUG" - var compiled = dust.compile(tpl, "compiled"); - dust.loadSource(compiled); - dust.render("compiled", {}, function(err, outp) { }) - - res.end(randomstring.generate()); - } -}); - -// doT -app.use('/dot', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - res.end(randomstring.generate() + doT.template(tpl)({}) + randomstring.generate()); - } -}); - -// doT blind endpoint -app.use('/blind/dot', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - doT.template(tpl)({}); - res.end(randomstring.generate()); - } -}); - -// Marko -app.use('/marko', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - res.end(randomstring.generate() + marko.load(randomstring.generate(), tpl).renderSync() + randomstring.generate()); - } -}); - -// Marko blind endpoint -app.use('/blind/marko', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - marko.load(randomstring.generate(), tpl).renderSync() - res.end(randomstring.generate()); - } -}); - -// EJS -app.use('/ejs', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - res.end(randomstring.generate() + ejs.render(tpl) + randomstring.generate()); - } -}); - -// EJS blind endpoint -app.use('/blind/ejs', function(req, res){ - if(req.url) { - var url_parts = url.parse(req.url, true); - - var inj = url_parts.query.inj; - var tpl = ''; - if('tpl' in url_parts.query && url_parts.query.tpl != '') { - // Keep the formatting a-la-python - tpl = url_parts.query.tpl.replace('%s', inj); - } - else { - tpl = inj; - } - ejs.render(tpl); - res.end(randomstring.generate()); - } -}); - -//create node.js http server and listen on port -http.createServer(app).listen(15004); diff --git a/tests/env_php_tests/eval.php b/tests/env_php_tests/eval.php deleted file mode 100644 index eef6058..0000000 --- a/tests/env_php_tests/eval.php +++ /dev/null @@ -1,31 +0,0 @@ - : ' . $rendered); - echo generateRandomString(); -} -else { - error_log('DEBUG< : ' . $tpl); - ob_start(); - $rendered = eval($tpl); - ob_end_clean(); - error_log('DEBUG> : ' . $rendered); - echo generateRandomString(); -} -?> diff --git a/tests/env_php_tests/smarty-3.1.32-secured.php b/tests/env_php_tests/smarty-3.1.32-secured.php deleted file mode 100644 index ebb8be9..0000000 --- a/tests/env_php_tests/smarty-3.1.32-secured.php +++ /dev/null @@ -1,32 +0,0 @@ -clearAllCache(); - -$inj=$_GET["inj"]; -if(isset($_GET["tpl"]) && $_GET["tpl"] != "") { - // Keep the formatting a-la-python - $tpl=str_replace("%s", $inj, $_GET["tpl"]); -} -else { - $tpl=$inj; -} - -error_log('DEBUG< : ' . $tpl); -$rendered = $smarty->fetch('string:'.$tpl); -error_log('DEBUG> : ' . $rendered); - -if(!$_GET["blind"]) { - echo generateRandomString() . $rendered . generateRandomString(); -} -else { - echo generateRandomString(); -} -?> diff --git a/tests/env_php_tests/smarty-3.1.32-unsecured.php b/tests/env_php_tests/smarty-3.1.32-unsecured.php deleted file mode 100644 index 3a1af5a..0000000 --- a/tests/env_php_tests/smarty-3.1.32-unsecured.php +++ /dev/null @@ -1,38 +0,0 @@ -clearAllCache(); - -// Run render via CLI -if (php_sapi_name() == "cli") { - $_GET["inj"] = ''; - $_GET["tpl"] = ''; -} - -$inj=$_GET["inj"]; -if(isset($_GET["tpl"]) && $_GET["tpl"] != "") { - // Keep the formatting a-la-python - $tpl=str_replace("%s", $inj, $_GET["tpl"]); -} -else { - $tpl=$inj; -} - -error_log('DEBUG< : ' . $tpl); -$rendered = $smarty->fetch('string:'.$tpl); -error_log('DEBUG> : ' . $rendered); - -if(!$_GET["blind"]) { - echo generateRandomString() . $rendered . generateRandomString(); -} -else { - echo generateRandomString(); -} -?> diff --git a/tests/env_php_tests/twig-1.19.0-unsecured.php b/tests/env_php_tests/twig-1.19.0-unsecured.php deleted file mode 100644 index c5dfd50..0000000 --- a/tests/env_php_tests/twig-1.19.0-unsecured.php +++ /dev/null @@ -1,40 +0,0 @@ - $tpl, -)); -$twig = new Twig_Environment($loader); - -error_log('DEBUG<: ' . $tpl); -$rendered = $twig->render('tpl'); -error_log('DEBUG> : ' . $rendered); - -if(!$_GET["blind"]) { - echo generateRandomString() . $rendered . generateRandomString(); -} -else { - echo generateRandomString(); -} - ?> diff --git a/tests/env_php_tests/twig-1.20.0-secured.php b/tests/env_php_tests/twig-1.20.0-secured.php deleted file mode 100644 index 7554f64..0000000 --- a/tests/env_php_tests/twig-1.20.0-secured.php +++ /dev/null @@ -1,40 +0,0 @@ - $tpl, -)); -$twig = new Twig_Environment($loader); - -error_log('DEBUG<: ' . $tpl); -$rendered = $twig->render('tpl'); -error_log('DEBUG> : ' . $rendered); - -if(!$_GET["blind"]) { - echo generateRandomString() . $rendered . generateRandomString(); -} -else { - echo generateRandomString(); -} - ?> diff --git a/tests/env_py_tests/webserver.py b/tests/env_py_tests/webserver.py deleted file mode 100644 index 58726ff..0000000 --- a/tests/env_py_tests/webserver.py +++ /dev/null @@ -1,194 +0,0 @@ -from flask import Flask, request -app = Flask(__name__) -from mako.template import Template as MakoTemplates -from mako.lookup import TemplateLookup -from jinja2 import Environment as Jinja2Environment -import tornado.template -import random -import time - -try: - from string import lowercase as ascii_lowercase -except ImportError: - from string import ascii_lowercase - -mylookup = TemplateLookup(directories=['/tpl']) - -Jinja2Env = Jinja2Environment(line_statement_prefix='#') - -def shutdown_server(): - func = request.environ.get('werkzeug.server.shutdown') - if func is None: - raise RuntimeError('Not running with the Werkzeug Server') - func() - -def randomword(length = 8): - return ''.join(random.choice(ascii_lowercase) for i in range(length)) - -@app.route("/reflect/") -def reflect(engine): - - template = request.values.get('tpl') - if not template: - template = '%s' - - injection = request.values.get('inj') - - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - elif engine == 'eval': - return randomword() + str(eval(template % injection)) + randomword() - elif engine == 'tornado': - return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword() - -@app.route("/url//") -def url_reflect(engine, injection): - - template = request.values.get('tpl') - if not template: - template = '%s' - - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - elif engine == 'eval': - return randomword() + str(eval(template % injection)) + randomword() - elif engine == 'tornado': - return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword() - - -@app.route("/post/", methods = [ "POST" ]) -def postfunc(engine): - - template = request.values.get('tpl') - if not template: - template = '%s' - - injection = request.values.get('inj') - - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - - -@app.route("/header/") -def headerfunc(engine): - - template = request.headers.get('tpl') - if not template: - template = '%s' - - injection = request.headers.get('User-Agent') - - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - -@app.route("/put/", methods = [ "PUT" ]) -def putfunc(engine): - - template = request.values.get('tpl') - if not template: - template = '%s' - - injection = request.values.get('inj') - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - -@app.route("/limit/") -def limited(engine): - template = request.values.get('tpl') - if not template: - template = '%s' - - length = int(request.values.get('limit')) - - injection = request.values.get('inj', '') - if len(injection) > length: - return 'Inj too long' - - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - -@app.route("/startswith/") -def startswithtest(engine): - template = request.values.get('tpl') - if not template: - template = '%s' - - str_startswith = request.values.get('startswith') - - injection = request.values.get('inj', '') - if not injection.startswith(str_startswith): - return 'Missing startswith' - - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - - -@app.route("/blind/") -def blind(engine): - - template = request.values.get('tpl') - if not template: - template = '%s' - - injection = request.values.get('inj') - - if engine == 'mako': - MakoTemplates(template % injection, lookup=mylookup).render() - elif engine == 'jinja2': - Jinja2Env.from_string(template % injection).render() - elif engine == 'eval': - eval(template % injection) - elif engine == 'tornado': - tornado.template.Template(template % injection).generate() - - return randomword() - -@app.route("/reflect_cookieauth/") -def reflect_cookieauth(engine): - - if not request.cookies.get('SID') == 'SECRET': - return randomword() - - template = request.values.get('tpl') - if not template: - template = '%s' - - injection = request.values.get('inj') - - if engine == 'mako': - return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() - elif engine == 'jinja2': - return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() - elif engine == 'eval': - return randomword() + str(eval(template % injection)) + randomword() - elif engine == 'tornado': - return randomword() + tornado.template.Template(template % injection).generate() + randomword() - -@app.route("/delay/") -def delay(seconds = 1): - - time.sleep(seconds) - - return randomword() - -@app.route('/shutdown') -def shutdown(): - shutdown_server() - return 'Server shutting down...' - -if __name__ == "__main__": - app.run(host='127.0.0.1', port=15001, debug=False) diff --git a/tests/env_ruby_tests/config.ru b/tests/env_ruby_tests/config.ru deleted file mode 100644 index 1ef96a4..0000000 --- a/tests/env_ruby_tests/config.ru +++ /dev/null @@ -1,3 +0,0 @@ -require "./webserver" - -run Cuba \ No newline at end of file diff --git a/tests/env_ruby_tests/webserver.rb b/tests/env_ruby_tests/webserver.rb deleted file mode 100644 index 5984bdc..0000000 --- a/tests/env_ruby_tests/webserver.rb +++ /dev/null @@ -1,60 +0,0 @@ -require "cuba" -require "cuba/safe" - -require 'tilt' -require 'slim' -require 'erb' - -Cuba.plugin Cuba::Safe - -Cuba.define do - on get do - on "reflect/:engine" do |engine| - # Keep the formatting a-la-python - on param("inj"), param("tpl", "%s") do |inj, tpl| - - tpl = tpl.gsub('%s', inj) - - case engine - when "eval" - res.write eval(tpl) - when "slim" - template = Tilt['slim'].new() {|x| tpl} - res.write template.render - when "erb" - template = Tilt['erb'].new() {|x| tpl} - res.write template.render - else - res.write "#{engine} #{inj} #{tpl}" - end - - end - end - on "blind/:engine" do |engine| - # Keep the formatting a-la-python - on param("inj"), param("tpl", "%s") do |inj, tpl| - - tpl = tpl.gsub('%s', inj) - - case engine - when "eval" - eval(tpl) - when "slim" - template = Tilt['slim'].new() {|x| tpl} - template.render - when "erb" - template = Tilt['erb'].new() {|x| tpl} - template.render - else - res.write "blind #{engine} #{inj} #{tpl}" - end - - res.write "ok"; # for set 200 response status code - - end - end - on 'shutdown' do - exit! - end - end -end diff --git a/tests/run_channel_test.sh b/tests/run_channel_test.sh deleted file mode 100755 index cd246c4..0000000 --- a/tests/run_channel_test.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -e - -INSTANCE_NAME="tplmap-py" -IMAGE_NAME="tplmap-py-img" -PORT=15001 - -echo "Exposed testing APIs: - -http://localhost:15001/reflect/mako?inj=* -http://localhost:15001/reflect/jinja2?inj=* -http://localhost:15001/post/mako?inj=* -http://localhost:15001/post/jinja2?inj=* -http://localhost:15001/limit/mako?inj=* -http://localhost:15001/limit/jinja2?inj=* -http://localhost:15001/put/mako?inj=* -http://localhost:15001/put/jinja2?inj=* -" - -cd "$( dirname "${BASH_SOURCE[0]}" )"/../ - -docker rm -f $INSTANCE_NAME || echo '' -docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME -docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME - -# Wait until the http server is serving -until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do - sleep 1 -done - -# Launch python engines tests -docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_channel*.py' - -docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_java_tests.sh b/tests/run_java_tests.sh deleted file mode 100755 index dad2f7d..0000000 --- a/tests/run_java_tests.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -e - -INSTANCE_NAME="tplmap-java" -IMAGE_NAME="tplmap-java-img" -PORT=15003 - -echo "Exposed testing APIs: - -http://localhost:15003/velocity?inj=* -http://localhost:15003/velocity?inj=*&blind=1 -http://localhost:15003/freemarker?inj=* -http://localhost:15003/freemarker?inj=*&blind=1 -" - -cd "$( dirname "${BASH_SOURCE[0]}" )"/../ - -docker rm -f $INSTANCE_NAME || echo '' -docker build -f docker-envs/Dockerfile.java . -t $IMAGE_NAME -docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME - -# Wait until the http server is serving -until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do - sleep 1 -done -sleep 1 - -# Launch Java engines tests -docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_java_*.py' - -docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_node_tests.sh b/tests/run_node_tests.sh deleted file mode 100755 index 4c311ee..0000000 --- a/tests/run_node_tests.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -e - -INSTANCE_NAME="tplmap-node" -IMAGE_NAME="tplmap-node-img" -PORT=15004 - -echo "Exposed testing APIs: - -http://localhost:15004/pug?inj=* -http://localhost:15004/blind/pug?inj=* -http://localhost:15004/nunjucks?inj=* -http://localhost:15004/blind/nunjucks?inj=* -http://localhost:15004/javascript?inj=* -http://localhost:15004/blind/javascript?inj=* -http://localhost:15004/dot?inj=* -http://localhost:15004/blind/dot?inj=* -http://localhost:15004/dust?inj=* -http://localhost:15004/blind/dust?inj=* -http://localhost:15004/marko?inj=* -http://localhost:15004/blind/marko?inj=* -http://localhost:15004/ejs?inj=* -http://localhost:15004/blind/ejs?inj=* -" - -cd "$( dirname "${BASH_SOURCE[0]}" )"/../ - -docker rm -f $INSTANCE_NAME || echo '' -docker build -f docker-envs/Dockerfile.node . -t $IMAGE_NAME -docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME - -# Wait until the http server is serving -until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do - sleep 1 -done - -# Launch node engines tests -docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_node_*.py' - -docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_php_tests.sh b/tests/run_php_tests.sh deleted file mode 100755 index fdd68b1..0000000 --- a/tests/run_php_tests.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -e - -INSTANCE_NAME="tplmap-php" -IMAGE_NAME="tplmap-php-img" -PORT=15002 - -echo "Exposed testing APIs: - -http://localhost:15002/smarty-3.1.32-secured.php?inj=* -http://localhost:15002/smarty-3.1.32-unsecured.php?inj=* -http://localhost:15002/smarty-3.1.32-unsecured.php?inj=*&blind=1 -http://localhost:15002/twig-1.24.1-secured.php?inj=* -http://localhost:15002/eval.php?inj=* -http://localhost:15002/eval.php?inj=*&blind=1 -" - -cd "$( dirname "${BASH_SOURCE[0]}" )"/../ - -docker rm -f $INSTANCE_NAME || echo '' -docker build -f docker-envs/Dockerfile.php . -t $IMAGE_NAME -docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME - -# Wait until the http server is serving -until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do - sleep 1 -done - -# Launch PHP engines tests -docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_php_*.py' - -docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_python2_tests.sh b/tests/run_python2_tests.sh deleted file mode 100755 index 4245d4c..0000000 --- a/tests/run_python2_tests.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -e - -INSTANCE_NAME="tplmap-py2" -IMAGE_NAME="tplmap-py2-img" -PORT=15001 - -echo "Exposed testing APIs: - -http://localhost:15001/reflect/mako?inj=* -http://localhost:15001/reflect/jinja2?inj=* -http://localhost:15001/post/mako?inj=* -http://localhost:15001/post/jinja2?inj=* -http://localhost:15001/limit/mako?inj=* -http://localhost:15001/limit/jinja2?inj=* -http://localhost:15001/put/mako?inj=* -http://localhost:15001/put/jinja2?inj=* -" - -cd "$( dirname "${BASH_SOURCE[0]}" )"/../ - -docker rm -f $INSTANCE_NAME || echo '' -docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME -docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME - -# Wait until the http server is serving -until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do - sleep 1 -done - -# Launch python engines tests -docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_py_*.py' - -docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_python3_tests.sh b/tests/run_python3_tests.sh deleted file mode 100755 index a5a2c98..0000000 --- a/tests/run_python3_tests.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -e - -INSTANCE_NAME="tplmap-py3" -IMAGE_NAME="tplmap-py3-img" -GUESTPORT=15001 -PORT=15006 - -echo "Exposed testing APIs: - -http://localhost:15006/reflect/mako?inj=* -http://localhost:15006/reflect/jinja2?inj=* -http://localhost:15006/post/mako?inj=* -http://localhost:15006/post/jinja2?inj=* -http://localhost:15006/limit/mako?inj=* -http://localhost:15006/limit/jinja2?inj=* -http://localhost:15006/put/mako?inj=* -http://localhost:15006/put/jinja2?inj=* -" - -cd "$( dirname "${BASH_SOURCE[0]}" )"/../ - -docker rm -f $INSTANCE_NAME || echo '' -docker build -f docker-envs/Dockerfile.python3 . -t $IMAGE_NAME -docker run --rm --name $INSTANCE_NAME -p $PORT:$GUESTPORT -d $IMAGE_NAME - -# Wait until the http server is serving -until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do - sleep 1 -done - -# Launch python engines tests -docker exec -it $INSTANCE_NAME python2 -m unittest discover -v . 'test_py_*.py' - -docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_ruby_tests.sh b/tests/run_ruby_tests.sh deleted file mode 100755 index 4735ea1..0000000 --- a/tests/run_ruby_tests.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -e - -INSTANCE_NAME="tplmap-ruby" -IMAGE_NAME="tplmap-ruby-img" -PORT=15005 - -echo "Exposed testing APIs: - -http://localhost:15005/reflect/eval?inj=* -http://localhost:15005/blind/eval?inj=* -http://localhost:15005/reflect/slim?inj=* -http://localhost:15005/blind/slim?inj=* -http://localhost:15005/reflect/erb?inj=* -http://localhost:15005/blind/erb?inj=* -" - -cd "$( dirname "${BASH_SOURCE[0]}" )"/../ - -docker rm -f $INSTANCE_NAME || echo '' -docker build -f docker-envs/Dockerfile.ruby . -t $IMAGE_NAME -docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME - -# Wait until the http server is serving -until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do - sleep 1 -done - -# Launch ruby engines tests -docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_ruby_*.py' - -docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/test_channel.py b/tests/test_channel.py deleted file mode 100644 index de72685..0000000 --- a/tests/test_channel.py +++ /dev/null @@ -1,225 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.mako import Mako -from core.channel import Channel -from core.checks import detect_template_injection -import utils.loggers -import logging - -utils.loggers.stream_handler.setLevel(logging.FATAL) - -class ChannelTest(unittest.TestCase): - - expected_data = { - 'language': 'python', - 'engine': 'mako', - 'evaluate' : 'python' , - 'execute' : True, - 'write' : True, - 'read' : True, - 'trailer': '${%(trailer)s}', - 'header': '${%(header)s}', - 'render': '${%(code)s}', - 'prefix': '', - 'suffix': '', - 'bind_shell' : True, - 'reverse_shell': True - } - - def test_post_reflection(self): - - template = '%s' - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/post/mako', - 'force_level': [ 0, 0 ], - 'data' : 'inj=*&othervar=1', - 'injection_tag': '*', - 'technique': 'R' - - }) - detect_template_injection(channel, [ Mako ]) - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_url_reflection(self): - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/url/mako/AA*AA', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'R' - - }) - - detect_template_injection(channel, [ Mako ]) - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_header_reflection(self): - - template = '%s' - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/header/mako', - 'force_level': [ 0, 0 ], - 'headers' : [ 'User-Agent: *' ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_put_reflection(self): - - template = '%s' - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/put/mako', - 'data' : 'inj=*&othervar=1', - 'request' : 'PUT', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_custom_injection_tag(self): - - template = '%s' - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=~', - 'force_level': [ 0, 0 ], - 'injection_tag': '~', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - - def test_reflection_multiple_point_tag(self): - - template = '%s' - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&asd=1&asd2=*&inj=*&inj2=*&inj3=*', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_reflection_multiple_point_no_tag(self): - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd&inj2=asd2', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_no_reflection(self): - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/reflect/mako?inj2=asd2', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'RT' - }) - detect_template_injection(channel, [ Mako ]) - - self.assertEqual(channel.data, {}) - - def test_reflection_point_startswith(self): - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=thismustexists*&startswith=thismustexists', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_reflection_point_dont_startswith(self): - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=*&startswith=thismustexists', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - - self.assertEqual(channel.data, {}) - - - def test_quotes(self): - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'RT' - }) - obj = detect_template_injection(channel, [ Mako ]) - - result = obj.execute("""echo 1"2"'3'\\"\\'""") - self.assertEqual(result, """123"'""") - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/blind/mako?inj=asd', - 'force_level': [ 0, 0 ], - 'injection_tag': '*', - 'technique': 'RT' - }) - obj = detect_template_injection(channel, [ Mako ]) - - self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'""")) - - def test_auth_reflection(self): - - channel = Channel({ - 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*', - 'force_level': [ 0, 0 ], - 'headers' : [ 'Cookie: SID=SECRET' ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - - del channel.data['os'] - self.assertEqual(channel.data, self.expected_data) - - def test_wrong_auth_reflection(self): - - channel = Channel({ - 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*', - 'force_level': [ 0, 0 ], - 'headers' : [ 'Cookie: SID=WRONGSECRET' ], - 'injection_tag': '*', - 'technique': 'R' - }) - detect_template_injection(channel, [ Mako ]) - - self.assertEqual(channel.data, {}) \ No newline at end of file diff --git a/tests/test_java_freemarker.py b/tests/test_java_freemarker.py deleted file mode 100644 index 1a134a0..0000000 --- a/tests/test_java_freemarker.py +++ /dev/null @@ -1,65 +0,0 @@ -import unittest -import requests -import os -import sys - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.freemarker import Freemarker -from basetest import BaseTest - -class FreemarkerTest(unittest.TestCase, BaseTest): - - - expected_data = { - 'language': 'java', - 'engine': 'freemarker', - 'execute' : True, - 'trailer': '${%(trailer)s?c}', - 'header': '${%(header)s?c}', - 'render': '%(code)s', - 'write': True, - 'read': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'java', - 'engine': 'freemarker', - 'blind': True, - 'execute_blind' : True, - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s&blind=1' - - plugin = Freemarker - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (5, 5, '<#list %s as a>', { 'prefix' : '[1] as a><#list [1] as a>', 'suffix' : ''}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - (1, 0, '${ %s }', { 'prefix': '1}', 'suffix': '' }), - - (2, 1, '<#assign s = %s>', { 'prefix': '1>', 'suffix': '' }), - (5, 1, '<#-- %s -->', { 'prefix': '-->', 'suffix': '<#--' }), - (2, 1, '<#if 1 == %s>', { 'prefix': '1>', 'suffix' : ''}), - (2, 2, '<#if %s == 1>', { 'prefix': 'true>', 'suffix' : ''}), - (5, 3, '<#list [%s] as a>', { 'prefix' : '1] as a><#list [1] as a>', 'suffix' : ''}), - (5, 5, '<#list %s as a>', { 'prefix' : '[1] as a><#list [1] as a>', 'suffix' : ''}), - (1, 5, '<#assign ages = {"J":2, "%s":2}>', { 'prefix' : '1":1}]}', 'suffix' : ''}), - - #(1, 5, '${[1,2]%3Fjoin(%s)}', { 'prefix' : '[1])}', 'suffix' : ''}), - - ] diff --git a/tests/test_java_velocity.py b/tests/test_java_velocity.py deleted file mode 100644 index 0771d11..0000000 --- a/tests/test_java_velocity.py +++ /dev/null @@ -1,88 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.velocity import Velocity -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest - -class VelocityTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'java', - 'engine': 'velocity', - 'execute' : True, - 'trailer': '\n#set($t=%(trailer)s)\n${t}\n', - 'header': '\n#set($h=%(header)s)\n${h}\n', - 'render': '%(code)s', - 'write': True, - 'read': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'java', - 'engine': 'velocity', - 'blind': True, - 'execute_blind' : True, - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s&blind=1' - - plugin = Velocity - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - (1, 0, '#set( $a = "%s" )', { 'prefix' : '1")', 'suffix': ''}), - (1, 0, '#if(1 == %s)\n#end', { 'prefix' : '1)', 'suffix': ''}), - (3, 1, '#if(%s == 1)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}), - (3, 1, '#foreach($item in %s)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}), - (0, 0, '## comment %s', { }), - # TODO: fix those, they used to work - #(5, 0, '#[[%s]]# ', { }), - (0, 0, '${%s}', {}), - (0, 0, '${(%s)}', {}), - (3, 1, '#define( %s )a#end', { 'prefix': '1)#end#if(1==1)', 'suffix' : ''}), - (3, 1, '#define( $asd )%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), - (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), - ] - - - def test_custom_injection_tag(self): - - template = '#* %s *#' - - channel = Channel({ - 'url' : self.url.replace('*', '~') % template, - 'force_level': [ 5, 0 ], - 'injection_tag': '~', - 'technique': 'RT' - }) - - detect_template_injection(channel, [ self.plugin ]) - - expected_data = self.expected_data.copy() - expected_data.update({ 'prefix': '*#', 'suffix' : '#*'}) - - del channel.data['os'] - - self.assertEqual(channel.data, expected_data) \ No newline at end of file diff --git a/tests/test_node_dot.py b/tests/test_node_dot.py deleted file mode 100644 index 1d0b7e0..0000000 --- a/tests/test_node_dot.py +++ /dev/null @@ -1,56 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.dot import Dot -from basetest import BaseTest - - -class DotTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'javascript', - 'engine': 'dot', - 'evaluate' : 'javascript' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': '{{=%(code)s}}', - 'header': '{{=%(header)s}}', - 'trailer': '{{=%(trailer)s}}', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'javascript', - 'engine': 'dot', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'javascript', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15004/dot?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15004/blind/dot?inj=*&tpl=%s' - plugin = Dot - - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - (1, 1, "{{ %s }}", { 'prefix': '1;}}', 'suffix' : '{{1;' }), - ] \ No newline at end of file diff --git a/tests/test_node_dust.py b/tests/test_node_dust.py deleted file mode 100644 index b3764e4..0000000 --- a/tests/test_node_dust.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.dust import Dust -from basetest import BaseTest - - -class DustTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'javascript', - 'engine': 'dust', - 'write' : True, - 'execute_blind' : True, - 'prefix' : '', - 'suffix': '', - 'header': '%s', - 'trailer': '%s', - 'bind_shell' : True, - 'reverse_shell': True, - 'blind': True, - 'evaluate_blind': 'javascript' - } - - expected_data_blind = { - 'language': 'javascript', - 'engine': 'dust', - 'blind': True, - 'execute_blind' : True, - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True, - 'evaluate_blind': 'javascript' - } - - url = 'http://127.0.0.1:15004/dust?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15004/blind/dust?inj=*&tpl=%s' - plugin = Dust - - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (0, 0, '{%s|s}', { }), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - (0, 0, '{%s}', { }), - (0, 0, '{%s|s}', { }), - (1, 0, '{!%s!}', { 'prefix' : '!}', 'suffix' : '{!' }) - ] - - def test_upload(self): - pass - - def test_download(self): - pass \ No newline at end of file diff --git a/tests/test_node_ejs.py b/tests/test_node_ejs.py deleted file mode 100644 index 6f5c5b7..0000000 --- a/tests/test_node_ejs.py +++ /dev/null @@ -1,67 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.ejs import Ejs -from basetest import BaseTest - - -class EjsTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'javascript', - 'engine': 'ejs', - 'evaluate' : 'javascript' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': """%(code)s""", - 'header': """<%%- '%(header)s'+""", - 'trailer': """+'%(trailer)s' %%>""", - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'javascript', - 'engine': 'ejs', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'javascript', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15004/ejs?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15004/blind/ejs?inj=*&tpl=%s' - plugin = Ejs - - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - (1, 0, "<% %s %>", { 'prefix': '1%>', 'suffix' : '<%#' }), - (1, 1, "<% '%s' %>", { 'prefix': "1'%>", 'suffix' : '<%#' }), - (1, 1, '<% "%s" %>', { 'prefix': '1"%>', 'suffix' : '<%#' }), - (1, 0, '<%= %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), - (1, 0, '<%- %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), - (1, 0, '<%# %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), - (1, 0, '<%_ %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), - (1, 0, '<% %s -%>', { 'prefix': '1%>', 'suffix' : '<%#' }), - (1, 0, '<% %s _%>', { 'prefix': '1%>', 'suffix' : '<%#' }), - (2, 1, "<%- include('/etc/resolv.conf%s') %>", { 'prefix': "')%>", 'suffix' : '<%#' }), - (2, 2, '<%- include("/etc/resolv.conf%s") %>', { 'prefix': '")%>', 'suffix' : '<%#' }), - (3, 0, "<% 456/* AAA %s */-123 %>", { 'prefix': '*/%>', 'suffix': '<%#' }), - ] diff --git a/tests/test_node_javascript.py b/tests/test_node_javascript.py deleted file mode 100644 index ecd85a9..0000000 --- a/tests/test_node_javascript.py +++ /dev/null @@ -1,79 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.languages.javascript import Javascript -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest - - -class JavascriptTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'javascript', - 'engine': 'javascript', - 'evaluate' : 'javascript' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': """%(code)s""", - 'header': """'%(header)s'+""", - 'trailer': """+'%(trailer)s'""", - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'javascript', - 'engine': 'javascript', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'javascript', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15004/javascript?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15004/blind/javascript?inj=*&tpl=%s' - plugin = Javascript - - - blind_tests = [ - (0, 0, '%s', {}), - (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), - (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), - ] - - def test_custom_injection_tag(self): - - template = '/* %s */' - - channel = Channel({ - 'url' : self.url.replace('*', '~') % template, - 'force_level': [ 5, 0 ], - 'injection_tag': '~', - 'technique': 'RT' - }) - - detect_template_injection(channel, [ self.plugin ]) - - expected_data = self.expected_data.copy() - expected_data.update({ 'prefix': '*/', 'suffix' : '/*'}) - - del channel.data['os'] - - self.assertEqual(channel.data, expected_data) \ No newline at end of file diff --git a/tests/test_node_marko.py b/tests/test_node_marko.py deleted file mode 100644 index 8eccbeb..0000000 --- a/tests/test_node_marko.py +++ /dev/null @@ -1,58 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.marko import Marko -from basetest import BaseTest - - -class MarkoTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'javascript', - 'engine': 'marko', - 'evaluate' : 'javascript' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': '${%(code)s}', - 'header': '${"%(header)s"}', - 'trailer': '${"%(trailer)s"}', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'javascript', - 'engine': 'marko', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'javascript', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15004/marko?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15004/blind/marko?inj=*&tpl=%s' - plugin = Marko - - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - (1, 0, '${%s}', { 'prefix': '1}', 'suffix' : '${"1"' }), - (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }), - (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }), - ] \ No newline at end of file diff --git a/tests/test_node_nunjucks.py b/tests/test_node_nunjucks.py deleted file mode 100644 index 73b5478..0000000 --- a/tests/test_node_nunjucks.py +++ /dev/null @@ -1,68 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.nunjucks import Nunjucks -from basetest import BaseTest - - -class NunjucksTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'javascript', - 'engine': 'nunjucks', - 'evaluate' : 'javascript' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'trailer': '{{%(trailer)s}}', - 'header': '{{%(header)s}}', - 'render': '{{%(code)s}}', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'javascript', - 'engine': 'nunjucks', - 'evaluate_blind' : 'javascript', - 'blind': True, - 'execute_blind' : True, - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15004/nunjucks?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15004/blind/nunjucks?inj=*&tpl=%s' - plugin = Nunjucks - - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), - (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), - (0, 0, "{% block title %}%s{% endblock %}", {}), - (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), - (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), - (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), - (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), - (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), - (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}), - - # Comment blocks - (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), - ] \ No newline at end of file diff --git a/tests/test_node_pug.py b/tests/test_node_pug.py deleted file mode 100644 index 3a9ba02..0000000 --- a/tests/test_node_pug.py +++ /dev/null @@ -1,80 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.pug import Pug -from basetest import BaseTest - - -class PugTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'javascript', - 'engine': 'pug', - 'evaluate' : 'javascript' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'trailer': '\n= %(trailer)s\n', - 'header': '\n= %(header)s\n', - 'render': '\n= %(code)s\n', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'javascript', - 'engine': 'pug', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'javascript', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15004/pug?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15004/blind/pug?inj=*&tpl=%s' - plugin = Pug - - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - - (1, 0, 'a(href=\'%s\')', { 'prefix' : '1\')', 'suffix' : '//' }), - (1, 0, 'a(href="%s")', { 'prefix' : '1")', 'suffix' : '//' }), - (0, 0, '#container.%s', { }), - (2, 1, '#{%s}', { 'prefix' : '1}', 'suffix' : '//' }), - - (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }), - (2, 1, '- var a = %s', { 'prefix': '1\n', 'suffix' : '//' }), - - ] - - def test_reflection_quotes(self): - - obj, data = self._get_detection_obj_data(self.url % '') - - if obj.get('execute'): - result = obj.execute("""echo 1"2"'3'\\"\\'""") - self.assertEqual(result, """123"'""") - - if not self.url_blind: - return - - obj, data = self._get_detection_obj_data(self.url_blind % '') - if obj.get('execute_blind'): - self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'""")) \ No newline at end of file diff --git a/tests/test_php_php.py b/tests/test_php_php.py deleted file mode 100644 index 96063d1..0000000 --- a/tests/test_php_php.py +++ /dev/null @@ -1,88 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.languages.php import Php -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest, EXTRA_DOWNLOAD - - -class PhpTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'php', - 'engine': 'php', - 'evaluate' : 'php' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': """%(code)s""", - 'header': """print_r('%(header)s');""", - 'trailer': """print_r('%(trailer)s');""", - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'php', - 'engine': 'php', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'php', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://localhost:15002/eval.php?inj=*&tpl=%s' - url_blind = 'http://localhost:15002/eval.php?inj=*&tpl=%s&blind=1' - plugin = Php - - - blind_tests = [ - (0, 0, '%s', {}), - (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), - (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), - ] - - def test_download(self): - - # This is overriden due to the slight - # difference from the base test_download() - # obj.read('/dev/null') -> None - - obj, data = self._get_detection_obj_data(self.url % '') - self.assertEqual(data, self.expected_data) - - if not EXTRA_DOWNLOAD: - return - - # Normal ASCII file - readable_file = '/etc/resolv.conf' - content = open(readable_file, 'r').read() - self.assertEqual(content, obj.read(readable_file)) - - # Long binary file - readable_file = '/bin/ls' - content = open(readable_file, 'rb').read() - self.assertEqual(content, obj.read(readable_file)) - - # Non existant file - self.assertEqual(None, obj.read('/nonexistant')) - # Unpermitted file - self.assertEqual(None, obj.read('/etc/shadow')) - # Empty file - self.assertEqual(None, obj.read('/dev/null')) diff --git a/tests/test_php_smarty_secured.py b/tests/test_php_smarty_secured.py deleted file mode 100644 index e533f6d..0000000 --- a/tests/test_php_smarty_secured.py +++ /dev/null @@ -1,81 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.smarty import Smarty -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest - -class SmartySecuredTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'php', - 'engine': 'smarty', - 'trailer': '{%(trailer)s}', - 'header': '{%(header)s}', - 'render': '%(code)s', - 'prefix' : '', - 'suffix' : '', - } - - expected_data_blind = { - 'language': 'php', - 'engine': 'smarty', - 'evaluate_blind': 'php', - 'blind': True, - 'prefix' : '', - 'suffix' : '', - } - - url = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s&blind=1' - plugin = Smarty - - # The secured Smarty can't executes any PHP hence no sleep(1) hence no - # blind tests for now - blind_tests = [ - ] - - reflection_tests = [ - (0, 0, '%s', { }), - (0, 0, 'AAA%sAAA', {}), - (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}), - (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}), - (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}), - (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}), - (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}), - (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), - (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), - (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), - ] - - def test_custom_injection_tag(self): - - template = '{* %s *}' - - channel = Channel({ - 'url' : self.url.replace('*', '~') % template, - 'force_level': [ 5, 5 ], - 'injection_tag': '~', - 'technique': 'RT' - }) - - detect_template_injection(channel, [ self.plugin ]) - - expected_data = self.expected_data.copy() - expected_data.update({ 'prefix': '*}', 'suffix' : '{*'}) - - self.assertEqual(channel.data, expected_data) - - def test_download(self): - pass - - def test_upload(self): - pass - - def test_upload_blind(self): - pass \ No newline at end of file diff --git a/tests/test_php_smarty_unsecured.py b/tests/test_php_smarty_unsecured.py deleted file mode 100644 index 6c76cf7..0000000 --- a/tests/test_php_smarty_unsecured.py +++ /dev/null @@ -1,113 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.smarty import Smarty -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest, EXTRA_DOWNLOAD - -class SmartyUnsecuredTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'php', - 'engine': 'smarty', - 'evaluate' : 'php' , - 'execute' : True, - 'write': True, - 'read': True, - 'trailer': '{%(trailer)s}', - 'header': '{%(header)s}', - 'render': '%(code)s', - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'php', - 'engine': 'smarty', - 'evaluate_blind': 'php', - 'execute_blind': True, - 'write': True, - 'blind': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s' - url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s&blind=1' - plugin = Smarty - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), - ] - - reflection_tests = [ - (0, 0, '%s', { }), - (0, 0, 'AAA%sAAA', {}), - (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}), - (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}), - (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}), - (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}), - (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}), - (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), - (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), - (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), - - ] - - def test_custom_injection_tag(self): - - template = '{* %s *}' - - channel = Channel({ - 'url' : self.url.replace('*', '~') % template, - 'force_level': [ 5, 5 ], - 'injection_tag': '~', - 'technique': 'RT' - }) - - detect_template_injection(channel, [ self.plugin ]) - - expected_data = self.expected_data.copy() - expected_data.update({ 'prefix': '*}', 'suffix' : '{*'}) - - del channel.data['os'] - self.assertEqual(channel.data, expected_data) - - def test_download(self): - - # This is overriden due to the slight - # difference from the base test_download() - # obj.read('/dev/null') -> None - - obj, data = self._get_detection_obj_data(self.url % '') - self.assertEqual(data, self.expected_data) - - if not EXTRA_DOWNLOAD: - return - - # Normal ASCII file - readable_file = '/etc/resolv.conf' - content = open(readable_file, 'r').read() - self.assertEqual(content, obj.read(readable_file)) - - # Long binary file - readable_file = '/bin/ls' - content = open(readable_file, 'rb').read() - self.assertEqual(content, obj.read(readable_file)) - - # Non existant file - self.assertEqual(None, obj.read('/nonexistant')) - # Unpermitted file - self.assertEqual(None, obj.read('/etc/shadow')) - # Empty file - self.assertEqual(None, obj.read('/dev/null')) diff --git a/tests/test_php_twig_secured.py b/tests/test_php_twig_secured.py deleted file mode 100644 index cc5f865..0000000 --- a/tests/test_php_twig_secured.py +++ /dev/null @@ -1,56 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.twig import Twig -from core.channel import Channel -from basetest import BaseTest - -class TwigSecuredTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'php', - 'engine': 'twig', - 'trailer': '{{%(trailer)s}}', - 'header': '{{%(header)s}}', - 'render': '{{%(code)s}}', - 'prefix' : '', - 'suffix' : '', - } - - url = 'http://127.0.0.1:15002/twig-1.20.0-secured.php?tpl=%s&inj=*' - url_blind = '' - - plugin = Twig - - blind_tests = [ - - ] - - reflection_tests = [ - (0, 0, "%s", {}), - (0, 0, "AAA%sAAA", {}), - (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), - (0, 0, "{% block title %}%s{% endblock %}", {}), - (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), - (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), - (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), - (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), - (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), - (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}), - #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }), - ] - - # Defuse download tests, capabilities not available - def test_download(self): - pass - - # Defuse upload tests, capabilities not available - def test_upload(self): - pass - - def test_upload_blind(self): - pass \ No newline at end of file diff --git a/tests/test_php_twig_unsecured.py b/tests/test_php_twig_unsecured.py deleted file mode 100644 index a44e60a..0000000 --- a/tests/test_php_twig_unsecured.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.twig import Twig -from core.channel import Channel -from basetest import BaseTest - -class TwigUnsecuredTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'php', - 'engine': 'twig', - 'evaluate' : 'php', - 'execute' : True, - 'write': True, - 'read': True, - 'trailer': '{{%(trailer)s}}', - 'header': '{{%(header)s}}', - 'render': '{{%(code)s}}', - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'php', - 'engine': 'twig', - 'evaluate_blind': 'php', - 'execute_blind': True, - 'write': True, - 'blind': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*' - url_blind = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*&blind=1' - - plugin = Twig - - blind_tests = [ - - ] - - reflection_tests = [ - (0, 0, "%s", {}), - (0, 0, "AAA%sAAA", {}), - (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), - (0, 0, "{% block title %}%s{% endblock %}", {}), - (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), - (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), - (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), - (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), - (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), - (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}), - #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }), - ] \ No newline at end of file diff --git a/tests/test_py_jinja2.py b/tests/test_py_jinja2.py deleted file mode 100644 index 1718c85..0000000 --- a/tests/test_py_jinja2.py +++ /dev/null @@ -1,159 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(10, os.path.join(sys.path[0], '..')) -from plugins.engines.jinja2 import Jinja2 -from core.channel import Channel -from utils import rand -from utils import strings -from basetest import BaseTest - -class Jinja2Test(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'python', - 'engine': 'jinja2', - 'evaluate' : 'python' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix': '', - 'suffix': '', - 'trailer': '{{%(trailer)s}}', - 'header': '{{%(header)s}}', - 'render': '{{%(code)s}}', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'python', - 'engine': 'jinja2', - 'evaluate_blind': 'python', - 'execute_blind': True, - 'write': True, - 'blind': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15001/reflect/jinja2?tpl=%s&inj=*' - url_blind = 'http://127.0.0.1:15001/blind/jinja2?tpl=%s&inj=*' - - plugin = Jinja2 - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (1, 2, '{%% for a in %s: %%}\n{%% endfor %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), - ] - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - - # Reflecting tag ${} context - (1, 1, '{{%s}}', { 'prefix': '1}}', 'suffix' : '' }), - (1, 1, '{{ \'%s\' }}', { 'prefix': '1\'}}', 'suffix' : '' }), - (1, 1, '{{ "%s" }}', { 'prefix': '1"}}', 'suffix' : '' }), - (1, 3, '{{ """%s""" }}', { 'prefix': '1"}}', 'suffix' : '' }), # {{"""%s"""}} -> {{"""1"}} - (1, 2, '{{ "1"|join(%s) }}', { 'prefix': '1)}}', 'suffix' : '' }), - (1, 2, '{{ "1"|join(\'%s\') }}', { 'prefix': '1\')}}', 'suffix' : '' }), - (1, 2, '{{ "1"|join("%s") }}', { 'prefix': '1")}}', 'suffix' : '' }), - (1, 2, '{{ "1"|join("""%s""") }}', { 'prefix': '1")}}', 'suffix' : '' }), # {{("""%s""")}} -> {{("""1")]}} - - (1, 4, '{{[%s]}}', { 'prefix': '1]}}', 'suffix' : '' }), - (1, 3, '{{ [\'%s\'] }}', { 'prefix': '1\']}}', 'suffix' : '' }), - (1, 3, '{{ ["%s"] }}', { 'prefix': '1"]}}', 'suffix' : '' }), - (1, 3, '{{ ["""%s"""] }}', { 'prefix': '1"]}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}} - (1, 5, '{{ "1"|join([%s]) }}', { 'prefix': '1])}}', 'suffix' : '' }), - (1, 5, '{{ "1"|join([\'%s\'])) }}', { 'prefix': '1\'])}}', 'suffix' : '' }), - (1, 5, '{{ "1"|join(["%s"]) }}', { 'prefix': '1"])}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}} - - (1, 3, '{{{%s}}}', { 'prefix': '1:1}}}', 'suffix' : '' }), - (1, 3, '{{{1:%s}}}', { 'prefix': '1}}}', 'suffix' : '' }), - (1, 3, '{{ {1:\'%s\'} }}', { 'prefix': '1\'}}}', 'suffix' : '' }), - (1, 3, '{{ {1:"%s"} }}', { 'prefix': '1"}}}', 'suffix' : '' }), - (1, 3, '{{ {1:"""%s"""} }}', { 'prefix': '1"}}}', 'suffix' : '' }), - (1, 3, '{{{%s:1}}}', { 'prefix': '1:1}}}', 'suffix' : '' }), - (1, 3, '{{ {\'%s\':1} }}', { 'prefix': '1\':1}}}', 'suffix' : '' }), - (1, 3, '{{ {"%s":1} }}', { 'prefix': '1":1}}}', 'suffix' : '' }), - (1, 3, '{{ {"""%s""":1}} }', { 'prefix': '1":1}}}', 'suffix' : '' }), - - # if and for blocks context with {% %} - (1, 1, '{%% if %s: %%}\n{%% endif %%}', { 'prefix' : '1%}', 'suffix' : '' }), - (1, 2, '{%% for a in %s: %%}\n{%% endfor %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), - (1, 1, '{%% if %s==1: %%}\n{%% endif %%}', { 'prefix' : '1%}', 'suffix' : '' }), - (1, 1, '{%% if \'%s\'==1: %%}\n{%% endif %%}', { 'prefix' : '1\'%}', 'suffix' : '' }), - (1, 1, '{%% if "%s"==1: %%}\n{%% endif %%}', { 'prefix' : '1"%}', 'suffix' : '' }), - (1, 1, '{%% if """%s"""==1: %%}\n{%% endif %%}', { 'prefix' : '1"%}', 'suffix' : '' }), # if """%s""": -> if """1": - (1, 2, '{%% if (1, %s)==1: %%}\n{%% endif %%}', { 'prefix' : '1)%}', 'suffix' : '' }), - (1, 2, '{%% if (1, \'%s\')==1: %%}\n{%% endif %%}', { 'prefix' : '1\')%}', 'suffix' : '' }), - (1, 2, '{%% if (1, "%s")==1: %%}\n{%% endif %%}', { 'prefix' : '1")%}', 'suffix' : '' }), - (1, 2, '{%% if (1, """%s""")==1: %%}\n{%% endif %%}', { 'prefix' : '1")%}', 'suffix' : '' }), # if (1, """%s"""): -> if (1, """1"): - - (1, 3, '{%% if [%s]==1: %%}\n{%% endif %%}', { 'prefix' : '1]%}', 'suffix' : '' }), - (1, 3, '{%% if [\'%s\']==1: %%}\n{%% endif %%}', { 'prefix' : '1\']%}', 'suffix' : '' }), - (1, 3, '{%% if ["%s"]==1: %%}\n{%% endif %%}', { 'prefix' : '1"]%}', 'suffix' : '' }), - (1, 3, '{%% if ["""%s"""]==1: %%}\n{%% endif %%}', { 'prefix' : '1"]%}', 'suffix' : '' }), # if ["""%s"""]: -> if ["""1"]: - (1, 5, '{%% if (1, [%s])==1: %%}\n{%% endif %%}', { 'prefix' : '1])%}', 'suffix' : '' }), - (1, 5, '{%% if (1, [\'%s\'])==1: %%}\n{%% endif %%}', { 'prefix' : '1\'])%}', 'suffix' : '' }), - (1, 5, '{%% if (1, ["%s"])==1: %%}\n{%% endif %%}', { 'prefix' : '1"])%}', 'suffix' : '' }), - (1, 5, '{%% if (1, ["""%s"""])==1: %%}\n{%% endif %%}', { 'prefix' : '1"])%}', 'suffix' : '' }), # if (1, ["""%s"""]): -> if (1, ["""1"]): - - (1, 3, '{%% for a in {%s}: %%}\n{%% endfor %%}', { 'prefix' : '1:1}%}', 'suffix' : '' }), - (1, 3, '{%% if {%s:1}==1: %%}\n{%% endif %%}', { 'prefix' : '1:1}%}', 'suffix' : '' }), - (1, 3, '{%% if {\'%s\':1}==1: %%}\n{%% endif %%}', { 'prefix' : '1\':1}%}', 'suffix' : '' }), - (1, 3, '{%% if {"%s":1}==1: %%}\n{%% endif %%}', { 'prefix' : '1":1}%}', 'suffix' : '' }), - (1, 3, '{%% if {"""%s""":1}==1: %%}\n{%% endif %%}', { 'prefix' : '1":1}%}', 'suffix' : '' }), # if {"""%s""":1}: -> if {"""1":1}: - (1, 3, '{%% if {1:%s}==1: %%}\n{%% endif %%}', { 'prefix' : '1}%}', 'suffix' : '' }), - (1, 3, '{%% if {1:\'%s\'}==1: %%}\n{%% endif %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), - (1, 3, '{%% if {1:"%s"}==1: %%}\n{%% endif %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), - (1, 3, '{%% if {1:"""%s"""}==1: %%}\n{%% endif %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), # if {1:"""%s""":1}: -> if {1:"""1"}: - - # if and for blocks context with line_statement_prefix - (5, 5, '# if %s:\n# endif\n', { 'prefix' : '1\n', 'suffix' : '\n' }), - (5, 5, '# for a in %s:\n# endfor', { 'prefix' : '"1"\n', 'suffix' : '\n' }), - (5, 5, '# if %s==1:\n# endif', { 'prefix' : '1\n', 'suffix' : '\n' }), - (5, 5, '# if \'%s\'==1:\n# endif', { 'prefix' : '1\'\n', 'suffix' : '\n' }), - (5, 5, '# if "%s"==1:\n# endif', { 'prefix' : '1"\n', 'suffix' : '\n' }), - (5, 5, '# if """%s"""==1:\n# endif', { 'prefix' : '1"\n', 'suffix' : '\n' }), # if """%s""": -> if """1": - (5, 5, '# if (1, %s)==1:\n# endif', { 'prefix' : '1)\n', 'suffix' : '\n' }), - (5, 5, '# if (1, \'%s\')==1:\n# endif', { 'prefix' : '1\')\n', 'suffix' : '\n' }), - (5, 5, '# if (1, "%s")==1:\n# endif', { 'prefix' : '1")\n', 'suffix' : '\n' }), - (5, 5, '# if (1, """%s""")==1:\n# endif', { 'prefix' : '1")\n', 'suffix' : '\n' }), # if (1, """%s"""): -> if (1, """1"): - - (5, 5, '# if [%s]==1:\n# endif', { 'prefix' : '1]\n', 'suffix' : '\n' }), - (5, 5, '# if [\'%s\']==1:\n# endif', { 'prefix' : '1\']\n', 'suffix' : '\n' }), - (5, 5, '# if ["%s"]==1:\n# endif', { 'prefix' : '1"]\n', 'suffix' : '\n' }), - (5, 5, '# if ["""%s"""]==1:\n# endif', { 'prefix' : '1"]\n', 'suffix' : '\n' }), # if ["""%s"""]: -> if ["""1"]: - (5, 5, '# if (1, [%s])==1:\n# endif', { 'prefix' : '1])\n', 'suffix' : '\n' }), - (5, 5, '# if (1, [\'%s\'])==1:\n# endif', { 'prefix' : '1\'])\n', 'suffix' : '\n' }), - (5, 5, '# if (1, ["%s"])==1:\n# endif', { 'prefix' : '1"])\n', 'suffix' : '\n' }), - (5, 5, '# if (1, ["""%s"""])==1:\n# endif', { 'prefix' : '1"])\n', 'suffix' : '\n' }), # if (1, ["""%s"""]): -> if (1, ["""1"]): - - (5, 5, '# for a in {%s}:\n# endfor', { 'prefix' : '1:1}\n', 'suffix' : '\n' }), - (5, 5, '# if {%s:1}==1:\n# endif', { 'prefix' : '1:1}\n', 'suffix' : '\n' }), - (5, 5, '# if {\'%s\':1}==1:\n# endif', { 'prefix' : '1\':1}\n', 'suffix' : '\n' }), - (5, 5, '# if {"%s":1}==1:\n# endif', { 'prefix' : '1":1}\n', 'suffix' : '\n' }), - (5, 5, '# if {"""%s""":1}==1:\n# endif', { 'prefix' : '1":1}\n', 'suffix' : '\n' }), # if {"""%s""":1}: -> if {"""1":1}: - (5, 5, '# if {1:%s}==1:\n# endif', { 'prefix' : '1}\n', 'suffix' : '\n' }), - (5, 5, '# if {1:\'%s\'}==1:\n# endif', { 'prefix' : '1\'}\n', 'suffix' : '\n' }), - (5, 5, '# if {1:"%s"}==1:\n# endif', { 'prefix' : '1"}\n', 'suffix' : '\n' }), - (5, 5, '# if {1:"""%s"""}==1:\n# endif', { 'prefix' : '1"}\n', 'suffix' : '\n' }), # if {1:"""%s""":1}: -> if {1:"""1"}: - - # Comment blocks - (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), - - ] - - def test_reflection_limit(self): - - obj, data = self._get_detection_obj_data('http://127.0.0.1:15001/limit/jinja2?tpl=%s&inj=*&limit=20' % '') - - expected_data = { 'unreliable_render' : self.expected_data['render'], 'unreliable' : 'Jinja2' } - - self.assertEqual(data, expected_data) diff --git a/tests/test_py_mako.py b/tests/test_py_mako.py deleted file mode 100644 index 6ad8570..0000000 --- a/tests/test_py_mako.py +++ /dev/null @@ -1,177 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(10, os.path.join(sys.path[0], '..')) -from plugins.engines.mako import Mako -from core.channel import Channel -from utils import rand -from utils import strings -from basetest import BaseTest - -class MakoTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'python', - 'engine': 'mako', - 'evaluate' : 'python', - 'execute' : True, - 'read': True, - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'trailer': '${%(trailer)s}', - 'header': '${%(header)s}', - 'render': '${%(code)s}', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'python', - 'engine': 'mako', - 'blind': True, - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'evaluate_blind': 'python', - 'execute_blind': True, - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=*' - url_blind = 'http://127.0.0.1:15001/blind/mako?tpl=%s&inj=*' - - plugin = Mako - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }), - ] - - reflection_tests = [ - - # Text context - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - - # Reflecting tag ${} context - (1, 1, '${ %s = 1 }', { 'prefix': '1}', 'suffix' : '' }), - (1, 1, '${%s}', { 'prefix': '1}', 'suffix' : '' }), - (1, 1, '${ \'%s\' }', { 'prefix': '1\'}', 'suffix' : '' }), - (1, 1, '${ "%s" }', { 'prefix': '1"}', 'suffix' : '' }), - (1, 3, '${ """%s""" }', { 'prefix': '1"""}', 'suffix' : '' }), - (1, 2, '${ range(%s) }', { 'prefix': '1)}', 'suffix' : '' }), - (1, 2, '${ set(\'%s\') }', { 'prefix': '1\')}', 'suffix' : '' }), - (1, 2, '${ set("%s") }', { 'prefix': '1")}', 'suffix' : '' }), - (1, 3, '${ set("""%s""") }', { 'prefix': '1""")}', 'suffix' : '' }), - - (1, 3, '${[%s]}', { 'prefix': '1]}', 'suffix' : '' }), - (1, 3, '${ [\'%s\'] }', { 'prefix': '1\']}', 'suffix' : '' }), - (1, 3, '${ ["%s"] }', { 'prefix': '1"]}', 'suffix' : '' }), - (1, 3, '${ ["""%s"""] }', { 'prefix': '1"""]}', 'suffix' : '' }), - (1, 5, '${ set([%s]) }', { 'prefix': '1])}', 'suffix' : '' }), - (1, 5, '${ set([\'%s\']) }', { 'prefix': '1\'])}', 'suffix' : '' }), - (1, 5, '${ set(["%s"]) }', { 'prefix': '1"])}', 'suffix' : '' }), - (1, 5, '${ set(["""%s"""]) }', { 'prefix': '1"""])}', 'suffix' : '' }), - - (1, 3, '${{%s}}', { 'prefix': '1}}', 'suffix' : '' }), - (1, 3, '${{1:%s}}', { 'prefix': '1}}', 'suffix' : '' }), - (1, 3, '${ {1:\'%s\'} }', { 'prefix': '1\'}}', 'suffix' : '' }), - (1, 3, '${ {1:"%s"} }', { 'prefix': '1"}}', 'suffix' : '' }), - (1, 3, '${ {1:"""%s"""} }', { 'prefix': '1"""}}', 'suffix' : '' }), - (1, 3, '${{3:4, %s:1}}', { 'prefix': '1:1}}', 'suffix' : '' }), - (1, 3, '${ {\'%s\':1} }', { 'prefix': '1\'}}', 'suffix' : '' }), - (1, 3, '${ {"%s":1} }', { 'prefix': '1"}}', 'suffix' : '' }), - (1, 3, '${ {"""%s""":1} }', { 'prefix': '1"""}}', 'suffix' : '' }), - - # Code blocks context - (1, 1, '<%% %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), - (1, 1, '<%%! %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), - (1, 1, '<%% %s=1 %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), - (1, 1, '<%% a=%s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), - (1, 1, '<%% a=\'%s\' %%>', { 'prefix' : '1\'%>', 'suffix' : '<%#' }), - (1, 1, '<%% a="%s" %%>', { 'prefix' : '1"%>', 'suffix' : '<%#' }), - (1, 3, '<%% a="""%s""" %%>', { 'prefix' : '1"""%>', 'suffix' : '<%#' }), - (1, 3, '<%% a=range(%s) %%>', { 'prefix' : '1)%>', 'suffix' : '<%#' }), - (1, 3, '<%% a=\'\'.join(\'%s\') %%>', { 'prefix' : '1\')%>', 'suffix' : '<%#' }), - (1, 3, '<%% a=\'\'.join("%s") %%>', { 'prefix' : '1\")%>', 'suffix' : '<%#' }), - (1, 3, '<%% a=\'\'.join("""%s""") %%>', { 'prefix' : '1""")%>', 'suffix' : '<%#' }), - - - (1, 3, '<%% a=[%s] %%>', { 'prefix' : '1]%>', 'suffix' : '<%#' }), - (1, 3, '<%% a=[\'%s\'] %%>', { 'prefix' : '1\']%>', 'suffix' : '<%#' }), - (1, 3, '<%% a=["%s"] %%>', { 'prefix' : '1"]%>', 'suffix' : '<%#' }), - (1, 3, '<%% a=["""%s"""] %%>', { 'prefix' : '1"""]%>', 'suffix' : '<%#' }), - (1, 5, '<%% a=set([%s]) %%>', { 'prefix' : '1])%>', 'suffix' : '<%#' }), - (1, 5, '<%% a=set([\'%s\']) %%>', { 'prefix' : '1\'])%>', 'suffix' : '<%#' }), - (1, 5, '<%% a=set(["%s"]) %%>', { 'prefix' : '1"])%>', 'suffix' : '<%#' }), - (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }), - - (1, 3, '<%% a={%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={1:%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={1:\'%s\'} %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={1:"%s"} %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={1:"""%s"""} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={3:2, %s:1} %%>', { 'prefix' : '1:1}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={\'%s\':1}] %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={"%s":1}] %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }), - (1, 3, '<%% a={"""%s""":1} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }), - - # if and for blocks context - (5, 5, '%% if %s:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }), - (5, 5, '%% for a in %s:\n%% endfor', { 'prefix' : '"1":#\n', 'suffix' : '\n' }), - (5, 5, '%% if %s==1:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }), - (5, 5, '%% if \'%s\'==1:\n%% endif', { 'prefix' : '1\':#\n', 'suffix' : '\n' }), - (5, 5, '%% if "%s"==1:\n%% endif', { 'prefix' : '1":#\n', 'suffix' : '\n' }), - (5, 5, '%% if """%s"""==1:\n%% endif', { 'prefix' : '1""":#\n', 'suffix' : '\n' }), - (5, 5, '%% if (1, %s)==1:\n%% endif', { 'prefix' : '1):#\n', 'suffix' : '\n' }), - (5, 5, '%% if (1, \'%s\')==1:\n%% endif', { 'prefix' : '1\'):#\n', 'suffix' : '\n' }), - (5, 5, '%% if (1, "%s")==1:\n%% endif', { 'prefix' : '1"):#\n', 'suffix' : '\n' }), - (5, 5, '%% if (1, """%s""")==1:\n%% endif', { 'prefix' : '1"""):#\n', 'suffix' : '\n' }), - - (5, 5, '%% if [%s]==1:\n%% endif', { 'prefix' : '1]:#\n', 'suffix' : '\n' }), - (5, 5, '%% if [\'%s\']==1:\n%% endif', { 'prefix' : '1\']:#\n', 'suffix' : '\n' }), - (5, 5, '%% if ["%s"]==1:\n%% endif', { 'prefix' : '1"]:#\n', 'suffix' : '\n' }), - (5, 5, '%% if ["""%s"""]==1:\n%% endif', { 'prefix' : '1"""]:#\n', 'suffix' : '\n' }), - (5, 5, '%% if (1, [%s])==1:\n%% endif', { 'prefix' : '1]):#\n', 'suffix' : '\n' }), - (5, 5, '%% if (1, [\'%s\'])==1:\n%% endif', { 'prefix' : '1\']):#\n', 'suffix' : '\n' }), - (5, 5, '%% if (1, ["%s"])==1:\n%% endif', { 'prefix' : '1"]):#\n', 'suffix' : '\n' }), - - (5, 5, '%% if (1, ["""%s"""])==1:\n%% endif', { 'prefix' : '1"""]):#\n', 'suffix' : '\n' }), - - (5, 5, '%% for a in {%s}:\n%% endfor', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {%s:1}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {\'%s\':1}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {"%s":1}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {"""%s""":1}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {1:%s}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {1:\'%s\'}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {1:"%s"}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }), - (5, 5, '%% if {1:"""%s"""}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }), - - # Mako blocks. Skip <%block> which doesn't seem affecting the standard inj - # Inejcting includes e.g. '<%%include file="%s"/>' generates a missing file exception - (5, 1, '<%%doc> %s ', { 'prefix' : '', 'suffix' : '<%doc>' }), - #(5, 1, '<%%def name="a(x)"> %s ', { 'prefix' : '', 'suffix' : '<%def name="t(x)">' }), - (5, 1, '<%%text> %s ', { 'prefix' : '', 'suffix' : '<%text>' }), - - ] - - def test_reflection_limit(self): - template = '%s' - - channel = Channel({ - 'url' : 'http://127.0.0.1:15001/limit/mako?tpl=%s&inj=*&limit=20' % template, - 'injection_tag': '*', - 'technique': 'R' - }) - - Mako(channel).detect() - - expected_data = { 'unreliable_render' : self.expected_data['render'], 'unreliable' : 'Mako' } - - self.assertEqual(channel.data, expected_data) diff --git a/tests/test_py_python.py b/tests/test_py_python.py deleted file mode 100644 index 216c7ca..0000000 --- a/tests/test_py_python.py +++ /dev/null @@ -1,56 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.languages.python import Python -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest - - -class PythonTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'python', - 'engine': 'python', - 'evaluate' : 'python' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': """str(%(code)s)""", - 'header': """'%(header)s'+""", - 'trailer': """+'%(trailer)s'""", - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'python', - 'engine': 'python', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'python', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://localhost:15001/reflect/eval?inj=*&tpl=%s' - url_blind = 'http://localhost:15001/blind/eval?inj=*&tpl=%s' - plugin = Python - - blind_tests = [ - (0, 0, '%s', {}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - ] - \ No newline at end of file diff --git a/tests/test_py_tornado.py b/tests/test_py_tornado.py deleted file mode 100644 index e879c45..0000000 --- a/tests/test_py_tornado.py +++ /dev/null @@ -1,104 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(10, os.path.join(sys.path[0], '..')) -from plugins.engines.tornado import Tornado -from core.channel import Channel -from utils import rand -from utils import strings -from basetest import BaseTest - -class TornadoTest(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'python', - 'engine': 'tornado', - 'evaluate' : 'python' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix': '', - 'suffix': '', - 'trailer': '{{%(trailer)s}}', - 'header': '{{%(header)s}}', - 'render': '{{%(code)s}}', - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'python', - 'engine': 'tornado', - 'evaluate_blind': 'python', - 'execute_blind': True, - 'write': True, - 'blind': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://127.0.0.1:15001/reflect/tornado?tpl=%s&inj=*' - url_blind = 'http://127.0.0.1:15001/blind/tornado?tpl=%s&inj=*' - - plugin = Tornado - - blind_tests = [ - (0, 0, 'AAA%sAAA', {}), - (1, 2, '{%% for a in %s %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), - ] - reflection_tests = [ - (0, 0, '%s', {}), - (0, 0, 'AAA%sAAA', {}), - - # Reflecting tag ${} context - (1, 1, '{{%s}}', { 'prefix': '1}}', 'suffix' : '' }), - (1, 1, '{{ \'%s\' }}', { 'prefix': '1\'}}', 'suffix' : '' }), - (1, 1, '{{ "%s" }}', { 'prefix': '1"}}', 'suffix' : '' }), - (1, 3, '{{ """%s""" }}', { 'prefix': '1"""}}', 'suffix' : '' }), # {{"""%s"""}} -> {{"""1"}} - - (1, 4, '{{[%s]}}', { 'prefix': '1]}}', 'suffix' : '' }), - (1, 3, '{{ [\'%s\'] }}', { 'prefix': '1\']}}', 'suffix' : '' }), - (1, 3, '{{ ["%s"] }}', { 'prefix': '1"]}}', 'suffix' : '' }), - (1, 3, '{{ ["""%s"""] }}', { 'prefix': '1"""]}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}} - - # # if and for blocks context with {% %} - (1, 1, '{%% if %s: %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }), - (1, 2, '{%% for a in %s: %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), - (1, 1, '{%% if %s==1 %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }), - (1, 1, '{%% if \'%s\'==1 %%}\n{%% end %%}', { 'prefix' : '1\'%}', 'suffix' : '' }), - (1, 1, '{%% if "%s"==1 %%}\n{%% end %%}', { 'prefix' : '1"%}', 'suffix' : '' }), - (1, 3, '{%% if """%s"""==1 %%}\n{%% end %%}', { 'prefix' : '1"""%}', 'suffix' : '' }), # if """%s""": -> if """1": - (1, 2, '{%% if (1, %s)==1 %%}\n{%% end %%}', { 'prefix' : '1)%}', 'suffix' : '' }), - (1, 2, '{%% if (1, \'%s\')==1 %%}\n{%% end %%}', { 'prefix' : '1\')%}', 'suffix' : '' }), - (1, 2, '{%% if (1, "%s")==1 %%}\n{%% end %%}', { 'prefix' : '1")%}', 'suffix' : '' }), - (1, 3, '{%% if (1, """%s""")==1 %%}\n{%% end %%}', { 'prefix' : '1""")%}', 'suffix' : '' }), # if (1, """%s"""): -> if (1, """1"): - - (1, 3, '{%% if [%s]==1 %%}\n{%% end %%}', { 'prefix' : '1]%}', 'suffix' : '' }), - (1, 3, '{%% if [\'%s\']==1 %%}\n{%% end %%}', { 'prefix' : '1\']%}', 'suffix' : '' }), - (1, 3, '{%% if ["%s"]==1 %%}\n{%% end %%}', { 'prefix' : '1"]%}', 'suffix' : '' }), - (1, 3, '{%% if ["""%s"""]==1 %%}\n{%% end %%}', { 'prefix' : '1"""]%}', 'suffix' : '' }), # if ["""%s"""]: -> if ["""1"]: - (1, 5, '{%% if (1, [%s])==1 %%}\n{%% end %%}', { 'prefix' : '1])%}', 'suffix' : '' }), - (1, 5, '{%% if (1, [\'%s\'])==1 %%}\n{%% end %%}', { 'prefix' : '1\'])%}', 'suffix' : '' }), - (1, 5, '{%% if (1, ["%s"])==1 %%}\n{%% end %%}', { 'prefix' : '1"])%}', 'suffix' : '' }), - (1, 5, '{%% if (1, ["""%s"""])==1 %%}\n{%% end %%}', { 'prefix' : '1"""])%}', 'suffix' : '' }), # if (1, ["""%s"""]): -> if (1, ["""1"]): - - (1, 3, '{%% for a in {%s} %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), - (1, 3, '{%% if {%s:1}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), - (1, 3, '{%% if {\'%s\':1}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), - (1, 3, '{%% if {"%s":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), - (1, 3, '{%% if {"""%s""":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {"""%s""":1}: -> if {"""1":1}: - (1, 3, '{%% if {1:%s}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), - (1, 3, '{%% if {1:\'%s\'}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), - (1, 3, '{%% if {1:"%s"}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), - (1, 3, '{%% if {1:"""%s"""}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {1:"""%s""":1}: -> if {1:"""1"}: - - - # # Comment blocks - (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), - - ] diff --git a/tests/test_ruby_erb.py b/tests/test_ruby_erb.py deleted file mode 100644 index 64a2181..0000000 --- a/tests/test_ruby_erb.py +++ /dev/null @@ -1,57 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.erb import Erb -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest - - -class ErbTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'ruby', - 'engine': 'erb', - 'evaluate' : 'ruby' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': '"#{%(code)s}"', - 'header': """<%%= '%(header)s'+""", - 'trailer': """+'%(trailer)s' %%>""", - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'ruby', - 'engine': 'erb', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'ruby', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://localhost:15005/reflect/erb?inj=*&tpl=%s' - url_blind = 'http://localhost:15005/blind/erb?inj=*&tpl=%s' - plugin = Erb - - #TODO: write context escape tests - blind_tests = [ - (0, 0, '%s', {}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - ] - \ No newline at end of file diff --git a/tests/test_ruby_ruby.py b/tests/test_ruby_ruby.py deleted file mode 100644 index de96071..0000000 --- a/tests/test_ruby_ruby.py +++ /dev/null @@ -1,56 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.languages.ruby import Ruby -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest - - -class RubyTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'ruby', - 'engine': 'ruby', - 'evaluate' : 'ruby' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': '"#{%(code)s}"', - 'header': """'%(header)s'+""", - 'trailer': """+'%(trailer)s'""", - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'ruby', - 'engine': 'ruby', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'ruby', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://localhost:15005/reflect/eval?inj=*&tpl=%s' - url_blind = 'http://localhost:15005/blind/eval?inj=*&tpl=%s' - plugin = Ruby - - blind_tests = [ - (0, 0, '%s', {}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - ] - \ No newline at end of file diff --git a/tests/test_ruby_slim.py b/tests/test_ruby_slim.py deleted file mode 100644 index d2f4e2a..0000000 --- a/tests/test_ruby_slim.py +++ /dev/null @@ -1,57 +0,0 @@ -import unittest -import requests -import os -import sys -import random - -sys.path.insert(1, os.path.join(sys.path[0], '..')) -from plugins.engines.slim import Slim -from core.channel import Channel -from core.checks import detect_template_injection -from basetest import BaseTest - - -class SlimTests(unittest.TestCase, BaseTest): - - expected_data = { - 'language': 'ruby', - 'engine': 'slim', - 'evaluate' : 'ruby' , - 'execute' : True, - 'read' : True, - 'write' : True, - 'prefix' : '', - 'suffix': '', - 'render': '"#{%(code)s}"', - 'header': """=('%(header)s'+""", - 'trailer': """+'%(trailer)s')""", - 'bind_shell' : True, - 'reverse_shell': True - } - - expected_data_blind = { - 'language': 'ruby', - 'engine': 'slim', - 'blind': True, - 'execute_blind' : True, - 'evaluate_blind' : 'ruby', - 'write': True, - 'prefix' : '', - 'suffix' : '', - 'bind_shell' : True, - 'reverse_shell': True - } - - url = 'http://localhost:15005/reflect/slim?inj=*&tpl=%s' - url_blind = 'http://localhost:15005/blind/slim?inj=*&tpl=%s' - plugin = Slim - - #TODO: write context escape tests - blind_tests = [ - (0, 0, '%s', {}), - ] - - reflection_tests = [ - (0, 0, '%s', {}), - ] - \ No newline at end of file diff --git a/tests/tests.sh b/tests/tests.sh deleted file mode 100755 index 7dc6593..0000000 --- a/tests/tests.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -e - -cd "$( dirname "${BASH_SOURCE[0]}" )" - -for SCRIPT in ./run_*sh -do - if [ -f $SCRIPT -a -x $SCRIPT ] - then - echo -e "\n## Running $SCRIPT" - bash -e $SCRIPT --test - fi -done \ No newline at end of file diff --git a/utils/cliparser.py b/utils/cliparser.py index 6acea3f..ba04d3e 100644 --- a/utils/cliparser.py +++ b/utils/cliparser.py @@ -14,6 +14,8 @@ Example: ./tplmap -u 'http://www.target.com/page.php?id=1*' + or + docker run --rm tplmap:latest -u 'http://www.target.com/page.php?id=1*' """ @@ -62,8 +64,14 @@ def format_epilog(self, formatter): ) request.add_option("--proxy", dest="proxy", - help="Use a proxy to connect to the target URL" + help="Use a proxy to connect to the target URL." ) +request.add_option("-k", "--insecure", + dest="ssl_verf", + action="store_false", + default=True, + help="Allow insecure server connections." + ) # Detection options detection = OptionGroup(parser, "Detection" , "These options can be used to customize the detection phase.") diff --git a/utils/config.py b/utils/config.py index da21ad0..d8249b1 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,5 +1,4 @@ import os -import sys import yaml config = None diff --git a/utils/strings.py b/utils/strings.py index f8af1a6..8c8b0ce 100644 --- a/utils/strings.py +++ b/utils/strings.py @@ -1,4 +1,3 @@ -import json import base64 import hashlib From 759a24206e148ea7ca8bec0abaf2bc23a42eeab8 Mon Sep 17 00:00:00 2001 From: yagamiL Date: Tue, 26 Mar 2024 12:54:53 -0400 Subject: [PATCH 2/3] Revert "Update for docker support + Added ssl/tls verification flag" This reverts commit 9a7653718a9a0faebb5ed7e488985350517facfb. --- .dockerignore | 25 ++ .travis.yml | 7 + Dockerfile | 4 + burp_extension.py | 1 + burp_extension/README.md | 53 ++++ burp_extension/__init__.py | 0 burp_extension/burp_extender.py | 12 + burp_extension/channel.py | 35 +++ burp_extension/config_tab.py | 113 +++++++ burp_extension/scan_issue.py | 146 +++++++++ burp_extension/scanner_check.py | 28 ++ docker-envs/Dockerfile.java | 19 ++ docker-envs/Dockerfile.node | 15 + docker-envs/Dockerfile.php | 16 + docker-envs/Dockerfile.python2 | 13 + docker-envs/Dockerfile.python3 | 14 + docker-envs/Dockerfile.ruby | 12 + docker-envs/README.md | 16 + docker-envs/docker-compose.yml | 45 +++ tests/basetest.py | 202 ++++++++++++ tests/env_java_tests/spark-app/build.gradle | 25 ++ .../gradle/wrapper/gradle-wrapper.properties | 6 + .../env_java_tests/spark-app/settings.gradle | 2 + .../webframeworks/SparkApplication.java | 135 ++++++++ .../src/main/resources/templates/hello.html | 10 + tests/env_node_tests/connect-app.js | 289 ++++++++++++++++++ tests/env_php_tests/eval.php | 31 ++ tests/env_php_tests/smarty-3.1.32-secured.php | 32 ++ .../env_php_tests/smarty-3.1.32-unsecured.php | 38 +++ tests/env_php_tests/twig-1.19.0-unsecured.php | 40 +++ tests/env_php_tests/twig-1.20.0-secured.php | 40 +++ tests/env_py_tests/webserver.py | 194 ++++++++++++ tests/env_ruby_tests/config.ru | 3 + tests/env_ruby_tests/webserver.rb | 60 ++++ tests/run_channel_test.sh | 33 ++ tests/run_java_tests.sh | 30 ++ tests/run_node_tests.sh | 39 +++ tests/run_php_tests.sh | 31 ++ tests/run_python2_tests.sh | 33 ++ tests/run_python3_tests.sh | 34 +++ tests/run_ruby_tests.sh | 31 ++ tests/test_channel.py | 225 ++++++++++++++ tests/test_java_freemarker.py | 65 ++++ tests/test_java_velocity.py | 88 ++++++ tests/test_node_dot.py | 56 ++++ tests/test_node_dust.py | 64 ++++ tests/test_node_ejs.py | 67 ++++ tests/test_node_javascript.py | 79 +++++ tests/test_node_marko.py | 58 ++++ tests/test_node_nunjucks.py | 68 +++++ tests/test_node_pug.py | 80 +++++ tests/test_php_php.py | 88 ++++++ tests/test_php_smarty_secured.py | 81 +++++ tests/test_php_smarty_unsecured.py | 113 +++++++ tests/test_php_twig_secured.py | 56 ++++ tests/test_php_twig_unsecured.py | 64 ++++ tests/test_py_jinja2.py | 159 ++++++++++ tests/test_py_mako.py | 177 +++++++++++ tests/test_py_python.py | 56 ++++ tests/test_py_tornado.py | 104 +++++++ tests/test_ruby_erb.py | 57 ++++ tests/test_ruby_ruby.py | 56 ++++ tests/test_ruby_slim.py | 57 ++++ tests/tests.sh | 12 + 64 files changed, 3842 insertions(+) create mode 100644 .dockerignore create mode 100644 .travis.yml create mode 100644 burp_extension.py create mode 100644 burp_extension/README.md create mode 100644 burp_extension/__init__.py create mode 100644 burp_extension/burp_extender.py create mode 100644 burp_extension/channel.py create mode 100644 burp_extension/config_tab.py create mode 100644 burp_extension/scan_issue.py create mode 100644 burp_extension/scanner_check.py create mode 100644 docker-envs/Dockerfile.java create mode 100644 docker-envs/Dockerfile.node create mode 100644 docker-envs/Dockerfile.php create mode 100644 docker-envs/Dockerfile.python2 create mode 100644 docker-envs/Dockerfile.python3 create mode 100644 docker-envs/Dockerfile.ruby create mode 100644 docker-envs/README.md create mode 100644 docker-envs/docker-compose.yml create mode 100644 tests/basetest.py create mode 100644 tests/env_java_tests/spark-app/build.gradle create mode 100644 tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties create mode 100644 tests/env_java_tests/spark-app/settings.gradle create mode 100644 tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java create mode 100644 tests/env_java_tests/spark-app/src/main/resources/templates/hello.html create mode 100644 tests/env_node_tests/connect-app.js create mode 100644 tests/env_php_tests/eval.php create mode 100644 tests/env_php_tests/smarty-3.1.32-secured.php create mode 100644 tests/env_php_tests/smarty-3.1.32-unsecured.php create mode 100644 tests/env_php_tests/twig-1.19.0-unsecured.php create mode 100644 tests/env_php_tests/twig-1.20.0-secured.php create mode 100644 tests/env_py_tests/webserver.py create mode 100644 tests/env_ruby_tests/config.ru create mode 100644 tests/env_ruby_tests/webserver.rb create mode 100755 tests/run_channel_test.sh create mode 100755 tests/run_java_tests.sh create mode 100755 tests/run_node_tests.sh create mode 100755 tests/run_php_tests.sh create mode 100755 tests/run_python2_tests.sh create mode 100755 tests/run_python3_tests.sh create mode 100755 tests/run_ruby_tests.sh create mode 100644 tests/test_channel.py create mode 100644 tests/test_java_freemarker.py create mode 100644 tests/test_java_velocity.py create mode 100644 tests/test_node_dot.py create mode 100644 tests/test_node_dust.py create mode 100644 tests/test_node_ejs.py create mode 100644 tests/test_node_javascript.py create mode 100644 tests/test_node_marko.py create mode 100644 tests/test_node_nunjucks.py create mode 100644 tests/test_node_pug.py create mode 100644 tests/test_php_php.py create mode 100644 tests/test_php_smarty_secured.py create mode 100644 tests/test_php_smarty_unsecured.py create mode 100644 tests/test_php_twig_secured.py create mode 100644 tests/test_php_twig_unsecured.py create mode 100644 tests/test_py_jinja2.py create mode 100644 tests/test_py_mako.py create mode 100644 tests/test_py_python.py create mode 100644 tests/test_py_tornado.py create mode 100644 tests/test_ruby_erb.py create mode 100644 tests/test_ruby_ruby.py create mode 100644 tests/test_ruby_slim.py create mode 100755 tests/tests.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e4807a9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +__pycache__ +**/__pycache__/ +.vscode +Dockerfile +*.pyc +*.pyo +*.pyd +.Python +env +.tox +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.log +.git +.gitignore +.pytest_cache +.travis.yml +docker-envs +burp_extension +burp_extension.py +tests diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9a1b5d2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +sudo: required + +services: + - docker + +script: + ./tests/tests.sh diff --git a/Dockerfile b/Dockerfile index e8be6d9..a227127 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,10 @@ FROM python:3.9 WORKDIR /app COPY . /app +RUN useradd -ms /bin/bash user +RUN chown -R user:user /app +USER user + RUN python -m pip install --upgrade pip RUN pip install --no-cache-dir -r requirements.txt # Running the script when the container launches diff --git a/burp_extension.py b/burp_extension.py new file mode 100644 index 0000000..5aadc42 --- /dev/null +++ b/burp_extension.py @@ -0,0 +1 @@ +from burp_extension.burp_extender import BurpExtender diff --git a/burp_extension/README.md b/burp_extension/README.md new file mode 100644 index 0000000..d1c5d60 --- /dev/null +++ b/burp_extension/README.md @@ -0,0 +1,53 @@ +# Burp Suite Plugin + +Tplmap is able to run as a Burp Suite Extension. + +### Install + +Load burp_extension.py with following conditions. + +* Burp Suite edition: Professional +* The Python modules required for Tplmap are installed. + * PyYaml + * requests +* Extension type: Python + +An example of a simple setup procedure: + +1. Install Jython by installer +```sh +$ wget 'https://repo1.maven.org/maven2/org/python/jython-installer/2.7.2/jython-installer-2.7.2.jar' -O jython_installer.jar +$ mkdir "$HOME"/jython +$ java -jar jython_installer.jar -s -d "$HOME"/jython -t standard +$ rm jython_installer.jar +``` +2. Install additional Python modules +```sh +$ curl -sL 'https://github.com/yaml/pyyaml/archive/refs/tags/5.1.2.tar.gz' | tar xzf - +$ cd pyyaml-5.1.2 +$ "$HOME"/jython/bin/jython setup.py install +$ cd .. +$ curl -sL 'https://github.com/psf/requests/archive/refs/tags/v2.22.0.tar.gz' | tar xzf - +$ cd requests-2.22.0 +$ "$HOME"/jython/bin/jython setup.py install +$ cd .. +$ rm -rf pyyaml-5.1.2 requests-2.22.0 +``` +3. Run your Burp Suite +4. Open Jython file chooser dialog +[Extender] - [Options] - [Python Environment] - [Location of the Jython standalone JAR file] +5. Choose the file `$HOME/jython/jython.jar` +6. Load `burp_extender.py` as Python type burp extension + +### Scanning + +Configure scanning option from 'Tplmap' tab, and do an active scan. + +### Limitation + +Only the detection feature of Tplmap is available. +Exploitation feature is not implemented, use Tplmap CLI. + +The `--injection-tag` option is also not available, because this extension follows Burp's Insertion Point setting. + +If you need the `--injection-tag` option, you can use [Scan manual insertion point](https://github.com/ClementNotin/burp-scan-manual-insertion-point) extension. diff --git a/burp_extension/__init__.py b/burp_extension/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/burp_extension/burp_extender.py b/burp_extension/burp_extender.py new file mode 100644 index 0000000..46e5b61 --- /dev/null +++ b/burp_extension/burp_extender.py @@ -0,0 +1,12 @@ +from burp import IBurpExtender +from config_tab import ConfigTab +from scanner_check import ScannerCheck + +class BurpExtender( IBurpExtender ): + + def registerExtenderCallbacks( self, callbacks ): + configTab = ConfigTab( callbacks ) + callbacks.setExtensionName( 'Tplmap' ) + callbacks.addSuiteTab( configTab ) + callbacks.registerScannerCheck( ScannerCheck( callbacks, configTab ) ) + diff --git a/burp_extension/channel.py b/burp_extension/channel.py new file mode 100644 index 0000000..7b1af58 --- /dev/null +++ b/burp_extension/channel.py @@ -0,0 +1,35 @@ +class Channel: + + def __init__( self, callbacks, configTab, baseRequestResponse, insertionPoint, payloadPosition ): + self._callbacks = callbacks + self._helpers = callbacks.getHelpers() + self._configTab = configTab + self._baseRequestResponse = baseRequestResponse + self._insertionPoint = insertionPoint + self._payloadPosition = payloadPosition + self._request = self._helpers.analyzeRequest( baseRequestResponse ) + + self.url = self._request.getUrl() + self.args = { + 'level': self._configTab.getLevel(), + 'technique': self._configTab.getTechniques() } + self.data = {} + self.detect = False + self.messages = [] + + def req( self, injection ): + payload = injection if self._payloadPosition == 'replace' else self._insertionPoint.getBaseValue() + injection + checkRequest = self._insertionPoint.buildRequest( self._helpers.stringToBytes( payload ) ) + checkRequestResponse = self._callbacks.makeHttpRequest( self._baseRequestResponse.getHttpService(), checkRequest ) + self.messages.append( { + 'injection': injection, + 'requestResponse': checkRequestResponse + } ) + return self._helpers.bytesToString( checkRequestResponse.getResponse() ) + + def detected( self, technique, detail ): + self.detect = True + self.technique = technique + self.detail = detail + self.detect_offset = len( self.messages ) + diff --git a/burp_extension/config_tab.py b/burp_extension/config_tab.py new file mode 100644 index 0000000..eb09f33 --- /dev/null +++ b/burp_extension/config_tab.py @@ -0,0 +1,113 @@ +from burp import ITab + +from javax.swing import JPanel, GroupLayout, JLabel, JComboBox, JCheckBox +from java.awt import Dimension + +from core.checks import plugins + +class ConfigTab( ITab, JPanel ): + + def __init__( self, callbacks ): + self._callbacks = callbacks + self._helpers = callbacks.getHelpers() + self.__initLayout__() + + def __initLayout__( self ): + self._levelComboBox = JComboBox() + levelComboBoxSize = Dimension( 300, 30 ) + self._levelComboBox.setPreferredSize( levelComboBoxSize ) + self._levelComboBox.setMaximumSize( levelComboBoxSize ) + for level in range( 0, 6 ): + self._levelComboBox.addItem( str( level ) ) + + self._techRenderedCheckBox = JCheckBox( 'Rendered', True ) + self._techTimebasedCheckBox = JCheckBox( 'Time-based', True ) + + self._plugin_groups = {} + for plugin in plugins: + parent = plugin.__base__.__name__ + if not self._plugin_groups.has_key( parent ): + self._plugin_groups[ parent ] = [] + self._plugin_groups[ parent ].append( plugin ) + self._pluginCheckBoxes = [] + for pluginGroup in self._plugin_groups.values(): + for plugin in pluginGroup: + self._pluginCheckBoxes.append( PluginCheckBox( plugin ) ) + + self._positionReplaceCheckBox = JCheckBox( 'Replace', True ) + self._positionAppendCheckBox = JCheckBox( 'Append', False ) + + displayItems = ( + { + 'label': 'Level', + 'components': ( self._levelComboBox, ), + 'description': 'Level of code context escape to perform (1-5, Default:0).' + }, + { + 'label': 'Techniques', + 'components': ( self._techRenderedCheckBox, self._techTimebasedCheckBox, ), + 'description': 'Techniques R(endered) T(ime-based blind). Default: RT.' + }, + { + 'label': 'Template Engines', + 'components': self._pluginCheckBoxes, + 'description': 'Force back-end template engine to this value(s).' + }, + { + 'label': 'Payload position', + 'components': ( self._positionReplaceCheckBox, self._positionAppendCheckBox, ), + 'description': 'Scan payload position. This feature only appears in BurpExtension.' + } + ) + + layout = GroupLayout( self ) + self.setLayout( layout ) + layout.setAutoCreateGaps( True ) + layout.setAutoCreateContainerGaps( True ) + + labelWidth = 200 + hgroup = layout.createParallelGroup( GroupLayout.Alignment.LEADING ) + vgroup = layout.createSequentialGroup() + for displayItem in displayItems: + label = JLabel( displayItem.get( 'label' ) ) + label.setToolTipText( displayItem.get( 'description' ) ) + _hgroup = layout.createSequentialGroup().addComponent( label, labelWidth, labelWidth, labelWidth ) + _vgroup = layout.createParallelGroup( GroupLayout.Alignment.BASELINE ).addComponent( label ) + for component in displayItem.get( 'components' ): + _hgroup.addComponent( component ) + _vgroup.addComponent( component ) + hgroup.addGroup( _hgroup ) + vgroup.addGroup( _vgroup ) + + layout.setHorizontalGroup( hgroup ) + layout.setVerticalGroup( vgroup ) + + def getTabCaption( self ): + return 'Tplmap' + + def getUiComponent( self ): + return self + + def getLevel( self ): + return self._levelComboBox.getSelectedIndex() + + def getTechniques( self ): + return '%s%s' % ( 'R' if self._techRenderedCheckBox.isSelected() else '', 'T' if self._techTimebasedCheckBox.isSelected() else '' ) + + def getEngines( self ): + return [ checkbox.getPlugin() for checkbox in self._pluginCheckBoxes if checkbox.isSelected() ] + + def getPayloadPosition( self ): + return { 'replace': self._positionReplaceCheckBox.isSelected(), 'append': self._positionAppendCheckBox.isSelected() } + +class PluginCheckBox( JCheckBox ): + + def __init__( self, plugin ): + JCheckBox.__init__( self, plugin.__name__, True ) + self._plugin = plugin + parent = plugin.__base__.__name__ + tooltip = parent if( parent != 'Plugin' ) else 'eval' + self.setToolTipText( tooltip ) + + def getPlugin( self ): + return self._plugin diff --git a/burp_extension/scan_issue.py b/burp_extension/scan_issue.py new file mode 100644 index 0000000..01ceb4a --- /dev/null +++ b/burp_extension/scan_issue.py @@ -0,0 +1,146 @@ +from burp import IScanIssue + +from array import array +import cgi + +class ScanIssue( IScanIssue ): + + def __init__( self, callbacks, baseRequestResponse, insertionPoint, channel ): + self._callbacks = callbacks + self._helpers = callbacks.getHelpers() + self._baseRequestResponse = baseRequestResponse + self._insertionPoint = insertionPoint + self._channel = channel + + def getUrl( self ): + return self._helpers.analyzeRequest( self._baseRequestResponse ).getUrl() + + def getIssueName( self ): + return 'Server-side template injection' + + def getIssueType( self ): + return 0x08000000 + + def getSeverity( self ): + return 'High' + + def getConfidence( self ): + return 'Certain' + + def getIssueBackground( self ): + return None + + def getRemediationBackground( self ): + return None + + def getIssueDetail( self ): + prologue_template = """ + The {parameter} parameter appears to be vulnerable to server-side template injection attacks. + The template engine appears to be {template}.

+ """ + + render_template = """ + The payload {payload} was submitted in the {parameter} parameter. + This payload contains an {template} template statement.

+ The server response contained the string {rendered}. + This indicates that the payload is being interpreted by a server-side template engine.

+ """ + + blind_template = """ + The time-based blind payload {payload} was submitted in the {parameter} parameter. + 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.

+ """ + + info_template = """ + Identified Informations:
+
    +
  • Template engine: {template}
  • +
  • Server side language: {language}
  • +
  • Technique: {technique}
  • +
  • OS: {os}
  • +
+ Capabilities: +
    +
  • {execute_method}: {execute}
  • +
  • File read: {read}
  • +
  • File write: {write}
  • +
  • Bind shell: {bind_shell}
  • +
  • Reverse shell: {reverse_shell}
  • +
+ """ + + data = self._channel.data + parameter = cgi.escape( self._insertionPoint.getInsertionPointName() ) + template = data.get( 'engine' ) + language = data.get( 'language' ) + prologue = prologue_template.format( parameter=parameter, template=template, language=language ) + + if self._channel.technique == 'render': + payload = self._channel.messages[ self._channel.detect_offset - 1 ].get( 'injection' ) + technique_part = render_template.format( + parameter = parameter, + template = template, + payload = cgi.escape( payload ), + rendered = cgi.escape( self._channel.detail.get( 'expected' ) ) ) + execute_method = 'execute' + elif self._channel.technique == 'blind': + blind_true_payload = self._channel.messages[ self._channel.detect_offset - 2 ].get( 'injection' ) + detail = self._channel.detail + blind_true_detail = detail.get( 'blind_true' ) + average = ( detail.get( 'average' ) / 1000.0 ) + delta_true = blind_true_detail.get( 'end' ) - blind_true_detail.get( 'start' ) + delta_true_milliseconds = ( delta_true.seconds * 1000000.0 + delta_true.microseconds ) / 1000.0 + technique_part = blind_template.format( + payload = cgi.escape( blind_true_payload ), + parameter = parameter, + average = average, + delta = delta_true_milliseconds ) + execute_method = 'execute_blind' + + _okng = lambda f: 'OK' if f else 'NG' + info_part = info_template.format( + template = template, + language = language, + technique = self._channel.technique, + os = data.get( 'os', 'undetected' ), + execute_method = execute_method, + execute = _okng( data.get( execute_method ) ), + read = _okng( data.get( 'read' ) ), + write = _okng( data.get( 'write' ) ), + bind_shell = _okng( data.get( 'bind_shell' ) ), + reverse_shell = _okng( data.get( 'reverse_shell' ) ) ) + return prologue + technique_part + info_part + + def getRemediationDetail( self ): + return None + + def getHttpMessages( self ): + if self._channel.technique == 'render': + responseMarkString = self._channel.detail.get( 'expected' ) + detectedMessage = self._channel.messages[ self._channel.detect_offset - 1 ] + messages = [ self._markHttpMessage( detectedMessage.get( 'requestResponse' ), detectedMessage.get( 'injection' ), responseMarkString ) ] + elif self._channel.technique == 'blind': + blind_true_message = self._channel.messages[ self._channel.detect_offset - 2 ] + blind_false_message = self._channel.messages[ self._channel.detect_offset - 1 ] + messages = [ + self._markHttpMessage( blind_true_message.get( 'requestResponse' ), blind_true_message.get( 'injection' ), None ), + self._markHttpMessage( blind_false_message.get( 'requestResponse' ), blind_false_message.get( 'injection' ), None ) ] + for evaluate in self._channel.messages[ self._channel.detect_offset: ]: + messages.append( self._markHttpMessage( evaluate.get( 'requestResponse' ), evaluate.get( 'injection' ), None ) ) + return messages + + def getHttpService( self ): + return self._baseRequestResponse.getHttpService() + + def _markHttpMessage( self, requestResponse, injection, responseMarkString ): + responseMarkers = None + if responseMarkString: + response = requestResponse.getResponse() + responseMarkBytes = self._helpers.stringToBytes( responseMarkString ) + start = self._helpers.indexOf( response, responseMarkBytes, False, 0, len( response ) ) + if -1 < start: + responseMarkers = [ array( 'i',[ start, start + len( responseMarkBytes ) ] ) ] + + requestHighlights = [ self._insertionPoint.getPayloadOffsets( self._helpers.stringToBytes( injection ) ) ] + return self._callbacks.applyMarkers( requestResponse, requestHighlights, responseMarkers ) + diff --git a/burp_extension/scanner_check.py b/burp_extension/scanner_check.py new file mode 100644 index 0000000..9eca516 --- /dev/null +++ b/burp_extension/scanner_check.py @@ -0,0 +1,28 @@ +from burp import IScannerCheck + +from channel import Channel +from scan_issue import ScanIssue + +class ScannerCheck( IScannerCheck ): + + def __init__( self, callbacks, configTab ): + self._callbacks = callbacks + self._helpers = callbacks.getHelpers() + self._configTab = configTab + + def doPassiveScan( self, baseRequestResponse ): + return None + + def doActiveScan( self, baseRequestResponse, insertionPoint ): + for position in [ position for ( position, selected ) in self._configTab.getPayloadPosition().items() if selected ]: + channel = Channel( self._callbacks, self._configTab, baseRequestResponse, insertionPoint, position ) + for engineClass in self._configTab.getEngines(): + engine = engineClass( channel ) + engine.detect() + if channel.detect: + return [ ScanIssue( self._callbacks, baseRequestResponse, insertionPoint, channel ) ] + return None + + def consolidateDuplicateIssues( self, existingIssue, newIssue ): + return 0 + diff --git a/docker-envs/Dockerfile.java b/docker-envs/Dockerfile.java new file mode 100644 index 0000000..2243feb --- /dev/null +++ b/docker-envs/Dockerfile.java @@ -0,0 +1,19 @@ +FROM gradle:4.10.2-jdk8 + +USER root + +RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y +RUN pip install requests PyYAML + +COPY tests/env_java_tests/spark-app/ /apps/tests/env_java_tests/spark-app/ +WORKDIR /apps/tests/ + +# install dependencies +RUN cd env_java_tests/spark-app/ && sed -ie 's/id "com\.github\.johnrengelman\.shadow".*//' build.gradle && \ + gradle classes + +COPY . /apps/ + +EXPOSE 15003 + +CMD cd env_java_tests/spark-app/ && gradle run diff --git a/docker-envs/Dockerfile.node b/docker-envs/Dockerfile.node new file mode 100644 index 0000000..98e2bd1 --- /dev/null +++ b/docker-envs/Dockerfile.node @@ -0,0 +1,15 @@ +FROM node:10.12.0 + +RUN apt-get update && apt-get install --upgrade dnsutils python-pip libpython-dev -y +RUN pip install requests PyYAML + +COPY tests/env_node_tests/ /apps/tests/env_node_tests/ + +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 + +EXPOSE 15004 + +COPY . /apps/ +WORKDIR /apps/tests/ + +CMD cd /apps/tests/env_node_tests/ && node connect-app.js diff --git a/docker-envs/Dockerfile.php b/docker-envs/Dockerfile.php new file mode 100644 index 0000000..0454279 --- /dev/null +++ b/docker-envs/Dockerfile.php @@ -0,0 +1,16 @@ +FROM php:7.2.10-apache + +RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y +RUN pip install requests PyYAML + +RUN sed -i '0,/Listen [0-9]*/s//Listen 15002/' /etc/apache2/ports.conf + +RUN mkdir /var/www/html/lib/ && cd /var/www/html/lib && \ + curl -sL 'https://github.com/smarty-php/smarty/archive/v3.1.32.tar.gz' | tar xzf - && \ + curl -sL 'https://github.com/twigphp/Twig/archive/v1.20.0.tar.gz' | tar xzf - && \ + curl -sL 'https://github.com/twigphp/Twig/archive/v1.19.0.tar.gz' | tar xzf - + +COPY . /apps/ +COPY tests/env_php_tests/* /var/www/html/ + +WORKDIR /apps/tests/ diff --git a/docker-envs/Dockerfile.python2 b/docker-envs/Dockerfile.python2 new file mode 100644 index 0000000..5326af8 --- /dev/null +++ b/docker-envs/Dockerfile.python2 @@ -0,0 +1,13 @@ +FROM python:2.7.15 + +RUN pip install mako jinja2 flask tornado PyYAML requests +RUN apt-get update && apt-get install dnsutils -y + +COPY . /apps/ +WORKDIR /apps/tests/ + +RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py + +EXPOSE 15001 + +CMD python env_py_tests/webserver.py diff --git a/docker-envs/Dockerfile.python3 b/docker-envs/Dockerfile.python3 new file mode 100644 index 0000000..f9a56cd --- /dev/null +++ b/docker-envs/Dockerfile.python3 @@ -0,0 +1,14 @@ +FROM python:2.7.15 + +RUN apt-get update && apt-get install dnsutils python3-pip -y +RUN pip3 install mako jinja2 flask tornado +RUN pip install PyYAML requests + +COPY . /apps/ +WORKDIR /apps/tests/ + +RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py + +EXPOSE 15001 + +CMD python3 env_py_tests/webserver.py diff --git a/docker-envs/Dockerfile.ruby b/docker-envs/Dockerfile.ruby new file mode 100644 index 0000000..51ae139 --- /dev/null +++ b/docker-envs/Dockerfile.ruby @@ -0,0 +1,12 @@ +FROM ruby:2.5.1 + +RUN gem install slim tilt cuba rack +RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y +RUN pip install requests PyYAML + +COPY . /apps/ +WORKDIR /apps/tests/ + +EXPOSE 15005 + +CMD cd env_ruby_tests && rackup --host 0.0.0.0 --port 15005 diff --git a/docker-envs/README.md b/docker-envs/README.md new file mode 100644 index 0000000..aad9b2b --- /dev/null +++ b/docker-envs/README.md @@ -0,0 +1,16 @@ +# Running vulnerable test environment in Docker + +To setup vulnerable environments for your test, you can use tplmap's test environment with Docker. + +The following command starts all test environments: + +```sh +$ docker-compose up +``` + +Starts specified test environments: + +```sh +$ docker-compose up tplmap_test_python tplmap_test_php +``` + diff --git a/docker-envs/docker-compose.yml b/docker-envs/docker-compose.yml new file mode 100644 index 0000000..a805ca7 --- /dev/null +++ b/docker-envs/docker-compose.yml @@ -0,0 +1,45 @@ +version: '2' + +services: + tplmap_test_python: + build: + context: ../ + dockerfile: docker-envs/Dockerfile.python2 + restart: always + ports: + - "15001:15001" + tplmap_test_python3: + build: + context: ../ + dockerfile: docker-envs/Dockerfile.python3 + restart: always + ports: + - "15006:15001" + tplmap_test_php: + build: + context: ../ + dockerfile: docker-envs/Dockerfile.php + restart: always + ports: + - "15002:15002" + tplmap_test_java: + build: + context: ../ + dockerfile: docker-envs/Dockerfile.java + restart: always + ports: + - "15003:15003" + tplmap_test_node: + build: + context: ../ + dockerfile: docker-envs/Dockerfile.node + restart: always + ports: + - "15004:15004" + tplmap_test_ruby: + build: + context: ../ + dockerfile: docker-envs/Dockerfile.ruby + restart: always + ports: + - "15005:15005" diff --git a/tests/basetest.py b/tests/basetest.py new file mode 100644 index 0000000..cbff766 --- /dev/null +++ b/tests/basetest.py @@ -0,0 +1,202 @@ +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from core.channel import Channel +from core.checks import check_template_injection +from core.checks import detect_template_injection +from utils import rand +from utils import strings +import utils.loggers +import logging + +utils.loggers.stream_handler.setLevel(logging.FATAL) + +# Test levels limits +LEVEL_LIMIT = 0 +CLEVEL_LIMIT = 0 + +# Extra tests +EXTRA_UPLOAD = False +EXTRA_DOWNLOAD = False +EXTRA_UPLOAD_BLIND = False + +class BaseTest(object): + + def _get_detection_obj_data(self, url, level = 0, closure_level = 0, technique = 'RT'): + + channel = Channel({ + 'url' : url, + 'force_level': [ level, closure_level ], + 'injection_tag': '*', + 'technique': technique + }) + obj = detect_template_injection(channel, [ self.plugin ]) + + # Delete OS to make the tests portable + if 'os' in channel.data: + del channel.data['os'] + + return obj, channel.data + + + def test_detection(self): + + channel = Channel({ + 'url' : self.url, + 'level': 5, + 'injection_tag': '*', + 'technique': 'RT' + }) + check_template_injection(channel) + + # Delete OS to make the tests portable + if 'os' in channel.data: + del channel.data['os'] + + # Delete any unreliable engine detected + if 'unreliable' in channel.data: + del channel.data['unreliable'] + + self.assertEqual( + channel.data, + self.expected_data, + ) + + def test_reflection(self): + + for reflection_test in self.reflection_tests: + + level, clevel, template, channel_updates = reflection_test + + # Honour global level limit + if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT: + continue + + expected_data = self.expected_data.copy() + expected_data.update(channel_updates) + + obj, data = self._get_detection_obj_data(self.url % template, level, clevel, technique = 'R') + + self.assertEqual( + data, + expected_data, + msg = '\nreflection\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data)) + ) + + def test_blind(self): + + for blind_test in self.blind_tests: + + level, clevel, template, channel_updates = blind_test + + # Honour global level limit + if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT: + continue + + expected_data = self.expected_data_blind.copy() + expected_data.update(channel_updates) + + obj, data = self._get_detection_obj_data(self.url_blind % template, level, clevel, technique = 'T') + + self.assertEqual( + data, + expected_data, + msg = '\nblind\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data)) + ) + + def test_download(self): + + obj, data = self._get_detection_obj_data(self.url) + self.assertEqual(data, self.expected_data) + + if not EXTRA_DOWNLOAD: + return + + # Normal ASCII file + readable_file = '/etc/resolv.conf' + content = open(readable_file, 'r').read() + self.assertEqual(content, obj.read(readable_file)) + + # Long binary file + readable_file = '/bin/ls' + content = open(readable_file, 'rb').read() + self.assertEqual(content, obj.read(readable_file)) + + # Non existant file + self.assertEqual(None, obj.read('/nonexistant')) + # Unpermitted file + self.assertEqual(None, obj.read('/etc/shadow')) + # Empty file + self.assertEqual('', obj.read('/dev/null')) + + def test_upload(self): + + obj, data = self._get_detection_obj_data(self.url) + self.assertEqual(data, self.expected_data) + + if not EXTRA_UPLOAD: + return + + remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) + # Send long binary + data = open('/bin/ls', 'rb').read() + obj.write(data, remote_temp_path) + self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) + obj.execute('rm %s' % (remote_temp_path)) + + remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) + # Send short ASCII data, without removing it + data = 'SHORT ASCII DATA' + obj.write(data, remote_temp_path) + self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) + + # Try to append data without --force-overwrite and re-check the previous md5 + obj.write('APPENDED DATA', remote_temp_path) + self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) + + # Now set --force-overwrite and rewrite new data on the same file + obj.channel.args['force_overwrite'] = True + data = 'NEW DATA' + obj.write(data, remote_temp_path) + self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) + obj.execute('rm %s' % (remote_temp_path)) + + def test_upload_blind(self): + + obj, data = self._get_detection_obj_data( + self.url_blind, + technique = 'T' + ) + self.assertEqual(data, self.expected_data_blind) + + if not EXTRA_UPLOAD_BLIND: + return + + # Send file without --force-overwrite, should fail + remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) + obj.write('AAAA', remote_temp_path) + self.assertFalse(os.path.exists(remote_temp_path)) + + # Now set --force-overwrite and retry + obj.channel.args['force_overwrite'] = True + + # Send long binary + data = open('/bin/ls', 'rb').read() + obj.write(data, remote_temp_path) + + # Since it's blind, read md5 from disk + checkdata = open(remote_temp_path, 'rb').read() + self.assertEqual(strings.md5(checkdata), strings.md5(data)) + os.unlink(remote_temp_path) + + remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) + # Send short ASCII data + data = 'SHORT ASCII DATA' + obj.write(data, remote_temp_path) + + checkdata = open(remote_temp_path, 'rb').read() + self.assertEqual(strings.md5(checkdata), strings.md5(data)) + os.unlink(remote_temp_path) \ No newline at end of file diff --git a/tests/env_java_tests/spark-app/build.gradle b/tests/env_java_tests/spark-app/build.gradle new file mode 100644 index 0000000..6dbba88 --- /dev/null +++ b/tests/env_java_tests/spark-app/build.gradle @@ -0,0 +1,25 @@ +plugins { + id "java" + id "application" + id "com.github.johnrengelman.shadow" version "1.2.3" +} + +group 'org.tplmap.webframeworks' +version '1.0-SNAPSHOT' + + +sourceCompatibility = 1.8 +mainClassName = 'org.tplmap.webframeworks.SparkApplication' + + +repositories { + jcenter() + mavenCentral() +} + +dependencies { + compile 'com.sparkjava:spark-core:2.3' + compile group: 'org.freemarker', name: 'freemarker', version: '2.3.14' + compile group: 'org.apache.velocity', name: 'velocity', version: '1.6.2' + testCompile group: 'junit', name: 'junit', version: '4.11' +} diff --git a/tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties b/tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0d03d87 --- /dev/null +++ b/tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Feb 17 14:28:33 EET 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip diff --git a/tests/env_java_tests/spark-app/settings.gradle b/tests/env_java_tests/spark-app/settings.gradle new file mode 100644 index 0000000..de499d6 --- /dev/null +++ b/tests/env_java_tests/spark-app/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'spark-app' + diff --git a/tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java b/tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java new file mode 100644 index 0000000..80c2d50 --- /dev/null +++ b/tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java @@ -0,0 +1,135 @@ +package org.tplmap.webframeworks; +import spark.Request; +import spark.Response; +import spark.Route; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import java.io.StringReader; +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import org.apache.velocity.VelocityContext ; +import org.apache.velocity.app.VelocityEngine ; +import org.apache.velocity.exception.MethodInvocationException ; +import org.apache.velocity.exception.ParseErrorException ; +import org.apache.velocity.exception.ResourceNotFoundException ; +import org.apache.velocity.runtime.RuntimeConstants ; +import org.apache.velocity.runtime.log.LogChute ; +import org.apache.velocity.runtime.log.NullLogChute ; +import java.util.UUID; + +import static spark.Spark.*; + +public class SparkApplication { + +public static void main(String[] args) { + port(15003); + get("/freemarker", SparkApplication::freemarker); + get("/velocity", SparkApplication::velocity); +} + +public static Object velocity(Request request, Response response) { + + + // Get inj parameter, exit if none + String inj = request.queryParams("inj"); + if(inj == null) { + return ""; + } + + // Get tpl parameter + String tpl = request.queryParams("tpl"); + + // If tpl exists + if(tpl != null && !tpl.isEmpty()) { + // Keep the formatting a-la-python + tpl = tpl.replace("%s", inj); + } + else { + tpl = inj; + } + + String blind = request.queryParams("blind"); + + LogChute velocityLogChute = new NullLogChute() ; + VelocityEngine velocity; + StringWriter w; + try{ + velocity = new VelocityEngine() ; + // Turn off logging - catch exceptions and log ourselves + velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, velocityLogChute) ; + velocity.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8") ; + velocity.init() ; + + VelocityContext context = new VelocityContext(); + w = new StringWriter(); + + velocity.evaluate( context, w, "mystring", tpl ); + + + }catch(Exception e){ + e.printStackTrace(); + return ""; + } + + // Return out string if not blind + if(blind == null){ + return UUID.randomUUID().toString() + w.toString() + UUID.randomUUID().toString(); + } + else { + return UUID.randomUUID().toString(); + } +} + +public static Object freemarker(Request request, Response response) { + + // Get inj parameter, exit if none + String inj = request.queryParams("inj"); + if(inj == null) { + return ""; + } + + // Get tpl parameter + String tpl = request.queryParams("tpl"); + + // If tpl exists + if(tpl != null && !tpl.isEmpty()) { + // Keep the formatting a-la-python + tpl = tpl.replace("%s", inj); + } + else { + tpl = inj; + } + + // Get blind parameter + String blind = request.queryParams("blind"); + + // Generate template from "inj" + Template template; + try{ + template = new Template("name", new StringReader(tpl), new Configuration()); + }catch(IOException e){ + e.printStackTrace(); + return ""; + } + + // Write processed template to out + HashMap data = new HashMap(); + StringWriter out = new StringWriter(); + try{ + template.process(data, out); + }catch(TemplateException | IOException e){ + e.printStackTrace(); + return ""; + } + + // Return out string if not blind + if(blind == null){ + return UUID.randomUUID().toString() + out.toString() + UUID.randomUUID().toString(); + } + else { + return UUID.randomUUID().toString(); + } +} +} diff --git a/tests/env_java_tests/spark-app/src/main/resources/templates/hello.html b/tests/env_java_tests/spark-app/src/main/resources/templates/hello.html new file mode 100644 index 0000000..c9a6020 --- /dev/null +++ b/tests/env_java_tests/spark-app/src/main/resources/templates/hello.html @@ -0,0 +1,10 @@ + + + + + Hello world + + +

Hello, [[${name}]]!

+ + \ No newline at end of file diff --git a/tests/env_node_tests/connect-app.js b/tests/env_node_tests/connect-app.js new file mode 100644 index 0000000..a965c1c --- /dev/null +++ b/tests/env_node_tests/connect-app.js @@ -0,0 +1,289 @@ +var connect = require('connect'); +var http = require('http'); +var url = require('url'); +var pug = require('pug'); +var nunjucks = require('nunjucks'); +var dust = require('dustjs-linkedin'); +var dusthelpers = require('dustjs-helpers'); +var randomstring = require("randomstring"); +var doT=require('dot'); +var marko=require('marko'); +var ejs=require('ejs'); + +var app = connect(); + +// Pug +app.use('/pug', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + res.end(randomstring.generate() + pug.render(tpl) + randomstring.generate()); + } +}); + +// Pug blind endpoint +app.use('/blind/pug', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + console.log('PAYLOAD: ' + tpl); + pug.render(tpl) + res.end(randomstring.generate()); + } +}); + +// Nunjucks +app.use('/nunjucks', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + res.end(randomstring.generate() + nunjucks.renderString(tpl) + randomstring.generate()); + } +}); + +// Nunjucks blind endpoint +app.use('/blind/nunjucks', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + nunjucks.renderString(tpl); + res.end(randomstring.generate()); + } +}); + +// Javascript +app.use('/javascript', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + res.end(randomstring.generate() + String(eval(tpl)) + randomstring.generate()); + } +}); + +// Javascript blind endpoint +app.use('/blind/javascript', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + eval(tpl); + res.end(randomstring.generate()); + } +}); + +// Dust +app.use('/dust', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + + console.log('PAYLOAD: ' + tpl); + dust.debugLevel = "DEBUG" + output = ''; + var compiled = dust.compile(tpl, "compiled"); + dust.loadSource(compiled); + dust.render("compiled", {}, function(err, outp) { output = outp }) + res.end(randomstring.generate() + output + randomstring.generate()); + } +}); + +// Dust blind endpoint +app.use('/blind/dust', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + + console.log('PAYLOAD: ' + tpl); + dust.debugLevel = "DEBUG" + var compiled = dust.compile(tpl, "compiled"); + dust.loadSource(compiled); + dust.render("compiled", {}, function(err, outp) { }) + + res.end(randomstring.generate()); + } +}); + +// doT +app.use('/dot', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + res.end(randomstring.generate() + doT.template(tpl)({}) + randomstring.generate()); + } +}); + +// doT blind endpoint +app.use('/blind/dot', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + doT.template(tpl)({}); + res.end(randomstring.generate()); + } +}); + +// Marko +app.use('/marko', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + res.end(randomstring.generate() + marko.load(randomstring.generate(), tpl).renderSync() + randomstring.generate()); + } +}); + +// Marko blind endpoint +app.use('/blind/marko', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + marko.load(randomstring.generate(), tpl).renderSync() + res.end(randomstring.generate()); + } +}); + +// EJS +app.use('/ejs', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + res.end(randomstring.generate() + ejs.render(tpl) + randomstring.generate()); + } +}); + +// EJS blind endpoint +app.use('/blind/ejs', function(req, res){ + if(req.url) { + var url_parts = url.parse(req.url, true); + + var inj = url_parts.query.inj; + var tpl = ''; + if('tpl' in url_parts.query && url_parts.query.tpl != '') { + // Keep the formatting a-la-python + tpl = url_parts.query.tpl.replace('%s', inj); + } + else { + tpl = inj; + } + ejs.render(tpl); + res.end(randomstring.generate()); + } +}); + +//create node.js http server and listen on port +http.createServer(app).listen(15004); diff --git a/tests/env_php_tests/eval.php b/tests/env_php_tests/eval.php new file mode 100644 index 0000000..eef6058 --- /dev/null +++ b/tests/env_php_tests/eval.php @@ -0,0 +1,31 @@ + : ' . $rendered); + echo generateRandomString(); +} +else { + error_log('DEBUG< : ' . $tpl); + ob_start(); + $rendered = eval($tpl); + ob_end_clean(); + error_log('DEBUG> : ' . $rendered); + echo generateRandomString(); +} +?> diff --git a/tests/env_php_tests/smarty-3.1.32-secured.php b/tests/env_php_tests/smarty-3.1.32-secured.php new file mode 100644 index 0000000..ebb8be9 --- /dev/null +++ b/tests/env_php_tests/smarty-3.1.32-secured.php @@ -0,0 +1,32 @@ +clearAllCache(); + +$inj=$_GET["inj"]; +if(isset($_GET["tpl"]) && $_GET["tpl"] != "") { + // Keep the formatting a-la-python + $tpl=str_replace("%s", $inj, $_GET["tpl"]); +} +else { + $tpl=$inj; +} + +error_log('DEBUG< : ' . $tpl); +$rendered = $smarty->fetch('string:'.$tpl); +error_log('DEBUG> : ' . $rendered); + +if(!$_GET["blind"]) { + echo generateRandomString() . $rendered . generateRandomString(); +} +else { + echo generateRandomString(); +} +?> diff --git a/tests/env_php_tests/smarty-3.1.32-unsecured.php b/tests/env_php_tests/smarty-3.1.32-unsecured.php new file mode 100644 index 0000000..3a1af5a --- /dev/null +++ b/tests/env_php_tests/smarty-3.1.32-unsecured.php @@ -0,0 +1,38 @@ +clearAllCache(); + +// Run render via CLI +if (php_sapi_name() == "cli") { + $_GET["inj"] = ''; + $_GET["tpl"] = ''; +} + +$inj=$_GET["inj"]; +if(isset($_GET["tpl"]) && $_GET["tpl"] != "") { + // Keep the formatting a-la-python + $tpl=str_replace("%s", $inj, $_GET["tpl"]); +} +else { + $tpl=$inj; +} + +error_log('DEBUG< : ' . $tpl); +$rendered = $smarty->fetch('string:'.$tpl); +error_log('DEBUG> : ' . $rendered); + +if(!$_GET["blind"]) { + echo generateRandomString() . $rendered . generateRandomString(); +} +else { + echo generateRandomString(); +} +?> diff --git a/tests/env_php_tests/twig-1.19.0-unsecured.php b/tests/env_php_tests/twig-1.19.0-unsecured.php new file mode 100644 index 0000000..c5dfd50 --- /dev/null +++ b/tests/env_php_tests/twig-1.19.0-unsecured.php @@ -0,0 +1,40 @@ + $tpl, +)); +$twig = new Twig_Environment($loader); + +error_log('DEBUG<: ' . $tpl); +$rendered = $twig->render('tpl'); +error_log('DEBUG> : ' . $rendered); + +if(!$_GET["blind"]) { + echo generateRandomString() . $rendered . generateRandomString(); +} +else { + echo generateRandomString(); +} + ?> diff --git a/tests/env_php_tests/twig-1.20.0-secured.php b/tests/env_php_tests/twig-1.20.0-secured.php new file mode 100644 index 0000000..7554f64 --- /dev/null +++ b/tests/env_php_tests/twig-1.20.0-secured.php @@ -0,0 +1,40 @@ + $tpl, +)); +$twig = new Twig_Environment($loader); + +error_log('DEBUG<: ' . $tpl); +$rendered = $twig->render('tpl'); +error_log('DEBUG> : ' . $rendered); + +if(!$_GET["blind"]) { + echo generateRandomString() . $rendered . generateRandomString(); +} +else { + echo generateRandomString(); +} + ?> diff --git a/tests/env_py_tests/webserver.py b/tests/env_py_tests/webserver.py new file mode 100644 index 0000000..58726ff --- /dev/null +++ b/tests/env_py_tests/webserver.py @@ -0,0 +1,194 @@ +from flask import Flask, request +app = Flask(__name__) +from mako.template import Template as MakoTemplates +from mako.lookup import TemplateLookup +from jinja2 import Environment as Jinja2Environment +import tornado.template +import random +import time + +try: + from string import lowercase as ascii_lowercase +except ImportError: + from string import ascii_lowercase + +mylookup = TemplateLookup(directories=['/tpl']) + +Jinja2Env = Jinja2Environment(line_statement_prefix='#') + +def shutdown_server(): + func = request.environ.get('werkzeug.server.shutdown') + if func is None: + raise RuntimeError('Not running with the Werkzeug Server') + func() + +def randomword(length = 8): + return ''.join(random.choice(ascii_lowercase) for i in range(length)) + +@app.route("/reflect/") +def reflect(engine): + + template = request.values.get('tpl') + if not template: + template = '%s' + + injection = request.values.get('inj') + + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + elif engine == 'eval': + return randomword() + str(eval(template % injection)) + randomword() + elif engine == 'tornado': + return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword() + +@app.route("/url//") +def url_reflect(engine, injection): + + template = request.values.get('tpl') + if not template: + template = '%s' + + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + elif engine == 'eval': + return randomword() + str(eval(template % injection)) + randomword() + elif engine == 'tornado': + return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword() + + +@app.route("/post/", methods = [ "POST" ]) +def postfunc(engine): + + template = request.values.get('tpl') + if not template: + template = '%s' + + injection = request.values.get('inj') + + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + + +@app.route("/header/") +def headerfunc(engine): + + template = request.headers.get('tpl') + if not template: + template = '%s' + + injection = request.headers.get('User-Agent') + + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + +@app.route("/put/", methods = [ "PUT" ]) +def putfunc(engine): + + template = request.values.get('tpl') + if not template: + template = '%s' + + injection = request.values.get('inj') + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + +@app.route("/limit/") +def limited(engine): + template = request.values.get('tpl') + if not template: + template = '%s' + + length = int(request.values.get('limit')) + + injection = request.values.get('inj', '') + if len(injection) > length: + return 'Inj too long' + + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + +@app.route("/startswith/") +def startswithtest(engine): + template = request.values.get('tpl') + if not template: + template = '%s' + + str_startswith = request.values.get('startswith') + + injection = request.values.get('inj', '') + if not injection.startswith(str_startswith): + return 'Missing startswith' + + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + + +@app.route("/blind/") +def blind(engine): + + template = request.values.get('tpl') + if not template: + template = '%s' + + injection = request.values.get('inj') + + if engine == 'mako': + MakoTemplates(template % injection, lookup=mylookup).render() + elif engine == 'jinja2': + Jinja2Env.from_string(template % injection).render() + elif engine == 'eval': + eval(template % injection) + elif engine == 'tornado': + tornado.template.Template(template % injection).generate() + + return randomword() + +@app.route("/reflect_cookieauth/") +def reflect_cookieauth(engine): + + if not request.cookies.get('SID') == 'SECRET': + return randomword() + + template = request.values.get('tpl') + if not template: + template = '%s' + + injection = request.values.get('inj') + + if engine == 'mako': + return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() + elif engine == 'jinja2': + return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() + elif engine == 'eval': + return randomword() + str(eval(template % injection)) + randomword() + elif engine == 'tornado': + return randomword() + tornado.template.Template(template % injection).generate() + randomword() + +@app.route("/delay/") +def delay(seconds = 1): + + time.sleep(seconds) + + return randomword() + +@app.route('/shutdown') +def shutdown(): + shutdown_server() + return 'Server shutting down...' + +if __name__ == "__main__": + app.run(host='127.0.0.1', port=15001, debug=False) diff --git a/tests/env_ruby_tests/config.ru b/tests/env_ruby_tests/config.ru new file mode 100644 index 0000000..1ef96a4 --- /dev/null +++ b/tests/env_ruby_tests/config.ru @@ -0,0 +1,3 @@ +require "./webserver" + +run Cuba \ No newline at end of file diff --git a/tests/env_ruby_tests/webserver.rb b/tests/env_ruby_tests/webserver.rb new file mode 100644 index 0000000..5984bdc --- /dev/null +++ b/tests/env_ruby_tests/webserver.rb @@ -0,0 +1,60 @@ +require "cuba" +require "cuba/safe" + +require 'tilt' +require 'slim' +require 'erb' + +Cuba.plugin Cuba::Safe + +Cuba.define do + on get do + on "reflect/:engine" do |engine| + # Keep the formatting a-la-python + on param("inj"), param("tpl", "%s") do |inj, tpl| + + tpl = tpl.gsub('%s', inj) + + case engine + when "eval" + res.write eval(tpl) + when "slim" + template = Tilt['slim'].new() {|x| tpl} + res.write template.render + when "erb" + template = Tilt['erb'].new() {|x| tpl} + res.write template.render + else + res.write "#{engine} #{inj} #{tpl}" + end + + end + end + on "blind/:engine" do |engine| + # Keep the formatting a-la-python + on param("inj"), param("tpl", "%s") do |inj, tpl| + + tpl = tpl.gsub('%s', inj) + + case engine + when "eval" + eval(tpl) + when "slim" + template = Tilt['slim'].new() {|x| tpl} + template.render + when "erb" + template = Tilt['erb'].new() {|x| tpl} + template.render + else + res.write "blind #{engine} #{inj} #{tpl}" + end + + res.write "ok"; # for set 200 response status code + + end + end + on 'shutdown' do + exit! + end + end +end diff --git a/tests/run_channel_test.sh b/tests/run_channel_test.sh new file mode 100755 index 0000000..cd246c4 --- /dev/null +++ b/tests/run_channel_test.sh @@ -0,0 +1,33 @@ +#!/bin/bash -e + +INSTANCE_NAME="tplmap-py" +IMAGE_NAME="tplmap-py-img" +PORT=15001 + +echo "Exposed testing APIs: + +http://localhost:15001/reflect/mako?inj=* +http://localhost:15001/reflect/jinja2?inj=* +http://localhost:15001/post/mako?inj=* +http://localhost:15001/post/jinja2?inj=* +http://localhost:15001/limit/mako?inj=* +http://localhost:15001/limit/jinja2?inj=* +http://localhost:15001/put/mako?inj=* +http://localhost:15001/put/jinja2?inj=* +" + +cd "$( dirname "${BASH_SOURCE[0]}" )"/../ + +docker rm -f $INSTANCE_NAME || echo '' +docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME +docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME + +# Wait until the http server is serving +until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do + sleep 1 +done + +# Launch python engines tests +docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_channel*.py' + +docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_java_tests.sh b/tests/run_java_tests.sh new file mode 100755 index 0000000..dad2f7d --- /dev/null +++ b/tests/run_java_tests.sh @@ -0,0 +1,30 @@ +#!/bin/bash -e + +INSTANCE_NAME="tplmap-java" +IMAGE_NAME="tplmap-java-img" +PORT=15003 + +echo "Exposed testing APIs: + +http://localhost:15003/velocity?inj=* +http://localhost:15003/velocity?inj=*&blind=1 +http://localhost:15003/freemarker?inj=* +http://localhost:15003/freemarker?inj=*&blind=1 +" + +cd "$( dirname "${BASH_SOURCE[0]}" )"/../ + +docker rm -f $INSTANCE_NAME || echo '' +docker build -f docker-envs/Dockerfile.java . -t $IMAGE_NAME +docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME + +# Wait until the http server is serving +until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do + sleep 1 +done +sleep 1 + +# Launch Java engines tests +docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_java_*.py' + +docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_node_tests.sh b/tests/run_node_tests.sh new file mode 100755 index 0000000..4c311ee --- /dev/null +++ b/tests/run_node_tests.sh @@ -0,0 +1,39 @@ +#!/bin/bash -e + +INSTANCE_NAME="tplmap-node" +IMAGE_NAME="tplmap-node-img" +PORT=15004 + +echo "Exposed testing APIs: + +http://localhost:15004/pug?inj=* +http://localhost:15004/blind/pug?inj=* +http://localhost:15004/nunjucks?inj=* +http://localhost:15004/blind/nunjucks?inj=* +http://localhost:15004/javascript?inj=* +http://localhost:15004/blind/javascript?inj=* +http://localhost:15004/dot?inj=* +http://localhost:15004/blind/dot?inj=* +http://localhost:15004/dust?inj=* +http://localhost:15004/blind/dust?inj=* +http://localhost:15004/marko?inj=* +http://localhost:15004/blind/marko?inj=* +http://localhost:15004/ejs?inj=* +http://localhost:15004/blind/ejs?inj=* +" + +cd "$( dirname "${BASH_SOURCE[0]}" )"/../ + +docker rm -f $INSTANCE_NAME || echo '' +docker build -f docker-envs/Dockerfile.node . -t $IMAGE_NAME +docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME + +# Wait until the http server is serving +until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do + sleep 1 +done + +# Launch node engines tests +docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_node_*.py' + +docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_php_tests.sh b/tests/run_php_tests.sh new file mode 100755 index 0000000..fdd68b1 --- /dev/null +++ b/tests/run_php_tests.sh @@ -0,0 +1,31 @@ +#!/bin/bash -e + +INSTANCE_NAME="tplmap-php" +IMAGE_NAME="tplmap-php-img" +PORT=15002 + +echo "Exposed testing APIs: + +http://localhost:15002/smarty-3.1.32-secured.php?inj=* +http://localhost:15002/smarty-3.1.32-unsecured.php?inj=* +http://localhost:15002/smarty-3.1.32-unsecured.php?inj=*&blind=1 +http://localhost:15002/twig-1.24.1-secured.php?inj=* +http://localhost:15002/eval.php?inj=* +http://localhost:15002/eval.php?inj=*&blind=1 +" + +cd "$( dirname "${BASH_SOURCE[0]}" )"/../ + +docker rm -f $INSTANCE_NAME || echo '' +docker build -f docker-envs/Dockerfile.php . -t $IMAGE_NAME +docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME + +# Wait until the http server is serving +until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do + sleep 1 +done + +# Launch PHP engines tests +docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_php_*.py' + +docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_python2_tests.sh b/tests/run_python2_tests.sh new file mode 100755 index 0000000..4245d4c --- /dev/null +++ b/tests/run_python2_tests.sh @@ -0,0 +1,33 @@ +#!/bin/bash -e + +INSTANCE_NAME="tplmap-py2" +IMAGE_NAME="tplmap-py2-img" +PORT=15001 + +echo "Exposed testing APIs: + +http://localhost:15001/reflect/mako?inj=* +http://localhost:15001/reflect/jinja2?inj=* +http://localhost:15001/post/mako?inj=* +http://localhost:15001/post/jinja2?inj=* +http://localhost:15001/limit/mako?inj=* +http://localhost:15001/limit/jinja2?inj=* +http://localhost:15001/put/mako?inj=* +http://localhost:15001/put/jinja2?inj=* +" + +cd "$( dirname "${BASH_SOURCE[0]}" )"/../ + +docker rm -f $INSTANCE_NAME || echo '' +docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME +docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME + +# Wait until the http server is serving +until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do + sleep 1 +done + +# Launch python engines tests +docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_py_*.py' + +docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_python3_tests.sh b/tests/run_python3_tests.sh new file mode 100755 index 0000000..a5a2c98 --- /dev/null +++ b/tests/run_python3_tests.sh @@ -0,0 +1,34 @@ +#!/bin/bash -e + +INSTANCE_NAME="tplmap-py3" +IMAGE_NAME="tplmap-py3-img" +GUESTPORT=15001 +PORT=15006 + +echo "Exposed testing APIs: + +http://localhost:15006/reflect/mako?inj=* +http://localhost:15006/reflect/jinja2?inj=* +http://localhost:15006/post/mako?inj=* +http://localhost:15006/post/jinja2?inj=* +http://localhost:15006/limit/mako?inj=* +http://localhost:15006/limit/jinja2?inj=* +http://localhost:15006/put/mako?inj=* +http://localhost:15006/put/jinja2?inj=* +" + +cd "$( dirname "${BASH_SOURCE[0]}" )"/../ + +docker rm -f $INSTANCE_NAME || echo '' +docker build -f docker-envs/Dockerfile.python3 . -t $IMAGE_NAME +docker run --rm --name $INSTANCE_NAME -p $PORT:$GUESTPORT -d $IMAGE_NAME + +# Wait until the http server is serving +until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do + sleep 1 +done + +# Launch python engines tests +docker exec -it $INSTANCE_NAME python2 -m unittest discover -v . 'test_py_*.py' + +docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/run_ruby_tests.sh b/tests/run_ruby_tests.sh new file mode 100755 index 0000000..4735ea1 --- /dev/null +++ b/tests/run_ruby_tests.sh @@ -0,0 +1,31 @@ +#!/bin/bash -e + +INSTANCE_NAME="tplmap-ruby" +IMAGE_NAME="tplmap-ruby-img" +PORT=15005 + +echo "Exposed testing APIs: + +http://localhost:15005/reflect/eval?inj=* +http://localhost:15005/blind/eval?inj=* +http://localhost:15005/reflect/slim?inj=* +http://localhost:15005/blind/slim?inj=* +http://localhost:15005/reflect/erb?inj=* +http://localhost:15005/blind/erb?inj=* +" + +cd "$( dirname "${BASH_SOURCE[0]}" )"/../ + +docker rm -f $INSTANCE_NAME || echo '' +docker build -f docker-envs/Dockerfile.ruby . -t $IMAGE_NAME +docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME + +# Wait until the http server is serving +until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do + sleep 1 +done + +# Launch ruby engines tests +docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_ruby_*.py' + +docker stop $INSTANCE_NAME \ No newline at end of file diff --git a/tests/test_channel.py b/tests/test_channel.py new file mode 100644 index 0000000..de72685 --- /dev/null +++ b/tests/test_channel.py @@ -0,0 +1,225 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.mako import Mako +from core.channel import Channel +from core.checks import detect_template_injection +import utils.loggers +import logging + +utils.loggers.stream_handler.setLevel(logging.FATAL) + +class ChannelTest(unittest.TestCase): + + expected_data = { + 'language': 'python', + 'engine': 'mako', + 'evaluate' : 'python' , + 'execute' : True, + 'write' : True, + 'read' : True, + 'trailer': '${%(trailer)s}', + 'header': '${%(header)s}', + 'render': '${%(code)s}', + 'prefix': '', + 'suffix': '', + 'bind_shell' : True, + 'reverse_shell': True + } + + def test_post_reflection(self): + + template = '%s' + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/post/mako', + 'force_level': [ 0, 0 ], + 'data' : 'inj=*&othervar=1', + 'injection_tag': '*', + 'technique': 'R' + + }) + detect_template_injection(channel, [ Mako ]) + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_url_reflection(self): + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/url/mako/AA*AA', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'R' + + }) + + detect_template_injection(channel, [ Mako ]) + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_header_reflection(self): + + template = '%s' + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/header/mako', + 'force_level': [ 0, 0 ], + 'headers' : [ 'User-Agent: *' ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_put_reflection(self): + + template = '%s' + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/put/mako', + 'data' : 'inj=*&othervar=1', + 'request' : 'PUT', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_custom_injection_tag(self): + + template = '%s' + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=~', + 'force_level': [ 0, 0 ], + 'injection_tag': '~', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + + def test_reflection_multiple_point_tag(self): + + template = '%s' + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&asd=1&asd2=*&inj=*&inj2=*&inj3=*', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_reflection_multiple_point_no_tag(self): + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd&inj2=asd2', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_no_reflection(self): + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/reflect/mako?inj2=asd2', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'RT' + }) + detect_template_injection(channel, [ Mako ]) + + self.assertEqual(channel.data, {}) + + def test_reflection_point_startswith(self): + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=thismustexists*&startswith=thismustexists', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_reflection_point_dont_startswith(self): + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=*&startswith=thismustexists', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + + self.assertEqual(channel.data, {}) + + + def test_quotes(self): + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'RT' + }) + obj = detect_template_injection(channel, [ Mako ]) + + result = obj.execute("""echo 1"2"'3'\\"\\'""") + self.assertEqual(result, """123"'""") + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/blind/mako?inj=asd', + 'force_level': [ 0, 0 ], + 'injection_tag': '*', + 'technique': 'RT' + }) + obj = detect_template_injection(channel, [ Mako ]) + + self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'""")) + + def test_auth_reflection(self): + + channel = Channel({ + 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*', + 'force_level': [ 0, 0 ], + 'headers' : [ 'Cookie: SID=SECRET' ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + + del channel.data['os'] + self.assertEqual(channel.data, self.expected_data) + + def test_wrong_auth_reflection(self): + + channel = Channel({ + 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*', + 'force_level': [ 0, 0 ], + 'headers' : [ 'Cookie: SID=WRONGSECRET' ], + 'injection_tag': '*', + 'technique': 'R' + }) + detect_template_injection(channel, [ Mako ]) + + self.assertEqual(channel.data, {}) \ No newline at end of file diff --git a/tests/test_java_freemarker.py b/tests/test_java_freemarker.py new file mode 100644 index 0000000..1a134a0 --- /dev/null +++ b/tests/test_java_freemarker.py @@ -0,0 +1,65 @@ +import unittest +import requests +import os +import sys + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.freemarker import Freemarker +from basetest import BaseTest + +class FreemarkerTest(unittest.TestCase, BaseTest): + + + expected_data = { + 'language': 'java', + 'engine': 'freemarker', + 'execute' : True, + 'trailer': '${%(trailer)s?c}', + 'header': '${%(header)s?c}', + 'render': '%(code)s', + 'write': True, + 'read': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'java', + 'engine': 'freemarker', + 'blind': True, + 'execute_blind' : True, + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s&blind=1' + + plugin = Freemarker + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (5, 5, '<#list %s as a>', { 'prefix' : '[1] as a><#list [1] as a>', 'suffix' : ''}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + (1, 0, '${ %s }', { 'prefix': '1}', 'suffix': '' }), + + (2, 1, '<#assign s = %s>', { 'prefix': '1>', 'suffix': '' }), + (5, 1, '<#-- %s -->', { 'prefix': '-->', 'suffix': '<#--' }), + (2, 1, '<#if 1 == %s>', { 'prefix': '1>', 'suffix' : ''}), + (2, 2, '<#if %s == 1>', { 'prefix': 'true>', 'suffix' : ''}), + (5, 3, '<#list [%s] as a>', { 'prefix' : '1] as a><#list [1] as a>', 'suffix' : ''}), + (5, 5, '<#list %s as a>', { 'prefix' : '[1] as a><#list [1] as a>', 'suffix' : ''}), + (1, 5, '<#assign ages = {"J":2, "%s":2}>', { 'prefix' : '1":1}]}', 'suffix' : ''}), + + #(1, 5, '${[1,2]%3Fjoin(%s)}', { 'prefix' : '[1])}', 'suffix' : ''}), + + ] diff --git a/tests/test_java_velocity.py b/tests/test_java_velocity.py new file mode 100644 index 0000000..0771d11 --- /dev/null +++ b/tests/test_java_velocity.py @@ -0,0 +1,88 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.velocity import Velocity +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest + +class VelocityTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'java', + 'engine': 'velocity', + 'execute' : True, + 'trailer': '\n#set($t=%(trailer)s)\n${t}\n', + 'header': '\n#set($h=%(header)s)\n${h}\n', + 'render': '%(code)s', + 'write': True, + 'read': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'java', + 'engine': 'velocity', + 'blind': True, + 'execute_blind' : True, + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s&blind=1' + + plugin = Velocity + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + (1, 0, '#set( $a = "%s" )', { 'prefix' : '1")', 'suffix': ''}), + (1, 0, '#if(1 == %s)\n#end', { 'prefix' : '1)', 'suffix': ''}), + (3, 1, '#if(%s == 1)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}), + (3, 1, '#foreach($item in %s)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}), + (0, 0, '## comment %s', { }), + # TODO: fix those, they used to work + #(5, 0, '#[[%s]]# ', { }), + (0, 0, '${%s}', {}), + (0, 0, '${(%s)}', {}), + (3, 1, '#define( %s )a#end', { 'prefix': '1)#end#if(1==1)', 'suffix' : ''}), + (3, 1, '#define( $asd )%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), + (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), + ] + + + def test_custom_injection_tag(self): + + template = '#* %s *#' + + channel = Channel({ + 'url' : self.url.replace('*', '~') % template, + 'force_level': [ 5, 0 ], + 'injection_tag': '~', + 'technique': 'RT' + }) + + detect_template_injection(channel, [ self.plugin ]) + + expected_data = self.expected_data.copy() + expected_data.update({ 'prefix': '*#', 'suffix' : '#*'}) + + del channel.data['os'] + + self.assertEqual(channel.data, expected_data) \ No newline at end of file diff --git a/tests/test_node_dot.py b/tests/test_node_dot.py new file mode 100644 index 0000000..1d0b7e0 --- /dev/null +++ b/tests/test_node_dot.py @@ -0,0 +1,56 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.dot import Dot +from basetest import BaseTest + + +class DotTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'javascript', + 'engine': 'dot', + 'evaluate' : 'javascript' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': '{{=%(code)s}}', + 'header': '{{=%(header)s}}', + 'trailer': '{{=%(trailer)s}}', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'javascript', + 'engine': 'dot', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'javascript', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15004/dot?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15004/blind/dot?inj=*&tpl=%s' + plugin = Dot + + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + (1, 1, "{{ %s }}", { 'prefix': '1;}}', 'suffix' : '{{1;' }), + ] \ No newline at end of file diff --git a/tests/test_node_dust.py b/tests/test_node_dust.py new file mode 100644 index 0000000..b3764e4 --- /dev/null +++ b/tests/test_node_dust.py @@ -0,0 +1,64 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.dust import Dust +from basetest import BaseTest + + +class DustTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'javascript', + 'engine': 'dust', + 'write' : True, + 'execute_blind' : True, + 'prefix' : '', + 'suffix': '', + 'header': '%s', + 'trailer': '%s', + 'bind_shell' : True, + 'reverse_shell': True, + 'blind': True, + 'evaluate_blind': 'javascript' + } + + expected_data_blind = { + 'language': 'javascript', + 'engine': 'dust', + 'blind': True, + 'execute_blind' : True, + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True, + 'evaluate_blind': 'javascript' + } + + url = 'http://127.0.0.1:15004/dust?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15004/blind/dust?inj=*&tpl=%s' + plugin = Dust + + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (0, 0, '{%s|s}', { }), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + (0, 0, '{%s}', { }), + (0, 0, '{%s|s}', { }), + (1, 0, '{!%s!}', { 'prefix' : '!}', 'suffix' : '{!' }) + ] + + def test_upload(self): + pass + + def test_download(self): + pass \ No newline at end of file diff --git a/tests/test_node_ejs.py b/tests/test_node_ejs.py new file mode 100644 index 0000000..6f5c5b7 --- /dev/null +++ b/tests/test_node_ejs.py @@ -0,0 +1,67 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.ejs import Ejs +from basetest import BaseTest + + +class EjsTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'javascript', + 'engine': 'ejs', + 'evaluate' : 'javascript' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': """%(code)s""", + 'header': """<%%- '%(header)s'+""", + 'trailer': """+'%(trailer)s' %%>""", + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'javascript', + 'engine': 'ejs', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'javascript', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15004/ejs?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15004/blind/ejs?inj=*&tpl=%s' + plugin = Ejs + + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + (1, 0, "<% %s %>", { 'prefix': '1%>', 'suffix' : '<%#' }), + (1, 1, "<% '%s' %>", { 'prefix': "1'%>", 'suffix' : '<%#' }), + (1, 1, '<% "%s" %>', { 'prefix': '1"%>', 'suffix' : '<%#' }), + (1, 0, '<%= %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), + (1, 0, '<%- %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), + (1, 0, '<%# %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), + (1, 0, '<%_ %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), + (1, 0, '<% %s -%>', { 'prefix': '1%>', 'suffix' : '<%#' }), + (1, 0, '<% %s _%>', { 'prefix': '1%>', 'suffix' : '<%#' }), + (2, 1, "<%- include('/etc/resolv.conf%s') %>", { 'prefix': "')%>", 'suffix' : '<%#' }), + (2, 2, '<%- include("/etc/resolv.conf%s") %>', { 'prefix': '")%>', 'suffix' : '<%#' }), + (3, 0, "<% 456/* AAA %s */-123 %>", { 'prefix': '*/%>', 'suffix': '<%#' }), + ] diff --git a/tests/test_node_javascript.py b/tests/test_node_javascript.py new file mode 100644 index 0000000..ecd85a9 --- /dev/null +++ b/tests/test_node_javascript.py @@ -0,0 +1,79 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.languages.javascript import Javascript +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest + + +class JavascriptTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'javascript', + 'engine': 'javascript', + 'evaluate' : 'javascript' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': """%(code)s""", + 'header': """'%(header)s'+""", + 'trailer': """+'%(trailer)s'""", + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'javascript', + 'engine': 'javascript', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'javascript', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15004/javascript?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15004/blind/javascript?inj=*&tpl=%s' + plugin = Javascript + + + blind_tests = [ + (0, 0, '%s', {}), + (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), + (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), + ] + + def test_custom_injection_tag(self): + + template = '/* %s */' + + channel = Channel({ + 'url' : self.url.replace('*', '~') % template, + 'force_level': [ 5, 0 ], + 'injection_tag': '~', + 'technique': 'RT' + }) + + detect_template_injection(channel, [ self.plugin ]) + + expected_data = self.expected_data.copy() + expected_data.update({ 'prefix': '*/', 'suffix' : '/*'}) + + del channel.data['os'] + + self.assertEqual(channel.data, expected_data) \ No newline at end of file diff --git a/tests/test_node_marko.py b/tests/test_node_marko.py new file mode 100644 index 0000000..8eccbeb --- /dev/null +++ b/tests/test_node_marko.py @@ -0,0 +1,58 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.marko import Marko +from basetest import BaseTest + + +class MarkoTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'javascript', + 'engine': 'marko', + 'evaluate' : 'javascript' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': '${%(code)s}', + 'header': '${"%(header)s"}', + 'trailer': '${"%(trailer)s"}', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'javascript', + 'engine': 'marko', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'javascript', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15004/marko?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15004/blind/marko?inj=*&tpl=%s' + plugin = Marko + + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + (1, 0, '${%s}', { 'prefix': '1}', 'suffix' : '${"1"' }), + (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }), + (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }), + ] \ No newline at end of file diff --git a/tests/test_node_nunjucks.py b/tests/test_node_nunjucks.py new file mode 100644 index 0000000..73b5478 --- /dev/null +++ b/tests/test_node_nunjucks.py @@ -0,0 +1,68 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.nunjucks import Nunjucks +from basetest import BaseTest + + +class NunjucksTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'javascript', + 'engine': 'nunjucks', + 'evaluate' : 'javascript' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'trailer': '{{%(trailer)s}}', + 'header': '{{%(header)s}}', + 'render': '{{%(code)s}}', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'javascript', + 'engine': 'nunjucks', + 'evaluate_blind' : 'javascript', + 'blind': True, + 'execute_blind' : True, + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15004/nunjucks?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15004/blind/nunjucks?inj=*&tpl=%s' + plugin = Nunjucks + + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), + (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), + (0, 0, "{% block title %}%s{% endblock %}", {}), + (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), + (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), + (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), + (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), + (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), + (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}), + + # Comment blocks + (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), + ] \ No newline at end of file diff --git a/tests/test_node_pug.py b/tests/test_node_pug.py new file mode 100644 index 0000000..3a9ba02 --- /dev/null +++ b/tests/test_node_pug.py @@ -0,0 +1,80 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.pug import Pug +from basetest import BaseTest + + +class PugTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'javascript', + 'engine': 'pug', + 'evaluate' : 'javascript' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'trailer': '\n= %(trailer)s\n', + 'header': '\n= %(header)s\n', + 'render': '\n= %(code)s\n', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'javascript', + 'engine': 'pug', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'javascript', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15004/pug?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15004/blind/pug?inj=*&tpl=%s' + plugin = Pug + + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + + (1, 0, 'a(href=\'%s\')', { 'prefix' : '1\')', 'suffix' : '//' }), + (1, 0, 'a(href="%s")', { 'prefix' : '1")', 'suffix' : '//' }), + (0, 0, '#container.%s', { }), + (2, 1, '#{%s}', { 'prefix' : '1}', 'suffix' : '//' }), + + (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }), + (2, 1, '- var a = %s', { 'prefix': '1\n', 'suffix' : '//' }), + + ] + + def test_reflection_quotes(self): + + obj, data = self._get_detection_obj_data(self.url % '') + + if obj.get('execute'): + result = obj.execute("""echo 1"2"'3'\\"\\'""") + self.assertEqual(result, """123"'""") + + if not self.url_blind: + return + + obj, data = self._get_detection_obj_data(self.url_blind % '') + if obj.get('execute_blind'): + self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'""")) \ No newline at end of file diff --git a/tests/test_php_php.py b/tests/test_php_php.py new file mode 100644 index 0000000..96063d1 --- /dev/null +++ b/tests/test_php_php.py @@ -0,0 +1,88 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.languages.php import Php +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest, EXTRA_DOWNLOAD + + +class PhpTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'php', + 'engine': 'php', + 'evaluate' : 'php' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': """%(code)s""", + 'header': """print_r('%(header)s');""", + 'trailer': """print_r('%(trailer)s');""", + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'php', + 'engine': 'php', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'php', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://localhost:15002/eval.php?inj=*&tpl=%s' + url_blind = 'http://localhost:15002/eval.php?inj=*&tpl=%s&blind=1' + plugin = Php + + + blind_tests = [ + (0, 0, '%s', {}), + (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), + (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), + ] + + def test_download(self): + + # This is overriden due to the slight + # difference from the base test_download() + # obj.read('/dev/null') -> None + + obj, data = self._get_detection_obj_data(self.url % '') + self.assertEqual(data, self.expected_data) + + if not EXTRA_DOWNLOAD: + return + + # Normal ASCII file + readable_file = '/etc/resolv.conf' + content = open(readable_file, 'r').read() + self.assertEqual(content, obj.read(readable_file)) + + # Long binary file + readable_file = '/bin/ls' + content = open(readable_file, 'rb').read() + self.assertEqual(content, obj.read(readable_file)) + + # Non existant file + self.assertEqual(None, obj.read('/nonexistant')) + # Unpermitted file + self.assertEqual(None, obj.read('/etc/shadow')) + # Empty file + self.assertEqual(None, obj.read('/dev/null')) diff --git a/tests/test_php_smarty_secured.py b/tests/test_php_smarty_secured.py new file mode 100644 index 0000000..e533f6d --- /dev/null +++ b/tests/test_php_smarty_secured.py @@ -0,0 +1,81 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.smarty import Smarty +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest + +class SmartySecuredTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'php', + 'engine': 'smarty', + 'trailer': '{%(trailer)s}', + 'header': '{%(header)s}', + 'render': '%(code)s', + 'prefix' : '', + 'suffix' : '', + } + + expected_data_blind = { + 'language': 'php', + 'engine': 'smarty', + 'evaluate_blind': 'php', + 'blind': True, + 'prefix' : '', + 'suffix' : '', + } + + url = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s&blind=1' + plugin = Smarty + + # The secured Smarty can't executes any PHP hence no sleep(1) hence no + # blind tests for now + blind_tests = [ + ] + + reflection_tests = [ + (0, 0, '%s', { }), + (0, 0, 'AAA%sAAA', {}), + (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}), + (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}), + (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}), + (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}), + (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}), + (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), + (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), + (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), + ] + + def test_custom_injection_tag(self): + + template = '{* %s *}' + + channel = Channel({ + 'url' : self.url.replace('*', '~') % template, + 'force_level': [ 5, 5 ], + 'injection_tag': '~', + 'technique': 'RT' + }) + + detect_template_injection(channel, [ self.plugin ]) + + expected_data = self.expected_data.copy() + expected_data.update({ 'prefix': '*}', 'suffix' : '{*'}) + + self.assertEqual(channel.data, expected_data) + + def test_download(self): + pass + + def test_upload(self): + pass + + def test_upload_blind(self): + pass \ No newline at end of file diff --git a/tests/test_php_smarty_unsecured.py b/tests/test_php_smarty_unsecured.py new file mode 100644 index 0000000..6c76cf7 --- /dev/null +++ b/tests/test_php_smarty_unsecured.py @@ -0,0 +1,113 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.smarty import Smarty +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest, EXTRA_DOWNLOAD + +class SmartyUnsecuredTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'php', + 'engine': 'smarty', + 'evaluate' : 'php' , + 'execute' : True, + 'write': True, + 'read': True, + 'trailer': '{%(trailer)s}', + 'header': '{%(header)s}', + 'render': '%(code)s', + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'php', + 'engine': 'smarty', + 'evaluate_blind': 'php', + 'execute_blind': True, + 'write': True, + 'blind': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s' + url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s&blind=1' + plugin = Smarty + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), + ] + + reflection_tests = [ + (0, 0, '%s', { }), + (0, 0, 'AAA%sAAA', {}), + (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}), + (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}), + (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}), + (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}), + (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}), + (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), + (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), + (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), + + ] + + def test_custom_injection_tag(self): + + template = '{* %s *}' + + channel = Channel({ + 'url' : self.url.replace('*', '~') % template, + 'force_level': [ 5, 5 ], + 'injection_tag': '~', + 'technique': 'RT' + }) + + detect_template_injection(channel, [ self.plugin ]) + + expected_data = self.expected_data.copy() + expected_data.update({ 'prefix': '*}', 'suffix' : '{*'}) + + del channel.data['os'] + self.assertEqual(channel.data, expected_data) + + def test_download(self): + + # This is overriden due to the slight + # difference from the base test_download() + # obj.read('/dev/null') -> None + + obj, data = self._get_detection_obj_data(self.url % '') + self.assertEqual(data, self.expected_data) + + if not EXTRA_DOWNLOAD: + return + + # Normal ASCII file + readable_file = '/etc/resolv.conf' + content = open(readable_file, 'r').read() + self.assertEqual(content, obj.read(readable_file)) + + # Long binary file + readable_file = '/bin/ls' + content = open(readable_file, 'rb').read() + self.assertEqual(content, obj.read(readable_file)) + + # Non existant file + self.assertEqual(None, obj.read('/nonexistant')) + # Unpermitted file + self.assertEqual(None, obj.read('/etc/shadow')) + # Empty file + self.assertEqual(None, obj.read('/dev/null')) diff --git a/tests/test_php_twig_secured.py b/tests/test_php_twig_secured.py new file mode 100644 index 0000000..cc5f865 --- /dev/null +++ b/tests/test_php_twig_secured.py @@ -0,0 +1,56 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.twig import Twig +from core.channel import Channel +from basetest import BaseTest + +class TwigSecuredTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'php', + 'engine': 'twig', + 'trailer': '{{%(trailer)s}}', + 'header': '{{%(header)s}}', + 'render': '{{%(code)s}}', + 'prefix' : '', + 'suffix' : '', + } + + url = 'http://127.0.0.1:15002/twig-1.20.0-secured.php?tpl=%s&inj=*' + url_blind = '' + + plugin = Twig + + blind_tests = [ + + ] + + reflection_tests = [ + (0, 0, "%s", {}), + (0, 0, "AAA%sAAA", {}), + (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), + (0, 0, "{% block title %}%s{% endblock %}", {}), + (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), + (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), + (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), + (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), + (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), + (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}), + #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }), + ] + + # Defuse download tests, capabilities not available + def test_download(self): + pass + + # Defuse upload tests, capabilities not available + def test_upload(self): + pass + + def test_upload_blind(self): + pass \ No newline at end of file diff --git a/tests/test_php_twig_unsecured.py b/tests/test_php_twig_unsecured.py new file mode 100644 index 0000000..a44e60a --- /dev/null +++ b/tests/test_php_twig_unsecured.py @@ -0,0 +1,64 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.twig import Twig +from core.channel import Channel +from basetest import BaseTest + +class TwigUnsecuredTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'php', + 'engine': 'twig', + 'evaluate' : 'php', + 'execute' : True, + 'write': True, + 'read': True, + 'trailer': '{{%(trailer)s}}', + 'header': '{{%(header)s}}', + 'render': '{{%(code)s}}', + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'php', + 'engine': 'twig', + 'evaluate_blind': 'php', + 'execute_blind': True, + 'write': True, + 'blind': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*' + url_blind = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*&blind=1' + + plugin = Twig + + blind_tests = [ + + ] + + reflection_tests = [ + (0, 0, "%s", {}), + (0, 0, "AAA%sAAA", {}), + (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), + (0, 0, "{% block title %}%s{% endblock %}", {}), + (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), + (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), + (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), + (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), + (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), + (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}), + #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }), + ] \ No newline at end of file diff --git a/tests/test_py_jinja2.py b/tests/test_py_jinja2.py new file mode 100644 index 0000000..1718c85 --- /dev/null +++ b/tests/test_py_jinja2.py @@ -0,0 +1,159 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(10, os.path.join(sys.path[0], '..')) +from plugins.engines.jinja2 import Jinja2 +from core.channel import Channel +from utils import rand +from utils import strings +from basetest import BaseTest + +class Jinja2Test(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'python', + 'engine': 'jinja2', + 'evaluate' : 'python' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix': '', + 'suffix': '', + 'trailer': '{{%(trailer)s}}', + 'header': '{{%(header)s}}', + 'render': '{{%(code)s}}', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'python', + 'engine': 'jinja2', + 'evaluate_blind': 'python', + 'execute_blind': True, + 'write': True, + 'blind': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15001/reflect/jinja2?tpl=%s&inj=*' + url_blind = 'http://127.0.0.1:15001/blind/jinja2?tpl=%s&inj=*' + + plugin = Jinja2 + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (1, 2, '{%% for a in %s: %%}\n{%% endfor %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), + ] + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + + # Reflecting tag ${} context + (1, 1, '{{%s}}', { 'prefix': '1}}', 'suffix' : '' }), + (1, 1, '{{ \'%s\' }}', { 'prefix': '1\'}}', 'suffix' : '' }), + (1, 1, '{{ "%s" }}', { 'prefix': '1"}}', 'suffix' : '' }), + (1, 3, '{{ """%s""" }}', { 'prefix': '1"}}', 'suffix' : '' }), # {{"""%s"""}} -> {{"""1"}} + (1, 2, '{{ "1"|join(%s) }}', { 'prefix': '1)}}', 'suffix' : '' }), + (1, 2, '{{ "1"|join(\'%s\') }}', { 'prefix': '1\')}}', 'suffix' : '' }), + (1, 2, '{{ "1"|join("%s") }}', { 'prefix': '1")}}', 'suffix' : '' }), + (1, 2, '{{ "1"|join("""%s""") }}', { 'prefix': '1")}}', 'suffix' : '' }), # {{("""%s""")}} -> {{("""1")]}} + + (1, 4, '{{[%s]}}', { 'prefix': '1]}}', 'suffix' : '' }), + (1, 3, '{{ [\'%s\'] }}', { 'prefix': '1\']}}', 'suffix' : '' }), + (1, 3, '{{ ["%s"] }}', { 'prefix': '1"]}}', 'suffix' : '' }), + (1, 3, '{{ ["""%s"""] }}', { 'prefix': '1"]}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}} + (1, 5, '{{ "1"|join([%s]) }}', { 'prefix': '1])}}', 'suffix' : '' }), + (1, 5, '{{ "1"|join([\'%s\'])) }}', { 'prefix': '1\'])}}', 'suffix' : '' }), + (1, 5, '{{ "1"|join(["%s"]) }}', { 'prefix': '1"])}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}} + + (1, 3, '{{{%s}}}', { 'prefix': '1:1}}}', 'suffix' : '' }), + (1, 3, '{{{1:%s}}}', { 'prefix': '1}}}', 'suffix' : '' }), + (1, 3, '{{ {1:\'%s\'} }}', { 'prefix': '1\'}}}', 'suffix' : '' }), + (1, 3, '{{ {1:"%s"} }}', { 'prefix': '1"}}}', 'suffix' : '' }), + (1, 3, '{{ {1:"""%s"""} }}', { 'prefix': '1"}}}', 'suffix' : '' }), + (1, 3, '{{{%s:1}}}', { 'prefix': '1:1}}}', 'suffix' : '' }), + (1, 3, '{{ {\'%s\':1} }}', { 'prefix': '1\':1}}}', 'suffix' : '' }), + (1, 3, '{{ {"%s":1} }}', { 'prefix': '1":1}}}', 'suffix' : '' }), + (1, 3, '{{ {"""%s""":1}} }', { 'prefix': '1":1}}}', 'suffix' : '' }), + + # if and for blocks context with {% %} + (1, 1, '{%% if %s: %%}\n{%% endif %%}', { 'prefix' : '1%}', 'suffix' : '' }), + (1, 2, '{%% for a in %s: %%}\n{%% endfor %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), + (1, 1, '{%% if %s==1: %%}\n{%% endif %%}', { 'prefix' : '1%}', 'suffix' : '' }), + (1, 1, '{%% if \'%s\'==1: %%}\n{%% endif %%}', { 'prefix' : '1\'%}', 'suffix' : '' }), + (1, 1, '{%% if "%s"==1: %%}\n{%% endif %%}', { 'prefix' : '1"%}', 'suffix' : '' }), + (1, 1, '{%% if """%s"""==1: %%}\n{%% endif %%}', { 'prefix' : '1"%}', 'suffix' : '' }), # if """%s""": -> if """1": + (1, 2, '{%% if (1, %s)==1: %%}\n{%% endif %%}', { 'prefix' : '1)%}', 'suffix' : '' }), + (1, 2, '{%% if (1, \'%s\')==1: %%}\n{%% endif %%}', { 'prefix' : '1\')%}', 'suffix' : '' }), + (1, 2, '{%% if (1, "%s")==1: %%}\n{%% endif %%}', { 'prefix' : '1")%}', 'suffix' : '' }), + (1, 2, '{%% if (1, """%s""")==1: %%}\n{%% endif %%}', { 'prefix' : '1")%}', 'suffix' : '' }), # if (1, """%s"""): -> if (1, """1"): + + (1, 3, '{%% if [%s]==1: %%}\n{%% endif %%}', { 'prefix' : '1]%}', 'suffix' : '' }), + (1, 3, '{%% if [\'%s\']==1: %%}\n{%% endif %%}', { 'prefix' : '1\']%}', 'suffix' : '' }), + (1, 3, '{%% if ["%s"]==1: %%}\n{%% endif %%}', { 'prefix' : '1"]%}', 'suffix' : '' }), + (1, 3, '{%% if ["""%s"""]==1: %%}\n{%% endif %%}', { 'prefix' : '1"]%}', 'suffix' : '' }), # if ["""%s"""]: -> if ["""1"]: + (1, 5, '{%% if (1, [%s])==1: %%}\n{%% endif %%}', { 'prefix' : '1])%}', 'suffix' : '' }), + (1, 5, '{%% if (1, [\'%s\'])==1: %%}\n{%% endif %%}', { 'prefix' : '1\'])%}', 'suffix' : '' }), + (1, 5, '{%% if (1, ["%s"])==1: %%}\n{%% endif %%}', { 'prefix' : '1"])%}', 'suffix' : '' }), + (1, 5, '{%% if (1, ["""%s"""])==1: %%}\n{%% endif %%}', { 'prefix' : '1"])%}', 'suffix' : '' }), # if (1, ["""%s"""]): -> if (1, ["""1"]): + + (1, 3, '{%% for a in {%s}: %%}\n{%% endfor %%}', { 'prefix' : '1:1}%}', 'suffix' : '' }), + (1, 3, '{%% if {%s:1}==1: %%}\n{%% endif %%}', { 'prefix' : '1:1}%}', 'suffix' : '' }), + (1, 3, '{%% if {\'%s\':1}==1: %%}\n{%% endif %%}', { 'prefix' : '1\':1}%}', 'suffix' : '' }), + (1, 3, '{%% if {"%s":1}==1: %%}\n{%% endif %%}', { 'prefix' : '1":1}%}', 'suffix' : '' }), + (1, 3, '{%% if {"""%s""":1}==1: %%}\n{%% endif %%}', { 'prefix' : '1":1}%}', 'suffix' : '' }), # if {"""%s""":1}: -> if {"""1":1}: + (1, 3, '{%% if {1:%s}==1: %%}\n{%% endif %%}', { 'prefix' : '1}%}', 'suffix' : '' }), + (1, 3, '{%% if {1:\'%s\'}==1: %%}\n{%% endif %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), + (1, 3, '{%% if {1:"%s"}==1: %%}\n{%% endif %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), + (1, 3, '{%% if {1:"""%s"""}==1: %%}\n{%% endif %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), # if {1:"""%s""":1}: -> if {1:"""1"}: + + # if and for blocks context with line_statement_prefix + (5, 5, '# if %s:\n# endif\n', { 'prefix' : '1\n', 'suffix' : '\n' }), + (5, 5, '# for a in %s:\n# endfor', { 'prefix' : '"1"\n', 'suffix' : '\n' }), + (5, 5, '# if %s==1:\n# endif', { 'prefix' : '1\n', 'suffix' : '\n' }), + (5, 5, '# if \'%s\'==1:\n# endif', { 'prefix' : '1\'\n', 'suffix' : '\n' }), + (5, 5, '# if "%s"==1:\n# endif', { 'prefix' : '1"\n', 'suffix' : '\n' }), + (5, 5, '# if """%s"""==1:\n# endif', { 'prefix' : '1"\n', 'suffix' : '\n' }), # if """%s""": -> if """1": + (5, 5, '# if (1, %s)==1:\n# endif', { 'prefix' : '1)\n', 'suffix' : '\n' }), + (5, 5, '# if (1, \'%s\')==1:\n# endif', { 'prefix' : '1\')\n', 'suffix' : '\n' }), + (5, 5, '# if (1, "%s")==1:\n# endif', { 'prefix' : '1")\n', 'suffix' : '\n' }), + (5, 5, '# if (1, """%s""")==1:\n# endif', { 'prefix' : '1")\n', 'suffix' : '\n' }), # if (1, """%s"""): -> if (1, """1"): + + (5, 5, '# if [%s]==1:\n# endif', { 'prefix' : '1]\n', 'suffix' : '\n' }), + (5, 5, '# if [\'%s\']==1:\n# endif', { 'prefix' : '1\']\n', 'suffix' : '\n' }), + (5, 5, '# if ["%s"]==1:\n# endif', { 'prefix' : '1"]\n', 'suffix' : '\n' }), + (5, 5, '# if ["""%s"""]==1:\n# endif', { 'prefix' : '1"]\n', 'suffix' : '\n' }), # if ["""%s"""]: -> if ["""1"]: + (5, 5, '# if (1, [%s])==1:\n# endif', { 'prefix' : '1])\n', 'suffix' : '\n' }), + (5, 5, '# if (1, [\'%s\'])==1:\n# endif', { 'prefix' : '1\'])\n', 'suffix' : '\n' }), + (5, 5, '# if (1, ["%s"])==1:\n# endif', { 'prefix' : '1"])\n', 'suffix' : '\n' }), + (5, 5, '# if (1, ["""%s"""])==1:\n# endif', { 'prefix' : '1"])\n', 'suffix' : '\n' }), # if (1, ["""%s"""]): -> if (1, ["""1"]): + + (5, 5, '# for a in {%s}:\n# endfor', { 'prefix' : '1:1}\n', 'suffix' : '\n' }), + (5, 5, '# if {%s:1}==1:\n# endif', { 'prefix' : '1:1}\n', 'suffix' : '\n' }), + (5, 5, '# if {\'%s\':1}==1:\n# endif', { 'prefix' : '1\':1}\n', 'suffix' : '\n' }), + (5, 5, '# if {"%s":1}==1:\n# endif', { 'prefix' : '1":1}\n', 'suffix' : '\n' }), + (5, 5, '# if {"""%s""":1}==1:\n# endif', { 'prefix' : '1":1}\n', 'suffix' : '\n' }), # if {"""%s""":1}: -> if {"""1":1}: + (5, 5, '# if {1:%s}==1:\n# endif', { 'prefix' : '1}\n', 'suffix' : '\n' }), + (5, 5, '# if {1:\'%s\'}==1:\n# endif', { 'prefix' : '1\'}\n', 'suffix' : '\n' }), + (5, 5, '# if {1:"%s"}==1:\n# endif', { 'prefix' : '1"}\n', 'suffix' : '\n' }), + (5, 5, '# if {1:"""%s"""}==1:\n# endif', { 'prefix' : '1"}\n', 'suffix' : '\n' }), # if {1:"""%s""":1}: -> if {1:"""1"}: + + # Comment blocks + (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), + + ] + + def test_reflection_limit(self): + + obj, data = self._get_detection_obj_data('http://127.0.0.1:15001/limit/jinja2?tpl=%s&inj=*&limit=20' % '') + + expected_data = { 'unreliable_render' : self.expected_data['render'], 'unreliable' : 'Jinja2' } + + self.assertEqual(data, expected_data) diff --git a/tests/test_py_mako.py b/tests/test_py_mako.py new file mode 100644 index 0000000..6ad8570 --- /dev/null +++ b/tests/test_py_mako.py @@ -0,0 +1,177 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(10, os.path.join(sys.path[0], '..')) +from plugins.engines.mako import Mako +from core.channel import Channel +from utils import rand +from utils import strings +from basetest import BaseTest + +class MakoTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'python', + 'engine': 'mako', + 'evaluate' : 'python', + 'execute' : True, + 'read': True, + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'trailer': '${%(trailer)s}', + 'header': '${%(header)s}', + 'render': '${%(code)s}', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'python', + 'engine': 'mako', + 'blind': True, + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'evaluate_blind': 'python', + 'execute_blind': True, + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=*' + url_blind = 'http://127.0.0.1:15001/blind/mako?tpl=%s&inj=*' + + plugin = Mako + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }), + ] + + reflection_tests = [ + + # Text context + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + + # Reflecting tag ${} context + (1, 1, '${ %s = 1 }', { 'prefix': '1}', 'suffix' : '' }), + (1, 1, '${%s}', { 'prefix': '1}', 'suffix' : '' }), + (1, 1, '${ \'%s\' }', { 'prefix': '1\'}', 'suffix' : '' }), + (1, 1, '${ "%s" }', { 'prefix': '1"}', 'suffix' : '' }), + (1, 3, '${ """%s""" }', { 'prefix': '1"""}', 'suffix' : '' }), + (1, 2, '${ range(%s) }', { 'prefix': '1)}', 'suffix' : '' }), + (1, 2, '${ set(\'%s\') }', { 'prefix': '1\')}', 'suffix' : '' }), + (1, 2, '${ set("%s") }', { 'prefix': '1")}', 'suffix' : '' }), + (1, 3, '${ set("""%s""") }', { 'prefix': '1""")}', 'suffix' : '' }), + + (1, 3, '${[%s]}', { 'prefix': '1]}', 'suffix' : '' }), + (1, 3, '${ [\'%s\'] }', { 'prefix': '1\']}', 'suffix' : '' }), + (1, 3, '${ ["%s"] }', { 'prefix': '1"]}', 'suffix' : '' }), + (1, 3, '${ ["""%s"""] }', { 'prefix': '1"""]}', 'suffix' : '' }), + (1, 5, '${ set([%s]) }', { 'prefix': '1])}', 'suffix' : '' }), + (1, 5, '${ set([\'%s\']) }', { 'prefix': '1\'])}', 'suffix' : '' }), + (1, 5, '${ set(["%s"]) }', { 'prefix': '1"])}', 'suffix' : '' }), + (1, 5, '${ set(["""%s"""]) }', { 'prefix': '1"""])}', 'suffix' : '' }), + + (1, 3, '${{%s}}', { 'prefix': '1}}', 'suffix' : '' }), + (1, 3, '${{1:%s}}', { 'prefix': '1}}', 'suffix' : '' }), + (1, 3, '${ {1:\'%s\'} }', { 'prefix': '1\'}}', 'suffix' : '' }), + (1, 3, '${ {1:"%s"} }', { 'prefix': '1"}}', 'suffix' : '' }), + (1, 3, '${ {1:"""%s"""} }', { 'prefix': '1"""}}', 'suffix' : '' }), + (1, 3, '${{3:4, %s:1}}', { 'prefix': '1:1}}', 'suffix' : '' }), + (1, 3, '${ {\'%s\':1} }', { 'prefix': '1\'}}', 'suffix' : '' }), + (1, 3, '${ {"%s":1} }', { 'prefix': '1"}}', 'suffix' : '' }), + (1, 3, '${ {"""%s""":1} }', { 'prefix': '1"""}}', 'suffix' : '' }), + + # Code blocks context + (1, 1, '<%% %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), + (1, 1, '<%%! %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), + (1, 1, '<%% %s=1 %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), + (1, 1, '<%% a=%s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), + (1, 1, '<%% a=\'%s\' %%>', { 'prefix' : '1\'%>', 'suffix' : '<%#' }), + (1, 1, '<%% a="%s" %%>', { 'prefix' : '1"%>', 'suffix' : '<%#' }), + (1, 3, '<%% a="""%s""" %%>', { 'prefix' : '1"""%>', 'suffix' : '<%#' }), + (1, 3, '<%% a=range(%s) %%>', { 'prefix' : '1)%>', 'suffix' : '<%#' }), + (1, 3, '<%% a=\'\'.join(\'%s\') %%>', { 'prefix' : '1\')%>', 'suffix' : '<%#' }), + (1, 3, '<%% a=\'\'.join("%s") %%>', { 'prefix' : '1\")%>', 'suffix' : '<%#' }), + (1, 3, '<%% a=\'\'.join("""%s""") %%>', { 'prefix' : '1""")%>', 'suffix' : '<%#' }), + + + (1, 3, '<%% a=[%s] %%>', { 'prefix' : '1]%>', 'suffix' : '<%#' }), + (1, 3, '<%% a=[\'%s\'] %%>', { 'prefix' : '1\']%>', 'suffix' : '<%#' }), + (1, 3, '<%% a=["%s"] %%>', { 'prefix' : '1"]%>', 'suffix' : '<%#' }), + (1, 3, '<%% a=["""%s"""] %%>', { 'prefix' : '1"""]%>', 'suffix' : '<%#' }), + (1, 5, '<%% a=set([%s]) %%>', { 'prefix' : '1])%>', 'suffix' : '<%#' }), + (1, 5, '<%% a=set([\'%s\']) %%>', { 'prefix' : '1\'])%>', 'suffix' : '<%#' }), + (1, 5, '<%% a=set(["%s"]) %%>', { 'prefix' : '1"])%>', 'suffix' : '<%#' }), + (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }), + + (1, 3, '<%% a={%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={1:%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={1:\'%s\'} %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={1:"%s"} %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={1:"""%s"""} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={3:2, %s:1} %%>', { 'prefix' : '1:1}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={\'%s\':1}] %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={"%s":1}] %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }), + (1, 3, '<%% a={"""%s""":1} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }), + + # if and for blocks context + (5, 5, '%% if %s:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }), + (5, 5, '%% for a in %s:\n%% endfor', { 'prefix' : '"1":#\n', 'suffix' : '\n' }), + (5, 5, '%% if %s==1:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }), + (5, 5, '%% if \'%s\'==1:\n%% endif', { 'prefix' : '1\':#\n', 'suffix' : '\n' }), + (5, 5, '%% if "%s"==1:\n%% endif', { 'prefix' : '1":#\n', 'suffix' : '\n' }), + (5, 5, '%% if """%s"""==1:\n%% endif', { 'prefix' : '1""":#\n', 'suffix' : '\n' }), + (5, 5, '%% if (1, %s)==1:\n%% endif', { 'prefix' : '1):#\n', 'suffix' : '\n' }), + (5, 5, '%% if (1, \'%s\')==1:\n%% endif', { 'prefix' : '1\'):#\n', 'suffix' : '\n' }), + (5, 5, '%% if (1, "%s")==1:\n%% endif', { 'prefix' : '1"):#\n', 'suffix' : '\n' }), + (5, 5, '%% if (1, """%s""")==1:\n%% endif', { 'prefix' : '1"""):#\n', 'suffix' : '\n' }), + + (5, 5, '%% if [%s]==1:\n%% endif', { 'prefix' : '1]:#\n', 'suffix' : '\n' }), + (5, 5, '%% if [\'%s\']==1:\n%% endif', { 'prefix' : '1\']:#\n', 'suffix' : '\n' }), + (5, 5, '%% if ["%s"]==1:\n%% endif', { 'prefix' : '1"]:#\n', 'suffix' : '\n' }), + (5, 5, '%% if ["""%s"""]==1:\n%% endif', { 'prefix' : '1"""]:#\n', 'suffix' : '\n' }), + (5, 5, '%% if (1, [%s])==1:\n%% endif', { 'prefix' : '1]):#\n', 'suffix' : '\n' }), + (5, 5, '%% if (1, [\'%s\'])==1:\n%% endif', { 'prefix' : '1\']):#\n', 'suffix' : '\n' }), + (5, 5, '%% if (1, ["%s"])==1:\n%% endif', { 'prefix' : '1"]):#\n', 'suffix' : '\n' }), + + (5, 5, '%% if (1, ["""%s"""])==1:\n%% endif', { 'prefix' : '1"""]):#\n', 'suffix' : '\n' }), + + (5, 5, '%% for a in {%s}:\n%% endfor', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {%s:1}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {\'%s\':1}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {"%s":1}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {"""%s""":1}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {1:%s}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {1:\'%s\'}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {1:"%s"}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }), + (5, 5, '%% if {1:"""%s"""}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }), + + # Mako blocks. Skip <%block> which doesn't seem affecting the standard inj + # Inejcting includes e.g. '<%%include file="%s"/>' generates a missing file exception + (5, 1, '<%%doc> %s ', { 'prefix' : '', 'suffix' : '<%doc>' }), + #(5, 1, '<%%def name="a(x)"> %s ', { 'prefix' : '', 'suffix' : '<%def name="t(x)">' }), + (5, 1, '<%%text> %s ', { 'prefix' : '', 'suffix' : '<%text>' }), + + ] + + def test_reflection_limit(self): + template = '%s' + + channel = Channel({ + 'url' : 'http://127.0.0.1:15001/limit/mako?tpl=%s&inj=*&limit=20' % template, + 'injection_tag': '*', + 'technique': 'R' + }) + + Mako(channel).detect() + + expected_data = { 'unreliable_render' : self.expected_data['render'], 'unreliable' : 'Mako' } + + self.assertEqual(channel.data, expected_data) diff --git a/tests/test_py_python.py b/tests/test_py_python.py new file mode 100644 index 0000000..216c7ca --- /dev/null +++ b/tests/test_py_python.py @@ -0,0 +1,56 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.languages.python import Python +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest + + +class PythonTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'python', + 'engine': 'python', + 'evaluate' : 'python' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': """str(%(code)s)""", + 'header': """'%(header)s'+""", + 'trailer': """+'%(trailer)s'""", + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'python', + 'engine': 'python', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'python', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://localhost:15001/reflect/eval?inj=*&tpl=%s' + url_blind = 'http://localhost:15001/blind/eval?inj=*&tpl=%s' + plugin = Python + + blind_tests = [ + (0, 0, '%s', {}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + ] + \ No newline at end of file diff --git a/tests/test_py_tornado.py b/tests/test_py_tornado.py new file mode 100644 index 0000000..e879c45 --- /dev/null +++ b/tests/test_py_tornado.py @@ -0,0 +1,104 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(10, os.path.join(sys.path[0], '..')) +from plugins.engines.tornado import Tornado +from core.channel import Channel +from utils import rand +from utils import strings +from basetest import BaseTest + +class TornadoTest(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'python', + 'engine': 'tornado', + 'evaluate' : 'python' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix': '', + 'suffix': '', + 'trailer': '{{%(trailer)s}}', + 'header': '{{%(header)s}}', + 'render': '{{%(code)s}}', + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'python', + 'engine': 'tornado', + 'evaluate_blind': 'python', + 'execute_blind': True, + 'write': True, + 'blind': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://127.0.0.1:15001/reflect/tornado?tpl=%s&inj=*' + url_blind = 'http://127.0.0.1:15001/blind/tornado?tpl=%s&inj=*' + + plugin = Tornado + + blind_tests = [ + (0, 0, 'AAA%sAAA', {}), + (1, 2, '{%% for a in %s %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), + ] + reflection_tests = [ + (0, 0, '%s', {}), + (0, 0, 'AAA%sAAA', {}), + + # Reflecting tag ${} context + (1, 1, '{{%s}}', { 'prefix': '1}}', 'suffix' : '' }), + (1, 1, '{{ \'%s\' }}', { 'prefix': '1\'}}', 'suffix' : '' }), + (1, 1, '{{ "%s" }}', { 'prefix': '1"}}', 'suffix' : '' }), + (1, 3, '{{ """%s""" }}', { 'prefix': '1"""}}', 'suffix' : '' }), # {{"""%s"""}} -> {{"""1"}} + + (1, 4, '{{[%s]}}', { 'prefix': '1]}}', 'suffix' : '' }), + (1, 3, '{{ [\'%s\'] }}', { 'prefix': '1\']}}', 'suffix' : '' }), + (1, 3, '{{ ["%s"] }}', { 'prefix': '1"]}}', 'suffix' : '' }), + (1, 3, '{{ ["""%s"""] }}', { 'prefix': '1"""]}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}} + + # # if and for blocks context with {% %} + (1, 1, '{%% if %s: %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }), + (1, 2, '{%% for a in %s: %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), + (1, 1, '{%% if %s==1 %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }), + (1, 1, '{%% if \'%s\'==1 %%}\n{%% end %%}', { 'prefix' : '1\'%}', 'suffix' : '' }), + (1, 1, '{%% if "%s"==1 %%}\n{%% end %%}', { 'prefix' : '1"%}', 'suffix' : '' }), + (1, 3, '{%% if """%s"""==1 %%}\n{%% end %%}', { 'prefix' : '1"""%}', 'suffix' : '' }), # if """%s""": -> if """1": + (1, 2, '{%% if (1, %s)==1 %%}\n{%% end %%}', { 'prefix' : '1)%}', 'suffix' : '' }), + (1, 2, '{%% if (1, \'%s\')==1 %%}\n{%% end %%}', { 'prefix' : '1\')%}', 'suffix' : '' }), + (1, 2, '{%% if (1, "%s")==1 %%}\n{%% end %%}', { 'prefix' : '1")%}', 'suffix' : '' }), + (1, 3, '{%% if (1, """%s""")==1 %%}\n{%% end %%}', { 'prefix' : '1""")%}', 'suffix' : '' }), # if (1, """%s"""): -> if (1, """1"): + + (1, 3, '{%% if [%s]==1 %%}\n{%% end %%}', { 'prefix' : '1]%}', 'suffix' : '' }), + (1, 3, '{%% if [\'%s\']==1 %%}\n{%% end %%}', { 'prefix' : '1\']%}', 'suffix' : '' }), + (1, 3, '{%% if ["%s"]==1 %%}\n{%% end %%}', { 'prefix' : '1"]%}', 'suffix' : '' }), + (1, 3, '{%% if ["""%s"""]==1 %%}\n{%% end %%}', { 'prefix' : '1"""]%}', 'suffix' : '' }), # if ["""%s"""]: -> if ["""1"]: + (1, 5, '{%% if (1, [%s])==1 %%}\n{%% end %%}', { 'prefix' : '1])%}', 'suffix' : '' }), + (1, 5, '{%% if (1, [\'%s\'])==1 %%}\n{%% end %%}', { 'prefix' : '1\'])%}', 'suffix' : '' }), + (1, 5, '{%% if (1, ["%s"])==1 %%}\n{%% end %%}', { 'prefix' : '1"])%}', 'suffix' : '' }), + (1, 5, '{%% if (1, ["""%s"""])==1 %%}\n{%% end %%}', { 'prefix' : '1"""])%}', 'suffix' : '' }), # if (1, ["""%s"""]): -> if (1, ["""1"]): + + (1, 3, '{%% for a in {%s} %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), + (1, 3, '{%% if {%s:1}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), + (1, 3, '{%% if {\'%s\':1}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), + (1, 3, '{%% if {"%s":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), + (1, 3, '{%% if {"""%s""":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {"""%s""":1}: -> if {"""1":1}: + (1, 3, '{%% if {1:%s}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), + (1, 3, '{%% if {1:\'%s\'}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), + (1, 3, '{%% if {1:"%s"}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), + (1, 3, '{%% if {1:"""%s"""}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {1:"""%s""":1}: -> if {1:"""1"}: + + + # # Comment blocks + (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), + + ] diff --git a/tests/test_ruby_erb.py b/tests/test_ruby_erb.py new file mode 100644 index 0000000..64a2181 --- /dev/null +++ b/tests/test_ruby_erb.py @@ -0,0 +1,57 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.erb import Erb +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest + + +class ErbTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'ruby', + 'engine': 'erb', + 'evaluate' : 'ruby' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': '"#{%(code)s}"', + 'header': """<%%= '%(header)s'+""", + 'trailer': """+'%(trailer)s' %%>""", + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'ruby', + 'engine': 'erb', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'ruby', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://localhost:15005/reflect/erb?inj=*&tpl=%s' + url_blind = 'http://localhost:15005/blind/erb?inj=*&tpl=%s' + plugin = Erb + + #TODO: write context escape tests + blind_tests = [ + (0, 0, '%s', {}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + ] + \ No newline at end of file diff --git a/tests/test_ruby_ruby.py b/tests/test_ruby_ruby.py new file mode 100644 index 0000000..de96071 --- /dev/null +++ b/tests/test_ruby_ruby.py @@ -0,0 +1,56 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.languages.ruby import Ruby +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest + + +class RubyTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'ruby', + 'engine': 'ruby', + 'evaluate' : 'ruby' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': '"#{%(code)s}"', + 'header': """'%(header)s'+""", + 'trailer': """+'%(trailer)s'""", + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'ruby', + 'engine': 'ruby', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'ruby', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://localhost:15005/reflect/eval?inj=*&tpl=%s' + url_blind = 'http://localhost:15005/blind/eval?inj=*&tpl=%s' + plugin = Ruby + + blind_tests = [ + (0, 0, '%s', {}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + ] + \ No newline at end of file diff --git a/tests/test_ruby_slim.py b/tests/test_ruby_slim.py new file mode 100644 index 0000000..d2f4e2a --- /dev/null +++ b/tests/test_ruby_slim.py @@ -0,0 +1,57 @@ +import unittest +import requests +import os +import sys +import random + +sys.path.insert(1, os.path.join(sys.path[0], '..')) +from plugins.engines.slim import Slim +from core.channel import Channel +from core.checks import detect_template_injection +from basetest import BaseTest + + +class SlimTests(unittest.TestCase, BaseTest): + + expected_data = { + 'language': 'ruby', + 'engine': 'slim', + 'evaluate' : 'ruby' , + 'execute' : True, + 'read' : True, + 'write' : True, + 'prefix' : '', + 'suffix': '', + 'render': '"#{%(code)s}"', + 'header': """=('%(header)s'+""", + 'trailer': """+'%(trailer)s')""", + 'bind_shell' : True, + 'reverse_shell': True + } + + expected_data_blind = { + 'language': 'ruby', + 'engine': 'slim', + 'blind': True, + 'execute_blind' : True, + 'evaluate_blind' : 'ruby', + 'write': True, + 'prefix' : '', + 'suffix' : '', + 'bind_shell' : True, + 'reverse_shell': True + } + + url = 'http://localhost:15005/reflect/slim?inj=*&tpl=%s' + url_blind = 'http://localhost:15005/blind/slim?inj=*&tpl=%s' + plugin = Slim + + #TODO: write context escape tests + blind_tests = [ + (0, 0, '%s', {}), + ] + + reflection_tests = [ + (0, 0, '%s', {}), + ] + \ No newline at end of file diff --git a/tests/tests.sh b/tests/tests.sh new file mode 100755 index 0000000..7dc6593 --- /dev/null +++ b/tests/tests.sh @@ -0,0 +1,12 @@ +#!/bin/bash -e + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +for SCRIPT in ./run_*sh +do + if [ -f $SCRIPT -a -x $SCRIPT ] + then + echo -e "\n## Running $SCRIPT" + bash -e $SCRIPT --test + fi +done \ No newline at end of file From 0d5f709144c8f0bd8f162bc76d24da5732e68bc8 Mon Sep 17 00:00:00 2001 From: yagamiL Date: Tue, 26 Mar 2024 17:46:25 -0400 Subject: [PATCH 3/3] Reverting the deleted files --- .dockerignore | 3 +-- Dockerfile | 2 ++ tests/basetest.py | 2 -- tests/test_channel.py | 2 -- tests/test_java_freemarker.py | 1 - tests/test_java_velocity.py | 2 -- tests/test_node_dot.py | 2 -- tests/test_node_dust.py | 2 -- tests/test_node_ejs.py | 2 -- tests/test_node_javascript.py | 2 -- tests/test_node_marko.py | 2 -- tests/test_node_nunjucks.py | 2 -- tests/test_node_pug.py | 2 -- tests/test_php_php.py | 4 ---- tests/test_php_smarty_secured.py | 2 -- tests/test_php_smarty_unsecured.py | 2 -- tests/test_php_twig_secured.py | 3 --- tests/test_php_twig_unsecured.py | 3 --- tests/test_py_jinja2.py | 5 ----- tests/test_py_mako.py | 4 ---- tests/test_py_python.py | 4 ---- tests/test_py_tornado.py | 5 ----- tests/test_ruby_erb.py | 4 ---- tests/test_ruby_ruby.py | 4 ---- tests/test_ruby_slim.py | 4 ---- 25 files changed, 3 insertions(+), 67 deletions(-) diff --git a/.dockerignore b/.dockerignore index e4807a9..f2b0908 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,5 @@ __pycache__ -**/__pycache__/ +**/__pycache__ .vscode Dockerfile *.pyc @@ -7,7 +7,6 @@ Dockerfile *.pyd .Python env -.tox .coverage .coverage.* .cache diff --git a/Dockerfile b/Dockerfile index a227127..fed3b4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,8 +9,10 @@ USER user RUN python -m pip install --upgrade pip RUN pip install --no-cache-dir -r requirements.txt + # Running the script when the container launches ENTRYPOINT ["python", "tplmap.py"] + # Default cmd CMD ["-h"] diff --git a/tests/basetest.py b/tests/basetest.py index cbff766..e64efd9 100644 --- a/tests/basetest.py +++ b/tests/basetest.py @@ -1,7 +1,5 @@ -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from core.channel import Channel diff --git a/tests/test_channel.py b/tests/test_channel.py index de72685..9bf2395 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.mako import Mako diff --git a/tests/test_java_freemarker.py b/tests/test_java_freemarker.py index 1a134a0..1dd6988 100644 --- a/tests/test_java_freemarker.py +++ b/tests/test_java_freemarker.py @@ -1,5 +1,4 @@ import unittest -import requests import os import sys diff --git a/tests/test_java_velocity.py b/tests/test_java_velocity.py index 0771d11..fc71d2f 100644 --- a/tests/test_java_velocity.py +++ b/tests/test_java_velocity.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.velocity import Velocity diff --git a/tests/test_node_dot.py b/tests/test_node_dot.py index 1d0b7e0..3defcc4 100644 --- a/tests/test_node_dot.py +++ b/tests/test_node_dot.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.dot import Dot diff --git a/tests/test_node_dust.py b/tests/test_node_dust.py index b3764e4..6dec7e5 100644 --- a/tests/test_node_dust.py +++ b/tests/test_node_dust.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.dust import Dust diff --git a/tests/test_node_ejs.py b/tests/test_node_ejs.py index 6f5c5b7..09f99fa 100644 --- a/tests/test_node_ejs.py +++ b/tests/test_node_ejs.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.ejs import Ejs diff --git a/tests/test_node_javascript.py b/tests/test_node_javascript.py index ecd85a9..787c34c 100644 --- a/tests/test_node_javascript.py +++ b/tests/test_node_javascript.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.javascript import Javascript diff --git a/tests/test_node_marko.py b/tests/test_node_marko.py index 8eccbeb..699249a 100644 --- a/tests/test_node_marko.py +++ b/tests/test_node_marko.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.marko import Marko diff --git a/tests/test_node_nunjucks.py b/tests/test_node_nunjucks.py index 73b5478..4fc3ed1 100644 --- a/tests/test_node_nunjucks.py +++ b/tests/test_node_nunjucks.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.nunjucks import Nunjucks diff --git a/tests/test_node_pug.py b/tests/test_node_pug.py index 3a9ba02..8313066 100644 --- a/tests/test_node_pug.py +++ b/tests/test_node_pug.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.pug import Pug diff --git a/tests/test_php_php.py b/tests/test_php_php.py index 96063d1..bf48149 100644 --- a/tests/test_php_php.py +++ b/tests/test_php_php.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.php import Php -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest, EXTRA_DOWNLOAD diff --git a/tests/test_php_smarty_secured.py b/tests/test_php_smarty_secured.py index e533f6d..7376a05 100644 --- a/tests/test_php_smarty_secured.py +++ b/tests/test_php_smarty_secured.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.smarty import Smarty diff --git a/tests/test_php_smarty_unsecured.py b/tests/test_php_smarty_unsecured.py index 6c76cf7..436aba5 100644 --- a/tests/test_php_smarty_unsecured.py +++ b/tests/test_php_smarty_unsecured.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.smarty import Smarty diff --git a/tests/test_php_twig_secured.py b/tests/test_php_twig_secured.py index cc5f865..404e91f 100644 --- a/tests/test_php_twig_secured.py +++ b/tests/test_php_twig_secured.py @@ -1,12 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.twig import Twig -from core.channel import Channel from basetest import BaseTest class TwigSecuredTest(unittest.TestCase, BaseTest): diff --git a/tests/test_php_twig_unsecured.py b/tests/test_php_twig_unsecured.py index a44e60a..e397e2a 100644 --- a/tests/test_php_twig_unsecured.py +++ b/tests/test_php_twig_unsecured.py @@ -1,12 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.twig import Twig -from core.channel import Channel from basetest import BaseTest class TwigUnsecuredTest(unittest.TestCase, BaseTest): diff --git a/tests/test_py_jinja2.py b/tests/test_py_jinja2.py index 1718c85..69bf553 100644 --- a/tests/test_py_jinja2.py +++ b/tests/test_py_jinja2.py @@ -1,14 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(10, os.path.join(sys.path[0], '..')) from plugins.engines.jinja2 import Jinja2 -from core.channel import Channel -from utils import rand -from utils import strings from basetest import BaseTest class Jinja2Test(unittest.TestCase, BaseTest): diff --git a/tests/test_py_mako.py b/tests/test_py_mako.py index 6ad8570..01203a1 100644 --- a/tests/test_py_mako.py +++ b/tests/test_py_mako.py @@ -1,14 +1,10 @@ import unittest -import requests import os import sys -import random sys.path.insert(10, os.path.join(sys.path[0], '..')) from plugins.engines.mako import Mako from core.channel import Channel -from utils import rand -from utils import strings from basetest import BaseTest class MakoTest(unittest.TestCase, BaseTest): diff --git a/tests/test_py_python.py b/tests/test_py_python.py index 216c7ca..f94eec2 100644 --- a/tests/test_py_python.py +++ b/tests/test_py_python.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.python import Python -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest diff --git a/tests/test_py_tornado.py b/tests/test_py_tornado.py index e879c45..486c976 100644 --- a/tests/test_py_tornado.py +++ b/tests/test_py_tornado.py @@ -1,14 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(10, os.path.join(sys.path[0], '..')) from plugins.engines.tornado import Tornado -from core.channel import Channel -from utils import rand -from utils import strings from basetest import BaseTest class TornadoTest(unittest.TestCase, BaseTest): diff --git a/tests/test_ruby_erb.py b/tests/test_ruby_erb.py index 64a2181..d0c83bc 100644 --- a/tests/test_ruby_erb.py +++ b/tests/test_ruby_erb.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.erb import Erb -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest diff --git a/tests/test_ruby_ruby.py b/tests/test_ruby_ruby.py index de96071..1fc6a38 100644 --- a/tests/test_ruby_ruby.py +++ b/tests/test_ruby_ruby.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.ruby import Ruby -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest diff --git a/tests/test_ruby_slim.py b/tests/test_ruby_slim.py index d2f4e2a..d83ba42 100644 --- a/tests/test_ruby_slim.py +++ b/tests/test_ruby_slim.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.slim import Slim -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest