gauntlt 0.0.7 → 0.0.8

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.
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