buby 1.1.6-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|