timing_attack 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c07deac97e0cdfadcb0c1249fc09a950642030b
4
- data.tar.gz: b565450cb34a4c42a6e7a89394296546834c37cc
3
+ metadata.gz: 5287eec2e908eee5eb488df3f54f34331db4657e
4
+ data.tar.gz: 411d19b42241bd668205812445bae18139bb8292
5
5
  SHA512:
6
- metadata.gz: 27f0cbb88c3038b68d16764d8170a924c0c344117545c8ca37877e854d7b80526297b37f866c53cdc1ad0f5615df354dd898ba3267dd5ccb428db4d6d66a25af
7
- data.tar.gz: 0507d9712a8d68d75d5686c7aa3e32e9042a3f7f9116dda113eac59bfed863e5a9203315a57bb1c0e5e38732bcfc3ff0e661f82f87b0b40be6a0d05f61d3b35e
6
+ metadata.gz: 494cc10f5f13d3fc1057c8b697cfa5ab3a66f1982f08d8178105729ec0c3feb672510408a53c426ad719197d1cbc2d81f79b5420cda67ae2bdb54818540d6470
7
+ data.tar.gz: d89c6979cb2cb61cd73bafaec6d5d5e0304a87e65e09ceec332dd0623d49e2384d22ba80fa81f6ff5912bd26f0abc0e9c7caad727ad6a70cf7a8dfd7940b0964
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Gem Version](https://badge.fury.io/rb/timing_attack.svg)](http://badge.fury.io/rb/timing_attack)
1
2
  # timing_attack
2
3
 
3
4
  Profile web applications, sorting inputs into two categories based on
data/bin/console CHANGED
@@ -3,12 +3,5 @@
3
3
  require "bundler/setup"
4
4
  require "timing_attack"
5
5
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
6
+ require "pry"
7
+ Pry.start
data/exe/timing_attack CHANGED
@@ -1,68 +1,98 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'timing_attack'
3
3
 
4
- options = {}
5
- opt_parser = OptionParser.new do |opts|
6
- opts.program_name = File.basename(__FILE__)
7
- opts.banner = "#{opts.program_name} [options] -u <target> <inputs>"
8
- opts.on("-u URL", "--url URL", "URL of endpoint to profile") { |str| options[:url] = str }
9
- opts.on("-n NUM", "--number NUM", "Requests per input (default: 50)") do |num|
10
- options[:iterations] = num.to_i
4
+ class TimingAttackCli
5
+ attr_reader :options
6
+ def initialize
7
+ @options = {}
11
8
  end
12
- opts.on("-c NUM", "--concurrency NUM", "Number of concurrent requests (default: 15)") do |num|
13
- options[:concurrency] = num.to_i
14
- end
15
- opts.on("-t NUM", "--threshold NUM", "Minimum threshold, in seconds, for meaningfulness (default: 0.025)") do |num|
16
- options[:threshold] = num.to_f
17
- end
18
- opts.on("-p", "--post", "Use POST, not GET") { |bool| options[:method] = bool ? :post : :get }
19
- opts.on("-q", "--quiet", "Quiet mode (don't display progress bars)") { |bool| options[:verbose] = !bool }
20
- opts.on("--brute_force", "Brute force mode") { |bool| options[:brute_force] = bool }
21
- opts.on("--parameters STR", "JSON hash of parameters. 'INPUT' will be replaced with the attack string") do |str|
22
- options[:params] = JSON.parse(str)
9
+
10
+ def run
11
+ parse_options
12
+ sanitize_options
13
+ execute_attack
23
14
  end
24
- opts.on("--body STR", "JSON of body paramets to be sent to Typhoeus. 'INPUT' will be replaced with the attack string") do |str|
25
- options[:body] = JSON.parse(str)
15
+
16
+ private
17
+
18
+ def opt_parser
19
+ @opt_parser ||= OptionParser.new do |opts|
20
+ opts.program_name = File.basename(__FILE__)
21
+ opts.banner = "#{opts.program_name} [options] -u <target> <inputs>"
22
+ opts.on("-u URL", "--url URL", "URL of endpoint to profile") { |str| options[:url] = str }
23
+ opts.on("-n NUM", "--number NUM", "Requests per input (default: 50)") do |num|
24
+ options[:iterations] = num.to_i
25
+ end
26
+ opts.on("-c NUM", "--concurrency NUM", "Number of concurrent requests (default: 15)") do |num|
27
+ options[:concurrency] = num.to_i
28
+ end
29
+ opts.on("-t NUM", "--threshold NUM", "Minimum threshold, in seconds, for meaningfulness (default: 0.025)") do |num|
30
+ options[:threshold] = num.to_f
31
+ end
32
+ opts.on("-p", "--post", "Use POST, not GET") { |bool| options[:method] = bool ? :post : :get }
33
+ opts.on("-q", "--quiet", "Quiet mode (don't display progress bars)") { |bool| options[:verbose] = !bool }
34
+ opts.on("-b", "--brute-force", "Brute force mode") { |bool| options[:brute_force] = bool }
35
+ opts.on("--parameters STR", "JSON hash of parameters. 'INPUT' will be replaced with the attack string") do |str|
36
+ options[:params] = JSON.parse(str)
37
+ end
38
+ opts.on("--body STR", "JSON of body paramets to be sent to Typhoeus. 'INPUT' will be replaced with the attack string") do |str|
39
+ options[:body] = JSON.parse(str)
40
+ end
41
+ opts.on("--http-username STR", "HTTP basic authentication username. 'INPUT' will be replaced with the attack string") do |str|
42
+ options[:basic_auth_username] = str
43
+ end
44
+ opts.on("--http-password STR", "HTTP basic authentication password. 'INPUT' will be replaced with the attack string") do |str|
45
+ options[:basic_auth_password] = str
46
+ end
47
+ opts.on("--percentile NUM", "Use NUMth percentile for calculations (default: 3)") { |num| options[:percentile] = num.to_i }
48
+ opts.on("--mean", "Use mean for calculations") { |bool| options[:mean] = bool }
49
+ opts.on("--median", "Use median for calculations") { |bool| options[:median] = bool }
50
+ opts.on_tail("-v", "--version", "Print version information") do
51
+ gem = Gem::Specification.find_by_name('timing_attack')
52
+ puts "#{gem.name} #{gem.version}"
53
+ exit
54
+ end
55
+ opts.on_tail("-h", "--help", "Display this screen") { puts opts ; exit }
56
+ end
26
57
  end
27
- opts.on("--percentile NUM", "Use NUMth percentile for calculations (default: 3)") { |num| options[:percentile] = num.to_i }
28
- opts.on("--mean", "Use mean for calculations") { |bool| options[:mean] = bool }
29
- opts.on("--median", "Use median for calculations") { |bool| options[:median] = bool }
30
- opts.on_tail("-v", "--version", "Print version information") do
31
- gem = Gem::Specification.find_by_name('timing_attack')
32
- puts "#{gem.name} #{gem.version}"
33
- exit
58
+
59
+ def parse_options
60
+ begin
61
+ opt_parser.parse!
62
+ rescue OptionParser::InvalidOption => e
63
+ STDERR.puts e.message
64
+ puts opt_parser
65
+ exit
66
+ end
34
67
  end
35
- opts.on_tail("-h", "--help", "Display this screen") { puts opts ; exit }
36
- end
37
68
 
38
- begin
39
- opt_parser.parse!
40
- rescue OptionParser::InvalidOption => e
41
- STDERR.puts e.message
42
- puts opt_parser
43
- exit
44
- end
45
- options[:verbose] = true if options[:verbose].nil?
46
- if options[:percentile]
47
- options.delete(:mean)
48
- elsif options[:median]
49
- options[:percentile] = 50
50
- elsif options[:mean]
51
- options.delete(:percentile)
52
- end
69
+ def sanitize_options
70
+ options[:verbose] = true if options[:verbose].nil?
71
+ if options[:percentile]
72
+ options.delete(:mean)
73
+ elsif options[:median]
74
+ options[:percentile] = 50
75
+ elsif options[:mean]
76
+ options.delete(:percentile)
77
+ end
78
+ end
53
79
 
54
- begin
55
- atk = if options.delete(:brute_force)
56
- TimingAttack::BruteForcer.new(options: options)
57
- else
58
- TimingAttack::Enumerator.new(inputs: ARGV, options: options)
59
- end
60
- atk.run!
61
- rescue ArgumentError => e
62
- STDERR.puts e.message
63
- puts opt_parser
64
- exit
65
- rescue Interrupt => e
66
- puts "\nCaught interrupt, exiting"
67
- exit
80
+ def execute_attack
81
+ begin
82
+ atk = if options.delete(:brute_force)
83
+ TimingAttack::BruteForcer.new(options: options)
84
+ else
85
+ TimingAttack::Enumerator.new(inputs: ARGV, options: options)
86
+ end
87
+ atk.run!
88
+ rescue ArgumentError => e
89
+ STDERR.puts e.message
90
+ puts opt_parser
91
+ exit
92
+ rescue Interrupt
93
+ puts "\nCaught interrupt, exiting"
94
+ exit
95
+ end
96
+ end
68
97
  end
98
+ TimingAttackCli.new.run
@@ -1,5 +1,15 @@
1
1
  module TimingAttack
2
2
  module Attacker
3
+ def initialize(options: {}, inputs: [])
4
+ @options = default_options.merge(options)
5
+ raise ArgumentError.new("Must provide url") if url.nil?
6
+ unless specified_input_option?
7
+ msg = "'#{INPUT_FLAG}' not found in url, parameters, body, or HTTP authentication options"
8
+ raise ArgumentError.new(msg)
9
+ end
10
+ raise ArgumentError.new("Iterations can't be < 3") if iterations < 3
11
+ end
12
+
3
13
  def run!
4
14
  if verbose?
5
15
  puts "Target: #{url}"
@@ -29,7 +39,30 @@ module TimingAttack
29
39
  concurrency: 15,
30
40
  params: {},
31
41
  body: {},
42
+ basic_auth_username: "",
43
+ basic_auth_password: ""
32
44
  }.freeze
