Skip to content

Commit 3b89137

Browse files
committed
Update SARIF converter, fix #90, #91
1 parent 6f88801 commit 3b89137

File tree

3 files changed

+79
-144
lines changed

3 files changed

+79
-144
lines changed

action.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Update Pipfile
44
```bash
5-
PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock
5+
PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock
66
PIPENV_IGNORE_VIRTUALENVS=1 pipenv sync
77
PIPENV_IGNORE_VIRTUALENVS=1 pipenv run pip freeze > requirements.txt
88
```

mobsfscan/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
__title__ = 'mobsfscan'
77
__authors__ = 'Ajin Abraham'
88
__copyright__ = f'Copyright {datetime.now().year} Ajin Abraham, OpenSecurity'
9-
__version__ = '0.4.0'
9+
__version__ = '0.4.1'
1010
__version_info__ = tuple(int(i) for i in __version__.split('.'))
1111
__all__ = [
1212
'__title__',

mobsfscan/formatters/sarif.py

+77-142
Original file line numberDiff line numberDiff line change
@@ -1,203 +1,138 @@
11
# -*- coding: utf_8 -*-
2-
"""Sarif output format.
2+
"""SARIF output formatter for MobSF scan results.
33
4-
Based on https://github.com/microsoft/bandit-sarif-formatter/
5-
blob/master/bandit_sarif_formatter/formatter.py
4+
Based on https://github.com/microsoft/bandit-sarif-formatter/blob/master/bandit_sarif_formatter/formatter.py
5+
MIT License, Copyright (c) Microsoft Corporation.
66
7-
Copyright (c) Microsoft. All Rights Reserved.
8-
MIT License
9-
10-
Copyright (c) Microsoft Corporation.
11-
12-
Permission is hereby granted, free of charge, to any person obtaining a copy
13-
of this software and associated documentation files (the "Software"), to deal
14-
in the Software without restriction, including without limitation the rights
15-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16-
copies of the Software, and to permit persons to whom the Software is
17-
furnished to do so, subject to the following conditions:
18-
19-
The above copyright notice and this permission notice shall be included in all
20-
copies or substantial portions of the Software.
21-
22-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28-
SOFTWARE
297
"""
308
from datetime import datetime
319
from pathlib import PurePath
3210
import urllib.parse as urlparse
33-
3411
import sarif_om as om
35-
3612
from jschema_to_python.to_json import to_json
3713

38-
3914
TS_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
4015

41-
4216
def level_from_severity(severity):
43-
if severity == 'ERROR':
44-
return 'error'
45-
elif severity == 'WARNING':
46-
return 'warning'
47-
elif severity == 'INFO':
48-
return 'note'
49-
else:
50-
return 'none'
51-
17+
return {
18+
'ERROR': 'error',
19+
'WARNING': 'warning',
20+
'INFO': 'note'
21+
}.get(severity, 'none')
5222

5323
def to_uri(file_path):
5424
pure_path = PurePath(file_path)
55-
if pure_path.is_absolute():
56-
return pure_path.as_uri()
57-
else:
58-
posix_path = pure_path.as_posix() # Replace backslashes with slashes.
59-
return urlparse.quote(posix_path) # %-encode special characters.
60-
61-
62-
def get_rule_name(rule_id):
63-
normalized = []
64-
noms = rule_id.split('_')
65-
for nom in noms:
66-
normalized.append(nom.capitalize())
67-
return ''.join(normalized)
25+
return pure_path.as_uri() if pure_path.is_absolute() else urlparse.quote(pure_path.as_posix())
6826

27+
def format_rule_name(rule_id):
28+
return ''.join(word.capitalize() for word in rule_id.split('_'))
6929

7030
def add_results(path, scan_results, run):
7131
if run.results is None:
7232
run.results = []
73-
res = {}
74-
res.update(scan_results.get('results', []))
33+
res = scan_results.get('results', {})
7534
rules = {}
7635
rule_indices = {}
7736

7837
for rule_id, issue_dict in res.items():
79-
results = create_rule_results(path, rule_id, issue_dict, rules, rule_indices)
80-
run.results += results
38+
rule_results = create_rule_results(path, rule_id, issue_dict, rules, rule_indices)
39+
run.results.extend(rule_results)
8140

82-
if len(rules) > 0:
41+
if rules:
8342
run.tool.driver.rules = list(rules.values())
8443

85-
8644
def create_rule_results(path, rule_id, issue_dict, rules, rule_indices):
8745
rule_results = []
88-
if rule_id in rules:
89-
rule = rules[rule_id]
90-
rule_index = rule_indices[rule_id]
91-
else:
92-
doc = issue_dict['metadata'].get('reference')
93-
if not doc:
94-
doc = ('https://mobile-security.gitbook.io/'
95-
'mobile-security-testing-guide/')
46+
rule, rule_index = rules.get(rule_id), rule_indices.get(rule_id)
47+
48+
if not rule:
49+
doc = issue_dict['metadata'].get('reference') or 'https://mobile-security.gitbook.io/mobile-security-testing-guide/'
9650
cwe_id = issue_dict['metadata']['cwe'].split(':')[0].lower()
9751
rule = om.ReportingDescriptor(
9852
id=rule_id,
99-
name=get_rule_name(rule_id),
53+
name=format_rule_name(rule_id),
10054
help_uri=doc,
101-
properties={
102-
'tags': ['security', f'external/cwe/{cwe_id}'],
103-
},
55+
properties={'tags': ['security', f'external/cwe/{cwe_id}']}
10456
)
10557
rule_index = len(rules)
10658
rules[rule_id] = rule
10759
rule_indices[rule_id] = rule_index
10860

109-
files = issue_dict.get('files', [])
110-
111-
# if there are locations - we iterate over them and create
112-
# a separete result for each location
113-
if files:
114-
for item in files:
115-
locations = []
116-
physical_location = om.PhysicalLocation(
117-
artifact_location=om.ArtifactLocation(
118-
uri=to_uri(item['file_path'])),
61+
for item in issue_dict.get('files', []):
62+
location = create_location(item)
63+
rule_results.append(create_result(rule, rule_index, issue_dict, [location]))
64+
65+
if not issue_dict.get('files'):
66+
default_location = om.Location(
67+
physical_location=om.PhysicalLocation(
68+
artifact_location=om.ArtifactLocation(uri=path[0]),
69+
region=om.Region(
70+
start_line=1,
71+
end_line=1,
72+
start_column=1,
73+
end_column=1,
74+
snippet=om.ArtifactContent(text='Missing Best Practice')
75+
)
11976
)
120-
physical_location.region = om.Region(
77+
)
78+
rule_results.append(create_result(rule, rule_index, issue_dict, [default_location]))
79+
80+
return rule_results
81+
82+
def create_location(item):
83+
return om.Location(
84+
physical_location=om.PhysicalLocation(
85+
artifact_location=om.ArtifactLocation(uri=to_uri(item['file_path'])),
86+
region=om.Region(
12187
start_line=item['match_lines'][0],
12288
end_line=item['match_lines'][1],
12389
start_column=item['match_position'][0],
12490
end_column=item['match_position'][1],
125-
snippet=om.ArtifactContent(text=item['match_string']),
91+
snippet=om.ArtifactContent(text=item['match_string'])
12692
)
127-
locations.append(om.Location(physical_location=physical_location))
128-
rule_results.append(om.Result(
129-
rule_id=rule.id,
130-
rule_index=rule_index,
131-
message=om.Message(text=issue_dict['metadata']['description']),
132-
level=level_from_severity(issue_dict['metadata']['severity']),
133-
locations=locations,
134-
properties={
135-
'owasp-mobile': issue_dict['metadata']['owasp-mobile'],
136-
'masvs': issue_dict['metadata']['masvs'],
137-
'cwe': issue_dict['metadata']['cwe'],
138-
'reference': issue_dict['metadata']['reference'],
139-
},
140-
))
141-
# if there are no locations - only create a single resuklt
142-
else:
143-
locations = []
144-
artifact = om.PhysicalLocation(
145-
artifact_location=om.ArtifactLocation(
146-
uri=path[0]),
14793
)
148-
artifact.region = om.Region(
149-
start_line=1,
150-
end_line=1,
151-
start_column=1,
152-
end_column=1,
153-
snippet=om.ArtifactContent(text='Missing Best Practice'),
154-
)
155-
locations.append(om.Location(physical_location=artifact))
156-
rule_results.append(om.Result(
157-
rule_id=rule.id,
158-
rule_index=rule_index,
159-
message=om.Message(text=issue_dict['metadata']['description']),
160-
level=level_from_severity(issue_dict['metadata']['severity']),
161-
locations=locations,
162-
properties={
163-
'owasp-mobile': issue_dict['metadata']['owasp-mobile'],
164-
'masvs': issue_dict['metadata']['masvs'],
165-
'cwe': issue_dict['metadata']['cwe'],
166-
'reference': issue_dict['metadata']['reference'],
167-
},
168-
))
169-
170-
return rule_results
94+
)
17195

96+
def create_result(rule, rule_index, issue_dict, locations):
97+
return om.Result(
98+
rule_id=rule.id,
99+
rule_index=rule_index,
100+
message=om.Message(text=issue_dict['metadata']['description']),
101+
level=level_from_severity(issue_dict['metadata']['severity']),
102+
locations=locations,
103+
properties={
104+
'owasp-mobile': issue_dict['metadata']['owasp-mobile'],
105+
'masvs': issue_dict['metadata']['masvs'],
106+
'cwe': issue_dict['metadata']['cwe'],
107+
'reference': issue_dict['metadata']['reference'],
108+
}
109+
)
172110

173111
def sarif_output(outfile, scan_results, mobsfscan_version, path):
174112
log = om.SarifLog(
175-
schema_uri=('https://raw.githubusercontent.com/oasis-tcs/'
176-
'sarif-spec/master/Schemata/sarif-schema-2.1.0.json'),
113+
schema_uri='https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
177114
version='2.1.0',
178-
runs=[
179-
om.Run(
180-
tool=om.Tool(driver=om.ToolComponent(
181-
name='mobsfscan',
182-
information_uri='https://github.com/MobSF/mobsfscan',
183-
semantic_version=mobsfscan_version,
184-
version=mobsfscan_version),
185-
),
186-
invocations=[
187-
om.Invocation(
188-
end_time_utc=datetime.utcnow().strftime(TS_FORMAT),
189-
execution_successful=True,
190-
),
191-
],
192-
),
193-
],
115+
runs=[om.Run(
116+
tool=om.Tool(driver=om.ToolComponent(
117+
name='mobsfscan',
118+
information_uri='https://github.com/MobSF/mobsfscan',
119+
semantic_version=mobsfscan_version,
120+
version=mobsfscan_version
121+
)),
122+
invocations=[om.Invocation(
123+
end_time_utc=datetime.utcnow().strftime(TS_FORMAT),
124+
execution_successful=True
125+
)]
126+
)]
194127
)
195128
run = log.runs[0]
196129
add_results(path, scan_results, run)
197130
json_out = to_json(log)
131+
198132
if outfile:
199133
with open(outfile, 'w') as of:
200134
of.write(json_out)
201135
else:
202136
print(json_out)
137+
203138
return json_out

0 commit comments

Comments
 (0)