Skip to content

Commit 6947649

Browse files
authored
Multiple Features (Scan timeout, Firebase Remote Config, Search Scans) (#2441)
Support time out for SAST and Binary scans Search by MD5, package name, file name and app name. Search REST API + docs + tests Firebase remote config check [FEATURE] Add support for Firebase Remote Config information #2429 autopep8
1 parent d817c0b commit 6947649

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+443145
-343076
lines changed

.dockerignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ mobsf/downloads
2424
mobsf/uploads
2525
mobsf/debug.log
2626
mobsf/secret
27-
mobsf/StaticAnalyzer/test_files/
27+
mobsf/StaticAnalyzer/test_files/
28+
TODO.md

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,4 @@ mobsf/secret
7878
mobsf/StaticAnalyzer/migrations
7979
mobsf/MobSF/windows_vm_priv_key.asc
8080
mobsf/setup_done.txt
81+
TODO.md

mobsf/MalwareAnalyzer/views/android/apkid.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from mobsf.MobSF.utils import (
88
append_scan_status,
9+
run_with_timeout,
910
settings_enabled,
1011
)
1112

@@ -49,13 +50,28 @@ def apkid_analysis(checksum, apk_file):
4950
)
5051
rules = options.rules_manager.load()
5152
scanner = Scanner(rules, options)
52-
res = scanner.scan_file(apk_file)
53+
findings = {}
54+
res = None
5355
try:
54-
findings = output._build_json_output(res)['files']
56+
res = run_with_timeout(
57+
scanner.scan_file,
58+
settings.BINARY_ANALYSIS_TIMEOUT,
59+
apk_file)
60+
except Exception as e:
61+
msg = 'APKID scan timed out'
62+
logger.error(msg)
63+
append_scan_status(
64+
checksum,
65+
msg,
66+
str(e))
67+
try:
68+
if res:
69+
findings = output._build_json_output(res)['files']
5570
except AttributeError:
5671
# apkid >= 2.0.3
5772
try:
58-
findings = output.build_json_output(res)['files']
73+
if res:
74+
findings = output.build_json_output(res)['files']
5975
except AttributeError:
6076
msg = (
6177
'yara-python dependency required by '
@@ -66,7 +82,6 @@ def apkid_analysis(checksum, apk_file):
6682
checksum,
6783
msg,
6884
'Missing dependency')
69-
findings = {}
7085
sanitized = {}
7186
for item in findings:
7287
filename = item['filename']

mobsf/MobSF/init.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
logger = logging.getLogger(__name__)
1818

