Skip to content

Commit 4b394a2

Browse files
authoredNov 21, 2024··
[4.2.4] Async analysis REST API support, fix timeout handle function, Qa (#2456)
* Async analysis REST API support & Docs * Fix timeout handle function * Code QA untar permissions
1 parent a7fa827 commit 4b394a2

File tree

10 files changed

+106
-15
lines changed

10 files changed

+106
-15
lines changed
 

‎mobsf/DynamicAnalyzer/views/common/shared.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def onerror(func, path, exc_info):
5959
_, exc, _ = exc_info
6060
if exc.errno == errno.EACCES: # Permission error
6161
try:
62-
os.chmod(path, 0o777)
62+
os.chmod(path, 0o755)
6363
func(path)
6464
except Exception:
6565
pass

‎mobsf/MobSF/init.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
logger = logging.getLogger(__name__)
2020

21-
VERSION = '4.2.3'
21+
VERSION = '4.2.4'
2222
BANNER = r"""
2323
__ __ _ ____ _____ _ _ ____
2424
| \/ | ___ | |__/ ___|| ___|_ _| || | |___ \

‎mobsf/MobSF/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
re_path(r'^api/v1/scan$', api_sz.api_scan),
8989
re_path(r'^api/v1/search$', api_sz.api_search),
9090
re_path(r'^api/v1/scan_logs$', api_sz.api_scan_logs),
91+
re_path(r'^api/v1/tasks$', api_sz.api_tasks),
9192
re_path(r'^api/v1/delete_scan$', api_sz.api_delete_scan),
9293
re_path(r'^api/v1/download_pdf$', api_sz.api_pdf_report),
9394
re_path(r'^api/v1/report_json$', api_sz.api_json_report),

‎mobsf/MobSF/views/api/api_middleware.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
def make_api_response(data, status=OK):
1515
"""Make API Response."""
1616
resp = JsonResponse(
17-
data=data, # lgtm [py/stack-trace-exposure]
18-
status=status)
17+
data=data,
18+
status=status,
19+
safe=False)
1920
resp['Access-Control-Allow-Origin'] = '*'
2021
resp['Access-Control-Allow-Methods'] = 'POST'
2122
resp['Access-Control-Allow-Headers'] = 'Authorization, X-Mobsf-Api-Key'

‎mobsf/MobSF/views/api/api_static_analysis.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from mobsf.StaticAnalyzer.views.android.static_analyzer import static_analyzer
2424
from mobsf.StaticAnalyzer.views.ios.views import view_source as ios_view_source
2525
from mobsf.StaticAnalyzer.views.ios.static_analyzer import static_analyzer_ios
26+
from mobsf.StaticAnalyzer.views.common.async_task import list_tasks
2627
from mobsf.StaticAnalyzer.views.common.shared_func import compare_apps
2728
from mobsf.StaticAnalyzer.views.common.suppression import (
2829
delete_suppression,
@@ -109,10 +110,19 @@ def api_scan_logs(request):
109110
if not resp:
110111
return make_api_response(
111112
{'error': 'No scan logs found'}, 400)
112-
response = make_api_response({
113-
'logs': resp,
114-
}, 200)
115-
return response
113+
return make_api_response({'logs': resp}, 200)
114+
115+
116+
117+
@request_method(['POST'])
118+
@csrf_exempt
119+
def api_tasks(request):
120+
"""POST - Get Scan Queue."""
121+
resp = list_tasks(request, True)
122+
if not resp:
123+
return make_api_response(
124+
{'error': 'Scan queue empty'}, 400)
125+
return make_api_response(resp, 200)
116126

117127

118128
@request_method(['POST'])

‎mobsf/StaticAnalyzer/views/android/apk.py

+2
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ def apk_analysis(request, app_dic, rescan, api):
297297
if settings.ASYNC_ANALYSIS:
298298
return async_analysis(
299299
checksum,
300+
api,
300301
app_dic.get('app_name', ''),
301302
apk_analysis_task, checksum, app_dic, rescan)
302303
context, err = apk_analysis_task(checksum, app_dic, rescan)
@@ -452,6 +453,7 @@ def src_analysis(request, app_dic, rescan, api):
452453
if settings.ASYNC_ANALYSIS:
453454
return async_analysis(
454455
checksum,
456+
api,
455457
app_dic.get('app_name', ''),
456458
src_analysis_task, checksum, app_dic, rescan, pro_type)
457459
context = src_analysis_task(checksum, app_dic, rescan, pro_type)

‎mobsf/StaticAnalyzer/views/common/async_task.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
@receiver(post_execute)
3636
def detect_timeout(sender, task, **kwargs):
3737
"""Detect scan task timeout."""
38-
if 'Task exceeded maximum timeout' in task['result']:
38+
result = task.get('result')
39+
if isinstance(result, str) and 'Task exceeded maximum timeout' in result:
3940
task_id = task['id']
4041
EnqueuedTask.objects.filter(task_id=task_id).update(
4142
app_name='Failed',
@@ -45,7 +46,7 @@ def detect_timeout(sender, task, **kwargs):
4546
logger.error('Task %s exceeded maximum timeout', task_id)
4647

4748

48-
def async_analysis(checksum, app_name, func, *args):
49+
def async_analysis(checksum, api, file_name, func, *args, **kwargs):
4950
"""Async Analysis Task."""
5051
# Check if the task is already completed
5152
recent = RecentScansDB.objects.filter(MD5=checksum)
@@ -62,11 +63,17 @@ def async_analysis(checksum, app_name, func, *args):
6263
if queued_recently:
6364
if scan_completed:
6465
# scan already completed recently
65-
logger.warning('Analysis already completed in the last 60 minutes')
66+
msg = 'Analysis already completed in the last 60 minutes'
67+
logger.warning(msg)
68+
if api:
69+
return {'task_id': None, 'message': msg}
6670
return HttpResponseRedirect('/tasks?q=completed')
6771
elif active_recently:
6872
# scan not completed but active recently
69-
logger.warning('Analysis already enqueued in the last 60 minutes')
73+
msg = 'Analysis already enqueued in the last 60 minutes'
74+
logger.warning(msg)
75+
if api:
76+
return {'task_id': None, 'message': msg}
7077
return HttpResponseRedirect('/tasks?q=queued')
7178

7279
# Clear old tasks
@@ -80,6 +87,7 @@ def async_analysis(checksum, app_name, func, *args):
8087
.values_list('id', flat=True)[:task_count - queue_size])
8188
# Delete tasks by IDs
8289
EnqueuedTask.objects.filter(id__in=oldest_task_ids).delete()
90+
8391
# Enqueue the task
8492
task_id = async_task(
8593
func,
@@ -89,10 +97,12 @@ def async_analysis(checksum, app_name, func, *args):
8997
EnqueuedTask.objects.create(
9098
task_id=task_id,
9199
checksum=checksum,
92-
file_name=app_name[:254])
100+
file_name=file_name[:254])
93101
msg = f'Scan Queued with ID: {task_id}'
94102
logger.info(msg)
95103
append_scan_status(checksum, msg)
104+
if api:
105+
return {'task_id': task_id, 'message': msg}
96106
return HttpResponseRedirect('/tasks')
97107

98108

@@ -118,7 +128,7 @@ def get_live_status(enq):
118128

119129
@login_required
120130
@require_http_methods(['POST', 'GET'])
121-
def list_tasks(request):
131+
def list_tasks(request, api=False):
122132
if request.method == 'POST':
123133
enqueued = EnqueuedTask.objects.all().order_by('-created_at')
124134
task_data = []
@@ -133,6 +143,8 @@ def list_tasks(request):
133143
'completed_at': enq.completed_at,
134144
'status': get_live_status(enq),
135145
})
146+
if api:
147+
return task_data
136148
return JsonResponse(task_data, safe=False)
137149
context = {
138150
'title': 'Scan Tasks',

‎mobsf/StaticAnalyzer/views/ios/ipa.py

+2
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def ipa_analysis(request, app_dic, rescan, api):
258258
if settings.ASYNC_ANALYSIS:
259259
return async_analysis(
260260
checksum,
261+
api,
261262
app_dic.get('file_name', ''),
262263
ipa_analysis_task, checksum, app_dic, rescan)
263264
context, err = ipa_analysis_task(checksum, app_dic, rescan)
@@ -343,6 +344,7 @@ def ios_analysis(request, app_dic, rescan, api):
343344
if settings.ASYNC_ANALYSIS:
344345
return async_analysis(
345346
checksum,
347+
api,
346348
app_dic.get('file_name', ''),
347349
ios_analysis_task, checksum, app_dic, rescan)
348350
context = ios_analysis_task(checksum, app_dic, rescan)

‎mobsf/templates/general/apidocs.html

+63
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ <h1>Static Analysis</h1>
3232
<li><code>api/v1/scan_logs</code> - <a href="#scan-logs-api">Display Live Scan Logs</a></li>
3333
<li><code>api/v1/search</code> - <a href="#search-api">Search a Scan</a></li>
3434
<li><code>api/v1/scans</code> - <a href="#recent-scans-api">Display Recent Scans</a></li>
35+
<li><code>api/v1/tasks</code> - <a href="#tasks-api">Display Scan Tasks</a></li>
3536
<li><code>api/v1/delete_scan</code> - <a href="#delete-scan-api">Delete a Scan</a></li>
3637
<li><code>api/v1/scorecard</code> - <a href="#scorecard-api">App Scorecard</a></li>
3738
<li><code>api/v1/download_pdf</code> - <a href="#generate-pdf-report-api">Download PDF Report</a></li>
@@ -961,6 +962,68 @@ <h2><a id="recent-scans-api"></a><strong>Display Recent Scans API</strong></h2>
961962
</ul>
962963

963964
<hr />
965+
<h2><a id="tasks-api"></a><strong>Scan Tasks API</strong></h2>
966+
<p>Displays the scan tasks queue, accessible only when the asynchronous scan queue is enabled.</p>
967+
<ul>
968+
<li>
969+
<p><strong>URL:</strong> <code>/api/v1/tasks</code></p>
970+
</li>
971+
<li>
972+
<p><strong>Method:</strong> <code>POST</code></p>
973+
</li>
974+
<li>
975+
<p><strong>Header:</strong> <code>Authorization: &lt;api_key&gt;</code> <strong>Or</strong> <code>X-Mobsf-Api-Key: &lt;api_key&gt;</code></p>
976+
</li>
977+
</ul>
978+
<br />
979+
<ul>
980+
<li>
981+
<p><strong>Success Response:</strong></p>
982+
<ul>
983+
<li>
984+
<strong>Code:</strong> <code>200</code><br />
985+
<strong>Content-Type:</strong> <code>application/json; charset=utf-8</code> <br />
986+
<strong>Content:</strong> <code>JSON Contents</code>
987+
</li>
988+
</ul>
989+
</li>
990+
<li>
991+
<p><strong>Error Response:</strong></p>
992+
<ul>
993+
<li>
994+
<strong>Code:</strong> <code>500 Internal Server Error</code> or <code>405 Method Not Allowed</code> or <code>422 Unprocessable Entity</code><br />
995+
<strong>Content-Type:</strong> <code>application/json; charset=utf-8</code><br />
996+
<strong>Content:</strong> <code>{&quot;error&quot;: &lt;error message&gt; }</code>
997+
</li>
998+
</ul>
999+
<p>OR</p>
1000+
<ul>
1001+
<li>
1002+
<strong>Code:</strong> <code>401 Unauthorized</code><br />
1003+
<strong>Content-Type:</strong> <code>application/json; charset=utf-8</code><br />
1004+
<strong>Content:</strong> <code>{&quot;error&quot;: &quot;You are unauthorized to make this request.&quot; }</code>
1005+
</li>
1006+
</ul>
1007+
</li>
1008+
<li>
1009+
<p><strong>Sample Call:</strong></p>
1010+
<ul>
1011+
<li>
1012+
<pre><code>curl -X POST --url http://localhost:8000/api/v1/tasks -H &quot;Authorization: {{ api_key}}&quot;
1013+
</code></pre>
1014+
</li>
1015+
</ul>
1016+
<p>OR</p>
1017+
<ul>
1018+
<li>
1019+
<pre><code>curl -X POST --url http://localhost:8000/api/v1/tasks -H &quot;X-Mobsf-Api-Key: {{ api_key}}&quot;
1020+
</code></pre>
1021+
</li>
1022+
</ul>
1023+
</li>
1024+
</ul>
1025+
<hr />
1026+
9641027
<h2><a id="compare-api"></a><strong>Compare Apps API</strong></h2>
9651028
<p>API to Compare scan results.</p>
9661029
<ul>

‎pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "mobsf"
3-
version = "4.2.3"
3+
version = "4.2.4"
44
description = "Mobile Security Framework (MobSF) is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis."
55
keywords = ["mobsf", "mobile security framework", "mobile security", "security tool", "static analysis", "dynamic analysis", "malware analysis"]
66
authors = ["Ajin Abraham <ajin@opensecurity.in>"]

0 commit comments

Comments
 (0)
Please sign in to comment.