33
45
  end
46
+
47
+ def option_contains_input?(obj)
48
+ case obj
49
+ when String
50
+ obj.include?(INPUT_FLAG)
51
+ when Symbol
52
+ option_contains_input?(obj.to_s)
53
+ when Array
54
+ obj.any? {|el| option_contains_input?(el) }
55
+ when Hash
56
+ option_contains_input?(obj.keys) || option_contains_input?(obj.values)
57
+ end
58
+ end
59
+
60
+ def input_options
61
+ @input_options ||= %i(basic_auth_password basic_auth_username body params url)
62
+ end
63
+
64
+ def specified_input_option?
65
+ input_options.any? { |opt| option_contains_input?(options[opt]) }
66
+ end
34
67
  end
35
68
  end
@@ -3,8 +3,7 @@ module TimingAttack
3
3
  include TimingAttack::Attacker
4
4
 
5
5
  def initialize(options: {})
6
- @options = default_options.merge(options)
7
- raise ArgumentError.new("Must provide :url key") if url.nil?
6
+ super(options: options)
8
7
  @known = ""
9
8
  end
10
9
 
@@ -4,10 +4,8 @@ module TimingAttack
4
4
 
5
5
  def initialize(inputs: [], options: {})
6
6
  @inputs = inputs
7
- @options = default_options.merge(options)
8
- raise ArgumentError.new("url is a required argument") unless options.has_key? :url
9
7
  raise ArgumentError.new("Need at least 2 inputs") if inputs.count < 2
