gauntlt 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +7 -1
  3. data/Gemfile +1 -1
  4. data/README.md +14 -8
  5. data/bin/gauntlt +28 -46
  6. data/examples/curl/cookies.attack +12 -0
  7. data/examples/curl/simple.attack +9 -0
  8. data/examples/curl/verbs.attack +19 -0
  9. data/examples/nmap/os_detection.attack +16 -0
  10. data/examples/nmap/simple.attack +16 -0
  11. data/examples/nmap/tcp_ping_ports.attack +16 -0
  12. data/examples/nmap/xml_output.attack +18 -0
  13. data/examples/sslyze/sslyze.attack +23 -0
  14. data/features/attack.feature +30 -19
  15. data/features/attacks/curl.feature +23 -15
  16. data/features/attacks/nmap.feature +16 -93
  17. data/features/attacks/sqlmap.feature +2 -3
  18. data/features/attacks/sslyze.feature +7 -29
  19. data/features/help.feature +3 -30
  20. data/features/step_definitions/config_steps.rb +1 -1
  21. data/features/step_definitions/support_steps.rb +15 -0
  22. data/features/support/hooks.rb +2 -2
  23. data/features/tags.feature +44 -0
  24. data/gauntlt.gemspec +0 -2
  25. data/lib/gauntlt.rb +7 -11
  26. data/lib/gauntlt/attack.rb +26 -22
  27. data/lib/gauntlt/attack_adapters/curl.rb +35 -1
  28. data/lib/gauntlt/attack_adapters/support/cli_helper.rb +1 -1
  29. data/lib/gauntlt/attack_adapters/support/cookie_helper.rb +3 -10
  30. data/lib/gauntlt/version.rb +1 -1
  31. data/spec/gauntlt/attack_spec.rb +13 -35
  32. data/spec/gauntlt_spec.rb +7 -14
  33. metadata +12 -30
  34. data/features/attacks/cookies.feature +0 -25
  35. data/features/attacks/http_methods.feature +0 -33
  36. data/features/step_definitions/aruba_extension_steps.rb +0 -3
  37. data/features/support/attack_steps.rb +0 -1
  38. data/features/support/profile/profile.xml +0 -5
  39. data/lib/gauntlt/attack_adapters/cookies.rb +0 -11
  40. data/lib/gauntlt/attack_adapters/http_methods.rb +0 -12
@@ -9,9 +9,8 @@ Feature: sqlmap attack
9
9
  Background:
10
10
  Given "sqlmap" is installed
11
11
  """
12
- When I run `gauntlt attack --name sqlmap --attack-file sqlmap.attack`
13
- Then it should pass
14
- And the output should contain:
12
+ When I run `gauntlt`
13
+ Then it should pass with:
15
14
  """
16
15
  1 step (1 passed)
17
16
  """
@@ -1,36 +1,14 @@
1
1
  Feature: sslyze attack
2
2
 
3
+ @slow
3
4
  Scenario:
4
5
  Given an attack "sslyze" exists
5
- And a file named "sslyze.attack" with:
6
- """
7
- Feature: Run sslyze against a target
8
-
9
- Background:
10
- Given "sslyze" is installed
11
- And the target hostname is "google.com"
12
-
13
- Scenario: Ensure no anonymous certificates
14
- When I launch an "sslyze" attack with:
15
- \"\"\"
16
- python <sslyze_path> <hostname>:443
17
- \"\"\"
18
- Then the output should not contain:
19
- \"\"\"
20
- Anon
21
- \"\"\"
22
-
23
- # Scenario: Make sure that the certificate key size is at least 2048
24
- # Given the target hostname is "google.com"
25
- # When I launch an "sslyze" attack with:
26
- # \"\"\"
27
- # python <sslyze_path> <hostname>:443
28
- # \"\"\"
29
- # Then the key size should be at least 2048
30
- """
31
- When I run `gauntlt attack --name sslyze --attack-file sslyze.attack`
32
- Then it should pass
33
- And the output should contain:
6
+ And I copy the attack files from the "examples/sslyze" folder
7
+ And the following attack files exist:
8
+ | filename |
9
+ | sslyze.attack |
10
+ When I run `gauntlt`
11
+ Then it should pass with:
34
12
  """
35
13
  4 steps (4 passed)
36
14
  """
@@ -8,35 +8,8 @@ Feature: Display help info
8
8
  When I run `gauntlt --help`
9
9
  Then the output should contain:
10
10
  """
