Skip to content

Commit 09d8436

Browse files
authored
Merge pull request #109 from Santandersecurityresearch/develop
Changes for v1.2.0
2 parents 0dba774 + aa89a99 commit 09d8436

File tree

7 files changed

+95
-14
lines changed

7 files changed

+95
-14
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[![GitHub release](https://img.shields.io/github/release/Santandersecurityresearch/DrHeader.svg)](https://GitHub.com/Santandersecurityresearch/DrHeader/releases/)
2-
[![GitHub commits](https://img.shields.io/github/commits-since/Santandersecurityresearch/DrHeader/v1.1.1.svg)](https://GitHub.com/Santandersecurityresearch/DrHeader/commit/)
2+
[![GitHub commits](https://img.shields.io/github/commits-since/Santandersecurityresearch/DrHeader/v1.2.0.svg)](https://GitHub.com/Santandersecurityresearch/DrHeader/commit/)
33
[![Github all releases](https://img.shields.io/github/downloads/Santandersecurityresearch/DrHeader/total.svg)](https://GitHub.com/Santandersecurityresearch/DrHeader/releases/)
44
[![HitCount](http://hits.dwyl.io/Santandersecurityresearch/DrHeader.svg)](http://hits.dwyl.io/Santandersecurityresearch/DrHeader)
55
[![Total alerts](https://img.shields.io/lgtm/alerts/g/Santandersecurityresearch/DrHeader.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Santandersecurityresearch/DrHeader/alerts/)
@@ -68,6 +68,7 @@ There are a number of parameters you can specify during bulk scans, these are:
6868
| --json | Output report as json |
6969
| --debug | Show error messages |
7070
| --rules FILENAME | Use custom rule set |
71+
| --rules-uri URL | Use custom rule set, to download from a remote server |
7172
| --merge | Merge custom rule set on top of default set |
7273
| --help | Show this message and exit |
7374
| --junit | Creates a junit report in `./reports/junit.xml` folder |

drheader/cli.py

+58-9
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
import click
99
import jsonschema
1010
import sys
11+
import os
1112
import validators
1213
from tabulate import tabulate
1314

1415
from drheader import Drheader
1516
from drheader.cli_utils import echo_bulk_report, file_junit_report
16-
from drheader.utils import load_rules
17+
from drheader.utils import load_rules, get_rules_from_uri
18+
19+
20+
EXIT_CODE_NO_ERROR = os.EX_OK
21+
EXIT_CODE_FAILURE = os.EX_SOFTWARE
1722

1823

1924
@click.group()
@@ -31,8 +36,9 @@ def scan():
3136
@click.option('--json', 'json_output', help='Output report as json', is_flag=True)
3237
@click.option('--debug', help='Show error messages', is_flag=True)
3338
@click.option('--rules', 'rule_file', help='Use custom rule set', type=click.File())
39+
@click.option('--rules-uri', 'rule_uri', help='Use custom rule set, downloaded from URI')
3440
@click.option('--merge', help='Merge custom file rules, on top of default rules', is_flag=True)
35-
def compare(file, json_output, debug, rule_file, merge):
41+
def compare(file, json_output, debug, rule_file, rule_uri, merge):
3642
"""
3743
If you have headers you would like to test with drheader, you can "compare" them with your ruleset this command.
3844
@@ -60,7 +66,7 @@ def compare(file, json_output, debug, rule_file, merge):
6066
...
6167
]
6268
"""
63-
69+
exit_code = EXIT_CODE_NO_ERROR
6470
audit = []
6571
schema = {
6672
"type": "array",
@@ -90,38 +96,64 @@ def compare(file, json_output, debug, rule_file, merge):
9096
except Exception as e:
9197
raise click.ClickException(e)
9298

99+
if rule_uri and not rule_file:
100+
if not validators.url(rule_uri):
101+
raise click.ClickException(message='"{}" is not a valid URL.'.format(rule_uri))
102+
try:
103+
rule_file = get_rules_from_uri(rule_uri)
104+
except Exception as e:
105+
if debug:
106+
raise click.ClickException(e)
107+
else:
108+
raise click.ClickException('No content retrieved from rules-uri.')
109+
93110
rules = load_rules(rule_file, merge)
94111

95112
for i in data:
96113
logging.debug('Analysing : {}'.format(i['url']))
97114
drheader_instance = Drheader(url=i['url'], headers=i['headers'])
98115
drheader_instance.analyze(rules)
99116
audit.append({'url': i['url'], 'report': drheader_instance.report})
117+
if drheader_instance.report:
118+
exit_code = EXIT_CODE_FAILURE
100119

101120
echo_bulk_report(audit, json_output)
121+
sys.exit(exit_code)
102122

103123

104124
@scan.command()
105125
@click.argument('target_url', required=True)
106126
@click.option('--json', 'json_output', help='Output report as json', is_flag=True)
107127
@click.option('--debug', help='Show error messages', is_flag=True)
108128
@click.option('--rules', 'rule_file', help='Use custom rule set', type=click.File())
129+
@click.option('--rules-uri', 'rule_uri', help='Use custom rule set, downloaded from URI')
109130
@click.option('--merge', help='Merge custom file rules, on top of default rules', is_flag=True)
110131
@click.option('--junit', help='Produces a junit report with the result of the scan', is_flag=True)
111-
def single(target_url, json_output, debug, rule_file, merge, junit):
132+
def single(target_url, json_output, debug, rule_file, rule_uri, merge, junit):
112133
"""
113134
Scan a single http(s) endpoint with drheader.
114135
115136
NOTE: URL parameters are currently only supported on bulk scans.
116137
"""
117-
138+
exit_code = EXIT_CODE_NO_ERROR
118139
if debug:
119140
logging.basicConfig(level=logging.DEBUG)
120141

121142
logging.debug('Validating: {}'.format(target_url))
122143
if not validators.url(target_url):
123144
raise click.ClickException(message='"{}" is not a valid URL.'.format(target_url))
124145

146+
if rule_uri and not rule_file:
147+
if not validators.url(rule_uri):
148+
raise click.ClickException(message='"{}" is not a valid URL.'.format(rule_uri))
149+
try:
150+
rule_file = get_rules_from_uri(rule_uri)
151+
except Exception as e:
152+
if debug:
153+
raise click.ClickException(e)
154+
else:
155+
raise click.ClickException('No content retrieved from rules-uri.')
156+
125157
rules = load_rules(rule_file, merge)
126158

127159
try:
@@ -142,6 +174,9 @@ def single(target_url, json_output, debug, rule_file, merge, junit):
142174
else:
143175
raise click.ClickException('Failed to analyze headers.')
144176

177+
if drheader_instance.report:
178+
exit_code = EXIT_CODE_FAILURE
179+
145180
if json_output:
146181
click.echo(json.dumps(drheader_instance.report))
147182
else:
@@ -158,7 +193,7 @@ def single(target_url, json_output, debug, rule_file, merge, junit):
158193
click.echo(tabulate(values, tablefmt="presto"))
159194
if junit:
160195
file_junit_report(rules, drheader_instance.report)
161-
return 0
196+
sys.exit(exit_code)
162197

163198

164199
@scan.command()
@@ -169,8 +204,9 @@ def single(target_url, json_output, debug, rule_file, merge, junit):
169204
@click.option('--json', 'json_output', help='Output report as json', is_flag=True)
170205
@click.option('--debug', help='Show error messages', is_flag=True)
171206
@click.option('--rules', 'rule_file', help='Use custom rule set', type=click.File())
207+
@click.option('--rules-uri', 'rule_uri', help='Use custom rule set, downloaded from URI')
172208
@click.option('--merge', help='Merge custom file rules, on top of default rules', is_flag=True)
173-
def bulk(file, json_output, post, input_format, debug, rule_file, merge):
209+
def bulk(file, json_output, post, input_format, debug, rule_file, rule_uri, merge):
174210
"""
175211
Scan multiple http(s) endpoints with drheader.
176212
@@ -195,7 +231,7 @@ def bulk(file, json_output, post, input_format, debug, rule_file, merge):
195231
196232
NOTE: URL parameters are currently only supported on bulk scans.
197233
"""
198-
234+
exit_code = EXIT_CODE_NO_ERROR
199235
audit = []
200236
urls = []
201237
schema = {
@@ -235,6 +271,17 @@ def bulk(file, json_output, post, input_format, debug, rule_file, merge):
235271

236272
logging.debug('Found {} URLs'.format(len(urls)))
237273

274+
if rule_uri and not rule_file:
275+
if not validators.url(rule_uri):
276+
raise click.ClickException(message='"{}" is not a valid URL.'.format(rule_uri))
277+
try:
278+
rule_file = get_rules_from_uri(rule_uri)
279+
except Exception as e:
280+
if debug:
281+
raise click.ClickException(e)
282+
else:
283+
raise click.ClickException('No content retrieved from rules-uri.')
284+
238285
rules = load_rules(rule_file, merge)
239286

240287
for i, v in enumerate(urls):
@@ -243,9 +290,11 @@ def bulk(file, json_output, post, input_format, debug, rule_file, merge):
243290
logging.debug('Analysing: {}...'.format(v))
244291
drheader_instance.analyze(rules)
245292
audit.append({'url': v['url'], 'report': drheader_instance.report})
293+
if drheader_instance.report:
294+
exit_code = EXIT_CODE_FAILURE
246295

247296
echo_bulk_report(audit, json_output)
248-
return 0
297+
sys.exit(exit_code)
249298

250299

251300
if __name__ == "__main__":

drheader/utils.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
import logging
66
import os
7-
7+
import io
8+
import requests
89
import yaml
910

1011

@@ -61,3 +62,18 @@ def merge_rules(default_rules, custom_rules):
6162
default_rules['Headers'][rule] = custom_rules['Headers'][rule]
6263

6364
return default_rules
65+
66+
67+
def get_rules_from_uri(URI):
68+
"""
69+
Retrieves custom rule set from URL
70+
:param URI: URL to your custom rules file
71+
:type URI: uri
72+
:return: rules file
73+
:rtype: file
74+
"""
75+
download = requests.get(URI)
76+
if not download.content:
77+
raise Exception('No content retrieved from {}'.format(URI))
78+
file = io.BytesIO(download.content)
79+
return file

requirements_dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ validators==0.14.0
1616
unittest2==1.1.0
1717
xmlunittest==0.5.0
1818
junit-xml==1.9
19+
responses==0.10.12

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.1.1
2+
current_version = 1.2.0
33
commit = True
44
tag = True
55

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,6 @@
4949
test_suite='tests',
5050
tests_require=test_requirements,
5151
url='https://github.com/santandersecurityresearch/drheader',
52-
version='1.1.1',
52+
version='1.2.0',
5353
zip_safe=False,
5454
)

tests/test_utils.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import os
77
import yaml
88
import unittest2
9+
import responses
910

10-
from drheader.utils import load_rules
11+
from drheader.utils import load_rules, get_rules_from_uri
1112

1213

1314
class TestUtilsFunctions(unittest2.TestCase):
@@ -45,6 +46,19 @@ def test_load_rules_bad_parameter(self):
4546
with self.assertRaises(AttributeError):
4647
load_rules(2)
4748

49+
@responses.activate
50+
def test_get_rules_from_uri_wrong_URI(self):
51+
responses.add(responses.GET, 'http://mydomain.com/custom.yml', status=404)
52+
with self.assertRaises(Exception):
53+
get_rules_from_uri("http://mydomain.com/custom.yml")
54+
55+
@responses.activate
56+
def test_get_rules_from_uri_good_URI(self):
57+
responses.add(responses.GET, 'http://localhost:8080/custom.yml', json=self.custom_rules, status=200)
58+
file = get_rules_from_uri("http://localhost:8080/custom.yml")
59+
content = yaml.safe_load(file.read())
60+
self.assertEqual(content, self.custom_rules)
61+
4862

4963
# start unittest2 to run these tests
5064
if __name__ == "__main__":

0 commit comments

Comments
 (0)