tinfoil 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +82 -0
- data/Rakefile +11 -0
- data/bin/tinfoil +19 -0
- data/lib/tinfoil.rb +8 -0
- data/lib/tinfoil/cli.rb +124 -0
- data/lib/tinfoil/errors.rb +7 -0
- data/lib/tinfoil/scanner.rb +97 -0
- data/lib/tinfoil/secure_header.rb +36 -0
- data/lib/tinfoil/version.rb +3 -0
- data/test/.gitkeep +0 -0
- data/test/tinfoil/.gitkeep +0 -0
- data/test/tinfoil/cli_test.rb +56 -0
- data/test/tinfoil/scanner_test.rb +108 -0
- data/test/tinfoil/secure_header_test.rb +36 -0
- data/test/tinfoil/version_test.rb +11 -0
- data/tinfoil.gemspec +25 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 24543aafbae5bc2c8abcfb5a457307e8b6f54f57
|
4
|
+
data.tar.gz: 9c41fc211de0394bbd07f9c7977a1b50a4f8da0c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3855221e020ef16674c58a86d67449abb75dbe76f0e020a6a2480213e14f5075688f425c76d4c6bfbe2ba529667965a4985d59783cdb112ec43f0094fbe43c20
|
7
|
+
data.tar.gz: 6e2f09c95ddd5795293029e3466117a53d406d6e62036910359859d178d9fe27ea34b4f844b13f95875a8631f4c2d208c0b33155882d0da8bab3fa336eaf9811
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Scott Brown
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Tinfoil
|
2
|
+
|
3
|
+
Tinfoil is a command-line utility that scans a Web server externally to listen for its usage of HTTP secure headers. This utility will scan for the following secure headers:
|
4
|
+
|
5
|
+
* [Strict-Transport-Security](http://tools.ietf.org/html/rfc6797)
|
6
|
+
* [X-XSS-Protection](http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx)
|
7
|
+
* [X-Content-Type-Options](http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx)
|
8
|
+
* [X-Frame-Options](http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-01)
|
9
|
+
* [Content-Security-Policy](http://www.w3.org/TR/CSP/)
|
10
|
+
|
11
|
+
Not all of these headers are required at all times, so you should use your best judgement when you see something is missing on your Web server. Best of all, you can selectively ignore the headers that you do not yet support.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Install it through RubyGems:
|
16
|
+
|
17
|
+
$ gem install tinfoil
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Scan a single server
|
22
|
+
|
23
|
+
$ tinfoil www.example.com
|
24
|
+
|
25
|
+
Specifying the protocol, ``http://`` or ``https://``, is not required.
|
26
|
+
|
27
|
+
You can selectively ignore the secure headers or protocols that you do not want. For example, to ignore anything related to SSL or the Content-Security-Policy header:
|
28
|
+
|
29
|
+
$ tinfoil --ignore-https --ignore-csp --ignore-sts www.example.com
|
30
|
+
|
31
|
+
To view the other options available, you can use the ``--help`` argument.
|
32
|
+
|
33
|
+
## Example
|
34
|
+
|
35
|
+
The following examples test the secure header support for the main [Github.com](http://github.com) website.
|
36
|
+
|
37
|
+
This checks both SSL and non-SSL versions of github.com. The tool shows that nothing is present on the non-SSL version (because it redirects) and then shows which headers are present on the SSL version.
|
38
|
+
|
39
|
+
greenhole:tinfoil scott$ bin/tinfoil github.com
|
40
|
+
protocol: http
|
41
|
+
protocol: https
|
42
|
+
headers:
|
43
|
+
Strict-Transport-Security: exists
|
44
|
+
X-XSS-Protection: exists
|
45
|
+
X-Content-Type-Options: exists
|
46
|
+
X-Frame-Options: exists
|
47
|
+
Content-Security-Policy: exists
|
48
|
+
|
49
|
+
Since we only care about the SSL version of github.com, we tell tinfoil to ignore the non-SSL version.
|
50
|
+
|
51
|
+
greenhole:tinfoil scott$ bin/tinfoil --ignore-http github.com
|
52
|
+
protocol: https
|
53
|
+
headers:
|
54
|
+
Strict-Transport-Security: exists
|
55
|
+
X-XSS-Protection: exists
|
56
|
+
X-Content-Type-Options: exists
|
57
|
+
X-Frame-Options: exists
|
58
|
+
Content-Security-Policy: exists
|
59
|
+
|
60
|
+
And, for kicks, we now tell tinfoil to ignore the Content-Security-Policy header for no good reason.
|
61
|
+
|
62
|
+
greenhole:tinfoil scott$ bin/tinfoil --ignore-http --ignore-csp github.com
|
63
|
+
protocol: https
|
64
|
+
headers:
|
65
|
+
Strict-Transport-Security: exists
|
66
|
+
X-XSS-Protection: exists
|
67
|
+
X-Content-Type-Options: exists
|
68
|
+
X-Frame-Options: exists
|
69
|
+
Content-Security-Policy: ignored
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
1. Fork it
|
74
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
75
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
77
|
+
5. Create new Pull Request
|
78
|
+
|
79
|
+
## License
|
80
|
+
|
81
|
+
See [LICENSE.txt](LICENSE.txt) for more information.
|
82
|
+
|
data/Rakefile
ADDED
data/bin/tinfoil
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path('../../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
GEM_NAME = 'tinfoil'
|
6
|
+
|
7
|
+
begin
|
8
|
+
require GEM_NAME
|
9
|
+
rescue LoadError
|
10
|
+
require 'rubygems'
|
11
|
+
require GEM_NAME
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
Tinfoil::CLI.run(ARGV)
|
16
|
+
rescue Tinfoil::AbnormalProgramExitError
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
data/lib/tinfoil.rb
ADDED
data/lib/tinfoil/cli.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Tinfoil
|
5
|
+
class CLI
|
6
|
+
DEFAULT_OUTPUT_FILE = 'results.txt'
|
7
|
+
|
8
|
+
def self.run (args, stdout=$stdout, stderr=$stderr)
|
9
|
+
@@options = default_options
|
10
|
+
parse(args, @@options)
|
11
|
+
|
12
|
+
if @@server.nil?
|
13
|
+
$stderr.puts banner
|
14
|
+
raise AbnormalProgramExitError
|
15
|
+
end
|
16
|
+
|
17
|
+
scanner = Tinfoil::Scanner.new
|
18
|
+
result = scanner.scan(@@server, @@options)
|
19
|
+
|
20
|
+
result.each_pair do |protocol, headers|
|
21
|
+
$stdout.puts "protocol: #{protocol}"
|
22
|
+
|
23
|
+
$stdout.puts "headers:"
|
24
|
+
headers.each do |header|
|
25
|
+
$stdout.puts "\t#{header}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse (args, options)
|
34
|
+
optparse = OptionParser.new do |opts|
|
35
|
+
opts.on('-h', '--help', 'Display this screen') do
|
36
|
+
$stdout.puts summary
|
37
|
+
$stdout.puts opts
|
38
|
+
raise AbnormalProgramExitError
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('--ignore-http', 'Ignores the http protocol') do
|
42
|
+
verbose("Ignoring HTTP")
|
43
|
+
options.ignore_protocols << 'http'
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('--ignore-https', 'Ignores the https protocol') do
|
47
|
+
verbose("Ignoring HTTPS")
|
48
|
+
options.ignore_protocols << 'https'
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on('--ignore-sts', 'Ignores the existence check for the Strict-Transport-Security header') do
|
52
|
+
verbose("Ignoring Strict-Transport-Security header")
|
53
|
+
options.ignore_headers << 'Strict-Transport-Security'
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on('--ignore-fo', 'Ignores the existence check for the X-Frame-Options header') do
|
57
|
+
verbose("Ignoring X-Frame-Options header")
|
58
|
+
options.ignore_headers << 'X-Frame-Options'
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on('--ignore-csp', 'Ignores the existence check for the Content-Security-Policy header') do
|
62
|
+
verbose("Ignoring Content-Security-Policy header")
|
63
|
+
options.ignore_headers << 'Content-Security-Policy'
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on('--ignore-xss', 'Ignores the existence check for the X-XSS-Protection header') do
|
67
|
+
verbose("Ignoring X-XSS-Protection header")
|
68
|
+
options.ignore_headers << 'X-XSS-Protection'
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on('--ignore-cto', 'Ignores the existence check for the X-Content-Type-Options header') do
|
72
|
+
verbose("Ignoring X-Content-Type-Options header")
|
73
|
+
options.ignore_headers << 'X-Content-Type-Options'
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on('-t', '--timeout [SECONDS]', Integer, "Change the timeout value. Default: #{options.timeout} seconds.") do |timeout|
|
77
|
+
verbose("Timeout set to #{timeout} seconds")
|
78
|
+
options.timeout = timeout
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on_tail('-v', '--verbose', 'Enable verbose output') do
|
82
|
+
options.verbose = true
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on_tail('--version', 'Display the version') do
|
86
|
+
$stdout.puts Tinfoil::VERSION
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.banner = banner
|
90
|
+
end
|
91
|
+
|
92
|
+
optparse.parse!(args)
|
93
|
+
|
94
|
+
@@server = args.first
|
95
|
+
end
|
96
|
+
|
97
|
+
def default_options
|
98
|
+
options = OpenStruct.new
|
99
|
+
options.verbose = false
|
100
|
+
options.timeout = 10 #seconds
|
101
|
+
options.ignore_headers = []
|
102
|
+
options.ignore_protocols = []
|
103
|
+
|
104
|
+
return options
|
105
|
+
end
|
106
|
+
|
107
|
+
def verbose (msg)
|
108
|
+
$stdout.puts "[CLI] #{msg}" if @@options.verbose
|
109
|
+
end
|
110
|
+
|
111
|
+
def banner
|
112
|
+
"Usage: tinfoil [options] DOMAIN"
|
113
|
+
end
|
114
|
+
|
115
|
+
def summary
|
116
|
+
string = "tinfoil #{Tinfoil::VERSION} (c) 2014 Scott Brown\n\n"
|
117
|
+
string << "Scans one or more Web servers for the presence of secure headers. This helps in discovering servers vulnerable to Web-based attack vectors.\n\n"
|
118
|
+
|
119
|
+
return string
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'net/http'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Tinfoil
|
6
|
+
class Scanner
|
7
|
+
def scan (domain, options = OpenStruct.new)
|
8
|
+
@options = options
|
9
|
+
server_result = {}
|
10
|
+
|
11
|
+
[:http, :https].each do |protocol|
|
12
|
+
unless options.ignore_protocols.include?(protocol.to_s)
|
13
|
+
server_result[protocol] = call_server("#{protocol}://" + domain) || []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
return server_result
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def call_server (url)
|
23
|
+
headers = []
|
24
|
+
verbose("Connecting to #{url}")
|
25
|
+
response = nil
|
26
|
+
begin
|
27
|
+
Timeout::timeout (@options.timeout) do
|
28
|
+
response = Net::HTTP.get_response(URI(url))
|
29
|
+
end
|
30
|
+
rescue OpenSSL::SSL::SSLError
|
31
|
+
verbose("SSL error found. Skipping.")
|
32
|
+
rescue Timeout::Error
|
33
|
+
return []
|
34
|
+
end
|
35
|
+
|
36
|
+
case response
|
37
|
+
when Net::HTTPSuccess
|
38
|
+
verbose("Status 200 OK. Processing response headers...")
|
39
|
+
|
40
|
+
[ SecureHeader::Type::STS, SecureHeader::Type::XSS, SecureHeader::Type::CTO, SecureHeader::Type::FO, SecureHeader::Type::CSP ].each do |type|
|
41
|
+
header = SecureHeader.new(type)
|
42
|
+
if @options.ignore_headers.include?(type)
|
43
|
+
verbose("#{type} header ignored.")
|
44
|
+
header.ignore = true
|
45
|
+
else
|
46
|
+
if response[type]
|
47
|
+
verbose("#{type} header found.")
|
48
|
+
header.exists = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
headers << header
|
52
|
+
end
|
53
|
+
when Net::HTTPRedirection
|
54
|
+
verbose("HTTP redirection found. Skipping.")
|
55
|
+
else
|
56
|
+
verbose("Unknown error occurred.")
|
57
|
+
end
|
58
|
+
|
59
|
+
return headers
|
60
|
+
end
|
61
|
+
|
62
|
+
def process_file
|
63
|
+
raise NoSuchFileError if File.exists?(@options.file)
|
64
|
+
raise InvalidFileAccessError if File.readable?(@options.file)
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_server
|
68
|
+
response = Net::HTTP.get_response(URI(@options.server))
|
69
|
+
|
70
|
+
case response
|
71
|
+
when Net::HTTPSuccess
|
72
|
+
if response['x-frame-options']
|
73
|
+
x_frame_options = true
|
74
|
+
end
|
75
|
+
if response['x-content-type-options']
|
76
|
+
x_content_type_options = true
|
77
|
+
end
|
78
|
+
if response['x-xss-protection']
|
79
|
+
x_xss_protection = true
|
80
|
+
end
|
81
|
+
if response['strict-transport-security']
|
82
|
+
strict_transport_security = true
|
83
|
+
end
|
84
|
+
if response['content-security-policy']
|
85
|
+
content_security_policy = true
|
86
|
+
end
|
87
|
+
when Net::HTTPRedirection
|
88
|
+
else
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def verbose (msg)
|
93
|
+
puts "[SCANNER] #{msg}" if @options.verbose
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Tinfoil
|
2
|
+
class SecureHeader
|
3
|
+
class Type
|
4
|
+
STS = 'Strict-Transport-Security'
|
5
|
+
CSP = 'Content-Security-Policy'
|
6
|
+
XSS = 'X-XSS-Protection'
|
7
|
+
FO = 'X-Frame-Options'
|
8
|
+
CTO = 'X-Content-Type-Options'
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :name, :ignore, :exists
|
12
|
+
|
13
|
+
def initialize (name)
|
14
|
+
@name = name
|
15
|
+
@ignore = false
|
16
|
+
@exists = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def status
|
20
|
+
if @ignore
|
21
|
+
"ignored"
|
22
|
+
else
|
23
|
+
if @exists
|
24
|
+
"exists"
|
25
|
+
else
|
26
|
+
"missing"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"#{@name}: #{status}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
data/test/.gitkeep
ADDED
File without changes
|
File without changes
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'mocha/test_unit'
|
3
|
+
require_relative '../../lib/tinfoil'
|
4
|
+
|
5
|
+
module Tinfoil
|
6
|
+
class CLITest < Test::Unit::TestCase
|
7
|
+
def test_run_with_no_args_should_cause_program_exit
|
8
|
+
assert_raises Tinfoil::AbnormalProgramExitError do
|
9
|
+
capture_stderr do
|
10
|
+
Tinfoil::CLI.run([])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_run_with_help_arg_causes_program_exit
|
16
|
+
assert_raises Tinfoil::AbnormalProgramExitError do
|
17
|
+
capture_stdout do
|
18
|
+
Tinfoil::CLI.run(['-h'])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_run_with_domain_arg
|
24
|
+
assert_nothing_raised do
|
25
|
+
capture_stdout do
|
26
|
+
Tinfoil::CLI.run(['example.com'])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def capture_stdout(&block)
|
34
|
+
original_stdout = $stdout
|
35
|
+
$stdout = fake = StringIO.new
|
36
|
+
begin
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
$stdout = original_stdout
|
40
|
+
end
|
41
|
+
fake.string
|
42
|
+
end
|
43
|
+
|
44
|
+
def capture_stderr(&block)
|
45
|
+
original_stderr = $stderr
|
46
|
+
$stderr = fake = StringIO.new
|
47
|
+
begin
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
$stderr = original_stderr
|
51
|
+
end
|
52
|
+
fake.string
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'mocha/test_unit'
|
3
|
+
require 'ostruct'
|
4
|
+
require_relative '../../lib/tinfoil'
|
5
|
+
|
6
|
+
module Tinfoil
|
7
|
+
class ScannerTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
@scanner = Tinfoil::Scanner.new
|
10
|
+
|
11
|
+
@options = OpenStruct.new
|
12
|
+
@options.verbose = false
|
13
|
+
@options.timeout = 10
|
14
|
+
@options.ignore_headers = []
|
15
|
+
@options.ignore_protocols = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
@scanner = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_scan_returns_correct_result_for_one_server
|
23
|
+
mock_remote_call do |result|
|
24
|
+
assert_equal 2, result.size
|
25
|
+
assert result.has_key?(:http)
|
26
|
+
assert result.has_key?(:https)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_scan_ignores_http_when_ordered
|
31
|
+
@options.ignore_protocols << "http"
|
32
|
+
mock_remote_call do |result|
|
33
|
+
assert_equal 1, result.size
|
34
|
+
assert !result.has_key?(:http)
|
35
|
+
assert result.has_key?(:https)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_scan_ignores_https_when_ordered
|
40
|
+
@options.ignore_protocols << 'https'
|
41
|
+
mock_remote_call do |result|
|
42
|
+
assert_equal 1, result.size
|
43
|
+
assert result.has_key?(:http), 'http should be present'
|
44
|
+
assert !result.has_key?(:https), 'https should not be present'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_scan_returns_empty_array_after_timeout
|
49
|
+
Net::HTTP.stubs(:get_response).raises(Timeout::Error)
|
50
|
+
domain = 'example.com'
|
51
|
+
result = @scanner.scan(domain, @options)
|
52
|
+
|
53
|
+
assert_equal 0, result[:http].size
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_scan_ignores_header_when_ordered
|
57
|
+
[ SecureHeader::Type::STS, SecureHeader::Type::XSS, SecureHeader::Type::CTO, SecureHeader::Type::FO, SecureHeader::Type::CSP ].each do |header_name|
|
58
|
+
@options.ignore_headers << header_name
|
59
|
+
mock_remote_call do |result|
|
60
|
+
assert_not_nil result[:http].select { |h| h.name == header_name && h.ignore }.first
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_scan_show_header_exists
|
66
|
+
res = custom_response(Tinfoil::SecureHeader::Type::FO)
|
67
|
+
mock_remote_call(res) do |result|
|
68
|
+
assert_not_nil result[:http].select { |h| h.name == Tinfoil::SecureHeader::Type::FO && h.exists }.first
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_scan_show_missing_header
|
73
|
+
res = custom_response()
|
74
|
+
mock_remote_call(res) do |result|
|
75
|
+
assert_not_nil result[:http].select { |h| h.name == Tinfoil::SecureHeader::Type::CSP && !h.exists }.first
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def default_response
|
82
|
+
res = Net::HTTPSuccess.new('1.1',200,'OK')
|
83
|
+
res.add_field Tinfoil::SecureHeader::Type::FO, 'foo'
|
84
|
+
res.add_field Tinfoil::SecureHeader::Type::CSP, 'foo'
|
85
|
+
res.add_field Tinfoil::SecureHeader::Type::XSS, 'foo'
|
86
|
+
res.add_field Tinfoil::SecureHeader::Type::STS, 'foo'
|
87
|
+
res.add_field Tinfoil::SecureHeader::Type::CTO, 'foo'
|
88
|
+
return res
|
89
|
+
end
|
90
|
+
|
91
|
+
def custom_response (*headers)
|
92
|
+
res = Net::HTTPSuccess.new('1.1',200,'OK')
|
93
|
+
headers.each do |h|
|
94
|
+
res.add_field h, 'foo'
|
95
|
+
end
|
96
|
+
return res
|
97
|
+
end
|
98
|
+
|
99
|
+
def mock_remote_call (res = nil, &block)
|
100
|
+
res = default_response if res.nil?
|
101
|
+
Net::HTTP.expects(:get_response).at_least_once.returns(res)
|
102
|
+
domain = 'example.com'
|
103
|
+
result = @scanner.scan(domain, @options)
|
104
|
+
yield result
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../lib/tinfoil/secure_header'
|
3
|
+
|
4
|
+
module Tinfoil
|
5
|
+
class SecureHeaderTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@header_name = 'SomeHeader'
|
8
|
+
@header = SecureHeader.new(@header_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
@header = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_status_returns_ignored
|
16
|
+
@header.ignore = true
|
17
|
+
assert_equal "ignored", @header.status
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_status_returns_exists
|
21
|
+
@header.exists = true
|
22
|
+
assert_equal "exists", @header.status
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_status_returns_missing
|
26
|
+
@header.exists = false
|
27
|
+
assert_equal "missing", @header.status
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_to_string_returns_correctly
|
31
|
+
@header.exists = true
|
32
|
+
assert_equal "SomeHeader: exists", @header.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
data/tinfoil.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tinfoil/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "tinfoil"
|
8
|
+
spec.version = Tinfoil::VERSION
|
9
|
+
spec.authors = ["Scott Brown"]
|
10
|
+
spec.email = ["scott@justplainsimple.com"]
|
11
|
+
spec.description = %q{A gem to scan a Web server and report whether it contains any or all secure headers}
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.bindir = 'bin'
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "mocha"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tinfoil
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Brown
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mocha
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A gem to scan a Web server and report whether it contains any or all
|
56
|
+
secure headers
|
57
|
+
email:
|
58
|
+
- scott@justplainsimple.com
|
59
|
+
executables:
|
60
|
+
- tinfoil
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/tinfoil
|
70
|
+
- lib/tinfoil.rb
|
71
|
+
- lib/tinfoil/cli.rb
|
72
|
+
- lib/tinfoil/errors.rb
|
73
|
+
- lib/tinfoil/scanner.rb
|
74
|
+
- lib/tinfoil/secure_header.rb
|
75
|
+
- lib/tinfoil/version.rb
|
76
|
+
- test/.gitkeep
|
77
|
+
- test/tinfoil/.gitkeep
|
78
|
+
- test/tinfoil/cli_test.rb
|
79
|
+
- test/tinfoil/scanner_test.rb
|
80
|
+
- test/tinfoil/secure_header_test.rb
|
81
|
+
- test/tinfoil/version_test.rb
|
82
|
+
- tinfoil.gemspec
|
83
|
+
homepage: ''
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.2.0
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: A gem to scan a Web server and report whether it contains any or all secure
|
107
|
+
headers
|
108
|
+
test_files:
|
109
|
+
- test/.gitkeep
|
110
|
+
- test/tinfoil/.gitkeep
|
111
|
+
- test/tinfoil/cli_test.rb
|
112
|
+
- test/tinfoil/scanner_test.rb
|
113
|
+
- test/tinfoil/secure_header_test.rb
|
114
|
+
- test/tinfoil/version_test.rb
|