diff --git a/.gitignore b/.gitignore index 44698257..6d07301a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ MobSF/windows_vm_priv_key.asc # C extensions *.so +.vscode/ + # Distribution / packaging .Python venv/ diff --git a/MobSF/forms.py b/MobSF/forms.py index f5a903c4..de214f16 100755 --- a/MobSF/forms.py +++ b/MobSF/forms.py @@ -2,3 +2,28 @@ class UploadFileForm(forms.Form): file = forms.FileField() + + +class FormUtil(object): + + def __init__(self, form): + self.form = form + + @staticmethod + def errors_message(form): + """ + :param form forms.Form + form.errors.get_json_data() django 2.0 or higher + + :return + example { "error": "file This field is required." } + """ + errors_messages = [] + for k, value in form.errors.get_json_data().items(): + errors_messages.append( + (k + ' ' + ' , '.join([i['message'] for i in value]))) + return '; '.join(errors_messages) + + @staticmethod + def errors(form): + return form.errors.items() diff --git a/MobSF/rest_api.py b/MobSF/rest_api.py index 8d5d3350..343fa4ce 100755 --- a/MobSF/rest_api.py +++ b/MobSF/rest_api.py @@ -5,7 +5,7 @@ from .forms import UploadFileForm from MobSF.views import ( - upload, + Upload, delete_scan ) from MobSF.utils import ( @@ -18,9 +18,10 @@ from StaticAnalyzer.views.ios.static_analyzer import static_analyzer_ios from StaticAnalyzer.views.windows import staticanalyzer_windows -from django.http import HttpResponse +from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponseNotAllowed from django.views.decorators.csrf import csrf_exempt +POST = 'POST' def make_api_response(data, status=200): """Make API Response""" @@ -41,15 +42,12 @@ def api_auth(meta): @csrf_exempt def api_upload(request): """POST - Upload API""" - if request.method == 'POST': - resp = upload(request, True) - if "error" in resp: - response = make_api_response(resp, 500) - else: - response = make_api_response(resp) + if request.method == POST: + upload = Upload(request) + return upload.upload_api() else: - response = make_api_response({"error": "Method Not Allowed"}, 405) - return response + return HttpResponseNotAllowed([POST]) + @csrf_exempt diff --git a/MobSF/scanning.py b/MobSF/scanning.py new file mode 100644 index 00000000..89631a28 --- /dev/null +++ b/MobSF/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/urls.py b/MobSF/urls.py index b7f6609d..6100c8e7 100755 --- a/MobSF/urls.py +++ b/MobSF/urls.py @@ -18,7 +18,7 @@ urlpatterns = [ # Examples: url(r'^$', MobSF.views.index), - url(r'^upload/$', MobSF.views.upload), + 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), diff --git a/MobSF/utils.py b/MobSF/utils.py index dfb6d77c..ed53cdc8 100755 --- a/MobSF/utils.py +++ b/MobSF/utils.py @@ -606,3 +606,28 @@ def check_basic_env(): JAVA_DIRECTORY = "/usr/bin/" ''') os.kill(os.getpid(), signal.SIGTERM) + + + +class FileType(object): + def __init__(self, file_type, file_name_lower): + self.file_type = file_type + self.file_name_lower = file_name_lower + + def is_allow_file(self): + """ + return bool + """ + if self.is_apk() or self.is_zip() or self.is_ipa() or self.is_appx(): + return True + return False + + def is_apk(self): + return (self.file_type in settings.APK_MIME) and self.file_name_lower.endswith('.apk') + + def is_zip(self): + return (self.file_type in settings.ZIP_MIME) and self.file_name_lower.endswith('.zip') + def is_ipa(self): + return (self.file_type in settings.IPA_MIME) and self.file_name_lower.endswith('.ipa') + def is_appx(self): + return (self.file_type in settings.APPX_MIME) and self.file_name_lower.endswith('.appx') \ No newline at end of file diff --git a/MobSF/views.py b/MobSF/views.py index 5b4621c6..ec637839 100755 --- a/MobSF/views.py +++ b/MobSF/views.py @@ -2,49 +2,27 @@ """ MobSF File Upload and Home Routes """ +import json import os -import hashlib -import shutil import platform -import json import re - +import shutil from wsgiref.util import FileWrapper -from django.shortcuts import render -from django.http import HttpResponse -from django.http import HttpResponseRedirect + from django.conf import settings -from django.utils import timezone -from MobSF.utils import ( - print_n_send_error_response, - PrintException, - isDirExists, - isFileExists, - api_key -) -from StaticAnalyzer.models import RecentScansDB -from StaticAnalyzer.models import ( - StaticAnalyzerAndroid, - StaticAnalyzerIPA, - StaticAnalyzerIOSZIP, - StaticAnalyzerWindows, -) -from .forms import UploadFileForm - - -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") +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): """ @@ -55,144 +33,111 @@ def index(request): return render(request, template, context) -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 - - -def upload(request, api=False): +class Upload(object): """ Handle File Upload based on App type """ - api_response = {} - response_data = {} - try: - response_data['url'] = '' - response_data['description'] = '' - response_data['status'] = 'error' - if request.method == 'POST': - form = UploadFileForm(request.POST, request.FILES) - if form.is_valid(): - file_type = request.FILES['file'].content_type - print("[INFO] MIME Type: " + file_type + - " FILE: " + request.FILES['file'].name) - if ((file_type in settings.APK_MIME) and - request.FILES['file'].name.lower().endswith('.apk')): - # APK - md5 = handle_uploaded_file(request.FILES['file'], '.apk') - if api: - api_response["hash"] = md5 - api_response["scan_type"] = "apk" - api_response["file_name"] = request.FILES['file'].name - response_data['url'] = ('StaticAnalyzer/?name=' + request.FILES['file'].name + - '&type=apk&checksum=' + md5) - response_data['status'] = 'success' - add_to_recent_scan( - request.FILES['file'].name, md5, response_data['url']) - print("\n[INFO] Performing Static Analysis of Android APK") - elif ((file_type in settings.ZIP_MIME) and - request.FILES['file'].name.lower().endswith('.zip')): - # Android /iOS Zipped Source - md5 = handle_uploaded_file(request.FILES['file'], '.zip') - if api: - api_response["hash"] = md5 - api_response["scan_type"] = "zip" - api_response["file_name"] = request.FILES['file'].name - response_data['url'] = ('StaticAnalyzer/?name=' + request.FILES['file'].name + - '&type=zip&checksum=' + md5) - response_data['status'] = 'success' - add_to_recent_scan( - request.FILES['file'].name, md5, response_data['url']) - print( - "\n[INFO] Performing Static Analysis of Android/iOS Source Code") - elif ((file_type in settings.IPA_MIME) and - request.FILES['file'].name.lower().endswith('.ipa')): - # iOS Binary - if platform.system() in ["Darwin", "Linux"]: - md5 = handle_uploaded_file( - request.FILES['file'], '.ipa') - if api: - api_response["hash"] = md5 - api_response["scan_type"] = "ipa" - api_response["file_name"] = request.FILES[ - 'file'].name - response_data['url'] = ('StaticAnalyzer_iOS/?name=' + - request.FILES['file'].name + - '&type=ipa&checksum=' + md5) - response_data['status'] = 'success' - add_to_recent_scan( - request.FILES['file'].name, md5, response_data['url']) - print("\n[INFO] Performing Static Analysis of iOS IPA") - else: - if api: - api_response[ - "error"] = "Static Analysis of iOS IPA requires Mac or Linux" - response_data['url'] = 'mac_only/' - response_data['status'] = 'success' - print( - "\n[ERROR] Static Analysis of iOS IPA requires Mac or Linux") - # Windows APPX - elif (file_type in settings.APPX_MIME) and request.FILES['file'].name.lower().endswith('.appx'): - md5 = handle_uploaded_file(request.FILES['file'], '.appx') - if api: - api_response["hash"] = md5 - api_response["scan_type"] = "appx" - api_response["file_name"] = request.FILES['file'].name - response_data['url'] = 'StaticAnalyzer_Windows/?name=' + \ - request.FILES['file'].name + \ - '&type=appx&checksum=' + md5 - response_data['status'] = 'success' - add_to_recent_scan( - request.FILES['file'].name, md5, response_data['url']) - print("\n[INFO] Performing Static Analysis of Windows APP") - else: - if api: - api_response["error"] = "File format not Supported!" - response_data['url'] = '' - response_data['description'] = 'File format not Supported!' - response_data['status'] = 'error' - print("\n[ERROR] File format not Supported!") + + def __init__(self, request): + self.request = request + self.form = UploadFileForm(request.POST, request.FILES) - else: - if api: - api_response["error"] = "Invalid Form Data!" - response_data['url'] = '' - response_data['description'] = 'Invalid Form Data!' - response_data['status'] = 'error' - print("\n[ERROR] Invalid Form Data!") - else: - if api: - api_response["error"] = "Method not Supported!" - response_data['url'] = '' + @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!' - response_data['status'] = 'error' print("\n[ERROR] Method not Supported!") form = UploadFileForm() - except: - PrintException("[ERROR] Uploading File:") - if api: - return api_response - else: - if response_data['status'] == 'error': - resp = HttpResponse(json.dumps( - response_data), content_type="application/json; charset=utf-8", status=500) - else: - resp = HttpResponse(json.dumps(response_data), - content_type="application/json; charset=utf-8") - resp['Access-Control-Allow-Origin'] = '*' - return resp + 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):