Skip to content

Commit 35428a7

Browse files
Merge pull request #30 from Santandersecurityresearch/dev
Adds CLI tool
2 parents 3987ec1 + 64d9b77 commit 35428a7

File tree

14 files changed

+374
-38
lines changed

14 files changed

+374
-38
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
grep -i 'current_version = ' setup.cfg | head -1 | tr -d 'current_version = '
6666
- name: Push changes if PR 'to-branch' is main
6767
if: github.base_ref == 'main'
68-
uses: ad-m/github-push-action@master
68+
uses: ad-m/github-push-action@v0.6.0
6969
with:
7070
github_token: ${{ secrets.GITHUB_TOKEN }}
7171
branch: ${{ github.head_ref }}

.gitignore

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.pyc
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
.hypothesis/
48+
.pytest_cache/
49+
50+
# Translations
51+
*.mo
52+
*.pot
53+
54+
# Django stuff:
55+
*.log
56+
local_settings.py
57+
58+
# Flask stuff:
59+
instance/
60+
.webassets-cache
61+
62+
# Scrapy stuff:
63+
.scrapy
64+
65+
# Sphinx documentation
66+
docs/_build/
67+
docs/_static/*
68+
!docs/_static/.keep
69+
70+
# PyBuilder
71+
target/
72+
73+
# Jupyter Notebook
74+
.ipynb_checkpoints
75+
76+
# pyenv
77+
.python-version
78+
79+
# celery beat schedule file
80+
celerybeat-schedule
81+
82+
# SageMath parsed files
83+
*.sage.py
84+
85+
# dotenv
86+
.env
87+
88+
# virtualenv
89+
.venv
90+
venv/
91+
ENV/
92+
.DS_Store
93+
94+
# Spyder project settings
95+
.spyderproject
96+
.spyproject
97+
98+
# Rope project settings
99+
.ropeproject
100+
101+
# mkdocs documentation
102+
/site
103+
104+
# mypy
105+
.mypy_cache/
106+
107+
# jetbrains
108+
.idea/
109+
110+
# junit reports folder
111+
reports

README.md

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,16 @@ Often, CORS configurations make use of wildcards, for example accepting anything
4949

5050
# How Do I Install It?
5151

52-
This project was developed with Python 3.9, but should work with any Python 3.x version.
53-
54-
corsair_scan has been designed to be used as a Python module , so the easiest way to install it is using pip.
52+
This project was developed with Python 3.9, but should work with any Python 3.x version. The best way to install it is using pip.
5553

5654
`pip3 install corsair_scan --user`
5755

5856

5957
# How Do I Use It?
6058

61-
At the moment, corsair_scan is intended to be used as a Python package. However, we plan to release this as a command line tool (CLI) in future releases.
59+
Corsair can be used both as a python module or as a CLI.
60+
61+
### **Python Module**
6262

6363
The method that performs the CORS scan is corsair_scan. Here is its definition:
6464

@@ -88,10 +88,10 @@ Receives a list of requests and a parameter to enable/disable certificate check
8888

8989

9090

91-
# Example
91+
### Example
9292

9393
```
94-
import corsair_scan
94+
import corsair_scan.corsair_scan as corsair
9595
url_data = {}
9696
data = []
9797
verb = 'GET'
@@ -108,7 +108,7 @@ url_data['params'] = params
108108
url_data['headers'] = headers
109109
data.append(url_data)
110110
111-
print (corsair_scan.corsair_scan(data, verify=True))
111+
print (corsair.corsair_scan(data, verify=True))
112112
```
113113

114114

@@ -154,12 +154,56 @@ Response:
154154

155155

156156

157+
### **CLI**
158+
159+
As part of the pip package installation, a CLI is installed. The syntax to execute is as follows:
160+
161+
`corsair FILE [-nv/--noverify][-r/--report]`
162+
163+
The CLI needs a json file with a list of requests perform the scan. An example file is provided [as part of the tests](test/testfiles/json_test.json)
164+
165+
:
166+
167+
```
168+
[{
169+
"verb": "GET",
170+
"url": "https://example.com/",
171+
"params": "user=user1&password=1234",
172+
"headers": {
173+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
174+
"Accept-Language": "en-GB,en;q=0.5",
175+
"Connection": "keep-alive",
176+
"Upgrade-Insecure-Requests": "1",
177+
"Origin": "https://example.com",
178+
"Host": "example.com"
179+
}
180+
}]
181+
182+
```
183+
184+
185+
186+
There are also two optional parameters:
187+
188+
- -nv / --noverify: To skip certificate validation, if you are testing against a site with a self-signed certificate
189+
- -r / --report: Saves the report as a JSON file
190+
191+
192+
193+
### Example
194+
195+
196+
197+
<img align="left" src="images/cli-report.png" alt="CLI report" style="zoom:80%;" />
198+
199+
200+
157201

158202

159203
## Roadmap
160204

161-
* Release corsair_scan as a CLI tool
162-
* Read url data from a text file
205+
* Release corsair_scan as a CLI tool
206+
* Read url data from a text file
163207
* Improve reports format
164208

165209
# Who Is Behind It?

corsair_scan/corsair_cli.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
##################################################
5+
# Santander UK Security Engineering team.
6+
##################################################
7+
# MIT License Copyright (c) 2021 Grupo Santander
8+
##################################################
9+
# Author: Javier Dominguez Ruiz (@javixeneize)
10+
# Version: 1.0
11+
##################################################
12+
13+
import corsair_scan.corsair_scan as corsair
14+
import click
15+
import json
16+
import sys
17+
18+
19+
def get_data_from_file(filename):
20+
try:
21+
with open(filename) as f:
22+
try:
23+
data = json.loads(f.read())
24+
if type(data) == dict:
25+
datalist = []
26+
datalist.append(data)
27+
return datalist
28+
else:
29+
return data
30+
except json.decoder.JSONDecodeError:
31+
print('Error. The format does not appear to be correct, please review')
32+
sys.exit(2)
33+
except FileNotFoundError:
34+
print('Error. File not found')
35+
sys.exit(2)
36+
37+
38+
@click.command()
39+
@click.argument('file', required=True)
40+
@click.option('-nv', '--noverify', 'verify', help='Skips security certificate check', is_flag=True, default=True)
41+
@click.option('-r', '--report', 'report', help='Saves the report in a JSON file')
42+
def run_cli_scan(file, verify, report):
43+
"""Corsair CLI requires as parameter a file in JSON format with the data to run the scan.
44+
This data can be a single request, or a list of requests"""
45+
data = get_data_from_file(file)
46+
corsair_report = corsair.corsair_scan(data, verify)
47+
if corsair_report.get('report'):
48+
if report:
49+
with open(report, 'w') as file:
50+
file.write(json.dumps(corsair_report))
51+
print("Report generated in {}".format(report))
52+
else:
53+
print(corsair_report)
54+
else:
55+
print("There was an error running corsair. Please check the input data is correct")

corsair_scan/corsair_scan.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@ def corsair_scan(data: list, verify: bool = True) -> dict:
2929
final_report: dict = {}
3030
for url in data:
3131
single_report: dict = corsair_scan_single_url(url, verify)
32-
full_report.append(single_report.copy())
32+
if single_report:
33+
full_report.append(single_report.copy())
3334
final_report['summary'] = filter_report(full_report)
3435
final_report['report'] = full_report
3536
return final_report
3637

3738

3839
def corsair_scan_single_url(url_data: dict, verify: bool = True) -> dict:
39-
report: dict = {'url': url_data.get('url'), 'verb': url_data.get('verb')}
40-
if validate_data(url_data.get('url'), url_data.get('verb')):
40+
report: dict = {}
41+
if validate_data(url_data.get('url'), url_data.get('verb'), url_data.get('headers')):
42+
report = {'url': url_data.get('url'), 'verb': url_data.get('verb')}
4143
report['fake_origin'] = validate_response(url_data, SM_ORIGIN, verify)
4244
if url_data.get('headers').get('Origin'):
4345
parsed_url = urlparse(url_data.get('headers').get('Origin'))
@@ -104,5 +106,6 @@ def filter_report(report_list: list) -> dict:
104106
return (final_report)
105107

106108

107-
def validate_data(url, verb):
108-
return validators.url(url) and verb.lower() in VERBS
109+
def validate_data(url, verb, headers):
110+
if url and verb and headers:
111+
return validators.url(url) and verb.lower() in VERBS

images/cli-report.png

393 KB
Loading

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
validators>=0.14.3
22
requests>=2.23.0
3-
urllib3>=1.25.9
4-
tldextract>=2.2.3
3+
urllib3>=1.26.4
4+
tldextract>=2.2.3
5+
click>=7.1.2

requirements_dev.txt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
validators>=0.14.3
22
requests>=2.23.0
3-
urllib3>=1.25.9
3+
urllib3>=1.26.4
44
mock>=4.0.2
5-
pytest==3.8.2
6-
pytest-cov==2.5.1
5+
pytest==6.2.4
6+
pytest-cov==2.12.0
77
wheel
88
tldextract>=2.2.3
9-
flake8==3.7.9
10-
bandit==1.6.2
11-
safety==1.8.7
12-
bump2version==1.0.0
13-
twine==3.1.1
9+
flake8==3.9.2
10+
bandit==1.7.0
11+
safety==1.10.3
12+
bump2version==1.0.1
13+
twine==3.4.1
14+
click>=7.1.2
15+

setup.cfg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.1.0
2+
current_version = 0.2.0
33
commit = True
44
tag = False
55

@@ -8,8 +8,8 @@ search = version='{current_version}'
88
replace = version='{new_version}'
99

1010
[bumpversion:file:setup.cfg]
11-
search = current_version = '{current_version}'
12-
replace = current_version = '{new_version}'
11+
search = current_version = {current_version}
12+
replace = current_version = {new_version}
1313

1414
[bdist_wheel]
1515
universal = 1

0 commit comments

Comments
 (0)