cross 0.60.0 → 0.70.0
Sign up to get free protection for your applications and to get access to all the features.
- 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