timing_attack 0.4.1 → 0.5.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.
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