From 0c123100ba889ff3ae370eb7e2280963aff64609 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 12:28:24 +0530 Subject: [PATCH 01/19] temp fix to allow independent script execution --- scripts/start_avd.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/start_avd.py b/scripts/start_avd.py index ab222fbf..ad860fdc 100644 --- a/scripts/start_avd.py +++ b/scripts/start_avd.py @@ -4,7 +4,6 @@ import subprocess import django from os.path import dirname, abspath -from scripts.qcow2 import Qcow2 MobSF_path = dirname(dirname(abspath(__file__))) @@ -194,4 +193,7 @@ def main(): if __name__ == '__main__': + from qcow2 import Qcow2 sys.exit(main()) +else: + from scripts.qcow2 import Qcow2 From d9c2fe3d415a3f3129bc0157f1469ea5f86a2cc9 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:22:05 +0530 Subject: [PATCH 02/19] code QA --- Dockerfile | 1 + MobSF/settings.py | 4 +- MobSF/urls.py | 38 ++-- MobSF/utils.py | 2 +- MobSF/views/__init__.py | 0 MobSF/views/api/__init__.py | 0 MobSF/views/api/rest_api.py | 181 +++++++++++++++ MobSF/views/api/rest_api_middleware.py | 20 ++ MobSF/views/home.py | 292 +++++++++++++++++++++++++ MobSF/views/rest_api.py | 169 ++++++++++++++ MobSF/views/rest_api_middleware.py | 14 ++ MobSF/views/scanning.py | 119 ++++++++++ MobSF/views/views.py | 292 +++++++++++++++++++++++++ {MobSF => scripts}/kali_fix.sh | 0 14 files changed, 1110 insertions(+), 22 deletions(-) create mode 100644 MobSF/views/__init__.py create mode 100644 MobSF/views/api/__init__.py create mode 100755 MobSF/views/api/rest_api.py create mode 100644 MobSF/views/api/rest_api_middleware.py create mode 100755 MobSF/views/home.py create mode 100755 MobSF/views/rest_api.py create mode 100644 MobSF/views/rest_api_middleware.py create mode 100644 MobSF/views/scanning.py create mode 100755 MobSF/views/views.py rename {MobSF => scripts}/kali_fix.sh (100%) diff --git a/Dockerfile b/Dockerfile index 94b5edc3..26c964ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,6 +72,7 @@ WORKDIR /root/Mobile-Security-Framework-MobSF/MobSF RUN sed -i 's/USE_HOME = False/USE_HOME = True/g' settings.py #Kali fix to support 32 bit execution +WORKDIR /root/Mobile-Security-Framework-MobSF/scripts RUN ./kali_fix.sh #Install Dependencies diff --git a/MobSF/settings.py b/MobSF/settings.py index 08594077..7ce8b54c 100755 --- a/MobSF/settings.py +++ b/MobSF/settings.py @@ -195,7 +195,7 @@ ) MIDDLEWARE = ( - 'MobSF.rest_api_middleware.RestApiAuthMiddleware', + 'MobSF.views.api.rest_api_middleware.RestApiAuthMiddleware', ) ROOT_URLCONF = 'MobSF.urls' WSGI_APPLICATION = 'MobSF.wsgi.application' @@ -440,4 +440,4 @@ #================VirtualBox Settings============ VBOX = utils.FindVbox(False) -#=============================================== +#=============================================== \ No newline at end of file diff --git a/MobSF/urls.py b/MobSF/urls.py index 6100c8e7..a4fdae14 100755 --- a/MobSF/urls.py +++ b/MobSF/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url -import MobSF.views -import MobSF.rest_api +import MobSF.views.home +import MobSF.views.api.rest_api import DynamicAnalyzer.views.android.dynamic import StaticAnalyzer.views.android.static_analyzer import StaticAnalyzer.views.android.java @@ -17,18 +17,18 @@ urlpatterns = [ # Examples: - url(r'^$', MobSF.views.index), - url(r'^upload/$', MobSF.views.Upload.as_view), - url(r'^download/', MobSF.views.download), - url(r'^about$', MobSF.views.about), - url(r'^api_docs$', MobSF.views.api_docs), - url(r'^recent_scans/$', MobSF.views.recent_scans), - url(r'^delete_scan/$', MobSF.views.delete_scan), - url(r'^search$', MobSF.views.search), - url(r'^error/$', MobSF.views.error), - url(r'^not_found/$', MobSF.views.not_found), - url(r'^zip_format/$', MobSF.views.zip_format), - url(r'^mac_only/$', MobSF.views.mac_only), + url(r'^$', MobSF.views.home.index), + url(r'^upload/$', MobSF.views.home.Upload.as_view), + url(r'^download/', MobSF.views.home.download), + url(r'^about$', MobSF.views.home.about), + url(r'^api_docs$', MobSF.views.home.api_docs), + url(r'^recent_scans/$', MobSF.views.home.recent_scans), + url(r'^delete_scan/$', MobSF.views.home.delete_scan), + url(r'^search$', MobSF.views.home.search), + url(r'^error/$', MobSF.views.home.error), + url(r'^not_found/$', MobSF.views.home.not_found), + url(r'^zip_format/$', MobSF.views.home.zip_format), + url(r'^mac_only/$', MobSF.views.home.mac_only), # Android Static Analysis url(r'^StaticAnalyzer/$', @@ -68,11 +68,11 @@ url(r'^capfuzz$', DynamicAnalyzer.views.android.dynamic.capfuzz_start), # REST API - url(r'^api/v1/upload$', MobSF.rest_api.api_upload), - url(r'^api/v1/scan$', MobSF.rest_api.api_scan), - url(r'^api/v1/delete_scan$', MobSF.rest_api.api_delete_scan), - url(r'^api/v1/download_pdf$', MobSF.rest_api.api_pdf_report), - url(r'^api/v1/report_json$', MobSF.rest_api.api_json_report), + url(r'^api/v1/upload$', MobSF.views.api.rest_api.api_upload), + url(r'^api/v1/scan$', MobSF.views.api.rest_api.api_scan), + url(r'^api/v1/delete_scan$', MobSF.views.api.rest_api.api_delete_scan), + url(r'^api/v1/download_pdf$', MobSF.views.api.rest_api.api_pdf_report), + url(r'^api/v1/report_json$', MobSF.views.api.rest_api.api_json_report), # Test url(r'^runtest/$', StaticAnalyzer.tests.start_test), diff --git a/MobSF/utils.py b/MobSF/utils.py index 23c8324d..bab82ef5 100755 --- a/MobSF/utils.py +++ b/MobSF/utils.py @@ -213,7 +213,7 @@ def migrate(BASE_DIR): def kali_fix(BASE_DIR): try: if platform.system() == "Linux" and platform.dist()[0] == "Kali": - fix_path = os.path.join(BASE_DIR, "MobSF/kali_fix.sh") + fix_path = os.path.join(BASE_DIR, "scripts/kali_fix.sh") subprocess.call(["chmod", "a+x", fix_path]) subprocess.call([fix_path], shell=True) except: diff --git a/MobSF/views/__init__.py b/MobSF/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MobSF/views/api/__init__.py b/MobSF/views/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MobSF/views/api/rest_api.py b/MobSF/views/api/rest_api.py new file mode 100755 index 00000000..dacab8b4 --- /dev/null +++ b/MobSF/views/api/rest_api.py @@ -0,0 +1,181 @@ +""" +MobSF REST API V 1 +""" +import json + +from django.http import ( + HttpResponse +) +from django.views.decorators.csrf import csrf_exempt + +from MobSF.views.home import ( + Upload, + delete_scan +) +from MobSF.utils import ( + api_key, + request_method +) +from StaticAnalyzer.views.shared_func import ( + pdf +) +from StaticAnalyzer.views.android.static_analyzer import ( + static_analyzer +) +from StaticAnalyzer.views.ios.static_analyzer import ( + static_analyzer_ios +) +from StaticAnalyzer.views.windows import ( + staticanalyzer_windows +) + + +POST = 'POST' + + +def make_api_response(data, status=200): + """Make API Response""" + api_resp = HttpResponse(json.dumps( + data, + sort_keys=True, + indent=4, + separators=(',', ': ')), + content_type="application/json; charset=utf-8", + status=status) + api_resp['Access-Control-Allow-Origin'] = '*' + return api_resp + + +def api_auth(meta): + """Check if API Key Matches""" + if "HTTP_AUTHORIZATION" in meta: + return bool(api_key() == meta["HTTP_AUTHORIZATION"]) + return False + + +@request_method(['POST']) +@csrf_exempt +def api_upload(request): + """POST - Upload API""" + upload = Upload(request) + return upload.upload_api() + + +@csrf_exempt +def api_scan(request): + """POST - Scan API""" + if request.method == 'POST': + params = ['scan_type', 'hash', 'file_name'] + if set(request.POST) >= set(params): + scan_type = request.POST['scan_type'] + # APK, Android ZIP and iOS ZIP + if scan_type in ["apk", "zip"]: + resp = static_analyzer(request, True) + if "type" in resp: + # For now it's only ios_zip + request.POST._mutable = True + request.POST['scan_type'] = "ios" + resp = static_analyzer_ios(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # IPA + elif scan_type == "ipa": + resp = static_analyzer_ios(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # APPX + elif scan_type == "appx": + resp = staticanalyzer_windows(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + + return response + + +@csrf_exempt +def api_delete_scan(request): + """POST - Delete a Scan""" + if request.method == 'POST': + if "hash" in request.POST: + resp = delete_scan(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + + return response + + +@csrf_exempt +def api_pdf_report(request): + """Generate and Download PDF""" + if request.method == 'POST': + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): + response = make_api_response(resp, 400) + else: + response = make_api_response(resp, 500) + elif "pdf_dat" in resp: + response = HttpResponse( + resp["pdf_dat"], content_type='application/pdf') + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) + else: + response = make_api_response( + {"error": "PDF Generation Error"}, 500) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + return response + + +@csrf_exempt +def api_json_report(request): + """Generate JSON Report""" + if request.method == 'POST': + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): + response = make_api_response(resp, 400) + else: + response = make_api_response(resp, 500) + elif "report_dat" in resp: + response = make_api_response(resp["report_dat"], 200) + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) + else: + response = make_api_response( + {"error": "JSON Generation Error"}, 500) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + return response diff --git a/MobSF/views/api/rest_api_middleware.py b/MobSF/views/api/rest_api_middleware.py new file mode 100644 index 00000000..9d9cce09 --- /dev/null +++ b/MobSF/views/api/rest_api_middleware.py @@ -0,0 +1,20 @@ +""" +REST API Middleware +""" +from django.utils.deprecation import MiddlewareMixin +from MobSF.views.api.rest_api import ( + api_auth, + make_api_response +) + +UNAUTHORIZED = 401 + + +class RestApiAuthMiddleware(MiddlewareMixin): + + def process_request(self, request): + if not request.path.startswith("/api/"): + return + if not api_auth(request.META): + return make_api_response( + {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) diff --git a/MobSF/views/home.py b/MobSF/views/home.py new file mode 100755 index 00000000..ad9207db --- /dev/null +++ b/MobSF/views/home.py @@ -0,0 +1,292 @@ +# -*- coding: utf_8 -*- +""" +MobSF File Upload and Home Routes +""" +import json +import os +import platform +import re +import shutil +from wsgiref.util import FileWrapper + +from django.conf import settings +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.shortcuts import render + +from MobSF.forms import FormUtil, UploadFileForm +from MobSF.views.scanning import Scanning +from MobSF.utils import (FileType, PrintException, api_key, isDirExists, + isFileExists, print_n_send_error_response) +from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid, + StaticAnalyzerIOSZIP, StaticAnalyzerIPA, + StaticAnalyzerWindows) + +LINUX_PLATFORM = ["Darwin", "Linux"] +HTTP_BAD_REQUEST = 400 + +def index(request): + """ + Index Route + """ + context = {'version': settings.MOBSF_VER} + template = "general/index.html" + return render(request, template, context) + + +class Upload(object): + """ + Handle File Upload based on App type + """ + + def __init__(self, request): + self.request = request + self.form = UploadFileForm(request.POST, request.FILES) + + @staticmethod + def as_view(request): + upload = Upload(request) + return upload.upload_html() + + def upload_html(self): + response_data = { + 'url': '', + 'description': '', + 'status': 'error' + } + request = self.request + resp = HttpResponse(json.dumps(response_data), + content_type="application/json; charset=utf-8") + resp['Access-Control-Allow-Origin'] = '*' + + if request.method != 'POST': + response_data['description'] = 'Method not Supported!' + print("\n[ERROR] Method not Supported!") + form = UploadFileForm() + resp['status'] = HTTP_BAD_REQUEST + return resp + + if not self.form.is_valid(): + response_data['description'] = 'Invalid Form Data!' + print("\n[ERROR] Invalid Form Data!") + resp['status'] = HTTP_BAD_REQUEST + return resp + + self.file_content_type = request.FILES['file'].content_type + self.file_name_lower = request.FILES['file'].name.lower() + self.file_type = FileType(self.file_content_type, self.file_name_lower) + if not self.file_type.is_allow_file(): + response_data['description'] = 'File format not Supported!' + print("\n[ERROR] File format not Supported!") + resp['status'] = HTTP_BAD_REQUEST + return resp + + if self.file_type.is_ipa(): + if platform.system() not in LINUX_PLATFORM: + data = { + 'error': "Static Analysis of iOS IPA requires Mac or Linux", + 'url': 'mac_only/', + 'status': 'success' + } + print("\n[ERROR] Static Analysis of iOS IPA requires Mac or Linux") + return data + + data = self.upload() + + response_data['url'] = data['url'] + response_data['status'] = data['status'] + return HttpResponse(json.dumps(response_data), + content_type="application/json; charset=utf-8") + + + + def upload_api(self): + api_response = { + } + + request = self.request + if not self.form.is_valid(): + api_response['error'] = FormUtil.errors_message(self.form) + return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) + + self.file_content_type = request.FILES['file'].content_type + self.file_name_lower = request.FILES['file'].name.lower() + self.file_type = FileType(self.file_content_type, self.file_name_lower) + + if not self.file_type.is_allow_file(): + api_response["error"] = "File format not Supported!" + return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) + data = self.upload() + return JsonResponse({ + 'scan_type': data['scan_type'], + 'hash': data['hash'], + 'file_name': data['file_name'] + }) + + def upload(self): + request = self.request + scanning = Scanning(request) + file_type = self.file_content_type + file_name_lower = self.file_name_lower + + print("[INFO] MIME Type: {} FILE: {}".format(file_type, file_name_lower)) + if self.file_type.is_apk(): + return scanning.scan_apk() + elif self.file_type.is_zip(): + return scanning.scan_zip() + elif self.file_type.is_ipa(): + return scanning.scan_ipa() + # Windows APPX + elif self.file_type.is_appx(): + return scanning.scan_appx() + + +def api_docs(request): + """ + API Docs Route + """ + context = {'title': 'REST API Docs', 'api_key': api_key()} + template = "general/apidocs.html" + return render(request, template, context) + + +def about(request): + """ + About Route + """ + context = {'title': 'About'} + template = "general/about.html" + return render(request, template, context) + + +def error(request): + """ + Error Route + """ + context = {'title': 'Error'} + template = "general/error.html" + return render(request, template, context) + + +def zip_format(request): + """ + Zip Format Message Route + """ + context = {'title': 'Zipped Source Instruction'} + template = "general/zip.html" + return render(request, template, context) + + +def mac_only(request): + """ + Mac Ony Message Route + """ + context = {'title': 'Supports OSX Only'} + template = "general/ios.html" + return render(request, template, context) + + +def not_found(request): + """ + Not Found Route + """ + context = {'title': 'Not Found'} + template = "general/not_found.html" + return render(request, template, context) + + +def recent_scans(request): + """ + Show Recent Scans Route + """ + db_obj = RecentScansDB.objects.all().order_by('-TS') + context = {'title': 'Recent Scans', 'entries': db_obj} + template = "general/recent.html" + return render(request, template, context) + + +def search(request): + """ + Search Scan by MD5 Route + """ + md5 = request.GET['md5'] + if re.match('[0-9a-f]{32}', md5): + db_obj = RecentScansDB.objects.filter(MD5=md5) + if db_obj.exists(): + return HttpResponseRedirect('/' + db_obj[0].URL) + else: + return HttpResponseRedirect('/not_found') + return HttpResponseRedirect('/error/') + + +def download(request): + """ + Download from MobSF Route + """ + try: + if request.method == 'GET': + allowed_exts = settings.ALLOWED_EXTENSIONS + filename = request.path.replace("/download/", "", 1) + # Security Checks + if "../" in filename: + print("\n[ATTACK] Path Traversal Attack detected") + return HttpResponseRedirect('/error/') + ext = os.path.splitext(filename)[1] + if ext in allowed_exts: + dwd_file = os.path.join(settings.DWD_DIR, filename) + if os.path.isfile(dwd_file): + wrapper = FileWrapper(open(dwd_file, "rb")) + response = HttpResponse( + wrapper, content_type=allowed_exts[ext]) + response['Content-Length'] = os.path.getsize(dwd_file) + return response + except: + PrintException("Error Downloading File") + return HttpResponseRedirect('/error/') + + +def delete_scan(request, api=False): + """ + Delete Scan from DB and remove the scan related files + """ + try: + if request.method == 'POST': + if api: + md5_hash = request.POST['hash'] + else: + md5_hash = request.POST['md5'] + data = {'deleted': 'no'} + if re.match('[0-9a-f]{32}', md5_hash): + # Delete DB Entries + scan = RecentScansDB.objects.filter(MD5=md5_hash) + if scan.exists(): + RecentScansDB.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerAndroid.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerIPA.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerIOSZIP.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerWindows.objects.filter(MD5=md5_hash).delete() + # Delete Upload Dir Contents + app_upload_dir = os.path.join(settings.UPLD_DIR, md5_hash) + if isDirExists(app_upload_dir): + shutil.rmtree(app_upload_dir) + # Delete Download Dir Contents + dw_dir = settings.DWD_DIR + for item in os.listdir(dw_dir): + item_path = os.path.join(dw_dir, item) + # Delete all related files + if isFileExists(item_path) and item.startswith(md5_hash + "-"): + os.remove(item_path) + # Delete related directories + if isDirExists(item_path) and item.startswith(md5_hash + "-"): + shutil.rmtree(item_path) + data = {'deleted': 'yes'} + if api: + return data + else: + return HttpResponse(json.dumps(data), content_type='application/json; charset=utf-8') + except Exception as exp: + msg = str(exp) + exp_doc = exp.__doc__ + if api: + return print_n_send_error_response(request, msg, True, exp_doc) + else: + return print_n_send_error_response(request, msg, False, exp_doc) diff --git a/MobSF/views/rest_api.py b/MobSF/views/rest_api.py new file mode 100755 index 00000000..00a1f04b --- /dev/null +++ b/MobSF/views/rest_api.py @@ -0,0 +1,169 @@ +""" +MobSF REST API V 1 +""" +import json + +from .forms import UploadFileForm +from MobSF.views import ( + Upload, + delete_scan +) +from MobSF.utils import ( + api_key +) +from StaticAnalyzer.views.shared_func import ( + pdf +) +from StaticAnalyzer.views.android.static_analyzer import static_analyzer +from StaticAnalyzer.views.ios.static_analyzer import static_analyzer_ios +from StaticAnalyzer.views.windows import staticanalyzer_windows + +from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponseNotAllowed +from django.views.decorators.csrf import csrf_exempt + +from MobSF.utils import request_method + +POST = 'POST' + +def make_api_response(data, status=200): + """Make API Response""" + api_resp = HttpResponse(json.dumps( + data, sort_keys=True, + indent=4, separators=(',', ': ')), content_type="application/json; charset=utf-8", status=status) + api_resp['Access-Control-Allow-Origin'] = '*' + return api_resp + + +def api_auth(meta): + """Check if API Key Matches""" + if "HTTP_AUTHORIZATION" in meta: + return bool(api_key() == meta["HTTP_AUTHORIZATION"]) + return False + + +@request_method(['POST']) +@csrf_exempt +def api_upload(request): + """POST - Upload API""" + upload = Upload(request) + return upload.upload_api() + + +@csrf_exempt +def api_scan(request): + """POST - Scan API""" + if request.method == 'POST': + params = ['scan_type', 'hash', 'file_name'] + if set(request.POST) >= set(params): + scan_type = request.POST['scan_type'] + # APK, Android ZIP and iOS ZIP + if scan_type in ["apk", "zip"]: + resp = static_analyzer(request, True) + if "type" in resp: + # For now it's only ios_zip + request.POST._mutable = True + request.POST['scan_type'] = "ios" + resp = static_analyzer_ios(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # IPA + elif scan_type == "ipa": + resp = static_analyzer_ios(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # APPX + elif scan_type == "appx": + resp = staticanalyzer_windows(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + + return response + + +@csrf_exempt +def api_delete_scan(request): + """POST - Delete a Scan""" + if request.method == 'POST': + if "hash" in request.POST: + resp = delete_scan(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + + return response + + +@csrf_exempt +def api_pdf_report(request): + """Generate and Download PDF""" + if request.method == 'POST': + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): + response = make_api_response(resp, 400) + else: + response = make_api_response(resp, 500) + elif "pdf_dat" in resp: + response = HttpResponse( + resp["pdf_dat"], content_type='application/pdf') + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) + else: + response = make_api_response( + {"error": "PDF Generation Error"}, 500) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + return response + + +@csrf_exempt +def api_json_report(request): + """Generate JSON Report""" + if request.method == 'POST': + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): + response = make_api_response(resp, 400) + else: + response = make_api_response(resp, 500) + elif "report_dat" in resp: + response = make_api_response(resp["report_dat"], 200) + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) + else: + response = make_api_response( + {"error": "JSON Generation Error"}, 500) + else: + response = make_api_response( + {"error": "Missing Parameters"}, 422) + else: + response = make_api_response({"error": "Method Not Allowed"}, 405) + return response diff --git a/MobSF/views/rest_api_middleware.py b/MobSF/views/rest_api_middleware.py new file mode 100644 index 00000000..305ac217 --- /dev/null +++ b/MobSF/views/rest_api_middleware.py @@ -0,0 +1,14 @@ + +from django.utils.deprecation import MiddlewareMixin +from .rest_api import api_auth, make_api_response + +UNAUTHORIZED = 401 + +class RestApiAuthMiddleware(MiddlewareMixin): + + def process_request(self, request): + if not request.path.startswith("/api/"): + return + if not api_auth(request.META): + return make_api_response( + {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) \ No newline at end of file diff --git a/MobSF/views/scanning.py b/MobSF/views/scanning.py new file mode 100644 index 00000000..89631a28 --- /dev/null +++ b/MobSF/views/scanning.py @@ -0,0 +1,119 @@ + +# from MobSF.views import handle_uploaded_file, add_to_recent_scan +import hashlib +import os + +from django.conf import settings +from django.utils import timezone + +from MobSF.utils import PrintException +from StaticAnalyzer.models import RecentScansDB + + +def add_to_recent_scan(name, md5, url): + """ + Add Entry to Database under Recent Scan + """ + try: + db_obj = RecentScansDB.objects.filter(MD5=md5) + if not db_obj.exists(): + new_db_obj = RecentScansDB( + NAME=name, MD5=md5, URL=url, TS=timezone.now()) + new_db_obj.save() + except: + PrintException("[ERROR] Adding Scan URL to Database") + + +def handle_uploaded_file(filecnt, typ): + """ + Write Uploaded File + """ + md5 = hashlib.md5() # modify if crash for large + for chunk in filecnt.chunks(): + md5.update(chunk) + md5sum = md5.hexdigest() + anal_dir = os.path.join(settings.UPLD_DIR, md5sum + '/') + if not os.path.exists(anal_dir): + os.makedirs(anal_dir) + with open(anal_dir + md5sum + typ, 'wb+') as destination: + for chunk in filecnt.chunks(): + destination.write(chunk) + return md5sum + + + +class Scanning(object): + + def __init__(self, request): + self.request = request + self.file = request.FILES['file'] + self.file_name = request.FILES['file'].name + + def scan_apk(self): + """ + """ + # APK + md5 = handle_uploaded_file(self.file, '.apk') + data = { + 'url': 'StaticAnalyzer/?name={}&type=apk&checksum={}'.format(self.file_name, md5), + 'status': 'success', + 'hash': md5, + 'scan_type': 'apk', + 'file_name': self.file_name + } + + add_to_recent_scan(self.file_name, md5, data['url']) + + print("\n[INFO] Performing Static Analysis of Android APK") + return data + + def scan_zip(self): + """ + """ + # Android /iOS Zipped Source + md5 = handle_uploaded_file(self.file, '.zip') + + data = { + 'url': 'StaticAnalyzer/?name={}&type=zip&checksum={}'.format(self.file_name, md5), + 'status': 'success', + 'hash': md5, + 'scan_type': 'zip', + 'file_name': self.file_name + } + + add_to_recent_scan(self.file_name, md5, data['url']) + print("\n[INFO] Performing Static Analysis of Android/iOS Source Code") + return data + + def scan_ipa(self): + """ + iOS Binary + """ + md5 = handle_uploaded_file(self.file, '.ipa') + data = { + 'hash': md5, + 'scan_type': 'ipa', + 'file_name': self.file_name, + 'url': 'StaticAnalyzer_iOS/?name={}&type=ipa&checksum={}'.format(self.file_name, md5), + 'status': 'success' + } + + add_to_recent_scan(self.file_name, md5, data['url']) + print("\n[INFO] Performing Static Analysis of iOS IPA") + return data + + def scan_appx(self): + """ + """ + md5 = handle_uploaded_file(self.file, '.appx') + data = { + 'hash': md5, + 'scan_type': 'appx', + 'file_name': self.file_name, + 'url': 'StaticAnalyzer_Windows/?name={}&type=appx&checksum={}'.format(self.file_name, md5), + 'status': 'success' + } + + add_to_recent_scan(self.file_name, md5, data['url']) + print("\n[INFO] Performing Static Analysis of Windows APP") + return data diff --git a/MobSF/views/views.py b/MobSF/views/views.py new file mode 100755 index 00000000..ec637839 --- /dev/null +++ b/MobSF/views/views.py @@ -0,0 +1,292 @@ +# -*- coding: utf_8 -*- +""" +MobSF File Upload and Home Routes +""" +import json +import os +import platform +import re +import shutil +from wsgiref.util import FileWrapper + +from django.conf import settings +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.shortcuts import render + +from MobSF.forms import FormUtil, UploadFileForm +from MobSF.scanning import Scanning +from MobSF.utils import (FileType, PrintException, api_key, isDirExists, + isFileExists, print_n_send_error_response) +from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid, + StaticAnalyzerIOSZIP, StaticAnalyzerIPA, + StaticAnalyzerWindows) + +LINUX_PLATFORM = ["Darwin", "Linux"] +HTTP_BAD_REQUEST = 400 + +def index(request): + """ + Index Route + """ + context = {'version': settings.MOBSF_VER} + template = "general/index.html" + return render(request, template, context) + + +class Upload(object): + """ + Handle File Upload based on App type + """ + + def __init__(self, request): + self.request = request + self.form = UploadFileForm(request.POST, request.FILES) + + @staticmethod + def as_view(request): + upload = Upload(request) + return upload.upload_html() + + def upload_html(self): + response_data = { + 'url': '', + 'description': '', + 'status': 'error' + } + request = self.request + resp = HttpResponse(json.dumps(response_data), + content_type="application/json; charset=utf-8") + resp['Access-Control-Allow-Origin'] = '*' + + if request.method != 'POST': + response_data['description'] = 'Method not Supported!' + print("\n[ERROR] Method not Supported!") + form = UploadFileForm() + resp['status'] = HTTP_BAD_REQUEST + return resp + + if not self.form.is_valid(): + response_data['description'] = 'Invalid Form Data!' + print("\n[ERROR] Invalid Form Data!") + resp['status'] = HTTP_BAD_REQUEST + return resp + + self.file_content_type = request.FILES['file'].content_type + self.file_name_lower = request.FILES['file'].name.lower() + self.file_type = FileType(self.file_content_type, self.file_name_lower) + if not self.file_type.is_allow_file(): + response_data['description'] = 'File format not Supported!' + print("\n[ERROR] File format not Supported!") + resp['status'] = HTTP_BAD_REQUEST + return resp + + if self.file_type.is_ipa(): + if platform.system() not in LINUX_PLATFORM: + data = { + 'error': "Static Analysis of iOS IPA requires Mac or Linux", + 'url': 'mac_only/', + 'status': 'success' + } + print("\n[ERROR] Static Analysis of iOS IPA requires Mac or Linux") + return data + + data = self.upload() + + response_data['url'] = data['url'] + response_data['status'] = data['status'] + return HttpResponse(json.dumps(response_data), + content_type="application/json; charset=utf-8") + + + + def upload_api(self): + api_response = { + } + + request = self.request + if not self.form.is_valid(): + api_response['error'] = FormUtil.errors_message(self.form) + return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) + + self.file_content_type = request.FILES['file'].content_type + self.file_name_lower = request.FILES['file'].name.lower() + self.file_type = FileType(self.file_content_type, self.file_name_lower) + + if not self.file_type.is_allow_file(): + api_response["error"] = "File format not Supported!" + return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) + data = self.upload() + return JsonResponse({ + 'scan_type': data['scan_type'], + 'hash': data['hash'], + 'file_name': data['file_name'] + }) + + def upload(self): + request = self.request + scanning = Scanning(request) + file_type = self.file_content_type + file_name_lower = self.file_name_lower + + print("[INFO] MIME Type: {} FILE: {}".format(file_type, file_name_lower)) + if self.file_type.is_apk(): + return scanning.scan_apk() + elif self.file_type.is_zip(): + return scanning.scan_zip() + elif self.file_type.is_ipa(): + return scanning.scan_ipa() + # Windows APPX + elif self.file_type.is_appx(): + return scanning.scan_appx() + + +def api_docs(request): + """ + API Docs Route + """ + context = {'title': 'REST API Docs', 'api_key': api_key()} + template = "general/apidocs.html" + return render(request, template, context) + + +def about(request): + """ + About Route + """ + context = {'title': 'About'} + template = "general/about.html" + return render(request, template, context) + + +def error(request): + """ + Error Route + """ + context = {'title': 'Error'} + template = "general/error.html" + return render(request, template, context) + + +def zip_format(request): + """ + Zip Format Message Route + """ + context = {'title': 'Zipped Source Instruction'} + template = "general/zip.html" + return render(request, template, context) + + +def mac_only(request): + """ + Mac Ony Message Route + """ + context = {'title': 'Supports OSX Only'} + template = "general/ios.html" + return render(request, template, context) + + +def not_found(request): + """ + Not Found Route + """ + context = {'title': 'Not Found'} + template = "general/not_found.html" + return render(request, template, context) + + +def recent_scans(request): + """ + Show Recent Scans Route + """ + db_obj = RecentScansDB.objects.all().order_by('-TS') + context = {'title': 'Recent Scans', 'entries': db_obj} + template = "general/recent.html" + return render(request, template, context) + + +def search(request): + """ + Search Scan by MD5 Route + """ + md5 = request.GET['md5'] + if re.match('[0-9a-f]{32}', md5): + db_obj = RecentScansDB.objects.filter(MD5=md5) + if db_obj.exists(): + return HttpResponseRedirect('/' + db_obj[0].URL) + else: + return HttpResponseRedirect('/not_found') + return HttpResponseRedirect('/error/') + + +def download(request): + """ + Download from MobSF Route + """ + try: + if request.method == 'GET': + allowed_exts = settings.ALLOWED_EXTENSIONS + filename = request.path.replace("/download/", "", 1) + # Security Checks + if "../" in filename: + print("\n[ATTACK] Path Traversal Attack detected") + return HttpResponseRedirect('/error/') + ext = os.path.splitext(filename)[1] + if ext in allowed_exts: + dwd_file = os.path.join(settings.DWD_DIR, filename) + if os.path.isfile(dwd_file): + wrapper = FileWrapper(open(dwd_file, "rb")) + response = HttpResponse( + wrapper, content_type=allowed_exts[ext]) + response['Content-Length'] = os.path.getsize(dwd_file) + return response + except: + PrintException("Error Downloading File") + return HttpResponseRedirect('/error/') + + +def delete_scan(request, api=False): + """ + Delete Scan from DB and remove the scan related files + """ + try: + if request.method == 'POST': + if api: + md5_hash = request.POST['hash'] + else: + md5_hash = request.POST['md5'] + data = {'deleted': 'no'} + if re.match('[0-9a-f]{32}', md5_hash): + # Delete DB Entries + scan = RecentScansDB.objects.filter(MD5=md5_hash) + if scan.exists(): + RecentScansDB.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerAndroid.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerIPA.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerIOSZIP.objects.filter(MD5=md5_hash).delete() + StaticAnalyzerWindows.objects.filter(MD5=md5_hash).delete() + # Delete Upload Dir Contents + app_upload_dir = os.path.join(settings.UPLD_DIR, md5_hash) + if isDirExists(app_upload_dir): + shutil.rmtree(app_upload_dir) + # Delete Download Dir Contents + dw_dir = settings.DWD_DIR + for item in os.listdir(dw_dir): + item_path = os.path.join(dw_dir, item) + # Delete all related files + if isFileExists(item_path) and item.startswith(md5_hash + "-"): + os.remove(item_path) + # Delete related directories + if isDirExists(item_path) and item.startswith(md5_hash + "-"): + shutil.rmtree(item_path) + data = {'deleted': 'yes'} + if api: + return data + else: + return HttpResponse(json.dumps(data), content_type='application/json; charset=utf-8') + except Exception as exp: + msg = str(exp) + exp_doc = exp.__doc__ + if api: + return print_n_send_error_response(request, msg, True, exp_doc) + else: + return print_n_send_error_response(request, msg, False, exp_doc) diff --git a/MobSF/kali_fix.sh b/scripts/kali_fix.sh similarity index 100% rename from MobSF/kali_fix.sh rename to scripts/kali_fix.sh From 781aefc74f040f817e7c91f6bf47725b67ea5299 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:25:59 +0530 Subject: [PATCH 03/19] Delete rest_api.py --- MobSF/rest_api.py | 169 ---------------------------------------------- 1 file changed, 169 deletions(-) delete mode 100755 MobSF/rest_api.py diff --git a/MobSF/rest_api.py b/MobSF/rest_api.py deleted file mode 100755 index 00a1f04b..00000000 --- a/MobSF/rest_api.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -MobSF REST API V 1 -""" -import json - -from .forms import UploadFileForm -from MobSF.views import ( - Upload, - delete_scan -) -from MobSF.utils import ( - api_key -) -from StaticAnalyzer.views.shared_func import ( - pdf -) -from StaticAnalyzer.views.android.static_analyzer import static_analyzer -from StaticAnalyzer.views.ios.static_analyzer import static_analyzer_ios -from StaticAnalyzer.views.windows import staticanalyzer_windows - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponseNotAllowed -from django.views.decorators.csrf import csrf_exempt - -from MobSF.utils import request_method - -POST = 'POST' - -def make_api_response(data, status=200): - """Make API Response""" - api_resp = HttpResponse(json.dumps( - data, sort_keys=True, - indent=4, separators=(',', ': ')), content_type="application/json; charset=utf-8", status=status) - api_resp['Access-Control-Allow-Origin'] = '*' - return api_resp - - -def api_auth(meta): - """Check if API Key Matches""" - if "HTTP_AUTHORIZATION" in meta: - return bool(api_key() == meta["HTTP_AUTHORIZATION"]) - return False - - -@request_method(['POST']) -@csrf_exempt -def api_upload(request): - """POST - Upload API""" - upload = Upload(request) - return upload.upload_api() - - -@csrf_exempt -def api_scan(request): - """POST - Scan API""" - if request.method == 'POST': - params = ['scan_type', 'hash', 'file_name'] - if set(request.POST) >= set(params): - scan_type = request.POST['scan_type'] - # APK, Android ZIP and iOS ZIP - if scan_type in ["apk", "zip"]: - resp = static_analyzer(request, True) - if "type" in resp: - # For now it's only ios_zip - request.POST._mutable = True - request.POST['scan_type'] = "ios" - resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # IPA - elif scan_type == "ipa": - resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # APPX - elif scan_type == "appx": - resp = staticanalyzer_windows(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - - return response - - -@csrf_exempt -def api_delete_scan(request): - """POST - Delete a Scan""" - if request.method == 'POST': - if "hash" in request.POST: - resp = delete_scan(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - - return response - - -@csrf_exempt -def api_pdf_report(request): - """Generate and Download PDF""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "pdf_dat" in resp: - response = HttpResponse( - resp["pdf_dat"], content_type='application/pdf') - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): - response = make_api_response(resp, 400) - else: - response = make_api_response( - {"error": "PDF Generation Error"}, 500) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - return response - - -@csrf_exempt -def api_json_report(request): - """Generate JSON Report""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "report_dat" in resp: - response = make_api_response(resp["report_dat"], 200) - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): - response = make_api_response(resp, 400) - else: - response = make_api_response( - {"error": "JSON Generation Error"}, 500) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - return response From aded35b9cb705e5256b3a9217ed821ec9db887d2 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:26:28 +0530 Subject: [PATCH 04/19] Delete rest_api_middleware.py --- MobSF/rest_api_middleware.py | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 MobSF/rest_api_middleware.py diff --git a/MobSF/rest_api_middleware.py b/MobSF/rest_api_middleware.py deleted file mode 100644 index 305ac217..00000000 --- a/MobSF/rest_api_middleware.py +++ /dev/null @@ -1,14 +0,0 @@ - -from django.utils.deprecation import MiddlewareMixin -from .rest_api import api_auth, make_api_response - -UNAUTHORIZED = 401 - -class RestApiAuthMiddleware(MiddlewareMixin): - - def process_request(self, request): - if not request.path.startswith("/api/"): - return - if not api_auth(request.META): - return make_api_response( - {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) \ No newline at end of file From 28e5d4bc6489eb9a22674db0b15f86e68db4ceff Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:26:43 +0530 Subject: [PATCH 05/19] Delete scanning.py --- MobSF/scanning.py | 119 ---------------------------------------------- 1 file changed, 119 deletions(-) delete mode 100644 MobSF/scanning.py diff --git a/MobSF/scanning.py b/MobSF/scanning.py deleted file mode 100644 index 89631a28..00000000 --- a/MobSF/scanning.py +++ /dev/null @@ -1,119 +0,0 @@ - -# from MobSF.views import handle_uploaded_file, add_to_recent_scan -import hashlib -import os - -from django.conf import settings -from django.utils import timezone - -from MobSF.utils import PrintException -from StaticAnalyzer.models import RecentScansDB - - -def add_to_recent_scan(name, md5, url): - """ - Add Entry to Database under Recent Scan - """ - try: - db_obj = RecentScansDB.objects.filter(MD5=md5) - if not db_obj.exists(): - new_db_obj = RecentScansDB( - NAME=name, MD5=md5, URL=url, TS=timezone.now()) - new_db_obj.save() - except: - PrintException("[ERROR] Adding Scan URL to Database") - - -def handle_uploaded_file(filecnt, typ): - """ - Write Uploaded File - """ - md5 = hashlib.md5() # modify if crash for large - for chunk in filecnt.chunks(): - md5.update(chunk) - md5sum = md5.hexdigest() - anal_dir = os.path.join(settings.UPLD_DIR, md5sum + '/') - if not os.path.exists(anal_dir): - os.makedirs(anal_dir) - with open(anal_dir + md5sum + typ, 'wb+') as destination: - for chunk in filecnt.chunks(): - destination.write(chunk) - return md5sum - - - -class Scanning(object): - - def __init__(self, request): - self.request = request - self.file = request.FILES['file'] - self.file_name = request.FILES['file'].name - - def scan_apk(self): - """ - """ - # APK - md5 = handle_uploaded_file(self.file, '.apk') - data = { - 'url': 'StaticAnalyzer/?name={}&type=apk&checksum={}'.format(self.file_name, md5), - 'status': 'success', - 'hash': md5, - 'scan_type': 'apk', - 'file_name': self.file_name - } - - add_to_recent_scan(self.file_name, md5, data['url']) - - print("\n[INFO] Performing Static Analysis of Android APK") - return data - - def scan_zip(self): - """ - """ - # Android /iOS Zipped Source - md5 = handle_uploaded_file(self.file, '.zip') - - data = { - 'url': 'StaticAnalyzer/?name={}&type=zip&checksum={}'.format(self.file_name, md5), - 'status': 'success', - 'hash': md5, - 'scan_type': 'zip', - 'file_name': self.file_name - } - - add_to_recent_scan(self.file_name, md5, data['url']) - print("\n[INFO] Performing Static Analysis of Android/iOS Source Code") - return data - - def scan_ipa(self): - """ - iOS Binary - """ - md5 = handle_uploaded_file(self.file, '.ipa') - data = { - 'hash': md5, - 'scan_type': 'ipa', - 'file_name': self.file_name, - 'url': 'StaticAnalyzer_iOS/?name={}&type=ipa&checksum={}'.format(self.file_name, md5), - 'status': 'success' - } - - add_to_recent_scan(self.file_name, md5, data['url']) - print("\n[INFO] Performing Static Analysis of iOS IPA") - return data - - def scan_appx(self): - """ - """ - md5 = handle_uploaded_file(self.file, '.appx') - data = { - 'hash': md5, - 'scan_type': 'appx', - 'file_name': self.file_name, - 'url': 'StaticAnalyzer_Windows/?name={}&type=appx&checksum={}'.format(self.file_name, md5), - 'status': 'success' - } - - add_to_recent_scan(self.file_name, md5, data['url']) - print("\n[INFO] Performing Static Analysis of Windows APP") - return data From 5db0139a3e7836f708751afc025e4afa0e43c104 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:27:54 +0530 Subject: [PATCH 06/19] Delete views.py --- MobSF/views.py | 292 ------------------------------------------------- 1 file changed, 292 deletions(-) delete mode 100755 MobSF/views.py diff --git a/MobSF/views.py b/MobSF/views.py deleted file mode 100755 index ec637839..00000000 --- a/MobSF/views.py +++ /dev/null @@ -1,292 +0,0 @@ -# -*- coding: utf_8 -*- -""" -MobSF File Upload and Home Routes -""" -import json -import os -import platform -import re -import shutil -from wsgiref.util import FileWrapper - -from django.conf import settings -from django.http import HttpResponse, HttpResponseRedirect, JsonResponse -from django.shortcuts import render - -from MobSF.forms import FormUtil, UploadFileForm -from MobSF.scanning import Scanning -from MobSF.utils import (FileType, PrintException, api_key, isDirExists, - isFileExists, print_n_send_error_response) -from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid, - StaticAnalyzerIOSZIP, StaticAnalyzerIPA, - StaticAnalyzerWindows) - -LINUX_PLATFORM = ["Darwin", "Linux"] -HTTP_BAD_REQUEST = 400 - -def index(request): - """ - Index Route - """ - context = {'version': settings.MOBSF_VER} - template = "general/index.html" - return render(request, template, context) - - -class Upload(object): - """ - Handle File Upload based on App type - """ - - def __init__(self, request): - self.request = request - self.form = UploadFileForm(request.POST, request.FILES) - - @staticmethod - def as_view(request): - upload = Upload(request) - return upload.upload_html() - - def upload_html(self): - response_data = { - 'url': '', - 'description': '', - 'status': 'error' - } - request = self.request - resp = HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") - resp['Access-Control-Allow-Origin'] = '*' - - if request.method != 'POST': - response_data['description'] = 'Method not Supported!' - print("\n[ERROR] Method not Supported!") - form = UploadFileForm() - resp['status'] = HTTP_BAD_REQUEST - return resp - - if not self.form.is_valid(): - response_data['description'] = 'Invalid Form Data!' - print("\n[ERROR] Invalid Form Data!") - resp['status'] = HTTP_BAD_REQUEST - return resp - - self.file_content_type = request.FILES['file'].content_type - self.file_name_lower = request.FILES['file'].name.lower() - self.file_type = FileType(self.file_content_type, self.file_name_lower) - if not self.file_type.is_allow_file(): - response_data['description'] = 'File format not Supported!' - print("\n[ERROR] File format not Supported!") - resp['status'] = HTTP_BAD_REQUEST - return resp - - if self.file_type.is_ipa(): - if platform.system() not in LINUX_PLATFORM: - data = { - 'error': "Static Analysis of iOS IPA requires Mac or Linux", - 'url': 'mac_only/', - 'status': 'success' - } - print("\n[ERROR] Static Analysis of iOS IPA requires Mac or Linux") - return data - - data = self.upload() - - response_data['url'] = data['url'] - response_data['status'] = data['status'] - return HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") - - - - def upload_api(self): - api_response = { - } - - request = self.request - if not self.form.is_valid(): - api_response['error'] = FormUtil.errors_message(self.form) - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) - - self.file_content_type = request.FILES['file'].content_type - self.file_name_lower = request.FILES['file'].name.lower() - self.file_type = FileType(self.file_content_type, self.file_name_lower) - - if not self.file_type.is_allow_file(): - api_response["error"] = "File format not Supported!" - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) - data = self.upload() - return JsonResponse({ - 'scan_type': data['scan_type'], - 'hash': data['hash'], - 'file_name': data['file_name'] - }) - - def upload(self): - request = self.request - scanning = Scanning(request) - file_type = self.file_content_type - file_name_lower = self.file_name_lower - - print("[INFO] MIME Type: {} FILE: {}".format(file_type, file_name_lower)) - if self.file_type.is_apk(): - return scanning.scan_apk() - elif self.file_type.is_zip(): - return scanning.scan_zip() - elif self.file_type.is_ipa(): - return scanning.scan_ipa() - # Windows APPX - elif self.file_type.is_appx(): - return scanning.scan_appx() - - -def api_docs(request): - """ - API Docs Route - """ - context = {'title': 'REST API Docs', 'api_key': api_key()} - template = "general/apidocs.html" - return render(request, template, context) - - -def about(request): - """ - About Route - """ - context = {'title': 'About'} - template = "general/about.html" - return render(request, template, context) - - -def error(request): - """ - Error Route - """ - context = {'title': 'Error'} - template = "general/error.html" - return render(request, template, context) - - -def zip_format(request): - """ - Zip Format Message Route - """ - context = {'title': 'Zipped Source Instruction'} - template = "general/zip.html" - return render(request, template, context) - - -def mac_only(request): - """ - Mac Ony Message Route - """ - context = {'title': 'Supports OSX Only'} - template = "general/ios.html" - return render(request, template, context) - - -def not_found(request): - """ - Not Found Route - """ - context = {'title': 'Not Found'} - template = "general/not_found.html" - return render(request, template, context) - - -def recent_scans(request): - """ - Show Recent Scans Route - """ - db_obj = RecentScansDB.objects.all().order_by('-TS') - context = {'title': 'Recent Scans', 'entries': db_obj} - template = "general/recent.html" - return render(request, template, context) - - -def search(request): - """ - Search Scan by MD5 Route - """ - md5 = request.GET['md5'] - if re.match('[0-9a-f]{32}', md5): - db_obj = RecentScansDB.objects.filter(MD5=md5) - if db_obj.exists(): - return HttpResponseRedirect('/' + db_obj[0].URL) - else: - return HttpResponseRedirect('/not_found') - return HttpResponseRedirect('/error/') - - -def download(request): - """ - Download from MobSF Route - """ - try: - if request.method == 'GET': - allowed_exts = settings.ALLOWED_EXTENSIONS - filename = request.path.replace("/download/", "", 1) - # Security Checks - if "../" in filename: - print("\n[ATTACK] Path Traversal Attack detected") - return HttpResponseRedirect('/error/') - ext = os.path.splitext(filename)[1] - if ext in allowed_exts: - dwd_file = os.path.join(settings.DWD_DIR, filename) - if os.path.isfile(dwd_file): - wrapper = FileWrapper(open(dwd_file, "rb")) - response = HttpResponse( - wrapper, content_type=allowed_exts[ext]) - response['Content-Length'] = os.path.getsize(dwd_file) - return response - except: - PrintException("Error Downloading File") - return HttpResponseRedirect('/error/') - - -def delete_scan(request, api=False): - """ - Delete Scan from DB and remove the scan related files - """ - try: - if request.method == 'POST': - if api: - md5_hash = request.POST['hash'] - else: - md5_hash = request.POST['md5'] - data = {'deleted': 'no'} - if re.match('[0-9a-f]{32}', md5_hash): - # Delete DB Entries - scan = RecentScansDB.objects.filter(MD5=md5_hash) - if scan.exists(): - RecentScansDB.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerAndroid.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerIPA.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerIOSZIP.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerWindows.objects.filter(MD5=md5_hash).delete() - # Delete Upload Dir Contents - app_upload_dir = os.path.join(settings.UPLD_DIR, md5_hash) - if isDirExists(app_upload_dir): - shutil.rmtree(app_upload_dir) - # Delete Download Dir Contents - dw_dir = settings.DWD_DIR - for item in os.listdir(dw_dir): - item_path = os.path.join(dw_dir, item) - # Delete all related files - if isFileExists(item_path) and item.startswith(md5_hash + "-"): - os.remove(item_path) - # Delete related directories - if isDirExists(item_path) and item.startswith(md5_hash + "-"): - shutil.rmtree(item_path) - data = {'deleted': 'yes'} - if api: - return data - else: - return HttpResponse(json.dumps(data), content_type='application/json; charset=utf-8') - except Exception as exp: - msg = str(exp) - exp_doc = exp.__doc__ - if api: - return print_n_send_error_response(request, msg, True, exp_doc) - else: - return print_n_send_error_response(request, msg, False, exp_doc) From 1e167f0df5c86d3f12f09619807dedc506f4d0d4 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:28:48 +0530 Subject: [PATCH 07/19] Delete rest_api.py --- MobSF/views/rest_api.py | 169 ---------------------------------------- 1 file changed, 169 deletions(-) delete mode 100755 MobSF/views/rest_api.py diff --git a/MobSF/views/rest_api.py b/MobSF/views/rest_api.py deleted file mode 100755 index 00a1f04b..00000000 --- a/MobSF/views/rest_api.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -MobSF REST API V 1 -""" -import json - -from .forms import UploadFileForm -from MobSF.views import ( - Upload, - delete_scan -) -from MobSF.utils import ( - api_key -) -from StaticAnalyzer.views.shared_func import ( - pdf -) -from StaticAnalyzer.views.android.static_analyzer import static_analyzer -from StaticAnalyzer.views.ios.static_analyzer import static_analyzer_ios -from StaticAnalyzer.views.windows import staticanalyzer_windows - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponseNotAllowed -from django.views.decorators.csrf import csrf_exempt - -from MobSF.utils import request_method - -POST = 'POST' - -def make_api_response(data, status=200): - """Make API Response""" - api_resp = HttpResponse(json.dumps( - data, sort_keys=True, - indent=4, separators=(',', ': ')), content_type="application/json; charset=utf-8", status=status) - api_resp['Access-Control-Allow-Origin'] = '*' - return api_resp - - -def api_auth(meta): - """Check if API Key Matches""" - if "HTTP_AUTHORIZATION" in meta: - return bool(api_key() == meta["HTTP_AUTHORIZATION"]) - return False - - -@request_method(['POST']) -@csrf_exempt -def api_upload(request): - """POST - Upload API""" - upload = Upload(request) - return upload.upload_api() - - -@csrf_exempt -def api_scan(request): - """POST - Scan API""" - if request.method == 'POST': - params = ['scan_type', 'hash', 'file_name'] - if set(request.POST) >= set(params): - scan_type = request.POST['scan_type'] - # APK, Android ZIP and iOS ZIP - if scan_type in ["apk", "zip"]: - resp = static_analyzer(request, True) - if "type" in resp: - # For now it's only ios_zip - request.POST._mutable = True - request.POST['scan_type'] = "ios" - resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # IPA - elif scan_type == "ipa": - resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # APPX - elif scan_type == "appx": - resp = staticanalyzer_windows(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - - return response - - -@csrf_exempt -def api_delete_scan(request): - """POST - Delete a Scan""" - if request.method == 'POST': - if "hash" in request.POST: - resp = delete_scan(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - - return response - - -@csrf_exempt -def api_pdf_report(request): - """Generate and Download PDF""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "pdf_dat" in resp: - response = HttpResponse( - resp["pdf_dat"], content_type='application/pdf') - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): - response = make_api_response(resp, 400) - else: - response = make_api_response( - {"error": "PDF Generation Error"}, 500) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - return response - - -@csrf_exempt -def api_json_report(request): - """Generate JSON Report""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "report_dat" in resp: - response = make_api_response(resp["report_dat"], 200) - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): - response = make_api_response(resp, 400) - else: - response = make_api_response( - {"error": "JSON Generation Error"}, 500) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) - else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - return response From e74677a2d713d69e89c4e73bdbc75bf6f225729a Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:29:16 +0530 Subject: [PATCH 08/19] Delete rest_api_middleware.py --- MobSF/views/rest_api_middleware.py | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 MobSF/views/rest_api_middleware.py diff --git a/MobSF/views/rest_api_middleware.py b/MobSF/views/rest_api_middleware.py deleted file mode 100644 index 305ac217..00000000 --- a/MobSF/views/rest_api_middleware.py +++ /dev/null @@ -1,14 +0,0 @@ - -from django.utils.deprecation import MiddlewareMixin -from .rest_api import api_auth, make_api_response - -UNAUTHORIZED = 401 - -class RestApiAuthMiddleware(MiddlewareMixin): - - def process_request(self, request): - if not request.path.startswith("/api/"): - return - if not api_auth(request.META): - return make_api_response( - {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) \ No newline at end of file From d499ff52ee092bc76fd1d952144ebad05d836072 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:29:45 +0530 Subject: [PATCH 09/19] Delete views.py --- MobSF/views/views.py | 292 ------------------------------------------- 1 file changed, 292 deletions(-) delete mode 100755 MobSF/views/views.py diff --git a/MobSF/views/views.py b/MobSF/views/views.py deleted file mode 100755 index ec637839..00000000 --- a/MobSF/views/views.py +++ /dev/null @@ -1,292 +0,0 @@ -# -*- coding: utf_8 -*- -""" -MobSF File Upload and Home Routes -""" -import json -import os -import platform -import re -import shutil -from wsgiref.util import FileWrapper - -from django.conf import settings -from django.http import HttpResponse, HttpResponseRedirect, JsonResponse -from django.shortcuts import render - -from MobSF.forms import FormUtil, UploadFileForm -from MobSF.scanning import Scanning -from MobSF.utils import (FileType, PrintException, api_key, isDirExists, - isFileExists, print_n_send_error_response) -from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid, - StaticAnalyzerIOSZIP, StaticAnalyzerIPA, - StaticAnalyzerWindows) - -LINUX_PLATFORM = ["Darwin", "Linux"] -HTTP_BAD_REQUEST = 400 - -def index(request): - """ - Index Route - """ - context = {'version': settings.MOBSF_VER} - template = "general/index.html" - return render(request, template, context) - - -class Upload(object): - """ - Handle File Upload based on App type - """ - - def __init__(self, request): - self.request = request - self.form = UploadFileForm(request.POST, request.FILES) - - @staticmethod - def as_view(request): - upload = Upload(request) - return upload.upload_html() - - def upload_html(self): - response_data = { - 'url': '', - 'description': '', - 'status': 'error' - } - request = self.request - resp = HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") - resp['Access-Control-Allow-Origin'] = '*' - - if request.method != 'POST': - response_data['description'] = 'Method not Supported!' - print("\n[ERROR] Method not Supported!") - form = UploadFileForm() - resp['status'] = HTTP_BAD_REQUEST - return resp - - if not self.form.is_valid(): - response_data['description'] = 'Invalid Form Data!' - print("\n[ERROR] Invalid Form Data!") - resp['status'] = HTTP_BAD_REQUEST - return resp - - self.file_content_type = request.FILES['file'].content_type - self.file_name_lower = request.FILES['file'].name.lower() - self.file_type = FileType(self.file_content_type, self.file_name_lower) - if not self.file_type.is_allow_file(): - response_data['description'] = 'File format not Supported!' - print("\n[ERROR] File format not Supported!") - resp['status'] = HTTP_BAD_REQUEST - return resp - - if self.file_type.is_ipa(): - if platform.system() not in LINUX_PLATFORM: - data = { - 'error': "Static Analysis of iOS IPA requires Mac or Linux", - 'url': 'mac_only/', - 'status': 'success' - } - print("\n[ERROR] Static Analysis of iOS IPA requires Mac or Linux") - return data - - data = self.upload() - - response_data['url'] = data['url'] - response_data['status'] = data['status'] - return HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") - - - - def upload_api(self): - api_response = { - } - - request = self.request - if not self.form.is_valid(): - api_response['error'] = FormUtil.errors_message(self.form) - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) - - self.file_content_type = request.FILES['file'].content_type - self.file_name_lower = request.FILES['file'].name.lower() - self.file_type = FileType(self.file_content_type, self.file_name_lower) - - if not self.file_type.is_allow_file(): - api_response["error"] = "File format not Supported!" - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) - data = self.upload() - return JsonResponse({ - 'scan_type': data['scan_type'], - 'hash': data['hash'], - 'file_name': data['file_name'] - }) - - def upload(self): - request = self.request - scanning = Scanning(request) - file_type = self.file_content_type - file_name_lower = self.file_name_lower - - print("[INFO] MIME Type: {} FILE: {}".format(file_type, file_name_lower)) - if self.file_type.is_apk(): - return scanning.scan_apk() - elif self.file_type.is_zip(): - return scanning.scan_zip() - elif self.file_type.is_ipa(): - return scanning.scan_ipa() - # Windows APPX - elif self.file_type.is_appx(): - return scanning.scan_appx() - - -def api_docs(request): - """ - API Docs Route - """ - context = {'title': 'REST API Docs', 'api_key': api_key()} - template = "general/apidocs.html" - return render(request, template, context) - - -def about(request): - """ - About Route - """ - context = {'title': 'About'} - template = "general/about.html" - return render(request, template, context) - - -def error(request): - """ - Error Route - """ - context = {'title': 'Error'} - template = "general/error.html" - return render(request, template, context) - - -def zip_format(request): - """ - Zip Format Message Route - """ - context = {'title': 'Zipped Source Instruction'} - template = "general/zip.html" - return render(request, template, context) - - -def mac_only(request): - """ - Mac Ony Message Route - """ - context = {'title': 'Supports OSX Only'} - template = "general/ios.html" - return render(request, template, context) - - -def not_found(request): - """ - Not Found Route - """ - context = {'title': 'Not Found'} - template = "general/not_found.html" - return render(request, template, context) - - -def recent_scans(request): - """ - Show Recent Scans Route - """ - db_obj = RecentScansDB.objects.all().order_by('-TS') - context = {'title': 'Recent Scans', 'entries': db_obj} - template = "general/recent.html" - return render(request, template, context) - - -def search(request): - """ - Search Scan by MD5 Route - """ - md5 = request.GET['md5'] - if re.match('[0-9a-f]{32}', md5): - db_obj = RecentScansDB.objects.filter(MD5=md5) - if db_obj.exists(): - return HttpResponseRedirect('/' + db_obj[0].URL) - else: - return HttpResponseRedirect('/not_found') - return HttpResponseRedirect('/error/') - - -def download(request): - """ - Download from MobSF Route - """ - try: - if request.method == 'GET': - allowed_exts = settings.ALLOWED_EXTENSIONS - filename = request.path.replace("/download/", "", 1) - # Security Checks - if "../" in filename: - print("\n[ATTACK] Path Traversal Attack detected") - return HttpResponseRedirect('/error/') - ext = os.path.splitext(filename)[1] - if ext in allowed_exts: - dwd_file = os.path.join(settings.DWD_DIR, filename) - if os.path.isfile(dwd_file): - wrapper = FileWrapper(open(dwd_file, "rb")) - response = HttpResponse( - wrapper, content_type=allowed_exts[ext]) - response['Content-Length'] = os.path.getsize(dwd_file) - return response - except: - PrintException("Error Downloading File") - return HttpResponseRedirect('/error/') - - -def delete_scan(request, api=False): - """ - Delete Scan from DB and remove the scan related files - """ - try: - if request.method == 'POST': - if api: - md5_hash = request.POST['hash'] - else: - md5_hash = request.POST['md5'] - data = {'deleted': 'no'} - if re.match('[0-9a-f]{32}', md5_hash): - # Delete DB Entries - scan = RecentScansDB.objects.filter(MD5=md5_hash) - if scan.exists(): - RecentScansDB.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerAndroid.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerIPA.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerIOSZIP.objects.filter(MD5=md5_hash).delete() - StaticAnalyzerWindows.objects.filter(MD5=md5_hash).delete() - # Delete Upload Dir Contents - app_upload_dir = os.path.join(settings.UPLD_DIR, md5_hash) - if isDirExists(app_upload_dir): - shutil.rmtree(app_upload_dir) - # Delete Download Dir Contents - dw_dir = settings.DWD_DIR - for item in os.listdir(dw_dir): - item_path = os.path.join(dw_dir, item) - # Delete all related files - if isFileExists(item_path) and item.startswith(md5_hash + "-"): - os.remove(item_path) - # Delete related directories - if isDirExists(item_path) and item.startswith(md5_hash + "-"): - shutil.rmtree(item_path) - data = {'deleted': 'yes'} - if api: - return data - else: - return HttpResponse(json.dumps(data), content_type='application/json; charset=utf-8') - except Exception as exp: - msg = str(exp) - exp_doc = exp.__doc__ - if api: - return print_n_send_error_response(request, msg, True, exp_doc) - else: - return print_n_send_error_response(request, msg, False, exp_doc) From cb2b5095e3cc33d0d6d4f4341453d346635036ff Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:30:32 +0530 Subject: [PATCH 10/19] Update settings.py --- MobSF/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobSF/settings.py b/MobSF/settings.py index 7ce8b54c..8d214dba 100755 --- a/MobSF/settings.py +++ b/MobSF/settings.py @@ -21,7 +21,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #============================================== -MOBSF_VER = "v1.0 Beta" +MOBSF_VER = "v1.0.1 Beta" BANNER = """ __ __ _ ____ _____ _ ___ | \/ | ___ | |__/ ___|| ___| __ _/ | / _ \ @@ -440,4 +440,4 @@ #================VirtualBox Settings============ VBOX = utils.FindVbox(False) -#=============================================== \ No newline at end of file +#=============================================== From 5410072b80e738782a06641c05273197faca5977 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:34:16 +0530 Subject: [PATCH 11/19] Update home.py --- MobSF/views/home.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/MobSF/views/home.py b/MobSF/views/home.py index ad9207db..ec40c7fe 100755 --- a/MobSF/views/home.py +++ b/MobSF/views/home.py @@ -10,16 +10,32 @@ from wsgiref.util import FileWrapper from django.conf import settings -from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.http import ( + HttpResponse, + HttpResponseRedirect, + JsonResponse +) from django.shortcuts import render - -from MobSF.forms import FormUtil, UploadFileForm +from MobSF.forms import ( + FormUtil, + UploadFileForm +) from MobSF.views.scanning import Scanning -from MobSF.utils import (FileType, PrintException, api_key, isDirExists, - isFileExists, print_n_send_error_response) -from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid, - StaticAnalyzerIOSZIP, StaticAnalyzerIPA, - StaticAnalyzerWindows) +from MobSF.utils import ( + FileType, + PrintException, + api_key, + isDirExists, + isFileExists, + print_n_send_error_response +) +from StaticAnalyzer.models import ( + RecentScansDB, + StaticAnalyzerAndroid, + StaticAnalyzerIOSZIP, + StaticAnalyzerIPA, + StaticAnalyzerWindows +) LINUX_PLATFORM = ["Darwin", "Linux"] HTTP_BAD_REQUEST = 400 From 3a27d66d9b433bf198670861df691324f3ebe035 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:47:15 +0530 Subject: [PATCH 12/19] proper intentation --- MobSF/views/home.py | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/MobSF/views/home.py b/MobSF/views/home.py index ec40c7fe..f1035a5c 100755 --- a/MobSF/views/home.py +++ b/MobSF/views/home.py @@ -11,35 +11,36 @@ from django.conf import settings from django.http import ( - HttpResponse, - HttpResponseRedirect, - JsonResponse + HttpResponse, + HttpResponseRedirect, + JsonResponse ) from django.shortcuts import render from MobSF.forms import ( - FormUtil, - UploadFileForm + FormUtil, + UploadFileForm ) from MobSF.views.scanning import Scanning from MobSF.utils import ( - FileType, - PrintException, - api_key, - isDirExists, - isFileExists, - print_n_send_error_response + FileType, + PrintException, + api_key, + isDirExists, + isFileExists, + print_n_send_error_response ) from StaticAnalyzer.models import ( - RecentScansDB, - StaticAnalyzerAndroid, - StaticAnalyzerIOSZIP, - StaticAnalyzerIPA, - StaticAnalyzerWindows + RecentScansDB, + StaticAnalyzerAndroid, + StaticAnalyzerIOSZIP, + StaticAnalyzerIPA, + StaticAnalyzerWindows ) LINUX_PLATFORM = ["Darwin", "Linux"] HTTP_BAD_REQUEST = 400 + def index(request): """ Index Route @@ -53,7 +54,7 @@ class Upload(object): """ Handle File Upload based on App type """ - + def __init__(self, request): self.request = request self.form = UploadFileForm(request.POST, request.FILES) @@ -71,7 +72,7 @@ def upload_html(self): } request = self.request resp = HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") + content_type="application/json; charset=utf-8") resp['Access-Control-Allow-Origin'] = '*' if request.method != 'POST': @@ -80,7 +81,7 @@ def upload_html(self): form = UploadFileForm() resp['status'] = HTTP_BAD_REQUEST return resp - + if not self.form.is_valid(): response_data['description'] = 'Invalid Form Data!' print("\n[ERROR] Invalid Form Data!") @@ -105,15 +106,13 @@ def upload_html(self): } print("\n[ERROR] Static Analysis of iOS IPA requires Mac or Linux") return data - + data = self.upload() response_data['url'] = data['url'] response_data['status'] = data['status'] return HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") - - + content_type="application/json; charset=utf-8") def upload_api(self): api_response = { @@ -144,7 +143,8 @@ def upload(self): file_type = self.file_content_type file_name_lower = self.file_name_lower - print("[INFO] MIME Type: {} FILE: {}".format(file_type, file_name_lower)) + print("[INFO] MIME Type: {} FILE: {}".format( + file_type, file_name_lower)) if self.file_type.is_apk(): return scanning.scan_apk() elif self.file_type.is_zip(): From a3712ae0fdb7d808049736a699646c6f718618b6 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 16:54:40 +0530 Subject: [PATCH 13/19] added support for CORs calls, fixes #709 --- MobSF/views/api/rest_api.py | 169 ++++++++++++------------- MobSF/views/api/rest_api_middleware.py | 12 +- MobSF/views/home.py | 21 +-- 3 files changed, 101 insertions(+), 101 deletions(-) diff --git a/MobSF/views/api/rest_api.py b/MobSF/views/api/rest_api.py index dacab8b4..4f97ac63 100755 --- a/MobSF/views/api/rest_api.py +++ b/MobSF/views/api/rest_api.py @@ -30,9 +30,6 @@ ) -POST = 'POST' - - def make_api_response(data, status=200): """Make API Response""" api_resp = HttpResponse(json.dumps( @@ -40,9 +37,11 @@ def make_api_response(data, status=200): sort_keys=True, indent=4, separators=(',', ': ')), - content_type="application/json; charset=utf-8", - status=status) + content_type="application/json; charset=utf-8", + status=status) api_resp['Access-Control-Allow-Origin'] = '*' + api_resp['Access-Control-Allow-Methods'] = 'POST' + api_resp['Access-Control-Allow-Headers'] = 'Authorization' return api_resp @@ -53,129 +52,121 @@ def api_auth(meta): return False -@request_method(['POST']) +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_upload(request): """POST - Upload API""" upload = Upload(request) - return upload.upload_api() + resp, code = upload.upload_api() + return make_api_response(resp, code) +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_scan(request): """POST - Scan API""" - if request.method == 'POST': - params = ['scan_type', 'hash', 'file_name'] - if set(request.POST) >= set(params): - scan_type = request.POST['scan_type'] - # APK, Android ZIP and iOS ZIP - if scan_type in ["apk", "zip"]: - resp = static_analyzer(request, True) - if "type" in resp: - # For now it's only ios_zip - request.POST._mutable = True - request.POST['scan_type'] = "ios" - resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # IPA - elif scan_type == "ipa": + params = ['scan_type', 'hash', 'file_name'] + if set(request.POST) >= set(params): + scan_type = request.POST['scan_type'] + # APK, Android ZIP and iOS ZIP + if scan_type in ["apk", "zip"]: + resp = static_analyzer(request, True) + if "type" in resp: + # For now it's only ios_zip + request.POST._mutable = True + request.POST['scan_type'] = "ios" resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # APPX - elif scan_type == "appx": - resp = staticanalyzer_windows(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # IPA + elif scan_type == "ipa": + resp = static_analyzer_ios(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # APPX + elif scan_type == "appx": + resp = staticanalyzer_windows(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_delete_scan(request): """POST - Delete a Scan""" - if request.method == 'POST': - if "hash" in request.POST: - resp = delete_scan(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) + if "hash" in request.POST: + resp = delete_scan(request, True) + if "error" in resp: + response = make_api_response(resp, 500) else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) + response = make_api_response(resp, 200) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_pdf_report(request): """Generate and Download PDF""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "pdf_dat" in resp: - response = HttpResponse( - resp["pdf_dat"], content_type='application/pdf') - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): response = make_api_response(resp, 400) else: - response = make_api_response( - {"error": "PDF Generation Error"}, 500) + response = make_api_response(resp, 500) + elif "pdf_dat" in resp: + response = HttpResponse( + resp["pdf_dat"], content_type='application/pdf') + response["Access-Control-Allow-Origin"] = "*" + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) else: response = make_api_response( - {"error": "Missing Parameters"}, 422) + {"error": "PDF Generation Error"}, 500) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_json_report(request): """Generate JSON Report""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "report_dat" in resp: - response = make_api_response(resp["report_dat"], 200) - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): response = make_api_response(resp, 400) else: - response = make_api_response( - {"error": "JSON Generation Error"}, 500) + response = make_api_response(resp, 500) + elif "report_dat" in resp: + response = make_api_response(resp["report_dat"], 200) + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) else: response = make_api_response( - {"error": "Missing Parameters"}, 422) + {"error": "JSON Generation Error"}, 500) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response diff --git a/MobSF/views/api/rest_api_middleware.py b/MobSF/views/api/rest_api_middleware.py index 9d9cce09..02f816c6 100644 --- a/MobSF/views/api/rest_api_middleware.py +++ b/MobSF/views/api/rest_api_middleware.py @@ -7,14 +7,20 @@ make_api_response ) -UNAUTHORIZED = 401 - class RestApiAuthMiddleware(MiddlewareMixin): + """ + Middleware + """ def process_request(self, request): + """ + Middleware to handle API Auth + """ if not request.path.startswith("/api/"): return + if request.method == "OPTIONS": + return make_api_response({}, 200) if not api_auth(request.META): return make_api_response( - {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) + {"error": "You are unauthorized to make this request."}, 401) diff --git a/MobSF/views/home.py b/MobSF/views/home.py index f1035a5c..6acc78a0 100755 --- a/MobSF/views/home.py +++ b/MobSF/views/home.py @@ -58,6 +58,9 @@ class Upload(object): def __init__(self, request): self.request = request self.form = UploadFileForm(request.POST, request.FILES) + self.file_content_type = None + self.file_name_lower = None + self.file_type = None @staticmethod def as_view(request): @@ -115,27 +118,27 @@ def upload_html(self): content_type="application/json; charset=utf-8") def upload_api(self): - api_response = { - } - + """ + API File Upload + """ + api_response = {} request = self.request if not self.form.is_valid(): api_response['error'] = FormUtil.errors_message(self.form) - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) - + return api_response, HTTP_BAD_REQUEST self.file_content_type = request.FILES['file'].content_type self.file_name_lower = request.FILES['file'].name.lower() self.file_type = FileType(self.file_content_type, self.file_name_lower) - if not self.file_type.is_allow_file(): api_response["error"] = "File format not Supported!" - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) + return api_response, HTTP_BAD_REQUEST data = self.upload() - return JsonResponse({ + api_response = { 'scan_type': data['scan_type'], 'hash': data['hash'], 'file_name': data['file_name'] - }) + } + return api_response, 200 def upload(self): request = self.request From a9326bab6c74a5c018e5cf4b09541f6000960c3e Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Sat, 1 Sep 2018 16:32:51 +0530 Subject: [PATCH 14/19] Code QA removed OPTIONS from request_method --- MobSF/views/api/rest_api.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/MobSF/views/api/rest_api.py b/MobSF/views/api/rest_api.py index 4f97ac63..1510d89d 100755 --- a/MobSF/views/api/rest_api.py +++ b/MobSF/views/api/rest_api.py @@ -32,11 +32,11 @@ def make_api_response(data, status=200): """Make API Response""" - api_resp = HttpResponse(json.dumps( - data, - sort_keys=True, - indent=4, - separators=(',', ': ')), + api_resp = HttpResponse( + json.dumps(data, + sort_keys=True, + indent=4, + separators=(',', ': ')), content_type="application/json; charset=utf-8", status=status) api_resp['Access-Control-Allow-Origin'] = '*' @@ -52,7 +52,7 @@ def api_auth(meta): return False -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_upload(request): """POST - Upload API""" @@ -61,7 +61,7 @@ def api_upload(request): return make_api_response(resp, code) -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_scan(request): """POST - Scan API""" @@ -100,7 +100,7 @@ def api_scan(request): return response -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_delete_scan(request): """POST - Delete a Scan""" @@ -116,7 +116,7 @@ def api_delete_scan(request): return response -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_pdf_report(request): """Generate and Download PDF""" @@ -124,7 +124,7 @@ def api_pdf_report(request): if set(request.POST) == set(params): resp = pdf(request, api=True) if "error" in resp: - if "Invalid scan hash" == resp.get("error"): + if resp.get("error") == "Invalid scan hash": response = make_api_response(resp, 400) else: response = make_api_response(resp, 500) @@ -132,9 +132,9 @@ def api_pdf_report(request): response = HttpResponse( resp["pdf_dat"], content_type='application/pdf') response["Access-Control-Allow-Origin"] = "*" - elif "Report not Found" == resp.get("report"): + elif resp.get("report") == "Report not Found": response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + elif resp.get("scan_type") == "Type is not Allowed": response = make_api_response(resp, 400) else: response = make_api_response( @@ -145,7 +145,7 @@ def api_pdf_report(request): return response -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_json_report(request): """Generate JSON Report""" @@ -153,15 +153,15 @@ def api_json_report(request): if set(request.POST) == set(params): resp = pdf(request, api=True) if "error" in resp: - if "Invalid scan hash" == resp.get("error"): + if resp.get("error") == "Invalid scan hash": response = make_api_response(resp, 400) else: response = make_api_response(resp, 500) elif "report_dat" in resp: response = make_api_response(resp["report_dat"], 200) - elif "Report not Found" == resp.get("report"): + elif resp.get("report") == "Report not Found": response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + elif resp.get("scan_type") == "Type is not Allowed": response = make_api_response(resp, 400) else: response = make_api_response( From 7bf2b14c30272284b2e5f0f34b51057dcaea61f4 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Thu, 6 Sep 2018 11:30:03 +0530 Subject: [PATCH 15/19] moved to Django's JsonResponse --- MobSF/views/api/rest_api.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/MobSF/views/api/rest_api.py b/MobSF/views/api/rest_api.py index 1510d89d..7e809597 100755 --- a/MobSF/views/api/rest_api.py +++ b/MobSF/views/api/rest_api.py @@ -1,10 +1,10 @@ """ MobSF REST API V 1 """ -import json from django.http import ( - HttpResponse + HttpResponse, + JsonResponse ) from django.views.decorators.csrf import csrf_exempt @@ -32,17 +32,11 @@ def make_api_response(data, status=200): """Make API Response""" - api_resp = HttpResponse( - json.dumps(data, - sort_keys=True, - indent=4, - separators=(',', ': ')), - content_type="application/json; charset=utf-8", - status=status) - api_resp['Access-Control-Allow-Origin'] = '*' - api_resp['Access-Control-Allow-Methods'] = 'POST' - api_resp['Access-Control-Allow-Headers'] = 'Authorization' - return api_resp + resp = JsonResponse(data=data, status=status) + resp['Access-Control-Allow-Origin'] = '*' + resp['Access-Control-Allow-Methods'] = 'POST' + resp['Access-Control-Allow-Headers'] = 'Authorization' + return resp def api_auth(meta): From f61c9c81aff3cf177473ada50d7ac919fcad4d63 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 13:22:05 +0530 Subject: [PATCH 16/19] code QA --- Dockerfile | 1 + MobSF/settings.py | 4 +- MobSF/urls.py | 38 +++++++-------- MobSF/utils.py | 2 +- MobSF/views/__init__.py | 0 MobSF/views/api/__init__.py | 0 MobSF/{ => views/api}/rest_api.py | 38 ++++++++++----- MobSF/{ => views/api}/rest_api_middleware.py | 14 ++++-- MobSF/{views.py => views/home.py} | 50 +++++++++++++------- MobSF/{ => views}/scanning.py | 0 {MobSF => scripts}/kali_fix.sh | 0 11 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 MobSF/views/__init__.py create mode 100644 MobSF/views/api/__init__.py rename MobSF/{ => views/api}/rest_api.py (87%) rename MobSF/{ => views/api}/rest_api_middleware.py (61%) rename MobSF/{views.py => views/home.py} (89%) rename MobSF/{ => views}/scanning.py (100%) rename {MobSF => scripts}/kali_fix.sh (100%) diff --git a/Dockerfile b/Dockerfile index 94b5edc3..26c964ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,6 +72,7 @@ WORKDIR /root/Mobile-Security-Framework-MobSF/MobSF RUN sed -i 's/USE_HOME = False/USE_HOME = True/g' settings.py #Kali fix to support 32 bit execution +WORKDIR /root/Mobile-Security-Framework-MobSF/scripts RUN ./kali_fix.sh #Install Dependencies diff --git a/MobSF/settings.py b/MobSF/settings.py index 08594077..8d214dba 100755 --- a/MobSF/settings.py +++ b/MobSF/settings.py @@ -21,7 +21,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #============================================== -MOBSF_VER = "v1.0 Beta" +MOBSF_VER = "v1.0.1 Beta" BANNER = """ __ __ _ ____ _____ _ ___ | \/ | ___ | |__/ ___|| ___| __ _/ | / _ \ @@ -195,7 +195,7 @@ ) MIDDLEWARE = ( - 'MobSF.rest_api_middleware.RestApiAuthMiddleware', + 'MobSF.views.api.rest_api_middleware.RestApiAuthMiddleware', ) ROOT_URLCONF = 'MobSF.urls' WSGI_APPLICATION = 'MobSF.wsgi.application' diff --git a/MobSF/urls.py b/MobSF/urls.py index 6100c8e7..a4fdae14 100755 --- a/MobSF/urls.py +++ b/MobSF/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url -import MobSF.views -import MobSF.rest_api +import MobSF.views.home +import MobSF.views.api.rest_api import DynamicAnalyzer.views.android.dynamic import StaticAnalyzer.views.android.static_analyzer import StaticAnalyzer.views.android.java @@ -17,18 +17,18 @@ urlpatterns = [ # Examples: - url(r'^$', MobSF.views.index), - url(r'^upload/$', MobSF.views.Upload.as_view), - url(r'^download/', MobSF.views.download), - url(r'^about$', MobSF.views.about), - url(r'^api_docs$', MobSF.views.api_docs), - url(r'^recent_scans/$', MobSF.views.recent_scans), - url(r'^delete_scan/$', MobSF.views.delete_scan), - url(r'^search$', MobSF.views.search), - url(r'^error/$', MobSF.views.error), - url(r'^not_found/$', MobSF.views.not_found), - url(r'^zip_format/$', MobSF.views.zip_format), - url(r'^mac_only/$', MobSF.views.mac_only), + url(r'^$', MobSF.views.home.index), + url(r'^upload/$', MobSF.views.home.Upload.as_view), + url(r'^download/', MobSF.views.home.download), + url(r'^about$', MobSF.views.home.about), + url(r'^api_docs$', MobSF.views.home.api_docs), + url(r'^recent_scans/$', MobSF.views.home.recent_scans), + url(r'^delete_scan/$', MobSF.views.home.delete_scan), + url(r'^search$', MobSF.views.home.search), + url(r'^error/$', MobSF.views.home.error), + url(r'^not_found/$', MobSF.views.home.not_found), + url(r'^zip_format/$', MobSF.views.home.zip_format), + url(r'^mac_only/$', MobSF.views.home.mac_only), # Android Static Analysis url(r'^StaticAnalyzer/$', @@ -68,11 +68,11 @@ url(r'^capfuzz$', DynamicAnalyzer.views.android.dynamic.capfuzz_start), # REST API - url(r'^api/v1/upload$', MobSF.rest_api.api_upload), - url(r'^api/v1/scan$', MobSF.rest_api.api_scan), - url(r'^api/v1/delete_scan$', MobSF.rest_api.api_delete_scan), - url(r'^api/v1/download_pdf$', MobSF.rest_api.api_pdf_report), - url(r'^api/v1/report_json$', MobSF.rest_api.api_json_report), + url(r'^api/v1/upload$', MobSF.views.api.rest_api.api_upload), + url(r'^api/v1/scan$', MobSF.views.api.rest_api.api_scan), + url(r'^api/v1/delete_scan$', MobSF.views.api.rest_api.api_delete_scan), + url(r'^api/v1/download_pdf$', MobSF.views.api.rest_api.api_pdf_report), + url(r'^api/v1/report_json$', MobSF.views.api.rest_api.api_json_report), # Test url(r'^runtest/$', StaticAnalyzer.tests.start_test), diff --git a/MobSF/utils.py b/MobSF/utils.py index 23c8324d..bab82ef5 100755 --- a/MobSF/utils.py +++ b/MobSF/utils.py @@ -213,7 +213,7 @@ def migrate(BASE_DIR): def kali_fix(BASE_DIR): try: if platform.system() == "Linux" and platform.dist()[0] == "Kali": - fix_path = os.path.join(BASE_DIR, "MobSF/kali_fix.sh") + fix_path = os.path.join(BASE_DIR, "scripts/kali_fix.sh") subprocess.call(["chmod", "a+x", fix_path]) subprocess.call([fix_path], shell=True) except: diff --git a/MobSF/views/__init__.py b/MobSF/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MobSF/views/api/__init__.py b/MobSF/views/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MobSF/rest_api.py b/MobSF/views/api/rest_api.py similarity index 87% rename from MobSF/rest_api.py rename to MobSF/views/api/rest_api.py index 00a1f04b..dacab8b4 100755 --- a/MobSF/rest_api.py +++ b/MobSF/views/api/rest_api.py @@ -3,33 +3,45 @@ """ import json -from .forms import UploadFileForm -from MobSF.views import ( +from django.http import ( + HttpResponse +) +from django.views.decorators.csrf import csrf_exempt + +from MobSF.views.home import ( Upload, delete_scan ) from MobSF.utils import ( - api_key + api_key, + request_method ) from StaticAnalyzer.views.shared_func import ( pdf ) -from StaticAnalyzer.views.android.static_analyzer import static_analyzer -from StaticAnalyzer.views.ios.static_analyzer import static_analyzer_ios -from StaticAnalyzer.views.windows import staticanalyzer_windows - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponseNotAllowed -from django.views.decorators.csrf import csrf_exempt +from StaticAnalyzer.views.android.static_analyzer import ( + static_analyzer +) +from StaticAnalyzer.views.ios.static_analyzer import ( + static_analyzer_ios +) +from StaticAnalyzer.views.windows import ( + staticanalyzer_windows +) -from MobSF.utils import request_method POST = 'POST' + def make_api_response(data, status=200): """Make API Response""" api_resp = HttpResponse(json.dumps( - data, sort_keys=True, - indent=4, separators=(',', ': ')), content_type="application/json; charset=utf-8", status=status) + data, + sort_keys=True, + indent=4, + separators=(',', ': ')), + content_type="application/json; charset=utf-8", + status=status) api_resp['Access-Control-Allow-Origin'] = '*' return api_resp @@ -87,7 +99,7 @@ def api_scan(request): {"error": "Missing Parameters"}, 422) else: response = make_api_response({"error": "Method Not Allowed"}, 405) - + return response diff --git a/MobSF/rest_api_middleware.py b/MobSF/views/api/rest_api_middleware.py similarity index 61% rename from MobSF/rest_api_middleware.py rename to MobSF/views/api/rest_api_middleware.py index 305ac217..9d9cce09 100644 --- a/MobSF/rest_api_middleware.py +++ b/MobSF/views/api/rest_api_middleware.py @@ -1,14 +1,20 @@ - +""" +REST API Middleware +""" from django.utils.deprecation import MiddlewareMixin -from .rest_api import api_auth, make_api_response +from MobSF.views.api.rest_api import ( + api_auth, + make_api_response +) UNAUTHORIZED = 401 + class RestApiAuthMiddleware(MiddlewareMixin): - + def process_request(self, request): if not request.path.startswith("/api/"): return if not api_auth(request.META): return make_api_response( - {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) \ No newline at end of file + {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) diff --git a/MobSF/views.py b/MobSF/views/home.py similarity index 89% rename from MobSF/views.py rename to MobSF/views/home.py index ec637839..f1035a5c 100755 --- a/MobSF/views.py +++ b/MobSF/views/home.py @@ -10,20 +10,37 @@ from wsgiref.util import FileWrapper from django.conf import settings -from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.http import ( + HttpResponse, + HttpResponseRedirect, + JsonResponse +) from django.shortcuts import render - -from MobSF.forms import FormUtil, UploadFileForm -from MobSF.scanning import Scanning -from MobSF.utils import (FileType, PrintException, api_key, isDirExists, - isFileExists, print_n_send_error_response) -from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid, - StaticAnalyzerIOSZIP, StaticAnalyzerIPA, - StaticAnalyzerWindows) +from MobSF.forms import ( + FormUtil, + UploadFileForm +) +from MobSF.views.scanning import Scanning +from MobSF.utils import ( + FileType, + PrintException, + api_key, + isDirExists, + isFileExists, + print_n_send_error_response +) +from StaticAnalyzer.models import ( + RecentScansDB, + StaticAnalyzerAndroid, + StaticAnalyzerIOSZIP, + StaticAnalyzerIPA, + StaticAnalyzerWindows +) LINUX_PLATFORM = ["Darwin", "Linux"] HTTP_BAD_REQUEST = 400 + def index(request): """ Index Route @@ -37,7 +54,7 @@ class Upload(object): """ Handle File Upload based on App type """ - + def __init__(self, request): self.request = request self.form = UploadFileForm(request.POST, request.FILES) @@ -55,7 +72,7 @@ def upload_html(self): } request = self.request resp = HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") + content_type="application/json; charset=utf-8") resp['Access-Control-Allow-Origin'] = '*' if request.method != 'POST': @@ -64,7 +81,7 @@ def upload_html(self): form = UploadFileForm() resp['status'] = HTTP_BAD_REQUEST return resp - + if not self.form.is_valid(): response_data['description'] = 'Invalid Form Data!' print("\n[ERROR] Invalid Form Data!") @@ -89,15 +106,13 @@ def upload_html(self): } print("\n[ERROR] Static Analysis of iOS IPA requires Mac or Linux") return data - + data = self.upload() response_data['url'] = data['url'] response_data['status'] = data['status'] return HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") - - + content_type="application/json; charset=utf-8") def upload_api(self): api_response = { @@ -128,7 +143,8 @@ def upload(self): file_type = self.file_content_type file_name_lower = self.file_name_lower - print("[INFO] MIME Type: {} FILE: {}".format(file_type, file_name_lower)) + print("[INFO] MIME Type: {} FILE: {}".format( + file_type, file_name_lower)) if self.file_type.is_apk(): return scanning.scan_apk() elif self.file_type.is_zip(): diff --git a/MobSF/scanning.py b/MobSF/views/scanning.py similarity index 100% rename from MobSF/scanning.py rename to MobSF/views/scanning.py diff --git a/MobSF/kali_fix.sh b/scripts/kali_fix.sh similarity index 100% rename from MobSF/kali_fix.sh rename to scripts/kali_fix.sh From 9cbf65b67b2813fd6307d90723f442fe10f49b14 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Fri, 31 Aug 2018 16:54:40 +0530 Subject: [PATCH 17/19] added support for CORs calls, fixes #709 --- MobSF/views/api/rest_api.py | 169 ++++++++++++------------- MobSF/views/api/rest_api_middleware.py | 12 +- MobSF/views/home.py | 21 +-- 3 files changed, 101 insertions(+), 101 deletions(-) diff --git a/MobSF/views/api/rest_api.py b/MobSF/views/api/rest_api.py index dacab8b4..4f97ac63 100755 --- a/MobSF/views/api/rest_api.py +++ b/MobSF/views/api/rest_api.py @@ -30,9 +30,6 @@ ) -POST = 'POST' - - def make_api_response(data, status=200): """Make API Response""" api_resp = HttpResponse(json.dumps( @@ -40,9 +37,11 @@ def make_api_response(data, status=200): sort_keys=True, indent=4, separators=(',', ': ')), - content_type="application/json; charset=utf-8", - status=status) + content_type="application/json; charset=utf-8", + status=status) api_resp['Access-Control-Allow-Origin'] = '*' + api_resp['Access-Control-Allow-Methods'] = 'POST' + api_resp['Access-Control-Allow-Headers'] = 'Authorization' return api_resp @@ -53,129 +52,121 @@ def api_auth(meta): return False -@request_method(['POST']) +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_upload(request): """POST - Upload API""" upload = Upload(request) - return upload.upload_api() + resp, code = upload.upload_api() + return make_api_response(resp, code) +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_scan(request): """POST - Scan API""" - if request.method == 'POST': - params = ['scan_type', 'hash', 'file_name'] - if set(request.POST) >= set(params): - scan_type = request.POST['scan_type'] - # APK, Android ZIP and iOS ZIP - if scan_type in ["apk", "zip"]: - resp = static_analyzer(request, True) - if "type" in resp: - # For now it's only ios_zip - request.POST._mutable = True - request.POST['scan_type'] = "ios" - resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # IPA - elif scan_type == "ipa": + params = ['scan_type', 'hash', 'file_name'] + if set(request.POST) >= set(params): + scan_type = request.POST['scan_type'] + # APK, Android ZIP and iOS ZIP + if scan_type in ["apk", "zip"]: + resp = static_analyzer(request, True) + if "type" in resp: + # For now it's only ios_zip + request.POST._mutable = True + request.POST['scan_type'] = "ios" resp = static_analyzer_ios(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - # APPX - elif scan_type == "appx": - resp = staticanalyzer_windows(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) - else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # IPA + elif scan_type == "ipa": + resp = static_analyzer_ios(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) + # APPX + elif scan_type == "appx": + resp = staticanalyzer_windows(request, True) + if "error" in resp: + response = make_api_response(resp, 500) + else: + response = make_api_response(resp, 200) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_delete_scan(request): """POST - Delete a Scan""" - if request.method == 'POST': - if "hash" in request.POST: - resp = delete_scan(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp, 200) + if "hash" in request.POST: + resp = delete_scan(request, True) + if "error" in resp: + response = make_api_response(resp, 500) else: - response = make_api_response( - {"error": "Missing Parameters"}, 422) + response = make_api_response(resp, 200) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_pdf_report(request): """Generate and Download PDF""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "pdf_dat" in resp: - response = HttpResponse( - resp["pdf_dat"], content_type='application/pdf') - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): response = make_api_response(resp, 400) else: - response = make_api_response( - {"error": "PDF Generation Error"}, 500) + response = make_api_response(resp, 500) + elif "pdf_dat" in resp: + response = HttpResponse( + resp["pdf_dat"], content_type='application/pdf') + response["Access-Control-Allow-Origin"] = "*" + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) else: response = make_api_response( - {"error": "Missing Parameters"}, 422) + {"error": "PDF Generation Error"}, 500) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response +@request_method(['POST', 'OPTIONS']) @csrf_exempt def api_json_report(request): """Generate JSON Report""" - if request.method == 'POST': - params = ['scan_type', 'hash'] - if set(request.POST) == set(params): - resp = pdf(request, api=True) - if "error" in resp: - if "Invalid scan hash" == resp.get("error"): - response = make_api_response(resp, 400) - else: - response = make_api_response(resp, 500) - elif "report_dat" in resp: - response = make_api_response(resp["report_dat"], 200) - elif "Report not Found" == resp.get("report"): - response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + params = ['scan_type', 'hash'] + if set(request.POST) == set(params): + resp = pdf(request, api=True) + if "error" in resp: + if "Invalid scan hash" == resp.get("error"): response = make_api_response(resp, 400) else: - response = make_api_response( - {"error": "JSON Generation Error"}, 500) + response = make_api_response(resp, 500) + elif "report_dat" in resp: + response = make_api_response(resp["report_dat"], 200) + elif "Report not Found" == resp.get("report"): + response = make_api_response(resp, 404) + elif "Type is not Allowed" == resp.get("scan_type"): + response = make_api_response(resp, 400) else: response = make_api_response( - {"error": "Missing Parameters"}, 422) + {"error": "JSON Generation Error"}, 500) else: - response = make_api_response({"error": "Method Not Allowed"}, 405) + response = make_api_response( + {"error": "Missing Parameters"}, 422) return response diff --git a/MobSF/views/api/rest_api_middleware.py b/MobSF/views/api/rest_api_middleware.py index 9d9cce09..02f816c6 100644 --- a/MobSF/views/api/rest_api_middleware.py +++ b/MobSF/views/api/rest_api_middleware.py @@ -7,14 +7,20 @@ make_api_response ) -UNAUTHORIZED = 401 - class RestApiAuthMiddleware(MiddlewareMixin): + """ + Middleware + """ def process_request(self, request): + """ + Middleware to handle API Auth + """ if not request.path.startswith("/api/"): return + if request.method == "OPTIONS": + return make_api_response({}, 200) if not api_auth(request.META): return make_api_response( - {"error": "You are unauthorized to make this request."}, UNAUTHORIZED) + {"error": "You are unauthorized to make this request."}, 401) diff --git a/MobSF/views/home.py b/MobSF/views/home.py index f1035a5c..6acc78a0 100755 --- a/MobSF/views/home.py +++ b/MobSF/views/home.py @@ -58,6 +58,9 @@ class Upload(object): def __init__(self, request): self.request = request self.form = UploadFileForm(request.POST, request.FILES) + self.file_content_type = None + self.file_name_lower = None + self.file_type = None @staticmethod def as_view(request): @@ -115,27 +118,27 @@ def upload_html(self): content_type="application/json; charset=utf-8") def upload_api(self): - api_response = { - } - + """ + API File Upload + """ + api_response = {} request = self.request if not self.form.is_valid(): api_response['error'] = FormUtil.errors_message(self.form) - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) - + return api_response, HTTP_BAD_REQUEST self.file_content_type = request.FILES['file'].content_type self.file_name_lower = request.FILES['file'].name.lower() self.file_type = FileType(self.file_content_type, self.file_name_lower) - if not self.file_type.is_allow_file(): api_response["error"] = "File format not Supported!" - return JsonResponse(data=api_response, status=HTTP_BAD_REQUEST) + return api_response, HTTP_BAD_REQUEST data = self.upload() - return JsonResponse({ + api_response = { 'scan_type': data['scan_type'], 'hash': data['hash'], 'file_name': data['file_name'] - }) + } + return api_response, 200 def upload(self): request = self.request From 49906c153a61deec0fc6de5e84fab77242b14845 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Sat, 1 Sep 2018 16:32:51 +0530 Subject: [PATCH 18/19] Code QA removed OPTIONS from request_method --- MobSF/views/api/rest_api.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/MobSF/views/api/rest_api.py b/MobSF/views/api/rest_api.py index 4f97ac63..1510d89d 100755 --- a/MobSF/views/api/rest_api.py +++ b/MobSF/views/api/rest_api.py @@ -32,11 +32,11 @@ def make_api_response(data, status=200): """Make API Response""" - api_resp = HttpResponse(json.dumps( - data, - sort_keys=True, - indent=4, - separators=(',', ': ')), + api_resp = HttpResponse( + json.dumps(data, + sort_keys=True, + indent=4, + separators=(',', ': ')), content_type="application/json; charset=utf-8", status=status) api_resp['Access-Control-Allow-Origin'] = '*' @@ -52,7 +52,7 @@ def api_auth(meta): return False -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_upload(request): """POST - Upload API""" @@ -61,7 +61,7 @@ def api_upload(request): return make_api_response(resp, code) -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_scan(request): """POST - Scan API""" @@ -100,7 +100,7 @@ def api_scan(request): return response -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_delete_scan(request): """POST - Delete a Scan""" @@ -116,7 +116,7 @@ def api_delete_scan(request): return response -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_pdf_report(request): """Generate and Download PDF""" @@ -124,7 +124,7 @@ def api_pdf_report(request): if set(request.POST) == set(params): resp = pdf(request, api=True) if "error" in resp: - if "Invalid scan hash" == resp.get("error"): + if resp.get("error") == "Invalid scan hash": response = make_api_response(resp, 400) else: response = make_api_response(resp, 500) @@ -132,9 +132,9 @@ def api_pdf_report(request): response = HttpResponse( resp["pdf_dat"], content_type='application/pdf') response["Access-Control-Allow-Origin"] = "*" - elif "Report not Found" == resp.get("report"): + elif resp.get("report") == "Report not Found": response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + elif resp.get("scan_type") == "Type is not Allowed": response = make_api_response(resp, 400) else: response = make_api_response( @@ -145,7 +145,7 @@ def api_pdf_report(request): return response -@request_method(['POST', 'OPTIONS']) +@request_method(['POST']) @csrf_exempt def api_json_report(request): """Generate JSON Report""" @@ -153,15 +153,15 @@ def api_json_report(request): if set(request.POST) == set(params): resp = pdf(request, api=True) if "error" in resp: - if "Invalid scan hash" == resp.get("error"): + if resp.get("error") == "Invalid scan hash": response = make_api_response(resp, 400) else: response = make_api_response(resp, 500) elif "report_dat" in resp: response = make_api_response(resp["report_dat"], 200) - elif "Report not Found" == resp.get("report"): + elif resp.get("report") == "Report not Found": response = make_api_response(resp, 404) - elif "Type is not Allowed" == resp.get("scan_type"): + elif resp.get("scan_type") == "Type is not Allowed": response = make_api_response(resp, 400) else: response = make_api_response( From 84e44faab3098bb3dd228b74815022725872cb14 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Thu, 6 Sep 2018 11:30:03 +0530 Subject: [PATCH 19/19] moved to Django's JsonResponse --- MobSF/views/api/rest_api.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/MobSF/views/api/rest_api.py b/MobSF/views/api/rest_api.py index 1510d89d..7e809597 100755 --- a/MobSF/views/api/rest_api.py +++ b/MobSF/views/api/rest_api.py @@ -1,10 +1,10 @@ """ MobSF REST API V 1 """ -import json from django.http import ( - HttpResponse + HttpResponse, + JsonResponse ) from django.views.decorators.csrf import csrf_exempt @@ -32,17 +32,11 @@ def make_api_response(data, status=200): """Make API Response""" - api_resp = HttpResponse( - json.dumps(data, - sort_keys=True, - indent=4, - separators=(',', ': ')), - content_type="application/json; charset=utf-8", - status=status) - api_resp['Access-Control-Allow-Origin'] = '*' - api_resp['Access-Control-Allow-Methods'] = 'POST' - api_resp['Access-Control-Allow-Headers'] = 'Authorization' - return api_resp + resp = JsonResponse(data=data, status=status) + resp['Access-Control-Allow-Origin'] = '*' + resp['Access-Control-Allow-Methods'] = 'POST' + resp['Access-Control-Allow-Headers'] = 'Authorization' + return resp def api_auth(meta):