19-
VERSION = '4.1.0'
19+
VERSION = '4.1.1'
2020
BANNER = r"""
2121
__ __ _ ____ _____ _ _ _
2222
| \/ | ___ | |__/ ___|| ___|_ _| || | / |

mobsf/MobSF/settings.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@
330330
},
331331
},
332332
}
333-
JADX_TIMEOUT = int(os.getenv('MOBSF_JADX_TIMEOUT', 1800))
333+
JADX_TIMEOUT = int(os.getenv('MOBSF_JADX_TIMEOUT', 1000))
334+
SAST_TIMEOUT = int(os.getenv('MOBSF_SAST_TIMEOUT', 1000))
335+
BINARY_ANALYSIS_TIMEOUT = int(os.getenv('MOBSF_BINARY_ANALYSIS_TIMEOUT', 600))
334336
DISABLE_AUTHENTICATION = os.getenv('MOBSF_DISABLE_AUTHENTICATION')
335337
RATELIMIT = os.getenv('MOBSF_RATELIMIT', '7/m')
336338
USE_X_FORWARDED_HOST = bool(

mobsf/MobSF/urls.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
# Static Analysis
8686
re_path(r'^api/v1/upload$', api_sz.api_upload),
8787
re_path(r'^api/v1/scan$', api_sz.api_scan),
88+
re_path(r'^api/v1/search$', api_sz.api_search),
8889
re_path(r'^api/v1/scan_logs$', api_sz.api_scan_logs),
8990
re_path(r'^api/v1/delete_scan$', api_sz.api_delete_scan),
9091
re_path(r'^api/v1/download_pdf$', api_sz.api_pdf_report),
@@ -198,7 +199,6 @@
198199
re_path(r'^search$', home.search),
199200
re_path(r'^status/$', home.scan_status, name='status'),
200201
re_path(r'^error/$', home.error, name='error'),
201-
re_path(r'^not_found/$', home.not_found),
202202
re_path(r'^zip_format/$', home.zip_format),
203203
re_path(r'^dynamic_analysis/$', home.dynamic_analysis, name='dynamic'),
204204

mobsf/MobSF/utils.py

+27
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
re.UNICODE)
5555
EMAIL_REGEX = re.compile(r'[\w+.-]{1,20}@[\w-]{1,20}\.[\w]{2,10}')
5656
USERNAME_REGEX = re.compile(r'^\w[\w\-\@\.]{1,35}$')
57+
GOOGLE_API_KEY_REGEX = re.compile(r'AIza[0-9A-Za-z-_]{35}$')
58+
GOOGLE_APP_ID_REGEX = re.compile(r'\d{1,2}:\d{1,50}:android:[a-f0-9]{1,50}')
5759

5860

5961
class Color(object):
@@ -947,3 +949,28 @@ def get_scan_logs(checksum):
947949
msg = 'Fetching scan logs from the DB failed.'
948950
logger.exception(msg)
949951
return []
952+
953+
954+
class TaskTimeoutError(Exception):
955+
pass
956+
957+
958+
def run_with_timeout(func, limit, *args, **kwargs):
959+
def run_func(result, *args, **kwargs):
960+
result.append(func(*args, **kwargs))
961+
962+
result = []
963+
thread = threading.Thread(
964+
target=run_func,
965+
args=(result, *args),
966+
kwargs=kwargs)
967+
thread.start()
968+
thread.join(limit)
969+
970+
if thread.is_alive():
971+
msg = (f'function <{func.__name__}> '
972+
f'timed out after {limit} seconds')
973+
raise TaskTimeoutError(msg)
974+
if result:
975+
return result[0]
976+
return None

mobsf/MobSF/views/api/api_static_analysis.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
is_md5,
1313
)
1414
from mobsf.MobSF.views.helpers import request_method
15-
from mobsf.MobSF.views.home import RecentScans, Upload, delete_scan
15+
from mobsf.MobSF.views.home import (
16+
RecentScans,
17+
Upload,
18+
delete_scan,
19+
search,
20+
)
1621
from mobsf.MobSF.views.api.api_middleware import make_api_response
1722
from mobsf.StaticAnalyzer.views.android.views import view_source
1823
from mobsf.StaticAnalyzer.views.android.static_analyzer import static_analyzer
@@ -180,6 +185,21 @@ def api_json_report(request):
180185
return response
181186

182187

188+
@request_method(['POST'])
189+
@csrf_exempt
190+
def api_search(request):
191+
"""Search by checksum or text."""
192+
if 'query' not in request.POST:
193+
return make_api_response(
194+
{'error': 'Missing Parameters'}, 422)
195+
resp = search(request, api=True)
196+
if 'checksum' in resp:
197+
request.POST = {'hash': resp['checksum']}
198+
return api_json_report(request)
199+
elif 'error' in resp:
200+
return make_api_response(resp, 404)
201+
202+
183203
@request_method(['POST'])
184204
@csrf_exempt
185205
def api_view_source(request):

mobsf/MobSF/views/home.py

+40-27
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from mobsf.MobSF.forms import FormUtil, UploadFileForm
2424
from mobsf.MobSF.utils import (
25+
MD5_REGEX,
2526
api_key,
2627
get_md5,
2728
is_dir_exists,
@@ -244,16 +245,6 @@ def zip_format(request):
244245
return render(request, template, context)
245246

246247

247-
def not_found(request, *args):
248-
"""Not Found Route."""
249-
context = {
250-
'title': 'Not Found',
251-
'version': settings.MOBSF_VER,
252-
}
253-
template = 'general/not_found.html'
254-
return render(request, template, context)
255-
256-
257248
@login_required
258249
def dynamic_analysis(request):
259250
"""Dynamic Analysis Landing."""
@@ -337,18 +328,43 @@ def download_apk(request):
337328

338329

339330
@login_required
340-
def search(request):
341-
"""Search Scan by MD5 Route."""
342-
md5 = request.GET['md5']
343-
if re.match('[0-9a-f]{32}', md5):
344-
db_obj = RecentScansDB.objects.filter(MD5=md5)
345-
if db_obj.exists():
346-
e = db_obj[0]
347-
url = f'/{e.ANALYZER}/{e.MD5}/'
348-
return HttpResponseRedirect(url)
349-
else:
350-
return HttpResponseRedirect('/not_found/')
351-
return print_n_send_error_response(request, 'Invalid Scan Hash')
331+
def search(request, api=False):
332+
"""Search scan by checksum or text."""
333+
if request.method == 'POST':
334+
query = request.POST['query']
335+
else:
336+
query = request.GET['query']
337+
338+
if not query:
339+
msg = 'No search query provided.'
340+
return print_n_send_error_response(request, msg, api)
341+
342+
checksum = query if re.match(MD5_REGEX, query) else find_checksum(query)
343+
344+
if checksum and re.match(MD5_REGEX, checksum):
345+
db_obj = RecentScansDB.objects.filter(MD5=checksum).first()
346+
if db_obj:
347+
url = f'/{db_obj.ANALYZER}/{db_obj.MD5}/'
348+
if api:
349+
return {'checksum': db_obj.MD5}
350+
else:
351+
return HttpResponseRedirect(url)
352+
353+
msg = 'You can search by MD5, app name, package name, or file name.'
354+
return print_n_send_error_response(request, msg, api, 'Scan not found')
355+
356+
357+
def find_checksum(query):
358+
"""Get the first matching checksum from the database."""
359+
search_fields = ['FILE_NAME', 'PACKAGE_NAME', 'APP_NAME']
360+
361+
for field in search_fields:
362+
result = RecentScansDB.objects.filter(
363+
**{f'{field}__icontains': query}).first()
364+
if result:
365+
return result.MD5
366+
367+
return None
352368

353369
# AJAX
354370

@@ -453,7 +469,7 @@ def delete_scan(request, api=False):
453469
else:
454470
md5_hash = request.POST['md5']
455471
data = {'deleted': 'scan hash not found'}
456-
if re.match('[0-9a-f]{32}', md5_hash):
472+
if re.match(MD5_REGEX, md5_hash):
457473
# Delete DB Entries
458474
scan = RecentScansDB.objects.filter(MD5=md5_hash)
459475
if scan.exists():
@@ -485,10 +501,7 @@ def delete_scan(request, api=False):
485501
except Exception as exp:
486502
msg = str(exp)
487503
exp_doc = exp.__doc__
488-
if api:
489-
return print_n_send_error_response(request, msg, True, exp_doc)
490-
else:
491-
return print_n_send_error_response(request, msg, False, exp_doc)
504+
return print_n_send_error_response(request, msg, api, exp_doc)
492505

493506

494507
class RecentScans(object):

mobsf/StaticAnalyzer/tests.py

+23-6
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def static_analysis_test():
117117
logger.info(resp.content)
118118
return True
119119

120-
# Search by MD5
120+
# Search by MD5 and text
121121
if platform.system() in ['Darwin', 'Linux']:
122122
scan_md5s = ['02e7989c457ab67eb514a8328779f256',
123123
'82ab8b2193b3cfb1c737e3a786be363a',
@@ -133,18 +133,23 @@ def static_analysis_test():
133133
'57bb5be0ea44a755ada4a93885c3825e',
134134
'8179b557433835827a70510584f3143e',
135135
'7b0a23bffc80bac05739ea1af898daad']
136+
# Search by text
137+
queries = [
138+
'diva',
139+
'webview',
140+
]
136141
logger.info('Running Search test')
137-
for scan_md5 in scan_md5s:
138-
url = '/search?md5={}'.format(scan_md5)
142+
for q in scan_md5s + queries:
143+
url = f'/search?query={q}'
139144
resp = http_client.get(url, follow=True)
140145
assert (resp.status_code == 200)
141146
if resp.status_code == 200:
142-
logger.info('[OK] Search by MD5 test passed for %s', scan_md5)
147+
logger.info('[OK] Search by query test passed for %s', q)
143148
else:
144-
logger.error('Search by MD5 test failed for %s', scan_md5)
149+
logger.error('Search by query test failed for %s', q)
145150
logger.info(resp.content)
146151
return True
147-
logger.info('[OK] Search by MD5 tests completed')
152+
logger.info('[OK] Search by MD5 and text tests completed')
148153

149154
# Deleting Scan Results
150155
logger.info('Running Delete Scan Results test')
@@ -256,6 +261,18 @@ def api_test():
256261
logger.error('Scan Logs API test: %s', upl['hash'])
257262
return True
258263
logger.info('[OK] Static Analysis API test completed')
264+
# Search API Tests
265+
logger.info('Running Search API tests')
266+
for term in ['diva', 'webview', '52c50ae824e329ba8b5b7a0f523efffe']:
267+
resp = http_client.post(
268+
'/api/v1/search',
269+
{'query': term},
270+
HTTP_AUTHORIZATION=auth)
271+
if resp.status_code == 200:
272+
logger.info('[OK] Search API test: %s', term)
273+
else:
274+
logger.error('Search API test: %s', term)
275+
return True
259276
# PDF Tests
260277
logger.info('Running PDF Generation API Test')
261278
if platform.system() in ['Darwin', 'Linux']:

0 commit comments

Comments
 (0)