API_Fuzzer 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ require 'API_Fuzzer/vulnerability'
2
+ require 'API_Fuzzer/error'
3
+ require 'API_Fuzzer/request'
4
+
5
+ module API_Fuzzer
6
+ class IdorCheck
7
+ class << self
8
+ def scan(options = {})
9
+ @url = options[:url]
10
+ @params = options[:params]
11
+ @methods = options[:method]
12
+ @headers = options[:headers] || {}
13
+ @cookies = options[:cookies]
14
+ @vulnerabilities = []
15
+
16
+ fuzz_without_session
17
+ @vulnerabilities.uniq { |vuln| vuln.description }
18
+ end
19
+
20
+ def fuzz_without_session
21
+ @methods.each do |method|
22
+ response = API_Fuzzer::Request.send_api_request(
23
+ url: @url,
24
+ params: @params,
25
+ method: method,
26
+ headers: @headers,
27
+ cookies: @cookies
28
+ )
29
+
30
+ response_without_session = API_Fuzzer::Request.send_api_request(
31
+ url: @url,
32
+ params: @params,
33
+ method: method
34
+ )
35
+
36
+ fuzz_sensitive_files(response, method)
37
+ fuzz_match(response, response_without_session, method)
38
+ end
39
+ end
40
+
41
+ def fuzz_match(resp, resp_without_session, method)
42
+ @vulnerabilities << API_Fuzzer::Vulnerability.new(
43
+ type: 'HIGH',
44
+ value: "API doesn't have access control protection",
45
+ description: "Possible IDOR in #{method} #{@url}"
46
+ ) if resp.body.to_s == resp_without_session.body.to_s
47
+ end
48
+
49
+ def fuzz_sensitive_files(response, method)
50
+ file_url = /^((https?:\/\/)?(www\.)?([\da-z\.-]+)\.([a-z\.]{2,6})\/[\w \.-]+?\.(pdf|doc|docs|rtf)([a-zA-Z0-9=?]*?))$/
51
+ flagged_url = response.body.to_s.scan(file_url) || []
52
+ flagged_url.each do |url|
53
+ @vulnerabilities << API_Fuzzer::Vulnerability.new(
54
+ type: 'MEDIUM',
55
+ value: "File #{url} can be accessed without proper permissions",
56
+ description: "Access control violation in #{method} #{url}"
57
+ )
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,78 @@
1
+ require 'API_Fuzzer/vulnerability'
2
+ require 'API_Fuzzer/error'
3
+ require 'API_Fuzzer/request'
4
+
5
+ module API_Fuzzer
6
+ class PrivilegeEscalationCheck
7
+ class << self
8
+ def scan(options = {})
9
+ @url = options[:url]
10
+ @params = options[:params] || {}
11
+ @headers = options[:headers] || {}
12
+ @methods = options[:method] || []
13
+ @cookies = options[:cookies] || {}
14
+
15
+ @vulnerabilities = []
16
+ fuzz_privileges
17
+ @vulnerabilities.uniq { |vuln| vuln.description }
18
+ rescue Exception => e
19
+ Rails.logger.info e.message
20
+ end
21
+
22
+ def fuzz_privileges
23
+ id = /\A\d+\z/
24
+ uri = URI(@url)
25
+ path = uri.path
26
+ query = uri.query
27
+ url = @url
28
+ base_uri = query.nil? ? path : [path, query].join("?")
29
+ fragments = base_uri.split(/[\/,?,&]/) - ['']
30
+ fragments.each do |fragment|
31
+ if fragment.match(/\A(\w)+=(\w)*\z/)
32
+ key, value = fragment.split("=")
33
+ if value.match(id)
34
+ value = value.to_i
35
+ value += 1
36
+ url = url.gsub(fragment, [key, value].join("=")).chomp
37
+ fuzz_identity(url, @params)
38
+ end
39
+ elsif fragment.match(id)
40
+ value = fragment.to_i
41
+ value += 1
42
+ url = url.gsub(fragment, value.to_s).chomp if url
43
+ fuzz_identity(url, @params, url)
44
+ end
45
+ end
46
+ return if @params.empty?
47
+
48
+ parameters = @params
49
+ parameters.keys.each do |parameter|
50
+ value = parameters[parameter]
51
+ if value.match(id)
52
+ value = value.to_i
53
+ value += 1
54
+ info = [parameter, value].join(" ")
55
+ fuzz_identity(@url, parameters.merge(parameter, value), info)
56
+ end
57
+ end
58
+ end
59
+
60
+ def fuzz_identity(url, params, value)
61
+ @methods.each do |method|
62
+ response = API_Fuzzer::Request.send_api_request(
63
+ url: url,
64
+ method: method,
65
+ params: @params,
66
+ cookies: @cookies,
67
+ headers: @headers
68
+ )
69
+ @vulnerabilities << API_Fuzzer::Vulnerability.new(
70
+ type: 'HIGH',
71
+ value: "ID in #{value} parameter is vulnerable to Privilege Escalation vulnerability.",
72
+ description: "Privilege Escalation vulnerability in #{method} #{url}"
73
+ ) if response.code == 200
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,69 @@
1
+ require 'API_Fuzzer/vulnerability'
2
+ require 'API_Fuzzer/request'
3
+
4
+ module API_Fuzzer
5
+ class RateLimitCheck
6
+ def self.scan(options = {})
7
+ @url = options[:url]
8
+ @params = options[:params] || {}
9
+ @headers = options[:headers] || {}
10
+ @cookies = options[:cookies] || {}
11
+ @vulnerabilities = []
12
+ @limit = options[:limit] || 50
13
+ @methods = options[:method] || [:get]
14
+
15
+ @methods.each { |method| fuzz_api_requests(method) }
16
+ @vulnerabilities.uniq { |vuln| vuln.description }
17
+ end
18
+
19
+ def self.fuzz_api_requests(method)
20
+ initial_response = fetch_initial_response(method)
21
+
22
+ responses = []
23
+ @limit.times do
24
+ responses << API_Fuzzer::Request.send_api_request(
25
+ url: @url,
26
+ method: method,
27
+ cookies: @cookies,
28
+ headers: @headers,
29
+ params: @params
30
+ )
31
+ end
32
+
33
+ vulnerable = true
34
+ responses.each do |response|
35
+ if response.code == initial_response.code
36
+ content_length = response_content_length(response)
37
+ initial_content_length = response_content_length(initial_response)
38
+ if content_length != initial_content_length
39
+ vulnerable = false
40
+ break
41
+ end
42
+ else
43
+ vulnerable = false
44
+ break
45
+ end
46
+ end
47
+ @vulnerabilities << API_Fuzzer::Vulnerability.new(
48
+ description: "API is not rate limited for #{method} #{@url}",
49
+ value: "API doesn't have any ratelimiting protection enabled which can be implemented by either throttling request or using captcha",
50
+ type: 'LOW'
51
+ ) if vulnerable
52
+ end
53
+
54
+ private
55
+ def self.fetch_initial_response(method)
56
+ API_Fuzzer::Request.send_api_request(
57
+ url: @url,
58
+ method: method,
59
+ cookies: @cookies,
60
+ headers: @headers,
61
+ params: @params
62
+ )
63
+ end
64
+
65
+ def self.response_content_length(response)
66
+ response.headers['Content-Length'] || response.body.to_s.size
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,106 @@
1
+ require 'API_Fuzzer/vulnerability'
2
+ require 'API_Fuzzer/error'
3
+ require 'API_Fuzzer/request'
4
+ require 'uri'
5
+
6
+ module API_Fuzzer
7
+ class RedirectCheck
8
+ REDIRECT_URL = 'http://127.0.0.1:3000/ping'
9
+ ALLOWED_METHODS = [:get, :post]
10
+ class << self
11
+ def scan(options = {})
12
+ @url = options[:url]
13
+ @params = options[:params] || {}
14
+ @cookies = options[:cookies] || {}
15
+ @json = options[:json] || false
16
+ @headers = options[:headers] || {}
17
+
18
+ @vulnerabilities = []
19
+ fuzz_payload
20
+ return @vulnerabilities.uniq { |vuln| vuln.description }
21
+ rescue Exception => e
22
+ @vulnerabilities << API_Fuzzer::Error.new(
23
+ description: e.message,
24
+ status: 'ERROR',
25
+ value: e.backtrace
26
+ )
27
+ end
28
+
29
+ def fuzz_payload
30
+ uri = URI(@url)
31
+ path = uri.path
32
+ query = uri.query
33
+ # base_uri = query.nil? ? path : [path, query].join("?")
34
+ fragments = path.split(/[\/,?,&]/) - ['']
35
+ fragments << query.split('&') if query
36
+ fragments.flatten!
37
+ fragments.each do |fragment|
38
+ if fragment.match(/\A(\w+)=(.?*)\z/) && valid_url?($2)
39
+ url = @url.gsub($2, REDIRECT_URL).chomp
40
+ fuzz_fragment(url)
41
+ elsif valid_url?(fragment)
42
+ url = @url.gsub(fragment, REDIRECT_URL)
43
+ fuzz_fragment(url)
44
+ end
45
+ end
46
+ return if @params.empty?
47
+
48
+ @params.keys.each do |parameter|
49
+ fuzz_each_parameter(parameter) if valid_url? @params[parameter]
50
+ end
51
+ end
52
+
53
+ def fuzz_fragment(url)
54
+ ALLOWED_METHODS.each do |method|
55
+ begin
56
+ response = API_Fuzzer::Request.send_api_request(
57
+ url: url,
58
+ method: method,
59
+ cookies: @cookies,
60
+ params: @params,
61
+ headers: @headers
62
+ )
63
+
64
+ @vulnerabilities << API_Fuzzer::Vulnerability.new(
65
+ description: "Possible Open Redirect vulnerability in #{method} #{url}",
66
+ parameter: "URL: #{url}",
67
+ value: "[PAYLOAD] #{url.gsub(REDIRECT_URL, 'PAYLOAD_URL')}",
68
+ type: 'MEDIUM'
69
+ ) if response.headers['Location'] =~ /#{REDIRECT_URL}/
70
+ rescue Exception => e
71
+ puts e.message
72
+ end
73
+ end
74
+ end
75
+
76
+ def fuzz_each_parameter(parameter)
77
+ params = @params
78
+ params[parameter] = REDIRECT_URL
79
+ ALLOWED_METHODS.each do |method|
80
+ begin
81
+ response = API_Fuzzer::Request.send_api_request(
82
+ url: @url,
83
+ method: method,
84
+ cookies: @cookies,
85
+ params: params,
86
+ headers: @headers
87
+ )
88
+
89
+ @vulnerabilities << API_Fuzzer::Vulnerability.new(
90
+ description: "Possible Open Redirect vulnerability in #{method} #{url}",
91
+ parameter: "Parameter: #{parameter}",
92
+ value: "[PAYLOAD] #{params.to_s.gsub(REDIRECT_URL, 'PAYLOAD_URL')}",
93
+ type: 'MEDIUM'
94
+ ) if response.headers['LOCATION'] =~ /#{REDIRECT_URL}/
95
+ rescue Exception => e
96
+ puts e.message
97
+ end
98
+ end
99
+ end
100
+
101
+ def valid_url? url
102
+ url =~ URI.regexp
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,69 @@
1
+ require 'http'
2
+
3
+ module API_Fuzzer
4
+ class Request
5
+ attr_accessor :response, :request
6
+
7
+ class << self
8
+ def send_api_request(options = {})
9
+ @url = options.delete(:url)
10
+ @params = options.delete(:params) || {}
11
+ @method = options.delete(:method) || :get
12
+ @json = options.delete(:json) ? true : false
13
+ @body = options.delete(:body) ? true : false
14
+ @request = set_cookies_headers(options)
15
+ send_request
16
+ end
17
+ end
18
+
19
+ def response
20
+ @response
21
+ end
22
+
23
+ def success?
24
+ @response.code == 200
25
+ end
26
+
27
+ private
28
+
29
+ def self.set_cookies_headers(options = {})
30
+ cookies = options.delete(:cookies) || {}
31
+ headers = options.delete(:headers) || {}
32
+ request_object = HTTP.headers(headers).cookies(cookies)
33
+ request_object
34
+ end
35
+
36
+ def self.send_request
37
+ @response = case @method.to_sym
38
+ when :post
39
+ @request.post(@url, set_params)
40
+ when :put
41
+ @request.put(@url, set_params)
42
+ when :patch
43
+ @request.patch(@url, set_params)
44
+ when :head
45
+ @request.head(@url, set_params)
46
+ when :delete
47
+ @request.delete(@url, set_params)
48
+ else
49
+ @request.get(@url, set_params)
50
+ end
51
+ end
52
+
53
+ def self.set_params
54
+ if @json && !method_get?
55
+ { 'json' => @params }
56
+ elsif method_get?
57
+ { 'params' => @params }
58
+ elsif @body
59
+ { 'body' => @params }
60
+ else
61
+ { 'form' => @params }
62
+ end
63
+ end
64
+
65
+ def self.method_get?
66
+ @method.to_s == 'get'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,51 @@
1
+ require 'yaml'
2
+ require 'API_Fuzzer/vulnerability'
3
+
4
+ module API_Fuzzer
5
+
6
+ class InvalidResponse < StandardError; end
7
+
8
+ class ResourceInfo
9
+ # Accepts response and performs rules match based on the ruleset
10
+
11
+ class << self
12
+ def scan(response)
13
+ @response = response
14
+ if @response
15
+ fetch_rules
16
+ scan_rules
17
+ else
18
+ raise InvalidResponse, "Invalid response argument has been passed"
19
+ end
20
+ end
21
+
22
+ def fetch_rules
23
+ info_rules = File.expand_path('../../../rules', __FILE__)
24
+ @rules = YAML::load_file(File.join(info_rules, "info.yml"))['rules']
25
+ end
26
+
27
+ def scan_rules
28
+ @vulnerability_info = []
29
+
30
+ if @rules
31
+ headers = @response.headers.keys
32
+
33
+ @rules.each do |rule|
34
+ headers.each do |header|
35
+
36
+ if /#{rule['match'].downcase}/.match(header.downcase)
37
+ @vulnerability_info << API_Fuzzer::Vulnerability.new(
38
+ description: rule['description'],
39
+ value: [header, @response.headers[header].to_s].join(": "),
40
+ type: 'INFORMATIVE'
41
+ )
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ return @vulnerability_info
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ require 'API_Fuzzer/vulnerability'
2
+ require 'API_Fuzzer/error'
3
+ require 'API_Fuzzer/request'
4
+ require 'API_Fuzzer/sql_check'
5
+
6
+ module API_Fuzzer
7
+ class InvalidURLError < StandardError; end
8
+ class SqlBlindCheck < SqlCheck
9
+ PAYLOAD_PATH = '../../../payloads/blind_sql.txt'.freeze
10
+ SQL_ERRORS = []
11
+ SCAN_TIME = '20'
12
+ attr_accessor :payloads
13
+
14
+ def self.fuzz_each_parameter(parameter, payload)
15
+ @params[parameter] << payload
16
+ process_vulnerability(nil, payload)
17
+ end
18
+
19
+ def self.fuzz_each_fragment(url, payload)
20
+ process_vulnerability(url, payload)
21
+ end
22
+
23
+ def self.process_vulnerability(url, payload)
24
+ url = url ? url : @url
25
+ ALLOWED_METHODS.each do |method|
26
+ start_time = Time.now
27
+ response = API_Fuzzer::Request.send_api_request(
28
+ url: @url,
29
+ params: @params,
30
+ method: method,
31
+ cookies: @cookies,
32
+ headers: @headers
33
+ )
34
+ end_time = Time.now
35
+ diff = end_time - start_time
36
+ if diff > 20 && diff < 25
37
+ @vulnerabilities << API_Fuzzer::Vulnerability.new(
38
+ description: "Possible blind SQL injection in #{method} #{@url} parameter: #{parameter}",
39
+ value: "[PAYLOAD] #{payload}"
40
+ )
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.fetch_payloads
46
+ file = File.expand_path(PAYLOAD_PATH, __FILE__)
47
+ File.readlines(file).each do |line|
48
+ @payloads << line.gsub('__TIME__', SCAN_TIME).gsub('__MARK__', '20000000')
49
+ end
50
+ end
51
+ end
52
+ end