diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f2b0908 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +__pycache__ +**/__pycache__ +.vscode +Dockerfile +*.pyc +*.pyo +*.pyd +.Python +env +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.log +.git +.gitignore +.pytest_cache +.travis.yml +docker-envs +burp_extension +burp_extension.py +tests diff --git a/.gitignore b/.gitignore index d28288c..daa30dd 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,20 @@ node_modules # Jython *.class +# VS-code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + # Vim # swap .sw[a-p] diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dc5005e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File with Arguments", + "type": "debugpy", + "request": "launch", + "program": "tplmap.py", + "console": "integratedTerminal", + "args": "-u http://localhost/?name=so -k" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fed3b4c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.9 + +WORKDIR /app +COPY . /app + +RUN useradd -ms /bin/bash user +RUN chown -R user:user /app +USER user + +RUN python -m pip install --upgrade pip +RUN pip install --no-cache-dir -r requirements.txt + +# Running the script when the container launches +ENTRYPOINT ["python", "tplmap.py"] + +# Default cmd +CMD ["-h"] + diff --git a/README.md b/README.md index 075ae79..e14d6fd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ Tplmap ====== -> This project is no longer maintained. I'm happy to merge new PRs as long they don't break the [test suite](https://github.com/epinna/tplmap/wiki/Run-the-test-suite). - Tplmap assists the exploitation of Code Injection and Server-Side Template Injection vulnerabilities with a number of sandbox escape techniques to get access to the underlying operating system. The tool and its test suite are developed to research the SSTI vulnerability class and to be used as offensive security tool during web application penetration tests. @@ -98,7 +96,7 @@ Use `--os-shell` option to launch a pseudo-terminal on the target. ``` $ ./tplmap.py --os-shell -u 'http://www.target.com/page?name=John' -[+] Tplmap 0.5 +[+] Tplmap 0.3 Automatic Server-Side Template Injection Detection and Exploitation Tool [+] Run commands on the operating system. @@ -110,14 +108,21 @@ root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh ``` +### Using docker +You can use docker instead, if you encounter any issue with the script (mostly caused by python2 dependencies e.g yaml). +```console +$ docker build -t tplmap . +$ docker run --rm tplmap:latest -h +$ docker run --rm tplmap:latest -u 'http://www.target.com/page?name=jhon' +``` Supported template engines -------------------------- Tplmap supports over 15 template engines, unsandboxed template engines and generic _eval()_-like injections. -| Engine | Remote Command Execution | Blind | Code evaluation | File read | File write | -|------------------------|---------------|-------------------|-----------------|-----------|------------| +| Template engine | Remote Command Execution | Blind | Code evaluation | File read | File write | +|------------------------|-------|-------------------|-----------------|-----------|------------| | Mako | ✓ | ✓ | Python | ✓ | ✓ | | Jinja2 | ✓ | ✓ | Python | ✓ | ✓ | | Python (code eval) | ✓ | ✓ | Python | ✓ | ✓ | @@ -134,10 +139,9 @@ Tplmap supports over 15 template engines, unsandboxed template engines and gener | ERB | ✓ | ✓ | Ruby | ✓ | ✓ | | Smarty (unsecured) | ✓ | ✓ | PHP | ✓ | ✓ | | PHP (code eval) | ✓ | ✓ | PHP | ✓ | ✓ | -| Twig (<=1.19) | ✓ | ✓ | PHP | ✓ | ✓ | -| Freemarker | ✓ | ✓ | Java | ✓ | ✓ | -| Velocity | ✓ | ✓ | Java | ✓ | ✓ | -| Twig (>1.19) | × | × | × | × | × | +| Freemarker | ✓ | ✓ | × | ✓ | ✓ | +| Velocity | ✓ | ✓ | × | ✓ | ✓ | +| Twig | × | × | × | × | × | | Smarty (secured) | × | × | × | × | × | | Dust (> dustjs-helpers@1.5.0) | × | × | × | × | × | diff --git a/core/channel.py b/core/channel.py index 70d8289..5d75fdd 100644 --- a/core/channel.py +++ b/core/channel.py @@ -28,6 +28,7 @@ def __init__(self, args): self.injs = [] self.inj_idx = 0 + self.ssl_verf = self.args.get('ssl_verf') proxy = self.args.get('proxy') if proxy: @@ -201,6 +202,7 @@ def req(self, injection): post_params = deepcopy(self.post_params) header_params = deepcopy(self.header_params) url_params = self.base_url + ssl_verfication = self.ssl_verf # Pick current injection by index inj = deepcopy(self.injs[self.inj_idx]) @@ -298,9 +300,10 @@ def req(self, injection): data = post_params, headers = header_params, proxies = self.proxies, - # By default, SSL check is skipped. - # TODO: add a -k curl-like option to set this. - verify = False + # By default, SSL check is not skipped. + # WARNING : Using this option makes the transfer insecure. + # TODO: add a -k curl-like option to set this. (Done) + verify = ssl_verfication ).text except requests.exceptions.ConnectionError as e: if e and e[0] and e[0][0] == 'Connection aborted.': diff --git a/core/checks.py b/core/checks.py index 0d7f9e8..3bface0 100644 --- a/core/checks.py +++ b/core/checks.py @@ -17,11 +17,9 @@ from plugins.languages.php import Php from plugins.languages.python import Python from plugins.languages.ruby import Ruby -from core.channel import Channel from utils.loggers import log from core.clis import Shell, MultilineShell from core.tcpserver import TcpServer -import time import telnetlib import sys diff --git a/requirements.txt b/requirements.txt index 031fa4b..2a8be62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,3 @@ chardet==3.0.4 idna==2.8 requests==2.22.0 urllib3==1.24.1 -wsgiref==0.1.2 diff --git a/tests/basetest.py b/tests/basetest.py index cbff766..e64efd9 100644 --- a/tests/basetest.py +++ b/tests/basetest.py @@ -1,7 +1,5 @@ -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from core.channel import Channel diff --git a/tests/test_channel.py b/tests/test_channel.py index de72685..9bf2395 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.mako import Mako diff --git a/tests/test_java_freemarker.py b/tests/test_java_freemarker.py index 1a134a0..1dd6988 100644 --- a/tests/test_java_freemarker.py +++ b/tests/test_java_freemarker.py @@ -1,5 +1,4 @@ import unittest -import requests import os import sys diff --git a/tests/test_java_velocity.py b/tests/test_java_velocity.py index 0771d11..fc71d2f 100644 --- a/tests/test_java_velocity.py +++ b/tests/test_java_velocity.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.velocity import Velocity diff --git a/tests/test_node_dot.py b/tests/test_node_dot.py index 1d0b7e0..3defcc4 100644 --- a/tests/test_node_dot.py +++ b/tests/test_node_dot.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.dot import Dot diff --git a/tests/test_node_dust.py b/tests/test_node_dust.py index b3764e4..6dec7e5 100644 --- a/tests/test_node_dust.py +++ b/tests/test_node_dust.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.dust import Dust diff --git a/tests/test_node_ejs.py b/tests/test_node_ejs.py index 6f5c5b7..09f99fa 100644 --- a/tests/test_node_ejs.py +++ b/tests/test_node_ejs.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.ejs import Ejs diff --git a/tests/test_node_javascript.py b/tests/test_node_javascript.py index ecd85a9..787c34c 100644 --- a/tests/test_node_javascript.py +++ b/tests/test_node_javascript.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.javascript import Javascript diff --git a/tests/test_node_marko.py b/tests/test_node_marko.py index 8eccbeb..699249a 100644 --- a/tests/test_node_marko.py +++ b/tests/test_node_marko.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.marko import Marko diff --git a/tests/test_node_nunjucks.py b/tests/test_node_nunjucks.py index 73b5478..4fc3ed1 100644 --- a/tests/test_node_nunjucks.py +++ b/tests/test_node_nunjucks.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.nunjucks import Nunjucks diff --git a/tests/test_node_pug.py b/tests/test_node_pug.py index 3a9ba02..8313066 100644 --- a/tests/test_node_pug.py +++ b/tests/test_node_pug.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.pug import Pug diff --git a/tests/test_php_php.py b/tests/test_php_php.py index 96063d1..bf48149 100644 --- a/tests/test_php_php.py +++ b/tests/test_php_php.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.php import Php -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest, EXTRA_DOWNLOAD diff --git a/tests/test_php_smarty_secured.py b/tests/test_php_smarty_secured.py index e533f6d..7376a05 100644 --- a/tests/test_php_smarty_secured.py +++ b/tests/test_php_smarty_secured.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.smarty import Smarty diff --git a/tests/test_php_smarty_unsecured.py b/tests/test_php_smarty_unsecured.py index 6c76cf7..436aba5 100644 --- a/tests/test_php_smarty_unsecured.py +++ b/tests/test_php_smarty_unsecured.py @@ -1,8 +1,6 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.smarty import Smarty diff --git a/tests/test_php_twig_secured.py b/tests/test_php_twig_secured.py index cc5f865..404e91f 100644 --- a/tests/test_php_twig_secured.py +++ b/tests/test_php_twig_secured.py @@ -1,12 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.twig import Twig -from core.channel import Channel from basetest import BaseTest class TwigSecuredTest(unittest.TestCase, BaseTest): diff --git a/tests/test_php_twig_unsecured.py b/tests/test_php_twig_unsecured.py index a44e60a..e397e2a 100644 --- a/tests/test_php_twig_unsecured.py +++ b/tests/test_php_twig_unsecured.py @@ -1,12 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.twig import Twig -from core.channel import Channel from basetest import BaseTest class TwigUnsecuredTest(unittest.TestCase, BaseTest): diff --git a/tests/test_py_jinja2.py b/tests/test_py_jinja2.py index 1718c85..69bf553 100644 --- a/tests/test_py_jinja2.py +++ b/tests/test_py_jinja2.py @@ -1,14 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(10, os.path.join(sys.path[0], '..')) from plugins.engines.jinja2 import Jinja2 -from core.channel import Channel -from utils import rand -from utils import strings from basetest import BaseTest class Jinja2Test(unittest.TestCase, BaseTest): diff --git a/tests/test_py_mako.py b/tests/test_py_mako.py index 6ad8570..01203a1 100644 --- a/tests/test_py_mako.py +++ b/tests/test_py_mako.py @@ -1,14 +1,10 @@ import unittest -import requests import os import sys -import random sys.path.insert(10, os.path.join(sys.path[0], '..')) from plugins.engines.mako import Mako from core.channel import Channel -from utils import rand -from utils import strings from basetest import BaseTest class MakoTest(unittest.TestCase, BaseTest): diff --git a/tests/test_py_python.py b/tests/test_py_python.py index 216c7ca..f94eec2 100644 --- a/tests/test_py_python.py +++ b/tests/test_py_python.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.python import Python -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest diff --git a/tests/test_py_tornado.py b/tests/test_py_tornado.py index e879c45..486c976 100644 --- a/tests/test_py_tornado.py +++ b/tests/test_py_tornado.py @@ -1,14 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(10, os.path.join(sys.path[0], '..')) from plugins.engines.tornado import Tornado -from core.channel import Channel -from utils import rand -from utils import strings from basetest import BaseTest class TornadoTest(unittest.TestCase, BaseTest): diff --git a/tests/test_ruby_erb.py b/tests/test_ruby_erb.py index 64a2181..d0c83bc 100644 --- a/tests/test_ruby_erb.py +++ b/tests/test_ruby_erb.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.erb import Erb -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest diff --git a/tests/test_ruby_ruby.py b/tests/test_ruby_ruby.py index de96071..1fc6a38 100644 --- a/tests/test_ruby_ruby.py +++ b/tests/test_ruby_ruby.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.languages.ruby import Ruby -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest diff --git a/tests/test_ruby_slim.py b/tests/test_ruby_slim.py index d2f4e2a..d83ba42 100644 --- a/tests/test_ruby_slim.py +++ b/tests/test_ruby_slim.py @@ -1,13 +1,9 @@ import unittest -import requests import os import sys -import random sys.path.insert(1, os.path.join(sys.path[0], '..')) from plugins.engines.slim import Slim -from core.channel import Channel -from core.checks import detect_template_injection from basetest import BaseTest diff --git a/utils/cliparser.py b/utils/cliparser.py index 6acea3f..ba04d3e 100644 --- a/utils/cliparser.py +++ b/utils/cliparser.py @@ -14,6 +14,8 @@ Example: ./tplmap -u 'http://www.target.com/page.php?id=1*' + or + docker run --rm tplmap:latest -u 'http://www.target.com/page.php?id=1*' """ @@ -62,8 +64,14 @@ def format_epilog(self, formatter): ) request.add_option("--proxy", dest="proxy", - help="Use a proxy to connect to the target URL" + help="Use a proxy to connect to the target URL." ) +request.add_option("-k", "--insecure", + dest="ssl_verf", + action="store_false", + default=True, + help="Allow insecure server connections." + ) # Detection options detection = OptionGroup(parser, "Detection" , "These options can be used to customize the detection phase.") diff --git a/utils/config.py b/utils/config.py index da21ad0..d8249b1 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,5 +1,4 @@ import os -import sys import yaml config = None diff --git a/utils/strings.py b/utils/strings.py index f8af1a6..8c8b0ce 100644 --- a/utils/strings.py +++ b/utils/strings.py @@ -1,4 +1,3 @@ -import json import base64 import hashlib