10
- raise ArgumentError.new("Iterations can't be < 3") if iterations < 3
8
+ super(options: options)
11
9
  @attacks = inputs.map { |input| TestCase.new(input: input, options: @options) }
12
10
  end
13
11
 
@@ -1,7 +1,6 @@
1
1
  require 'uri'
2
2
  module TimingAttack
3
3
  class TestCase
4
- INPUT_FLAG = "INPUT"
5
4
 
6
5
  attr_reader :input
7
6
  def initialize(input: , options: {})
@@ -16,20 +15,36 @@ module TimingAttack
16
15
  )
17
16
  @params = params_from(options.fetch :params, {})
18
17
  @body = params_from(options.fetch :body, {})
18
+ @basic_auth_username = params_from(
19
+ options.fetch(:basic_auth_username, "")
20
+ )
21
+ @basic_auth_password = params_from(
22
+ options.fetch(:basic_auth_password, "")
23
+ )
19
24
  end
20
25
 
21
26
  def generate_hydra_request!
22
- req = Typhoeus::Request.new(
23
- url,
24
- method: options.fetch(:method),
25
- followlocation: true,
26
- params: params,
27
- body: body
28
- )
27
+ req = Typhoeus::Request.new(url, **typhoeus_opts)
29
28
  @hydra_requests.push req
30
29
  req
31
30
  end
32
31
 
32
+ def typhoeus_opts
33
+ {
34
+ method: options.fetch(:method),
35
+ followlocation: true,
36
+ }.tap do |h|
37
+ h[:params] = params unless params.empty?
38
+ h[:body] = body unless body.empty?
39
+ h[:userpwd] = typhoeus_basic_auth unless typhoeus_basic_auth.empty?
40
+ end
41
+ end
42
+
43
+ def typhoeus_basic_auth
44
+ return "" if basic_auth_username.empty? && basic_auth_password.empty?
45
+ "#{basic_auth_username}:#{basic_auth_password}"
46
+ end
47
+
33
48
  def process!
34
49
  @hydra_requests.each do |request|
35
50
  response = request.response
@@ -70,5 +85,6 @@ module TimingAttack
70
85
  end
71
86
 
72
87
  attr_reader :times, :options, :percentiles, :url, :params, :body
88
+ attr_reader :basic_auth_username, :basic_auth_password
73
89
  end
74
90
  end
@@ -1,3 +1,3 @@
1
1
  module TimingAttack
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/timing_attack.rb CHANGED
@@ -12,4 +12,5 @@ require "timing_attack/test_case"
12
12
  require "timing_attack/enumerator"
13
13
 
14
14
  module TimingAttack
15
+ INPUT_FLAG = "INPUT"
15
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timing_attack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Forrest Fleming
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-05 00:00:00.000000000 Z
11
+ date: 2017-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-progressbar