11
- usage: gauntlt attack [<args>]
12
- """
13
-
14
- Scenario: Attack help
15
- When I run `gauntlt attack -h -n nmap`
16
- Then the output should contain:
17
- """
18
- usage: gauntlt attack -n [attack-name] -a [attack-file]
19
- """
20
-
21
- Scenario: A user runs gauntlt without any arguments
22
- When I run `gauntlt`
23
- Then the output should contain:
24
- """
25
- Try --help for help
26
- """
27
-
28
- Scenario: A user runs the attack command without specifying attack name
29
- When I run `gauntlt attack`
30
- Then the output should contain:
31
- """
32
- Available attacks:
33
-
34
- cookies
35
- curl
36
- http_methods
37
- nmap
38
- sqlmap
39
- sslyze
11
+ gauntlt is a ruggedization framework
40
12
 
41
- try: gauntlt attack -n nmap
13
+ Usage:
14
+ gauntlt <path>+
42
15
  """
@@ -1,3 +1,3 @@
1
1
  Given /^an attack "(.*?)" exists$/ do |attack_name|
2
- Gauntlt.should have_attack(attack_name)
2
+ Gauntlt.attacks.should include(attack_name)
3
3
  end
@@ -2,4 +2,19 @@ Then /^debug$/ do
2
2
  require 'debugger'
3
3
  debugger
4
4
  nil
5
+ end
6
+
7
+ require 'pathname'
8
+ Given /^I copy the attack files from the "(.*?)" folder$/ do |folder|
9
+ Dir.glob("./#{folder}/**/*.attack").each do |path|
10
+ name = Pathname.new(path).basename.to_s
11
+ contents = File.read(path)
12
+ write_file(name, contents)
13
+ end
14
+ end
15
+
16
+ Given /^the following attack files exist:$/ do |table|
17
+ table.hashes.each do |hsh|
18
+ check_file_presence [hsh['filename']], true
19
+ end
5
20
  end
@@ -1,3 +1,3 @@
1
1
  Before('@slow') do
2
- @aruba_timeout_seconds = 10
3
- end
2
+ @aruba_timeout_seconds = 30
3
+ end
@@ -0,0 +1,44 @@
1
+ Feature: Run attacks by tag
2
+
3
+ Background:
4
+ Given an attack "nmap" exists
5
+ And a file named "nmap.attack" with:
6
+ """
7
+ Feature: my nmap attacks
8
+
9
+ @foo
10
+ Scenario: Foo
11
+ Given the target hostname is "foo"
12
+
13
+ @bar
14
+ Scenario: Bar
15
+ Given the target hostname is "bar"
16
+ """
17
+
18
+ Scenario: Run attack for one tag
19
+ When I run `gauntlt --tags @foo`
20
+ Then it should pass with:
21
+ """
22
+ Feature: my nmap attacks
23
+
24
+ @foo
25
+ """
26
+ And the stdout should contain:
27
+ """
28
+ 1 scenario (1 passed)
29
+ 1 step (1 passed)
30
+ """
31
+
32
+ Scenario: Run attack by exluding one tag
33
+ When I run `gauntlt --tags ~@foo`
34
+ Then it should pass with:
35
+ """
36
+ Feature: my nmap attacks
37
+
38
+ @bar
39
+ """
40
+ And the stdout should contain:
41
+ """
42
+ 1 scenario (1 passed)
43
+ 1 step (1 passed)
44
+ """
@@ -16,7 +16,6 @@ Gem::Specification.new do |s|
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ["lib"]
18
18
 
19
- # specify any dependencies here; for example:
20
19
  s.add_development_dependency "cucumber"
21
20
  s.add_development_dependency "rspec", "~> 2.11"
22
21
  s.add_development_dependency "aruba"
@@ -24,7 +23,6 @@ Gem::Specification.new do |s|
24
23
 
25
24
  s.add_runtime_dependency "cucumber"
26
25
  s.add_runtime_dependency "aruba"
27
- s.add_runtime_dependency "curb"
28
26
  s.add_runtime_dependency "nokogiri"
29
27
  s.add_runtime_dependency "trollop"
30
28
  end
@@ -13,27 +13,23 @@ module Gauntlt
13
13
 
14
14
  GAUNTLT_DIR = File.join(CURRENT_DIR, 'gauntlt')
15
15
 
16
- ATTACKS_DIR = File.join(GAUNTLT_DIR, 'attack_adapters')
16
+ ATTACK_ADAPTERS_DIR = File.join(GAUNTLT_DIR, 'attack_adapters')
17
17
 
18
- ATTACK_GLOB_PATTERN = ATTACKS_DIR + '/*.rb'
18
+ ATTACK_ADAPTERS_GLOB_PATTERN = ATTACK_ADAPTERS_DIR + '/*.rb'
19
19
 
20
20
  class << self
21
- def attack_files
22
- Dir.glob(ATTACK_GLOB_PATTERN)
21
+ def attack_adapters
22
+ Dir.glob(ATTACK_ADAPTERS_GLOB_PATTERN)
23
23
  end
24
24
 
25
25
  def attacks
26
- attack_files.map do |full_path|
26
+ attack_adapters.map do |full_path|
27
27
  File.basename(full_path, '.rb')
28
28
  end.sort
29
29
  end
30
30
 
31
- def has_attack?(name)
32
- attacks.include?(name)
33
- end
34
-
35
- def attack(name, options={})
36
- Attack.new(name, options).run
31
+ def attack(path, tags=[])
32
+ Attack.new(path, tags).run
37
33
  end
38
34
  end
39
35
  end
@@ -3,39 +3,43 @@ require 'cucumber/cli/main'
3
3
 
4
4
  module Gauntlt
5
5
  class Attack
6
- class NotFound < Exception; end
7
- class ExecutionFailed < Exception; end
8
-
9
- attr_accessor :name, :opts, :attack_file
10
-
11
- def initialize(name, opts={})
12
- if opts[:attack_file] && File.exists?( opts[:attack_file] )
13
- self.name = name
14
- self.opts = opts
15
- self.attack_file = opts[:attack_file]
16
- else
17
- raise NotFound.new("No '#{opts[:attack_file]}' attack found")
18
- end
19
- end
6
+ class NoFilesFound < StandardError; end
7
+ class ExecutionFailed < StandardError; end
20
8
 
21
- def base_dir
22
- File.expand_path( File.dirname(__FILE__) )
23
- end
9
+ attr_accessor :path, :attack_files, :tags
24
10
 
25
- def attacks_dir
26
- File.join(base_dir, "attack_adapters")
11
+ def initialize(path, tags=[])
12
+ self.path = path
13
+ self.attack_files = attack_files_for(path)
14
+ self.tags = tags
15
+
16
+ raise NoFilesFound.new("No files found in path: #{path}") if attack_files.empty?
27
17
  end
28
18
 
29
19
  def run
30
- @out = StringIO.new ""
20
+ args = attack_files + ['--strict', '--require', adapters_dir]
21
+ args += ['--tags', tags] unless tags.empty?
31
22
 
32
- cli = Cucumber::Cli::Main.new([self.attack_file, '--strict', '--require', self.attacks_dir], @out)
23
+ cli = Cucumber::Cli::Main.new(args)
33
24
 
34
25
  if cli.execute! # cucumber failed, returning true
35
26
  raise ExecutionFailed.new("Bad or undefined attack!")
36
27
  else # cucumber executed successfully, returning false
37
- @out.string
28
+ true
38
29
  end
39
30
  end
31
+
32
+ private
33
+ def attack_files_for(path)
34
+ path.split(' ').map{|p| Dir.glob(p)}.flatten
35
+ end
36
+
37
+ def base_dir
38
+ File.expand_path( File.dirname(__FILE__) )
39
+ end
40
+
41
+ def adapters_dir
42
+ File.join(base_dir, "attack_adapters")
43
+ end
40
44
  end
41
45
  end
@@ -1,3 +1,37 @@
1
+ When /^"curl" is installed$/ do
2
+ ensure_cli_installed("curl")
3
+ end
4
+
5
+ When /^I launch a "curl" attack$/ do
6
+ # curl custom output
7
+ # from:
8
+ # http://beerpla.net/2010/06/10/how-to-display-just-the-http-response-code-in-cli-curl/
9
+ #
10
+ # for more output variables, see:
11
+ # http://man.he.net/man1/curl
12
+ @raw_response = `curl --silent --output /dev/null --write-out "%{http_code}" "#{hostname}"`
13
+ @response = {
14
+ :code => @raw_response
15
+ }
16
+ end
17
+
18
+ When /^I launch a "curl" attack with:$/ do |command|
19
+ command.gsub!('<hostname>', hostname)
20
+ run command
21
+ end
22
+
1
23
  Then /^the response code should be "(.*?)"$/ do |http_code|
2
- @response.response_code.should == http_code.to_i
24
+ @response[:code].should == http_code
25
+ end
26
+
27
+ When /^I launch a "cookies" attack$/ do
28
+ set_cookies( cookies_for(hostname) )
29
+ end
30
+
31
+ Then /^the following cookies should be received:$/ do |table|
32
+ names = table.hashes.map{|h| h['name'] }
33
+ names.each do |name|
34
+ cookies.any?{|s| s =~ /^#{name}/}.should be_true
35
+ # TODO: check other values in table
36
+ end
3
37
  end
@@ -21,5 +21,5 @@ end
21
21
  World(Gauntlt::Support::CliHelper)
22
22
 
23
23
  Before('@slow') do
24
- @aruba_timeout_seconds = 10
24
+ @aruba_timeout_seconds = 30
25
25
  end
@@ -1,18 +1,11 @@
1
- require 'curb'
2
-
3
1
  module Gauntlt
4
2
  module Support
5
3
  module CookieHelper
6
4
  def cookies_for(url)
7
- [].tap do |returner|
8
- c = Curl::Easy.perform(url) do |curl|
9
- curl.follow_location = true
10
- curl.enable_cookies = true
5
+ output = `curl --include --location --head --silent "#{url}"`
11
6
 
12
- curl.on_header do |header|
13
- returner << "#{$1}=#{$2}" if header =~ /^Set-Cookie: ([^=]+)=([^;]+;)/
14
- end
15
- end
7
+ output.scan(/^Set-Cookie:.+$/).map do |header|
8
+ "#{$1}=#{$2}" if header =~ /^Set-Cookie: ([^=]+)=([^;]+;)/
16
9
  end
17
10
  end
18
11
 
@@ -1,3 +1,3 @@
1
1
  module Gauntlt
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -2,64 +2,42 @@ require 'spec_helper'
2
2
 
3
3
  describe Gauntlt::Attack do
4
4
  before do
5
- File.stub(:exists?).with(:bar).and_return(true)
5
+ Gauntlt::Attack.any_instance.stub(:attack_files_for).with(:foo).and_return([:bar])
6
6
  end
7
7
 
8
8
  subject{
9
- Gauntlt::Attack.new(:foo, :attack_file => :bar)
9
+ Gauntlt::Attack.new(:foo)
10
10
  }
11
11
 
12
12
  describe :initialize do
13
13
  context "attack file exists for passed name" do
14
- it "sets name and opts" do
15
- subject.name.should == :foo
16
- subject.opts.should == {:attack_file => :bar}
14
+ it "sets path and attack_files" do
15
+ subject.path.should == :foo
16
+ subject.attack_files.should == [:bar]
17
17
  end
18
18
  end
19
19
 
20
- context "attack file does not exist for passed name" do
20
+ context "attack_files_for returns an empty array" do
21
21
  it "raises an error if the attack file does not exist" do
22
- File.stub(:exists?).with(:bar).and_return(false)
22
+ Gauntlt::Attack.any_instance.stub(:attack_files_for).with(:foo).and_return([])
23
23
 
24
24
  expect {
25
- Gauntlt::Attack.new(:foo, :attack_file => :bar)
26
- }.to raise_error Gauntlt::Attack::NotFound
25
+ Gauntlt::Attack.new(:foo)
26
+ }.to raise_error Gauntlt::Attack::NoFilesFound
27
27
  end
28
28
  end
29
29
  end
30
30
 
31
- describe :base_dir do
32
- it "returns the full path for the attack.rb file" do
33
- File.should_receive(:dirname).and_return(:foo)
34
- File.should_receive(:expand_path).with(:foo)
35
-
36
- subject.base_dir
37
- end
38
- end
39
-
40
- describe :attacks_dir do
41
- it "joins attacks to base_dir" do
42
- subject.should_receive(:base_dir).and_return(:bar)
43
- File.should_receive(:join).with(:bar, 'attack_adapters')
44
-
45
- subject.attacks_dir
46
- end
47
- end
48
-
49
31
  describe :run do
50
32
  it "executes the attack file, specifies failure for undefined steps and specifies the attacks_dir" do
51
- subject.should_receive(:attacks_dir).and_return('/bar')
52
- subject.should_receive(:attack_file).and_return('/bar/baz.attack')
53
-
54
- mock_io = mock('io')
55
- mock_io.stub(:string)
56
- StringIO.stub(:new).and_return(mock_io)
33
+ subject.should_receive(:adapters_dir).and_return('/bar')
34
+ subject.should_receive(:attack_files).and_return(['/bar/baz.attack'])
57
35
 
58
36
  mock_cli = mock(Cucumber::Cli::Main)
59
37
  mock_cli.should_receive(:execute!)
60
- Cucumber::Cli::Main.should_receive(:new).with(['/bar/baz.attack', '--strict', '--require', '/bar'], mock_io).and_return(mock_cli)
38
+ Cucumber::Cli::Main.should_receive(:new).with(['/bar/baz.attack', '--strict', '--require', '/bar']).and_return(mock_cli)
61
39
 
62
- subject.run
40
+ subject.run.should be_true
63
41
  end
64
42
 
65
43
  it "returns nil if if Cucumber::Cli::Main.execute succeeds (i.e. returns nil)" do