buby 1.1.6-java
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +67 -0
- data/README.rdoc +289 -0
- data/Rakefile +34 -0
- data/bin/buby +108 -0
- data/buby.gemspec +37 -0
- data/java/buby.jar +0 -0
- data/java/src/BurpExtender.java +295 -0
- data/java/src/burp/IBurpExtender.java +136 -0
- data/java/src/burp/IBurpExtenderCallbacks.java +211 -0
- data/java/src/burp/IHttpRequestResponse.java +32 -0
- data/java/src/burp/IScanIssue.java +32 -0
- data/java/src/burp/IScanQueueItem.java +20 -0
- data/lib/buby.rb +772 -0
- data/lib/buby/extends.rb +4 -0
- data/lib/buby/extends/buby_array_wrapper.rb +41 -0
- data/lib/buby/extends/http_request_response.rb +95 -0
- data/lib/buby/extends/scan_issue.rb +36 -0
- data/samples/drb_buby.rb +31 -0
- data/samples/drb_sample_cli.rb +14 -0
- data/samples/mechanize_burp.rb +57 -0
- data/samples/poc_generator.rb +124 -0
- data/samples/verb_tamperer.rb +25 -0
- data/samples/watch_scan.rb +21 -0
- data/spec/buby_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/test_buby.rb +0 -0
- metadata +106 -0
data/lib/buby/extends.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
class Buby
|
3
|
+
class BubyArrayWrapper
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :array_obj
|
7
|
+
|
8
|
+
def initialize(obj)
|
9
|
+
@array_obj = obj
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](*args)
|
13
|
+
if args.size == 1 and args.first.kind_of? Numeric
|
14
|
+
self.array_obj[args[0]]
|
15
|
+
else
|
16
|
+
self.to_a(*args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def each
|
21
|
+
self.array_obj.size.times do |idx|
|
22
|
+
yield self.array_obj[idx]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def size
|
27
|
+
self.array_obj.size
|
28
|
+
end
|
29
|
+
alias length size
|
30
|
+
|
31
|
+
def first
|
32
|
+
return(self.array_obj[0]) if(self.size > 0)
|
33
|
+
end
|
34
|
+
|
35
|
+
def last
|
36
|
+
return self.array_obj[self.size - 1] if(self.size > 0)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
class Buby
|
4
|
+
|
5
|
+
class HttpRequestResponseList < BubyArrayWrapper
|
6
|
+
def initialize(obj)
|
7
|
+
HttpRequestResponseHelper.implant(obj[0]) if obj.size > 0
|
8
|
+
super(obj)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
module HttpRequestResponseHelper
|
15
|
+
|
16
|
+
# returns the response as a Ruby String object - returns an empty string
|
17
|
+
# if response is nil.
|
18
|
+
def response_str
|
19
|
+
return response().nil? ? "" : ::String.from_java_bytes(response())
|
20
|
+
end
|
21
|
+
alias response_string response_str
|
22
|
+
alias rsp_str response_str
|
23
|
+
|
24
|
+
|
25
|
+
# returns an array of response headers split into header name and value.
|
26
|
+
# For example:
|
27
|
+
# [
|
28
|
+
# ["HTTP/1.1 301 Moved Permanently"],
|
29
|
+
# ["Server", "Apache/1.3.41 ..."],
|
30
|
+
# ...
|
31
|
+
# ]
|
32
|
+
def response_headers
|
33
|
+
if headers=(@rsp_split ||= rsp_str.split(/\r?\n\r?\n/, 2))[0]
|
34
|
+
@rsp_headers ||= headers.split(/\r?\n/).map {|h| h.split(/\s*:\s*/,2)}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias rsp_headers response_headers
|
38
|
+
|
39
|
+
# Returns the message body of the response, minus headers
|
40
|
+
def response_body
|
41
|
+
(@rsp_split ||= rsp_str.split(/\r?\n\r?\n/, 2))[1]
|
42
|
+
end
|
43
|
+
alias rsp_body response_body
|
44
|
+
|
45
|
+
|
46
|
+
# Returns the full request as a Ruby String - returns an empty string if
|
47
|
+
# request is nil.
|
48
|
+
def request_str
|
49
|
+
return request().nil? ? "" : ::String.from_java_bytes(request())
|
50
|
+
end
|
51
|
+
alias request_string request_str
|
52
|
+
alias req_str request_str
|
53
|
+
|
54
|
+
|
55
|
+
# Returns a split array of headers. Example:
|
56
|
+
# [
|
57
|
+
# ["GET / HTTP/1.1"],
|
58
|
+
# ["Host", "www.example.org"],
|
59
|
+
# ["User-Agent", "Mozilla/5.0 (..."],
|
60
|
+
# ...
|
61
|
+
# ]
|
62
|
+
def request_headers
|
63
|
+
if headers=(@req_split ||= req_str.split(/\r?\n\r?\n/, 2))[0]
|
64
|
+
@req_headers ||= headers.split(/\r?\n/).map {|h| h.split(/\s*:\s*/,2)}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias req_headers request_headers
|
68
|
+
|
69
|
+
|
70
|
+
# Returns the request message body or an empty string if there is none.
|
71
|
+
def request_body
|
72
|
+
(@req_split ||= req_str.split(/\r?\n\r?\n/, 2))[1]
|
73
|
+
end
|
74
|
+
alias req_body request_body
|
75
|
+
|
76
|
+
|
77
|
+
# Returns a Ruby URI object derived from the java.net.URL object
|
78
|
+
def uri
|
79
|
+
@uri ||= URI.parse url.to_s if not url.nil?
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# one-shot method to implant ourselves onto a target object's class
|
84
|
+
# interface in ruby. All later instances will also get 'us' for free!
|
85
|
+
def self.implant(base)
|
86
|
+
return if @implanted
|
87
|
+
base.class.instance_eval { include(HttpRequestResponseHelper) }
|
88
|
+
@implanted = true
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def self.implanted? ; @implanted; end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
class Buby
|
4
|
+
|
5
|
+
class ScanIssuesList < BubyArrayWrapper
|
6
|
+
def initialize(obj)
|
7
|
+
ScanIssueHelper.implant(obj[0]) if obj.size > 0
|
8
|
+
super(obj)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module ScanIssueHelper
|
14
|
+
# Returns a Ruby URI object derived from the java.net.URL object
|
15
|
+
def uri
|
16
|
+
@uri ||= URI.parse url.to_s if not url.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
# one-shot method to implant ourselves onto a target object's class
|
20
|
+
# interface in ruby. All later instances will also get 'us' for free!
|
21
|
+
def self.implant(base)
|
22
|
+
return if @implanted
|
23
|
+
base.class.instance_eval { include(ScanIssueHelper) }
|
24
|
+
@implanted = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def http_messages
|
28
|
+
HttpRequestResponseList.new( self.getHttpMessages() )
|
29
|
+
end
|
30
|
+
alias messages http_messages
|
31
|
+
alias messages http_messages
|
32
|
+
|
33
|
+
def self.implanted? ; @implanted; end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
data/samples/drb_buby.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'buby'
|
4
|
+
require 'drb'
|
5
|
+
|
6
|
+
module DrbBuby
|
7
|
+
attr_reader :drb_server
|
8
|
+
|
9
|
+
def evt_register_callbacks(cb)
|
10
|
+
super(cb)
|
11
|
+
# cb.issueAlert("[DrbBuby] Service on: #{@drb_server.uri}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def init_DrbBuby
|
15
|
+
## want to bind the DRb service on a specific socket?
|
16
|
+
uri ='druby://127.0.0.1:9999'
|
17
|
+
## or let it choose one automatically:
|
18
|
+
# uri = nil
|
19
|
+
@drb_server = DRb.start_service uri, self
|
20
|
+
puts "[DrbBuby] Service on: #{@drb_server.uri}"
|
21
|
+
self.alert("[DrbBuby] Service on: #{@drb_server.uri}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if __FILE__ == $0
|
26
|
+
$burp = Buby.new
|
27
|
+
$burp.extend(DrbBuby)
|
28
|
+
$burp.start_burp()
|
29
|
+
$burp.init_DrbBuby
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# notice... we're using MRI ruby here, not JRuby (but either will work)
|
3
|
+
|
4
|
+
require 'drb'
|
5
|
+
|
6
|
+
unless drb_uri = ARGV.shift
|
7
|
+
STDERR.puts "Usage: #{File.basename $0} druby://<addr>:<port>"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
drb = DRbObject.new nil, drb_uri
|
12
|
+
rsp=drb.make_http_request 'example.com', 80, false, "GET / HTTP/1.0\r\n\r\n"
|
13
|
+
|
14
|
+
puts rsp
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env jruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'buby'
|
5
|
+
require 'mechanize'
|
6
|
+
require 'rbkb/http'
|
7
|
+
require 'irb'
|
8
|
+
|
9
|
+
include Java
|
10
|
+
|
11
|
+
# This Buby handler implementation simply keeps cookie state synched
|
12
|
+
# for a Mechanize agent by intercepting all requests sent through the
|
13
|
+
# Burp proxy. This lets you use Mechanize in tandem with your browser
|
14
|
+
# through Burp without having to fuss around with cookies.
|
15
|
+
module MechGlue
|
16
|
+
attr_accessor :mech_agent
|
17
|
+
|
18
|
+
def evt_proxy_message(*param)
|
19
|
+
msg_ref, is_req, rhost, rport, is_https, http_meth, url,
|
20
|
+
resourceType, status, req_content_type, message, action = param
|
21
|
+
|
22
|
+
if (not is_req) and (message =~ /Set-Cookie/i)
|
23
|
+
|
24
|
+
rsp = Rbkb::Http::Response.new(message, :ignore_content_length => true)
|
25
|
+
|
26
|
+
# Get an uri object ready for mechanize
|
27
|
+
uri = URI.parse(url)
|
28
|
+
uri.scheme = (is_https)? "https" : "http"
|
29
|
+
uri.host = rhost
|
30
|
+
uri.port = rport
|
31
|
+
|
32
|
+
# Grab cookies from headers:
|
33
|
+
rsp.headers.get_header_value('Set-Cookie').each do |cookie|
|
34
|
+
WWW::Mechanize::Cookie.parse(uri, cookie) do |c|
|
35
|
+
@mech_agent.cookie_jar.add(uri, c)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return(message)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
if __FILE__ == $0
|
45
|
+
$mech = WWW::Mechanize.new
|
46
|
+
#$mech.set_proxy('localhost', '8080')
|
47
|
+
|
48
|
+
$burp = Buby.new()
|
49
|
+
$burp.extend(MechGlue)
|
50
|
+
$burp.mech_agent = $mech
|
51
|
+
$burp.start_burp
|
52
|
+
|
53
|
+
puts "$burp is set to #{$burp.class}"
|
54
|
+
puts "$mech is set to #{$mech.class}"
|
55
|
+
IRB.start
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'htmlentities'
|
3
|
+
|
4
|
+
# This set up as a buby module extension. You can load it from the CLI when
|
5
|
+
# launching buby as follows:
|
6
|
+
#
|
7
|
+
# buby -r csrf_poc_generator.rb -e CsrfPocGenerator -i
|
8
|
+
#
|
9
|
+
module PocGenerator
|
10
|
+
HEX = %w{0 1 2 3 4 5 6 7 8 9 a b c d e f}
|
11
|
+
|
12
|
+
# Take a HTTP POST string and convert it to HTML proof of concept with a
|
13
|
+
# form full of hidden variables that gets auto-submitted on load.
|
14
|
+
#
|
15
|
+
# Especially handy for burp since you don't always know whether what you're
|
16
|
+
# playing with in repeater will actually work in the browser. Also handy for
|
17
|
+
# testing reflected/stored XSS if the request happens to require a POST.
|
18
|
+
#
|
19
|
+
# Handles www-form-urlencoded as well as multi-part. Note if your multi-part
|
20
|
+
# data has any files in it, they'll just get jammed into a hidden var with
|
21
|
+
# the rest. This may or may not be what you actually want.
|
22
|
+
#
|
23
|
+
# @param req can be a index into proxy_history, string, java byte array, or
|
24
|
+
# HttpRequestResponse object
|
25
|
+
#
|
26
|
+
# @param options:
|
27
|
+
# :uri = uri to set form action to (default - auto)
|
28
|
+
# :meth = form http method (default - POST)
|
29
|
+
# :enc = form enctype (default - 'application/www-form-urlencoded')
|
30
|
+
# :host = override 'host' in uri with this. (default - auto)
|
31
|
+
# :scheme = override 'scheme' in uri with this. (default - 'http')
|
32
|
+
# :port = override 'port' in uri with this. (default - blank)
|
33
|
+
#
|
34
|
+
# The URI, host scheme, and port are all automatically pulled from req if
|
35
|
+
# it is a HttpRequest. Otherwise, the code will attempt to gather this info
|
36
|
+
# from the Host: header and HTTP action. Note that in the latter case, the
|
37
|
+
# determination of http/https is impossible, so you'll want to specify the
|
38
|
+
# scheme
|
39
|
+
def post_to_poc(req, opts = {})
|
40
|
+
uri = opts[:uri]
|
41
|
+
meth = opts[:meth] || "POST"
|
42
|
+
enc = opts[:enc] || opts[:encoding] || 'application/www-form-urlencoded'
|
43
|
+
host = opts[:host]
|
44
|
+
scheme = opts[:scheme]
|
45
|
+
port = opts[:port]
|
46
|
+
|
47
|
+
reqstr =
|
48
|
+
if req.kind_of? String
|
49
|
+
req
|
50
|
+
elsif req.respond_to? :java_class and req.java_class.to_s == "[B"
|
51
|
+
String.from_java_bytes
|
52
|
+
elsif req.kind_of? Numeric
|
53
|
+
req = getProxyHistory()[req]
|
54
|
+
uri ||= req.uri
|
55
|
+
req.req_str
|
56
|
+
elsif req.respond_to?(:req_str)
|
57
|
+
req.req_str
|
58
|
+
end
|
59
|
+
|
60
|
+
params = getParameters(reqstr).to_a.select {|x| x[2] == "body parameter"}.map {|p| [p[0], p[1].chomp]}
|
61
|
+
headers = getHeaders(reqstr).to_a
|
62
|
+
verb, action, vers = headers.first.split(/\s+/, 3)
|
63
|
+
|
64
|
+
if headers.grep(/^content-type: application\/(?:x-)?(?:www-form-url|url-)encoded(?:\W|$)/i)
|
65
|
+
params = params.map do |p|
|
66
|
+
p.map do |f|
|
67
|
+
f.gsub(/%[a-f0-9]{2}/i) do |x|
|
68
|
+
((HEX.index(x[1,1].downcase) << 4) + HEX.index(x[2,1].downcase)).chr
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if uri.nil?
|
75
|
+
uri =
|
76
|
+
if req.respond_to? :uri
|
77
|
+
uri = req.uri
|
78
|
+
else
|
79
|
+
uri = URI.parse(action)
|
80
|
+
end
|
81
|
+
elsif uri.is_a? String
|
82
|
+
uri = URI.parse(action)
|
83
|
+
end
|
84
|
+
|
85
|
+
if opts[:host]
|
86
|
+
uri.host = host
|
87
|
+
uri.scheme = scheme if scheme
|
88
|
+
elsif host.nil? and hhost = headers.grep(/host: (.*)$/i){|h| $1 }.first
|
89
|
+
uri.host, uri.port = hhost.split(':', 2)
|
90
|
+
uri.scheme = scheme || 'http'
|
91
|
+
else
|
92
|
+
uri.scheme = scheme if scheme
|
93
|
+
end
|
94
|
+
|
95
|
+
uri.port = port if port
|
96
|
+
|
97
|
+
ret = %{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
98
|
+
<html><head></head>
|
99
|
+
<body>
|
100
|
+
<form name="doit_form" method="#{meth.to_s.upcase}" action=#{uri.to_s.inspect} enctype=#{enc.inspect}>
|
101
|
+
}
|
102
|
+
|
103
|
+
coder = HTMLEntities.new
|
104
|
+
params.each do |p|
|
105
|
+
name = coder.encode(p[0]).inspect
|
106
|
+
val = coder.encode(p[1]).inspect
|
107
|
+
ret << " <input type=\"hidden\" name=#{name} value=#{val} />\n"
|
108
|
+
end
|
109
|
+
|
110
|
+
ret << %{
|
111
|
+
<input type="submit" value="click me if this doesnt redirect'" />
|
112
|
+
</form>
|
113
|
+
<script> document.doit_form.submit(); </script>
|
114
|
+
</body>
|
115
|
+
</html>}
|
116
|
+
|
117
|
+
return ret
|
118
|
+
end
|
119
|
+
|
120
|
+
def init_CsrfPocGenerator
|
121
|
+
# nop
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env jruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'buby'
|
4
|
+
|
5
|
+
module VerbTamperer
|
6
|
+
def evt_proxy_message(*param)
|
7
|
+
msg_ref, is_req, rhost, rport, is_https, http_meth, url,
|
8
|
+
resourceType, status, req_content_type, message, action = param
|
9
|
+
|
10
|
+
if is_req and http_meth == "GET"
|
11
|
+
message[0,3] = "PET"
|
12
|
+
action[0] = Buby::ACTION_DONT_INTERCEPT
|
13
|
+
|
14
|
+
return super(*param).dup
|
15
|
+
else
|
16
|
+
return super(*param)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if __FILE__ == $0
|
22
|
+
$burp = Buby.new()
|
23
|
+
$burp.extend(VerbTamperer)
|
24
|
+
$burp.start_burp()
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module WatchScan
|
3
|
+
def evt_http_message(tool_name, is_request, message_info)
|
4
|
+
super(tool_name, is_request, message_info)
|
5
|
+
if tool_name == 'scanner'
|
6
|
+
if is_request
|
7
|
+
puts "#"*70, "# REQUEST: #{message_info.url.toString}", "#"*70
|
8
|
+
puts message_info.req_str
|
9
|
+
puts
|
10
|
+
else
|
11
|
+
puts "#"*70, "# RESPONSE: #{message_info.url.toString}", "#"*70
|
12
|
+
puts message_info.rsp_str
|
13
|
+
puts
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def init_WatchScan
|
19
|
+
puts "WatchScan module initialized"
|
20
|
+
end
|
21
|
+
end
|