diff --git a/.gitignore b/.gitignore index 6e9044fc4e..70206d1fbe 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ var/ *.egg-info/ .installed.cfg *.egg +*.pyc # PyInstaller # Usually these files are written by a python script from a template @@ -54,3 +55,12 @@ target/ #Mac .DS_Store + +#D2J Error file +classes-error.zip + +#MobSF Files +logs/ +uploads/ +db.sqlite3 +secret diff --git a/APITester/migrations/0001_initial.py b/APITester/migrations/0001_initial.py deleted file mode 100644 index aadc12a765..0000000000 --- a/APITester/migrations/0001_initial.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='ScopeURLSandTests', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('MD5', models.CharField(max_length=30)), - ('SCOPEURLS', models.TextField()), - ('SCOPETESTS', models.TextField()), - ], - ), - ] diff --git a/APITester/migrations/0002_auto_20160219_1153.py b/APITester/migrations/0002_auto_20160219_1153.py deleted file mode 100644 index 7a582ffc72..0000000000 --- a/APITester/migrations/0002_auto_20160219_1153.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('APITester', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='scopeurlsandtests', - name='MD5', - field=models.CharField(unique=True, max_length=30), - ), - ] diff --git a/APITester/migrations/0003_auto_20160219_1158.py b/APITester/migrations/0003_auto_20160219_1158.py deleted file mode 100644 index 0cb2ef01ed..0000000000 --- a/APITester/migrations/0003_auto_20160219_1158.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('APITester', '0002_auto_20160219_1153'), - ] - - operations = [ - migrations.AlterField( - model_name='scopeurlsandtests', - name='MD5', - field=models.CharField(max_length=30), - ), - ] diff --git a/APITester/migrations/0004_auto_20160219_1750.py b/APITester/migrations/0004_auto_20160219_1750.py deleted file mode 100644 index 2e2f362b08..0000000000 --- a/APITester/migrations/0004_auto_20160219_1750.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import APITester.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('APITester', '0003_auto_20160219_1158'), - ] - - operations = [ - migrations.AlterField( - model_name='scopeurlsandtests', - name='SCOPETESTS', - field=APITester.models.ListField(), - ), - migrations.AlterField( - model_name='scopeurlsandtests', - name='SCOPEURLS', - field=APITester.models.ListField(), - ), - ] diff --git a/APITester/migrations/__init__.py b/APITester/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/APITester/views.py b/APITester/views.py index c30d6be4c3..e1ef6bdd08 100644 --- a/APITester/views.py +++ b/APITester/views.py @@ -9,13 +9,14 @@ from django.utils.safestring import mark_safe from APITester.models import ScopeURLSandTests -from MobSF.exception_printer import PrintException +from MobSF.utils import PrintException, getMD5, is_number, findBetween + from random import randint,shuffle,choice from urlparse import urlparse from cgi import parse_qs import tornado.httpclient -import os,re,json,io,hashlib,datetime,socket,string +import os,re,json,io,datetime,socket,string from lxml import etree @register.filter @@ -1100,23 +1101,6 @@ def getIPList(url): PrintException("[ERROR] Getting IP(s) from URL") return ips -def findBetween(s, first, last): - try : - start = s.index(first) + len(first) - end = s.index(last,start) - return s[start:end] - except ValueError: - return "" - -def is_number(s): - try: - float(s) - return True - except ValueError: - return False - -def getMD5(data): - return hashlib.md5(data).hexdigest() #SSRF and XXE diff --git a/DynamicAnalyzer/pyWebProxy/pywebproxy.py b/DynamicAnalyzer/pyWebProxy/pywebproxy.py index b0c982397b..b8993ab93d 100644 --- a/DynamicAnalyzer/pyWebProxy/pywebproxy.py +++ b/DynamicAnalyzer/pyWebProxy/pywebproxy.py @@ -50,7 +50,7 @@ import re,sys,threading,json,codecs from multiprocessing import Process, Value, Lock from socket_wrapper import wrap_socket -from MobSF.exception_printer import PrintException +from MobSF.utils import PrintException from django.conf import settings kill = False #Global Variable that shows the state of Tornado Proxy diff --git a/DynamicAnalyzer/views.py b/DynamicAnalyzer/views.py index fe5c4ceaff..9e3425b406 100644 --- a/DynamicAnalyzer/views.py +++ b/DynamicAnalyzer/views.py @@ -7,11 +7,11 @@ from StaticAnalyzer.models import StaticAnalyzerAndroid from pyWebProxy.pywebproxy import * -from MobSF.exception_printer import PrintException +from MobSF.utils import PrintException,is_number,python_list,isBase64,isFileExists from MalwareAnalyzer.views import MalwareCheck import subprocess,os,re,shutil,tarfile,ntpath,platform,io,signal -import json,random,time,ast,sys,psutil,unicodedata,socket,threading,base64 +import json,random,time,sys,psutil,unicodedata,socket,threading,base64 import sqlite3 as sq #=================================== #Dynamic Analyzer Calls begins here! @@ -41,7 +41,7 @@ def DynamicAnalyzer(request): m=re.match('[0-9a-f]{32}',MD5) if m: # Delete ScreenCast Cache - SCREEN_FILE=os.path.join(settings.STATIC_DIR, 'screen/screen.png') + SCREEN_FILE=os.path.join(settings.SCREEN_DIR, 'screen.png') if os.path.exists(SCREEN_FILE): os.remove(SCREEN_FILE) # Delete Contents of Screenshot Dir @@ -582,18 +582,21 @@ def WebProxy(APKDIR,ip,port): def getADB(TOOLSDIR): print "\n[INFO] Getting ADB Location" try: - adb='adb' - if platform.system()=="Darwin": - adb_dir=os.path.join(TOOLSDIR, 'adb/mac/') - subprocess.call(["chmod", "777", adb_dir]) - adb=os.path.join(TOOLSDIR , 'adb/mac/adb') - elif platform.system()=="Linux": - adb_dir=os.path.join(TOOLSDIR, 'adb/linux/') - subprocess.call(["chmod", "777", adb_dir]) - adb=os.path.join(TOOLSDIR , 'adb/linux/adb') - elif platform.system()=="Windows": - adb=os.path.join(TOOLSDIR , 'adb/windows/adb.exe') - return adb + if len(settings.ADB_BINARY) > 0 and isFileExists(settings.ADB_BINARY): + return settings.ADB_BINARY + else: + adb='adb' + if platform.system()=="Darwin": + adb_dir=os.path.join(TOOLSDIR, 'adb/mac/') + subprocess.call(["chmod", "777", adb_dir]) + adb=os.path.join(TOOLSDIR , 'adb/mac/adb') + elif platform.system()=="Linux": + adb_dir=os.path.join(TOOLSDIR, 'adb/linux/') + subprocess.call(["chmod", "777", adb_dir]) + adb=os.path.join(TOOLSDIR , 'adb/linux/adb') + elif platform.system()=="Windows": + adb=os.path.join(TOOLSDIR , 'adb/windows/adb.exe') + return adb except: PrintException("[ERROR] Getting ADB Location") return "adb" @@ -747,6 +750,7 @@ def APIAnalysis(PKG,LOCATION): PrintException("[ERROR] Dynamic API Analysis") pass return list(set(API_NET)),list(set(API_BASE64)), list(set(API_FILEIO)), list(set(API_BINDER)), list(set(API_CRYPTO)), list(set(API_DEVICEINFO)), list(set(API_CNTVAL)), list(set(API_SMS)), list(set(API_SYSPROP)),list(set(API_DEXLOADER)),list(set(API_RELECT)),list(set(API_ACNTMNGER)),list(set(API_CMD)) + def Download(MD5,DWDDIR,APKDIR,PKG): print "\n[INFO] Generating Downloads" try: @@ -924,7 +928,7 @@ def ScreenCastService(): global tcp_server_mode print "\n[INFO] ScreenCast Service Status: " + tcp_server_mode try: - SCREEN_DIR=os.path.join(settings.STATIC_DIR, 'screen/') + SCREEN_DIR=settings.SCREEN_DIR if not os.path.exists(SCREEN_DIR): os.makedirs(SCREEN_DIR) @@ -993,26 +997,3 @@ def getIdentifier(): return settings.VM_IP + ":" + str(settings.VM_ADB_PORT) except: PrintException("[ERROR] Getting ADB Connection Identifier for Device/VM") - -def is_number(s): - try: - float(s) - return True - except ValueError: - pass - try: - unicodedata.numeric(s) - return True - except (TypeError, ValueError): - pass - return False - -def python_list(value): - if not value: - value = [] - if isinstance(value, list): - return value - return ast.literal_eval(value) - -def isBase64(str): - return re.match('^[A-Za-z0-9+/]+[=]{0,2}$', str) \ No newline at end of file diff --git a/LICENSES/procyon.txt b/LICENSES/procyon.txt new file mode 100644 index 0000000000..bc8a16b7c0 --- /dev/null +++ b/LICENSES/procyon.txt @@ -0,0 +1,55 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + + +You must cause any modified files to carry prominent notices stating that You changed the files; and + + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. diff --git a/MalwareAnalyzer/malwaredb/malwaredomainlist b/MalwareAnalyzer/malwaredb/malwaredomainlist index b83861be77..938b8f9843 100644 --- a/MalwareAnalyzer/malwaredb/malwaredomainlist +++ b/MalwareAnalyzer/malwaredb/malwaredomainlist @@ -2795,4 +2795,93 @@ "2016/04/27_06:44","kedz.ansonslimited.com/3zRWq4UCqD.php","199.80.52.48","48.0-26.52.80.199.in-addr.arpa.","Angler EK","Registrar Abuse Contact support@domainbox.com","40824","0","US", "2016/04/27_06:46","hqlgpteogw.jopresent.top/passionate/book-31107945","185.58.224.173","host173-224-58-185.static.arubacloud.com.","Angler EK","Registrant fitchewb@gmail.com","199883","0","IT", "2016/04/27_09:35","swell.1hpleft.com/cgqsxb4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrar Abuse Contact abuse@1and1.com","49981","0","NL", +"2016/04/27_10:25","upstart.88vid.com/cgqsxb4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/27_10:30","secrete.335sbs.com/cgqsxb4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrar Abuse Contact abuse@yesnic.com","49981","0","NL", +"2016/04/27_11:00","harrow.aa978.com/wnxxbw4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrant tmm121212@163.com","49981","0","NL", +"2016/04/27_12:08","warcraft-lich-king.ru/i4ospd","87.236.19.13","m2.hocking.beget.ru.","Locky ransomware","-","198610","0","RU", +"2016/04/27_12:08","haraccountants.co.uk/k9sjf","50.28.66.241","host3.withfriendship.com.","Locky ransomware","Hani Al Rasheed / -","32244","0","US", +"2016/04/27_12:10","aaacollectionsjewelry.com/ur8fgs","50.6.80.160","-","Locky ransomware","Registrant 152B755F75B54F748A558F8D2ADC46EF.PROTECT@WHOISGUARD.COM","32392","0","US", +"2016/04/27_12:24","jacur.carpetcleaning-services.com/fx/F/627/","80.78.241.93","vm21024.hv8.ru.","Angler EK","-","43146","0","RU", +"2016/04/27_13:05","adamauto.nl/gdh46ss","5.61.252.121","agreene.suite7.nl.","Locky ransomware","-","12859","0","NL", +"2016/04/27_13:05","directenergy.tv/l2isd","174.36.1.198","hs1.name.com.","Locky ransomware","-","36351","0","US", +"2016/04/27_13:05","games-k.ru/n8eis","92.53.96.36","debbie.timeweb.ru.","Locky ransomware","-","9123","0","RU", +"2016/04/27_13:05","jurang.tk/n2ysk","198.204.249.27","-","Locky ransomware","E-mail: abuse: abuse@freenom.com, copyright infringement: copyright@freenom.com","33387","0","US", +"2016/04/27_13:05","lbbc.pt/n8wisd","130.185.84.57","isicom.pt.","Locky ransomware","LBBC - ENGENHARIA, UNIPESSOAL LDA / geral@lbbc.pt","24768","0","PT", +"2016/04/27_13:05","l-dsk.com/k3isfa","5.101.153.21","m2.terra.beget.ru.","Locky ransomware","Registrar Abuse Contact abuse@ukrnames.com","198610","0","RU", +"2016/04/27_13:05","mavrinscorporation.ru/hd7fs","5.101.152.85","m2.yoda.beget.ru.","Locky ransomware","-","198610","0","RU", +"2016/04/27_13:05","myehelpers.com/j3ykf","103.8.25.112","svr41.internet-webhosting.com.","Locky ransomware","Registrant mail.eddielee@gmail.com","132241","0","MY", +"2016/04/27_13:05","onlinecrockpotrecipes.com/k2tspa","192.232.212.44","dse.dsent.info.","Locky ransomware","-","46606","0","US", +"2016/04/27_13:05","pediatriayvacunas.com/q0wps","148.163.122.3","corporate.vip1.noc401.com.","Locky ransomware","Registrar Abuse Contact compliance@domain-inc.net","53755","0","US", +"2016/04/27_13:05","soccerinsider.net/mys3ks","139.162.17.49","li859-49.members.linode.com.","Locky ransomware","-","63949","0","NL", +"2016/04/27_13:16","ionic.lohdeek.eu/birth/eWRva2c","185.58.224.173","host173-224-58-185.static.arubacloud.com.","Angler EK","NOT DISCLOSED! / admin@tldregistrarsolutions.com","199883","0","IT", +"2016/04/27_13:16","edbj.k99ms7dy1j.top/questions/1177773/wKkgWWWlR-HCaSWzlRJ-zskdVLNKt-VKALTXM-yEtkg-","185.141.25.153","-","Angler EK","Registrant maykoe@list.ru","60117","0","NL", +"2016/04/27_13:40","traveller.xtinder.cl/ighv4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/27_14:05","gloss.yyg97.com/qhjob4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrant tmm121212@163.com","49981","0","NL", +"2016/04/27_14:10","polish.yusufkisworo.com/qhjob4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrant parissaintgerman99@gmail.com","49981","0","NL", +"2016/04/27_14:18","lifeiscalling-sports.com/8759j3f434","23.229.237.128","ip-23-229-237-128.ip.secureserver.net.","Locky ransomware","Registrar Abuse Contact abuse@net4.com","26496","0","US", +"2016/04/27_14:18","caegpa.com/8759j3f434","192.185.160.227","ns766.websitewelcome.com.","Locky ransomware","-","20013","0","US", +"2016/04/27_14:18","teyseerlab.com/8759j3f434","107.180.51.235","ip-107-180-51-235.ip.secureserver.net.","Locky ransomware","-","26496","0","US", +"2016/04/27_14:22","ascryasuntovelallisten.ansonssolicitors.com/IqthNeCDeQ_es_lQTTHNzCM.php","80.78.241.93","vm21024.hv8.ru.","-","Registrar Abuse Contact support@domainbox.com","43146","0","RU", +"2016/04/27_14:22","ascryasuntovelallisten.ansonssolicitors.com/xYDOQ.php","80.78.241.93","vm21024.hv8.ru.","Angler EK","Registrar Abuse Contact support@domainbox.com","43146","0","RU", +"2016/04/27_14:25","delirium.zambagestion.com.ar/qhjob4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/27_14:55","butoxy.fashion2nv.co.uk/FgpaMs-WvVZ-DyPuEIb/lBcvY-5756648-PDjs/","51.254.240.57","-","Angler EK","Arshad Amir / -","16276","0","FR", +"2016/04/27_14:55","usdnqhhbwf.eihomie.eu/faith/magic-15835427","185.58.224.173","host173-224-58-185.static.arubacloud.com.","Angler EK","NOT DISCLOSED! / admin@tldregistrarsolutions.com","199883","0","IT", +"2016/04/27_15:40","strike.zeg9.com/stnpu4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrant 7D3B38F4A0DB461FBC37546714E010FF.PROTECT@WHOISGUARD.COM","49981","0","NL", +"2016/04/27_16:35","mangle.1hpleft.com/ssqus4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrar Abuse Contact abuse@1and1.com","49981","0","NL", +"2016/04/27_16:40","violent.88vid.com/ssqus4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/27_16:40","absxpintranet.in/8759j3f434","192.186.209.132","ip-192-186-209-132.ip.secureserver.net.","Locky ransomware","ABS Xpress / sayeed@absxpress.in","26496","0","US", +"2016/04/27_17:40","tooth.yyg97.com/noatt4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrant tmm121212@163.com","49981","0","NL", +"2016/04/27_17:50","endura.zeg9.com/noatt4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","Registrant 7D3B38F4A0DB461FBC37546714E010FF.PROTECT@WHOISGUARD.COM","49981","0","NL", +"2016/04/27_18:35","tank.wolfandrocks.com/gmtsij4.html","93.190.140.192","s5.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/27_19:28","ixrw.bqote1mfap.top/enthalpy/retaliating/233230593_myXAGzCRN.html","185.141.25.155","-","Angler EK","Registrant maykoe@list.ru","60117","0","NL", +"2016/04/27_19:28","yesrite.pw/0d9tm1a225955d_77296/spastics_raillery/ntbgg8pre2759j7fy_21705/depreciating?7523=9zxzo&porticoes-109=0053p60s&jackets=441","139.59.175.48","-","NuclearPack EK","-","202109","0","SG", +"2016/04/28_07:49","www.dezuiderwaard.nl/","195.238.74.87","www53.totaalholding.nl.","pseudo darkleech on compromised site leads to Angler EK","-","50673","0","NL", +"2016/04/28_07:49","cilohocltinkettle.coinetf.org/578472-bigger-amiss-intersects-artlessness-musically-has.gif","185.66.9.109","-","Angler EK","Thomas Theobald / info@hometownbiz.com","174","0","UA", +"2016/04/28_09:15","carved.windeploy.ca/erqyk4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_09:35","conquer.wybconsultores.cl/erqyk4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_09:40","switch.wolfandrocks.com/erqyk4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_10:20","matdim.zeg9.com/fhhb4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","Registrant 7D3B38F4A0DB461FBC37546714E010FF.PROTECT@WHOISGUARD.COM","49981","0","NL", +"2016/04/28_11:52","doumafestival.org/8778h4g","207.58.129.29","server101.arznet.com.","Locky ransomware","Emile Ellyeh / e.emile@ositcom.net","25847","0","US", +"2016/04/28_11:52","sar-decor.ru/x8skfa","81.177.139.41","-","Locky ransomware","-","8342","0","RU", +"2016/04/28_11:57","vaskogazdashullt.bball-hoops.com/professed/4279/54/31/221836","185.66.9.109","-","Angler EK","-","174","0","UA", +"2016/04/28_11:57","yykotit.jothoxe.eu/animal/shape-charge-11931014","185.58.224.173","host173-224-58-185.static.arubacloud.com.","Angler EK","NOT DISCLOSED! / admin@tldregistrarsolutions.com","199883","0","IT", +"2016/04/28_12:00","cover.visionfuzesj.com/fkeje4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_12:00","kollagen4you.se","46.252.206.1","n1nlhg198c1198.shr.prod.ams1.secureserver.net.","pseudo darkleech on compromised site leads to Angler EK","-","26496","0","NL", +"2016/04/28_12:05","number.vxshopping.com/fkeje4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_12:40","anemia.xgamegodni.pl/tyxk4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","bok@domena.pl","49981","0","NL", +"2016/04/28_13:50","metos.valah.net/osqfp4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","Registrant VLRYHAYES@GMAIL.COM","49981","0","NL", +"2016/04/28_13:55","norms.wolfandrocks.com/osqfp4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_14:30","eater.vempraruabelem.net.br/osqfp4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","Allan Henrique Fernandes Rendeiro / allan.rendeiro@gmail.com","49981","0","NL", +"2016/04/28_14:52","www.del-marine.com/","80.244.187.39","mail.ebnserver1.com.","pseudo darkleech on compromised site leads to Angler EK","Registrar Abuse Contact domainabuse@tucows.com","34934","0","GB", +"2016/04/28_14:55","devour.veterinarianativa.cl/osqfp4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_15:05","brush.vhsadvd.com.ar/osqfp4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_16:10","nomos.vidaprimaria.cl/hadzoo4.html","93.190.140.194","s7.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/28_16:25","demo.sinelnikds.ru/b6sida","128.199.242.96","-","Locky ransomware","-","133165","0","GB", +"2016/04/29_06:33","jalostunut-kidutettu.copperetf.org/UAk-WQNEmR-HeLlso/sVVU-0279-UWy/","192.169.190.195","ip-192-169-190-195.ip.secureserver.net.","Angler EK","Thomas Theobald / fundsetf@gmail.com","26496","0","US", +"2016/04/29_06:33","gquzubkuzc.lrfrfdna.eu/care/grin-19658818","185.58.224.173","host173-224-58-185.static.arubacloud.com.","Angler EK","NOT DISCLOSED! / admin@tldregistrarsolutions.com","199883","0","IT", +"2016/04/29_09:23","destellar.barrelshipping.info/grgL5sPp8i.php","85.25.41.92","static-ip-85-25-41-92.inaddr.ip-pool.com.","Angler EK","Thomas Theobald / fundsetf@gmail.com","8972","0","DE", +"2016/04/29_09:35","evade.tkathome.de/imgaw4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","hostmaster@1und1.de","49981","0","NL", +"2016/04/29_10:35","shoking.turklocation.com/imgaw4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","Registrar Abuse Contact onlinenic-enduser@onlinenic.com","49981","0","NL", +"2016/04/29_10:55","vacant.urbanpicnic.cl/imgaw4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/29_11:00","seldom.tuulinelikka.fi/imgaw4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/29_11:10","plug.vhsadvd.com.ar/wjefao4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/29_11:35","rosta.tkenlace.tk/fzkfnc4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","E-mail: abuse: abuse@freenom.com, copyright infringement: copyright@freenom.com","49981","0","NL", +"2016/04/29_11:40","xhokogo.topsexviet.com/fzkfnc4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","Registrant PGNFVLQGYB@WHOISPRIVACYPROTECT.COM","49981","0","NL", +"2016/04/29_12:09","www.yedasenerjitakimi.com/8778h4g","94.73.151.120","94-73-151-120.cizgi.net.tr.","Locky ransomware","Registrar Abuse Contact abuse@nicproxy.com","34619","0","TR", +"2016/04/29_12:09","netrition.com.br/v7jsa","186.202.153.214","-","Locky ransomware","MARCELO BISSOLOTTI FIORI / marcelo_fiori@hotmail.com","27715","0","BR", +"2016/04/29_13:25","rafik.termes.cl/mztvn4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/29_14:15","ninja.thaoluanlol.tk/mztvn4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","E-mail: abuse: abuse@freenom.com, copyright infringement: copyright@freenom.com","49981","0","NL", +"2016/04/29_15:15","akord.tschordi.ch/tvfxe4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/29_15:50","panda.thenigelcountdown.com/tvfxe4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","Registrar Abuse Contact abuse@1and1.com","49981","0","NL", +"2016/04/29_15:55","lalass.tallertapia.cl/tvfxe4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","-","49981","0","NL", +"2016/04/29_16:05","mores.ss-sole.com/tvfxe4.html","93.190.140.217","s9.sheermail.com.","gateway to Angler EK","Registrant ACCOUNTS@HOSTINGSOURCE.COM","49981","0","NL", +"2016/05/02_07:25","bethdiblathaim-genavam.itkeysolution.com/jz/R/65/","207.244.75.122","us117.ua-hosting.company.","Angler EK","Registrar Abuse Contact abuse@ascio.com","30633","0","US", +"2016/05/02_09:11","produktionsfreigabe.partydisplaysolutions.co.uk/inhumanities/5352/23/81/60785095.html","139.59.160.168","jobhost.bizz.","Angler EK","Sarah Yates / -","202109","0","SG", +"2016/05/02_10:06","livende-angolan.eastlondonbuildincontractors.co.uk/qkFOXztaD_lykc_IrAnIaQh.html","185.104.8.126","customer.clientshostname.com.","Angler EK","Marian P Ciulei / -","14576","0","NL", +"2016/05/02_11:54","superlative.eastlondonbuilding.uk/cloWiyltz/KvycQ/iAGlwP-SeNNjcZu/","139.59.160.168","jobhost.bizz.","Angler EK","MGB PRO CONTRACTOR / -","202109","0","SG", +"2016/05/02_11:54","hddeonews.com/","107.20.180.238","ec2-107-20-180-238.compute-1.amazonaws.com.","pseudo darkleech on compromised site leads to Angler EK","-","14618","0","US", +"2016/05/02_10:23","www.donneuropa.it/","23.23.85.3","ec2-23-23-85-3.compute-1.amazonaws.com.","pseudo darkleech on compromised site leads to Angler EK","-","14618","0","US", +"2016/05/02_12:05","erdolchendenpandhuiz.giovyphotography.co.uk/cwKgVP/qFnltAZ/ckBbB-jLykTouX/","185.73.221.95","-","Angler EK","Marian P Ciulei / -","32338","0","NL", +"2016/05/02_13:58","bangperbuck-epicnemial.masper.co.uk/questions/26870657/fxKivBpmV-ekNLv-rWvDXaWm-xvTAOc-","185.73.221.95","-","Angler EK","Marian ciulei / -","32338","0","NL", +"2016/05/02_14:27","chuta1deslate.fortmyersscreenrepair.org/uu/s/816/","185.73.221.95","-","Angler EK","Thomas Theobald / info@hometownbiz.com","32338","0","NL", diff --git a/MalwareAnalyzer/migrations/__init__.py b/MalwareAnalyzer/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/MalwareAnalyzer/views.py b/MalwareAnalyzer/views.py index 07c7557f8c..f2bf82d69b 100644 --- a/MalwareAnalyzer/views.py +++ b/MalwareAnalyzer/views.py @@ -1,10 +1,10 @@ from django.shortcuts import render from django.conf import settings -from MobSF.exception_printer import PrintException +from MobSF.utils import PrintException,isInternetAvailable,sha256 from urlparse import urlparse -import urllib2, hashlib, shutil, io, os, re +import urllib2,shutil,io,os,re #PATH MALWARE_DB_DIR=TOOLS_DIR=os.path.join(settings.BASE_DIR, 'MalwareAnalyzer/malwaredb/') @@ -76,22 +76,6 @@ def MalwareCheck(urllist): #Helper Functions -def isInternetAvailable(): - try: - response=urllib2.urlopen('http://216.58.220.46',timeout=5) - return True - except urllib2.URLError as err: pass - return False - -def sha256(file_path): - BLOCKSIZE = 65536 - hasher = hashlib.sha256() - with io.open(file_path,mode='rb') as afile: - buf = afile.read(BLOCKSIZE) - while len(buf) > 0: - hasher.update(buf) - buf = afile.read(BLOCKSIZE) - return (hasher.hexdigest()) def getDomains(urls): try: diff --git a/MobSF/exception_printer.py b/MobSF/exception_printer.py deleted file mode 100644 index f28f6c4b3a..0000000000 --- a/MobSF/exception_printer.py +++ /dev/null @@ -1,33 +0,0 @@ -import sys,linecache,os,time,datetime,platform -from django.conf import settings - -class Color(object): - GREEN = '\033[92m' - ORANGE = '\033[33m' - RED = '\033[91m' - BOLD = '\033[1m' - END = '\033[0m' - - -def PrintException(msg,web=False): - LOGPATH=settings.LOG_DIR - if not os.path.exists(LOGPATH): - os.makedirs(LOGPATH) - exc_type, exc_obj, tb = sys.exc_info() - f = tb.tb_frame - lineno = tb.tb_lineno - filename = f.f_code.co_filename - linecache.checkcache(filename) - line = linecache.getline(filename, lineno, f.f_globals) - ts = time.time() - st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') - dat= '\n['+st+']\n'+msg+' ({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj) - if platform.system()=="Windows": - print dat - else: - if web: - print Color.BOLD + Color.ORANGE + dat + Color.END - else: - print Color.BOLD + Color.RED + dat + Color.END - with open(LOGPATH + 'MobSF.log','a') as f: - f.write(dat) \ No newline at end of file diff --git a/MobSF/java.py b/MobSF/java.py deleted file mode 100644 index 167a566e8d..0000000000 --- a/MobSF/java.py +++ /dev/null @@ -1,75 +0,0 @@ -import subprocess,platform,re,os -from MobSF.exception_printer import PrintException -#Maintain JDK Version -JAVA_VER='1.7|1.8|1.9|2.0|2.1|2.2|2.3' -def FindJava(): - try: - if platform.system()=="Windows": - print "\n[INFO] Finding JDK Location in Windows...." - WIN_JAVA_LIST=["C:/Program Files/Java/","C:/Program Files (x86)/Java/"] #JDK 7 jdk1.7.0_17/bin/ - for WIN_JAVA_BASE in WIN_JAVA_LIST: - JDK=[] - for dirname in os.listdir(WIN_JAVA_BASE): - if "jdk" in dirname: - JDK.append(dirname) - if len(JDK)==1: - print "\n[INFO] Oracle JDK Identified. Looking for JDK 1.7 or above" - j=''.join(JDK) - if re.findall(JAVA_VER,j): - WIN_JAVA=WIN_JAVA_BASE+j+"/bin/" - args=[WIN_JAVA+"java"] - dat=RunProcess(args) - if "oracle" in dat: - print "\n[INFO] Oracle Java (JDK >= 1.7) is installed!" - return WIN_JAVA - elif len(JDK)>1: - print "\n[INFO] Multiple JDK Instances Identified. Looking for JDK 1.7 or above" - for j in JDK: - if re.findall(JAVA_VER,j): - WIN_JAVA=WIN_JAVA_BASE+j+"/bin/" - break - else: - WIN_JAVA="" - if len(WIN_JAVA)>1: - args=[WIN_JAVA+"java"] - dat=RunProcess(args) - if "oracle" in dat: - print "\n[INFO] Oracle Java (JDK >= 1.7) is installed!" - return WIN_JAVA - PrintException("[ERROR] Oracle JDK 1.7 or above is not found!") - return "" - else: - print "\n[INFO] Finding JDK Location in Linux/MAC...." - MAC_LINUX_JAVA="/usr/bin/" - args=[MAC_LINUX_JAVA+"java"] - dat=RunProcess(args) - if "oracle" in dat: - print "\n[INFO] Oracle Java is installed!" - args=[MAC_LINUX_JAVA+"java", '-version'] - dat=RunProcess(args) - f_line=dat.split("\n")[0] - if re.findall(JAVA_VER,f_line): - print "\n[INFO] JDK 1.7 or above is available" - return MAC_LINUX_JAVA - else: - PrintException("[ERROR] Please install Oracle JDK 1.7 or above") - return "" - else: - PrintException("[ERROR] Oracle Java JDK 1.7 or above is not found!") - return "" - except: - PrintException("[ERROR] Oracle Java (JDK >=1.7) is not found!") - return "" -def RunProcess(args): - try: - proc = subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,) - dat='' - while True: - line = proc.stdout.readline() - if not line: - break - dat+=line - return dat - except: - PrintException("[ERROR] Finding Java path - Cannot Run Process") - return "" \ No newline at end of file diff --git a/MobSF/settings.py b/MobSF/settings.py index 77afc2420b..b57f15d062 100644 --- a/MobSF/settings.py +++ b/MobSF/settings.py @@ -8,46 +8,110 @@ https://docs.djangoproject.com/en/dev/ref/settings/ """ -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -import os,platform -import java, vbox +import os,platform,imp +import utils + +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# MOBSF FRAMEWORK CONFIGURATIONS +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +#============================================== +MOBSF_VER = "v0.9.2 Beta" +BANNER =""" + __ __ _ ____ _____ ___ ___ ____ + | \/ | ___ | |__/ ___|| ___| __ __/ _ \ / _ \ |___ \ + | |\/| |/ _ \| '_ \___ \| |_ \ \ / / | | | (_) | __) | + | | | | (_) | |_) |__) | _| \ V /| |_| |\__, | / __/ + |_| |_|\___/|_.__/____/|_| \_/ \___(_) /_(_)_____| + +""" +utils.printMobSFverison(MOBSF_VER, BANNER) +#============================================== +#==========MobSF Home Directory================= +USE_HOME = False -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ +#True : All Uploads/Downloads will be stored in user's home directory +#False : All Uploads/Downloads will be stored in MobSF root directory +#If you need multiple users to share the scan results set this to False +#=============================================== -#Based on https://gist.github.com/ndarville/3452907#file-secret-key-gen-py -#SECRET_KEY = '#r$=rg*lit&!4nukg++@%k+n9#6fhkv_*a6)2t$n1b=*wpvptl' +MobSF_HOME = utils.getMobSFHome(USE_HOME) +#Logs Directory +LOG_DIR = os.path.join(MobSF_HOME, 'logs/') +#Download Directory +DWD_DIR = os.path.join(MobSF_HOME, 'downloads/') +#Screenshot Directory +SCREEN_DIR = os.path.join(MobSF_HOME, 'downloads/screen/') +#Upload Directory +UPLD_DIR = os.path.join(MobSF_HOME, 'uploads/') +#Database Directory +DB_DIR = os.path.join(MobSF_HOME, 'db.sqlite3') + +# Database +# https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': DB_DIR, + } +} +#=============================================== +#==========LOAD CONFIG FROM MobSF HOME========== +try: + #Update Config from MobSF Home Directory + if USE_HOME: + USER_CONFIG = os.path.join(MobSF_HOME,'config.py') + sett = imp.load_source('user_settings', USER_CONFIG) + locals().update({k: v for k, v in sett.__dict__.items() if not k.startswith("__")}) + CONFIG_HOME = True + else: + CONFIG_HOME = False +except: + utils.PrintException("[ERROR] Parsing Config") + CONFIG_HOME = False +#=============================================== + +#=============ALLOWED EXTENSIONS================ +ALLOWED_EXTENSIONS = { +".txt":"text/plain", +".png":"image/png", +".zip":"application/zip", +".tar":"application/x-tar" +} +#=============================================== + +#=====MOBSF SECRET GENERATION AND MIGRATION===== +#Based on https://gist.github.com/ndarville/3452907#file-secret-key-gen-py try: SECRET_KEY except NameError: - SECRET_FILE = os.path.join(BASE_DIR, "MobSF/secret") + SECRET_FILE = os.path.join(MobSF_HOME, "secret") try: SECRET_KEY = open(SECRET_FILE).read().strip() except IOError: try: - import random - SECRET_KEY = ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) + SECRET_KEY = utils.genRandom() secret = file(SECRET_FILE, 'w') secret.write(SECRET_KEY) secret.close() + utils.Migrate(BASE_DIR) except IOError: Exception('Please create a %s file with random characters \ to generate your secret key!' % SECRET_FILE) +#============================================= + +#============DJANGO SETTINGS ================= # SECURITY WARNING: don't run with debug turned on in production! -# ^ This is fine Do not turn it off untill MobSF framework moves from Beta to Stable -DEBUG = True +# ^ This is fine Do not turn it off until MobSF moves from Beta to Stable +DEBUG = True TEMPLATE_DEBUG = True - ALLOWED_HOSTS = [] - - # Application definition - INSTALLED_APPS = ( #'django.contrib.admin', 'django.contrib.auth', @@ -61,7 +125,6 @@ 'APITester', 'MalwareAnalyzer', ) - MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -72,15 +135,10 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', ) - ROOT_URLCONF = 'MobSF.urls' - WSGI_APPLICATION = 'MobSF.wsgi.application' - - # Internationalization # https://docs.djangoproject.com/en/dev/topics/i18n/ - LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True @@ -97,134 +155,142 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/dev/howto/static-files/ STATIC_URL = '/static/' -#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -MOBSF_VER = "v0.9.2 Beta" - -if platform.system()=="Windows": - print '\n\nMobile Security Framework '+ MOBSF_VER -else: - print '\n\n\033[1m\033[34mMobile Security Framework '+ MOBSF_VER +'\033[0m' - -# DO NOT EDIT ANYTHING ABOVE THIS -#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -#Logs Directory -LOG_DIR=os.path.join(BASE_DIR,'logs/') -#Static Directory -STATIC_DIR=os.path.join(BASE_DIR,'static/') -#Download Directory -DWD_DIR=os.path.join(STATIC_DIR, 'downloads/') -#Upload Directory -UPLD_DIR=os.path.join(BASE_DIR,'uploads/') -#Database Directory -DB_DIR = os.path.join(BASE_DIR, 'db.sqlite3') - -# Database -# https://docs.djangoproject.com/en/dev/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': DB_DIR, - } -} - -#==========DECOMPILER SETTINGS================ -DECOMPILER = "jd-core" -#Two Decompilers are available -#1. jd-core -#2. cfr -#============================================== - -#==========Dex to Jar Converter================ -JAR_CONVERTER = "d2j" -#Two dex to jar converters are available -#1. d2j -#2. enjarify -''' -enjarify requires python3. Install Python 3 and add the path to environment variable -PATH or provide the Python 3 path to PYTHON3_PATH variable in settings.py -ex: PYTHON3_PATH = "C:/Users//AppData/Local/Programs/Python/Python35-32/" -''' -PYTHON3_PATH = "" -#============================================== - -#============JAVA SETTINGS===================== -JAVA_PATH=java.FindJava() - -#Sample Java Path -#Windows - JAVA_PATH='C:/Program Files/Java/jdk1.7.0_17/bin/' -#OSX and Linux - JAVA_PATH='/usr/bin/' #=============================================== - -#==========SKIP CLASSES========================== -SKIP_CLASSES = ['android/support/','com/google/','android/content/','com/android/', -'com/facebook/','com/twitter/','twitter4j/','org/apache/','com/squareup/okhttp/', -'oauth/signpost/','org/chromium/'] -#================================================ - -#===============DEVICE Settings================= -REAL_DEVICE = False -DEVICE_IP = '192.168.1.18' -DEVICE_ADB_PORT = 5555 -DEVICE_TIMEOUT = 300 +if CONFIG_HOME: + print "[INFO] Loading User config from: " + USER_CONFIG +else: + ''' + IMPORTANT + If 'USE_HOME' is set to True, then below user configuration settings are not considered. + The user configuration will be loaded from config.py in MobSF Home directory. + ''' + #^CONFIG-START^: Do not edit this line + #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # MOBSF USER CONFIGURATIONS + #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + #==========SKIP CLASSES========================== + SKIP_CLASSES = ['android/support/','com/google/','android/content/','com/android/', + 'com/facebook/','com/twitter/','twitter4j/','org/apache/','com/squareup/okhttp/', + 'oauth/signpost/','org/chromium/'] + + #==============3rd Party Tools================= + ''' + If you want to use a different version of 3rd party tools used by MobSF. + You can do that by specifying the path here. If specified, MobSF will run + the tool from this location. + ''' + + #Android 3P Tools + DEX2JAR_BINARY = "" + BACKSMALI_BINARY = "" + AXMLPRINTER_BINARY = "" + CFR_DECOMPILER_BINARY = "" + JD_CORE_DECOMPILER_BINARY = "" + PROCYON_DECOMPILER_BINARY = "" + ADB_BINARY = "" + ENJARIFY_DIRECTORY = "" + + #iOS 3P Tools + OTOOL_BINARY = "" + CLASSDUMPZ_BINARY = "" + + #COMMON + JAVA_DIRECTORY = "" + VBOXMANAGE_BINARY = "" + + ''' + Examples: + JAVA_DIRECTORY = "C:/Program Files/Java/jdk1.7.0_17/bin/" + JAVA_DIRECTORY = "/usr/bin/" + DEX2JAR_BINARY = "/Users/ajin/dex2jar/d2j_invoke.sh" + ENJARIFY_DIRECTORY = "D:/enjarify/" + VBOXMANAGE_BINARY = "/usr/bin/VBoxManage" + CFR_DECOMPILER_BINARY = "/home/ajin/tools/cfr.jar" + ''' + #============================================== + + #=========Path Traversal - API Testing========== + CHECK_FILE = "/etc/passwd" + RESPONSE_REGEX = "root:|nobody:" + #=============================================== + + #=========Rate Limit Check - API Testing======== + RATE_REGISTER = 20 + RATE_LOGIN = 20 + #=============================================== + + #===============MobSF Cloud Settings============ + CLOUD_SERVER = 'http://opensecurity.in:8080' + ''' + This server validates SSRF and XXE during Web API Testing + See the source code of the cloud server from APITester/cloud/cloud_server.py + You can also host the cloud server. Host it on a public IP and point CLOUD_SERVER to that IP. + ''' + + #===============DEVICE SETTINGS================= + REAL_DEVICE = False + DEVICE_IP = '192.168.1.18' + DEVICE_ADB_PORT = 5555 + DEVICE_TIMEOUT = 300 + #=============================================== + #================VM SETTINGS =================== + #VM UUID + UUID='81c7edd3-6038-4024-9735-682bdbacab8b' + #Snapshot UUID + SUUID='434126a3-4966-42b8-9aa1-2c43028c6db5' + #IP of the MobSF VM + VM_IP='192.168.56.101' + VM_ADB_PORT = 5555 + VM_TIMEOUT = 100 + #============================================== + + #================HOST/PROXY SETTINGS ========== + PROXY_IP='192.168.56.1' #Host/Server/Proxy IP + PORT=1337 #Proxy Port + ROOT_CA='0025aabb.0' + SCREEN_IP = PROXY_IP #ScreenCast IP + SCREEN_PORT = 9339 #ScreenCast Port + #============================================== + + #========UPSTREAM PROXY SETTINGS ============== + #If you are behind a Proxy + UPSTREAM_PROXY_IP = None + UPSTREAM_PROXY_PORT = None + UPSTREAM_PROXY_USERNAME = None + UPSTREAM_PROXY_PASSWORD = None + #============================================== + + #==========DECOMPILER SETTINGS================= + + DECOMPILER = "jd-core" + #Two Decompilers are available + #1. jd-core + #2. cfr + #3. procyon + #============================================== + + #==========Dex to Jar Converter================ + JAR_CONVERTER = "d2j" + #Two Dex to Jar converters are available + #1. d2j + #2. enjarify + + ''' + enjarify requires python3. Install Python 3 and add the path to environment variable + PATH or provide the Python 3 path to "PYTHON3_PATH" variable in settings.py + ex: PYTHON3_PATH = "C:/Users/Ajin/AppData/Local/Programs/Python/Python35-32/" + ''' + PYTHON3_PATH = "" + #============================================== + #^CONFIG-END^: Do not edit this line + +#The below code should be loaded last. +#============JAVA SETTINGS====================== +JAVA_PATH=utils.FindJava() #=============================================== #================VirtualBox Settings============ -if REAL_DEVICE == False: - if platform.system()=="Windows": - VBOX='C:\Program Files\Oracle\VirtualBox\VBoxManage.exe' - #Path to VBoxManage.exe - else: - VBOX=vbox.FindVbox() - #Path to VBoxManage in Linux/OSX +VBOX = utils.FindVbox() #=============================================== - -#================VM SETTINGS ================== -#VM UUID -UUID='81c7edd3-6038-4024-9735-682bdbacab8b' -#Snapshot UUID -SUUID='434126a3-4966-42b8-9aa1-2c43028c6db5' -#IP of the MobSF VM -VM_IP='192.168.56.101' -VM_ADB_PORT = 5555 -VM_TIMEOUT = 100 -#============================================= - -#================HOST/PROXY SETTINGS =========== -PROXY_IP='192.168.56.1' #Host/Server/Proxy IP -PORT=1337 #Proxy Port -ROOT_CA='0025aabb.0' - -SCREEN_IP = PROXY_IP #ScreenCast IP -SCREEN_PORT = 9339 #ScreenCast Port -#=============================================== - -#===============UPSTREAM PROXY================== -#If you are behind a Proxy -UPSTREAM_PROXY_IP = None -UPSTREAM_PROXY_PORT = None -UPSTREAM_PROXY_USERNAME = None -UPSTREAM_PROXY_PASSWORD = None -#=============================================== - -#=========Path Traversal - API Testing========== -CHECK_FILE = "/etc/passwd" -RESPONSE_REGEX = "root:|nobody:" -#=============================================== - -#=========Rate Limit Check - API Testing======== -RATE_REGISTER = 20 -RATE_LOGIN = 20 -#=============================================== - -#===============MobSF Cloud Settings============ -CLOUD_SERVER = 'http://opensecurity.in:8080' -''' -This server validates SSRF and XXE during Web API Testing -See the source code of the cloud server from APITester/cloud/cloud_server.py -You can also host the cloud server. Host it on a public IP and point CLOUD_SERVER to that IP. -''' -#=============================================== - - - diff --git a/MobSF/urls.py b/MobSF/urls.py index d1ac5ae6e9..23a3700c91 100644 --- a/MobSF/urls.py +++ b/MobSF/urls.py @@ -5,6 +5,7 @@ # Examples: url(r'^$','MobSF.views.index', name='index'), url(r'^Upload/$', 'MobSF.views.Upload', name='Upload'), + url(r'^download/', 'MobSF.views.Download', name='download'), url(r'^about/$', 'MobSF.views.about', name='about'), url(r'^RecentScans/$', 'MobSF.views.RecentScans', name='RecentScans'), url(r'^Search/$', 'MobSF.views.Search', name='Search'), diff --git a/MobSF/utils.py b/MobSF/utils.py new file mode 100644 index 0000000000..bc3348b92b --- /dev/null +++ b/MobSF/utils.py @@ -0,0 +1,287 @@ +import os,platform,random,subprocess,re,sys,linecache,time,datetime,ntpath,hashlib,urllib2,io,ast,unicodedata +import settings + +def printMobSFverison(MOBSF_VER, BANNER): + if platform.system()=="Windows": + print '\n\nMobile Security Framework '+ MOBSF_VER + else: + print '\n\n\033[1m\033[34mMobile Security Framework '+ MOBSF_VER +'\033[0m' + print BANNER + +def createUserConfig(MobSF_HOME): + try: + CONFIG_PATH = os.path.join(MobSF_HOME,'config.py') + if isFileExists(CONFIG_PATH) == False: + SAMPLE_CONF = os.path.join(settings.BASE_DIR,"MobSF/settings.py") + with io.open(SAMPLE_CONF, mode='r', encoding="utf8", errors="ignore") as f: + dat=f.readlines() + CONFIG = list() + add = False + for line in dat: + if "^CONFIG-START^" in line: + add = True + if "^CONFIG-END^" in line: + break + if add: + CONFIG.append(line.lstrip()) + CONFIG.pop(0) + COMFIG_STR = ''.join(CONFIG) + with io.open(CONFIG_PATH, mode='w', encoding="utf8", errors="ignore") as f: + f.write(COMFIG_STR) + except: + PrintException("[ERROR] Cannot create config file") + +def getMobSFHome(useHOME): + try: + MobSF_HOME = "" + if useHOME: + MobSF_HOME = os.path.join(os.path.expanduser('~'),".MobSF") + #MobSF Home Directory + if not os.path.exists(MobSF_HOME): + os.makedirs(MobSF_HOME) + createUserConfig(MobSF_HOME) + else: + MobSF_HOME = settings.BASE_DIR + #Logs Directory + LOG_DIR=os.path.join(MobSF_HOME, 'logs/') + if not os.path.exists(LOG_DIR): + os.makedirs(LOG_DIR) + #Certs Directory + CERT_DIR=os.path.join(LOG_DIR, 'certs/') + if not os.path.exists(CERT_DIR): + os.makedirs(CERT_DIR) + #Download Directory + DWD_DIR=os.path.join(MobSF_HOME, 'downloads/') + if not os.path.exists(DWD_DIR): + os.makedirs(DWD_DIR) + #Screenshot Directory + SCREEN_DIR = os.path.join(DWD_DIR, 'screen/') + if not os.path.exists(SCREEN_DIR): + os.makedirs(SCREEN_DIR) + #Upload Directory + UPLD_DIR=os.path.join(MobSF_HOME, 'uploads/') + if not os.path.exists(UPLD_DIR): + os.makedirs(UPLD_DIR) + return MobSF_HOME + except: + PrintException("[ERROR] Creating MobSF Home Directory") + +def Migrate(BASE_DIR): + try: + manage = os.path.join(BASE_DIR,"manage.py") + args = ["python", manage, "migrate"] + subprocess.call(args) + except: + PrintException("[ERROR] Cannot Migrate") + +def FindVbox(): + try: + if settings.REAL_DEVICE == False: + if len(settings.VBOXMANAGE_BINARY) > 0 and isFileExists(settings.VBOXMANAGE_BINARY): + return settings.VBOXMANAGE_BINARY + if platform.system()=="Windows": + #Path to VBoxManage.exe + vbox_path=["C:\Program Files\Oracle\VirtualBox\VBoxManage.exe", + "C:\Program Files (x86)\Oracle\VirtualBox\VBoxManage.exe"] + for path in vbox_path: + if os.path.isfile(path): + return path + else: + #Path to VBoxManage in Linux/Mac + vbox_path = ["/usr/bin/VBoxManage", "/usr/local/bin/VBoxManage"] + for path in vbox_path: + if os.path.isfile(path): + return path + print "\n[WARNING] Could not find VirtualBox path." + except: + PrintException("[ERROR] Cannot find VirtualBox path.") + +#Maintain JDK Version +JAVA_VER='1.7|1.8|1.9|2.0|2.1|2.2|2.3' +def FindJava(): + try: + if len(settings.JAVA_DIRECTORY) > 0 and isDirExists(settings.JAVA_DIRECTORY): + return settings.JAVA_DIRECTORY + if platform.system()=="Windows": + print "\n[INFO] Finding JDK Location in Windows...." + WIN_JAVA_LIST=["C:/Program Files/Java/","C:/Program Files (x86)/Java/"] #JDK 7 jdk1.7.0_17/bin/ + for WIN_JAVA_BASE in WIN_JAVA_LIST: + JDK=[] + for dirname in os.listdir(WIN_JAVA_BASE): + if "jdk" in dirname: + JDK.append(dirname) + if len(JDK)==1: + print "\n[INFO] Oracle JDK Identified. Looking for JDK 1.7 or above" + j=''.join(JDK) + if re.findall(JAVA_VER,j): + WIN_JAVA=WIN_JAVA_BASE+j+"/bin/" + args=[WIN_JAVA+"java"] + dat=RunProcess(args) + if "oracle" in dat: + print "\n[INFO] Oracle Java (JDK >= 1.7) is installed!" + return WIN_JAVA + elif len(JDK)>1: + print "\n[INFO] Multiple JDK Instances Identified. Looking for JDK 1.7 or above" + for j in JDK: + if re.findall(JAVA_VER,j): + WIN_JAVA=WIN_JAVA_BASE+j+"/bin/" + break + else: + WIN_JAVA="" + if len(WIN_JAVA)>1: + args=[WIN_JAVA+"java"] + dat=RunProcess(args) + if "oracle" in dat: + print "\n[INFO] Oracle Java (JDK >= 1.7) is installed!" + return WIN_JAVA + PrintException("[ERROR] Oracle JDK 1.7 or above is not found!") + return "" + else: + print "\n[INFO] Finding JDK Location in Linux/MAC...." + MAC_LINUX_JAVA="/usr/bin/" + args=[MAC_LINUX_JAVA+"java"] + dat=RunProcess(args) + if "oracle" in dat: + print "\n[INFO] Oracle Java is installed!" + args=[MAC_LINUX_JAVA+"java", '-version'] + dat=RunProcess(args) + f_line=dat.split("\n")[0] + if re.findall(JAVA_VER,f_line): + print "\n[INFO] JDK 1.7 or above is available" + return MAC_LINUX_JAVA + else: + PrintException("[ERROR] Please install Oracle JDK 1.7 or above") + return "" + else: + PrintException("[ERROR] Oracle Java JDK 1.7 or above is not found!") + return "" + except: + PrintException("[ERROR] Oracle Java (JDK >=1.7) is not found!") + return "" + +def RunProcess(args): + try: + proc = subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,) + dat='' + while True: + line = proc.stdout.readline() + if not line: + break + dat+=line + return dat + except: + PrintException("[ERROR] Finding Java path - Cannot Run Process") + return "" + +class Color(object): + GREEN = '\033[92m' + ORANGE = '\033[33m' + RED = '\033[91m' + BOLD = '\033[1m' + END = '\033[0m' + + +def PrintException(msg,web=False): + try: + LOGPATH=settings.LOG_DIR + except: + LOGPATH = os.path.join(settings.BASE_DIR,"logs/") + if not os.path.exists(LOGPATH): + os.makedirs(LOGPATH) + exc_type, exc_obj, tb = sys.exc_info() + f = tb.tb_frame + lineno = tb.tb_lineno + filename = f.f_code.co_filename + linecache.checkcache(filename) + line = linecache.getline(filename, lineno, f.f_globals) + ts = time.time() + st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') + dat= '\n['+st+']\n'+msg+' ({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj) + if platform.system()=="Windows": + print dat + else: + if web: + print Color.BOLD + Color.ORANGE + dat + Color.END + else: + print Color.BOLD + Color.RED + dat + Color.END + with open(LOGPATH + 'MobSF.log','a') as f: + f.write(dat) + +def filename_from_path(path): + head, tail = ntpath.split(path) + return tail or ntpath.basename(head) + +def getMD5(data): + return hashlib.md5(data).hexdigest() + +def findBetween(s, first, last): + try : + start = s.index(first) + len(first) + end = s.index(last,start) + return s[start:end] + except ValueError: + return "" + +def is_number(s): + try: + float(s) + return True + except ValueError: + pass + try: + unicodedata.numeric(s) + return True + except (TypeError, ValueError): + pass + return False + +def python_list(value): + if not value: + value = [] + if isinstance(value, list): + return value + return ast.literal_eval(value) + +def python_dict(value): + if not value: + value = {} + if isinstance(value, dict): + return value + return ast.literal_eval(value) + + +def isBase64(str): + return re.match('^[A-Za-z0-9+/]+[=]{0,2}$', str) + +def isInternetAvailable(): + try: + response=urllib2.urlopen('http://216.58.220.46',timeout=5) + return True + except urllib2.URLError as err: pass + return False + +def sha256(file_path): + BLOCKSIZE = 65536 + hasher = hashlib.sha256() + with io.open(file_path,mode='rb') as afile: + buf = afile.read(BLOCKSIZE) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(BLOCKSIZE) + return (hasher.hexdigest()) + +def isFileExists(file_path): + if os.path.isfile(file_path): + return True + else: + return False + +def isDirExists(dir_path): + if os.path.isdir(dir_path): + return True + else: + return False + +def genRandom(): + return ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) + diff --git a/MobSF/vbox.py b/MobSF/vbox.py deleted file mode 100644 index b9601446bb..0000000000 --- a/MobSF/vbox.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -def FindVbox(): - vbox_path = ["/usr/bin/VBoxManage", "/usr/local/bin/VBoxManage"] - for path in vbox_path: - if os.path.isfile(path): - return path - print "\n[WARNING] Could not find VirtualBox path." - return vbox_path[0] \ No newline at end of file diff --git a/MobSF/views.py b/MobSF/views.py index b699987e12..1505bfaa5f 100644 --- a/MobSF/views.py +++ b/MobSF/views.py @@ -2,12 +2,16 @@ from django.shortcuts import render from django.http import HttpResponse from django.http import HttpResponseRedirect -from .forms import UploadFileForm from django.conf import settings from django.utils import timezone -import os, hashlib, platform, json,shutil,re -from MobSF.exception_printer import PrintException +from django.utils.encoding import smart_str +from django.core.servers.basehttp import FileWrapper + +from MobSF.utils import PrintException, filename_from_path from MobSF.models import RecentScansDB +from .forms import UploadFileForm + +import os, hashlib, platform, json,shutil,re def PushtoRecent(NAME,MD5,URL): try: @@ -19,7 +23,7 @@ def PushtoRecent(NAME,MD5,URL): PrintException("[ERROR] Adding Scan URL to Database") def index(request): - context = {} + context = {'version': settings.MOBSF_VER} template="index.html" return render(request,template,context) @@ -134,3 +138,25 @@ def Search(request): return HttpResponseRedirect('/NotFound') return HttpResponseRedirect('/error/') +def Download(request): + 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(file(dwd_file)) + 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/') + + diff --git a/README.md b/README.md index 585835f574..85e8c8ca72 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Mobile-Security-Framework (MobSF) -Version: v0.9.1 beta -![mobsecfav](https://cloud.githubusercontent.com/assets/4301109/7418958/68ec3d44-ef8f-11e4-97e2-b26a3d723814.png) +Version: v0.9.2 beta +

+ +

+ Mobile Security Framework (MobSF) is an intelligent, all-in-one open source mobile application (Android/iOS) automated pen-testing framework capable of performing static and dynamic analysis. It can be used for effective and fast security analysis of Android and iOS Applications and supports both binaries (APK & IPA) and zipped source code. MobSF can also perform Web API Security testing with it's API Fuzzer that can do Information Gathering, analyze Security Headers, identify Mobile API specific vulnerabilities like XXE, SSRF, Path Traversal, IDOR, and other logical issues related to Session and API Rate Limiting. @@ -51,4 +54,5 @@ Mobile Security Framework (MobSF) is an intelligent, all-in-one open source mobi * Tim Brown (@timb_machine) - For the iOS Binary Analysis Ruleset. * Abhinav Sejpal (@Abhinav_Sejpal) - For poking me with bugs and feature requests. * Anant Srivastava (@anantshri) - For Activity Tester Idea +* Amrutha VC (@amruthavc) - For the new MobSF logo diff --git a/StaticAnalyzer/tools/procyon-decompiler-0.5.30.jar b/StaticAnalyzer/tools/procyon-decompiler-0.5.30.jar new file mode 100644 index 0000000000..5dc8b1dd77 Binary files /dev/null and b/StaticAnalyzer/tools/procyon-decompiler-0.5.30.jar differ diff --git a/StaticAnalyzer/views.py b/StaticAnalyzer/views.py index e11cf9653f..28e7614fab 100644 --- a/StaticAnalyzer/views.py +++ b/StaticAnalyzer/views.py @@ -8,13 +8,13 @@ from django.template.defaulttags import register from StaticAnalyzer.models import StaticAnalyzerAndroid,StaticAnalyzerIPA,StaticAnalyzerIOSZIP -from MobSF.exception_printer import PrintException +from MobSF.utils import PrintException,python_list,python_dict,isDirExists,isFileExists from MalwareAnalyzer.views import MalwareCheck from xml.dom import minidom from .dvm_permissions import DVM_PERMISSIONS import sqlite3 as sq -import io,re,os,glob,hashlib, zipfile, subprocess,ntpath,shutil,platform,ast,sys,plistlib +import io,re,os,glob,hashlib, zipfile, subprocess,ntpath,shutil,platform,sys,plistlib try: import xhtml2pdf.pisa as pisa @@ -322,19 +322,6 @@ def ManifestView(request): PrintException("[ERROR] Viewing AndroidManifest.xml") return HttpResponseRedirect('/error/') -def python_list(value): - if not value: - value = [] - if isinstance(value, list): - return value - return ast.literal_eval(value) -def python_dict(value): - if not value: - value = {} - if isinstance(value, dict): - return value - return ast.literal_eval(value) - def StaticAnalyzer(request): try: #Input validation @@ -346,6 +333,7 @@ def StaticAnalyzer(request): MD5=request.GET['checksum'] #MD5 APP_DIR=os.path.join(settings.UPLD_DIR, MD5+'/') #APP DIRECTORY TOOLS_DIR=os.path.join(DIR, 'StaticAnalyzer/tools/') #TOOLS DIR + DWD_DIR = settings.DWD_DIR print "[INFO] Starting Analysis on : "+APP_NAME RESCAN= str(request.GET.get('rescan', 0)) if TYP=='apk': @@ -636,9 +624,12 @@ def StaticAnalyzer(request): APP_PATH=APP_DIR+APP_FILE #APP PATH print "[INFO] Extracting ZIP" FILES = Unzip(APP_PATH,APP_DIR) - CERTZ = GetHardcodedCertKeystore(FILES) #Check if Valid Directory Structure and get ZIP Type pro_type,Valid=ValidAndroidZip(APP_DIR) + if Valid and pro_type=='ios': + print "[INFO] Redirecting to iOS Source Code Analyzer" + return HttpResponseRedirect('/StaticAnalyzer_iOS/?name='+APP_NAME+'&type=ios&checksum='+MD5) + CERTZ = GetHardcodedCertKeystore(FILES) print "[INFO] ZIP Type - " + pro_type if Valid and (pro_type=='eclipse' or pro_type=='studio'): #ANALYSIS BEGINS @@ -800,9 +791,6 @@ def StaticAnalyzer(request): 'e_bro': EXPORTED_CNT["bro"], 'e_cnt': EXPORTED_CNT["cnt"], } - elif Valid and pro_type=='ios': - print "[INFO] Redirecting to iOS Source Code Analyzer" - return HttpResponseRedirect('/StaticAnalyzer_iOS/?name='+APP_NAME+'&type=ios&checksum='+MD5) else: return HttpResponseRedirect('/ZIP_FORMAT/') template="static_analysis_android_zip.html" @@ -849,7 +837,10 @@ def ReadManifest(APP_DIR,TOOLS_DIR,TYP,BIN): print "[INFO] Getting Manifest from Binary" print "[INFO] AXML -> XML" manifest=os.path.join(APP_DIR,"AndroidManifest.xml") - CP_PATH=TOOLS_DIR + 'AXMLPrinter2.jar' + if len(settings.AXMLPRINTER_BINARY) > 0 and isFileExists(settings.AXMLPRINTER_BINARY): + CP_PATH = settings.AXMLPRINTER_BINARY + else: + CP_PATH = os.path.join(TOOLS_DIR,'AXMLPrinter2.jar') args=[settings.JAVA_PATH+'java','-jar', CP_PATH, manifest] dat=subprocess.check_output(args) else: @@ -1061,10 +1052,15 @@ def Dex2Jar(APP_PATH,APP_DIR,TOOLS_DIR): D2J=os.path.join(TOOLS_DIR,'d2j2/d2j-dex2jar.sh') subprocess.call(["chmod", "777", D2J]) subprocess.call(["chmod", "777", INV]) + if len(settings.DEX2JAR_BINARY) > 0 and isFileExists(settings.DEX2JAR_BINARY): + D2J = settings.DEX2JAR_BINARY args=[D2J,APP_DIR+'classes.dex','-f','-o',APP_DIR +'classes.jar'] elif settings.JAR_CONVERTER == "enjarify": print "[INFO] Using JAR converter - Google enjarify" - WD=os.path.join(TOOLS_DIR,'enjarify/') + if len(settings.ENJARIFY_DIRECTORY) > 0 and isDirExists(settings.ENJARIFY_DIRECTORY): + WD = settings.ENJARIFY_DIRECTORY + else: + WD = os.path.join(TOOLS_DIR,'enjarify/') if platform.system()=="Windows": WinFixPython3(TOOLS_DIR) EJ=os.path.join(WD,'enjarify.bat') @@ -1087,9 +1083,12 @@ def Dex2Smali(APP_DIR,TOOLS_DIR): try: print "[INFO] DEX -> SMALI" DEX_PATH=APP_DIR+'classes.dex' - BS_PATH=TOOLS_DIR+ 'baksmali.jar' - OUTPUT=os.path.join(APP_DIR,'smali_source/') - args=[settings.JAVA_PATH+'java','-jar',BS_PATH,DEX_PATH,'-o',OUTPUT] + if len(settings.BACKSMALI_BINARY) > 0 and isFileExists(settings.BACKSMALI_BINARY): + BS_PATH = settings.BACKSMALI_BINARY + else: + BS_PATH = os.path.join(TOOLS_DIR,'baksmali.jar') + OUTPUT = os.path.join(APP_DIR,'smali_source/') + args = [settings.JAVA_PATH+'java','-jar',BS_PATH,DEX_PATH,'-o',OUTPUT] subprocess.call(args) except: PrintException("[ERROR] Converting DEX to SMALI") @@ -1100,11 +1099,23 @@ def Jar2Java(APP_DIR,TOOLS_DIR): JAR_PATH=APP_DIR + 'classes.jar' OUTPUT=os.path.join(APP_DIR, 'java_source/') if settings.DECOMPILER=='jd-core': - JD_PATH=TOOLS_DIR + 'jd-core.jar' + if len(settings.JD_CORE_DECOMPILER_BINARY) > 0 and isFileExists(settings.JD_CORE_DECOMPILER_BINARY): + JD_PATH = settings.JD_CORE_DECOMPILER_BINARY + else: + JD_PATH = os.path.join(TOOLS_DIR, 'jd-core.jar') args=[settings.JAVA_PATH+'java','-jar', JD_PATH, JAR_PATH,OUTPUT] elif settings.DECOMPILER=='cfr': - JD_PATH=TOOLS_DIR + 'cfr_0_115.jar' + if len(settings.CFR_DECOMPILER_BINARY) > 0 and isFileExists(settings.CFR_DECOMPILER_BINARY): + JD_PATH = settings.CFR_DECOMPILER_BINARY + else: + JD_PATH = os.path.join(TOOLS_DIR, 'cfr_0_115.jar') args=[settings.JAVA_PATH+'java','-jar', JD_PATH,JAR_PATH,'--outputdir',OUTPUT] + elif settings.DECOMPILER=="procyon": + if len(settings.PROCYON_DECOMPILER_BINARY) > 0 and isFileExists(settings.PROCYON_DECOMPILER_BINARY): + PD_PATH = settings.PROCYON_DECOMPILER_BINARY + else: + PD_PATH = os.path.join(TOOLS_DIR, 'procyon-decompiler-0.5.30.jar') + args=[settings.JAVA_PATH+'java','-jar',PD_PATH,JAR_PATH,'-o',OUTPUT] subprocess.call(args) except: PrintException("[ERROR] Converting JAR to JAVA") @@ -1221,10 +1232,29 @@ def ManifestAnalysis(mfxml,mainact): intents = mfxml.getElementsByTagName("intent-filter") actions = mfxml.getElementsByTagName("action") granturipermissions = mfxml.getElementsByTagName("grant-uri-permission") + permissions = mfxml.getElementsByTagName("permission") for node in manifest: package = node.getAttribute("package") RET='' EXPORTED=[] + PERMISSION_DICT = dict() + ##PERMISSION + for permission in permissions: + if permission.getAttribute("android:protectionLevel"): + protectionlevel = permission.getAttribute("android:protectionLevel") + if protectionlevel == "0x00000000": + protectionlevel = "normal" + elif protectionlevel == "0x00000001": + protectionlevel = "dangerous" + elif protectionlevel == "0x00000002": + protectionlevel = "signature" + elif protectionlevel == "0x00000003": + protectionlevel = "signatureOrSystem" + + PERMISSION_DICT[permission.getAttribute("android:name")] = protectionlevel + elif permission.getAttribute("android:name"): + PERMISSION_DICT[permission.getAttribute("android:name")] = "normal" + ##APPLICATIONS for application in applications: @@ -1278,11 +1308,14 @@ def ManifestAnalysis(mfxml,mainact): item=node.getAttribute("android:name") if node.getAttribute("android:permission"): #permission exists - perm = 'PERMISSION: '+node.getAttribute("android:permission") + perm = 'Permission: '+node.getAttribute("android:permission") isPermExist = True if item!=mainact: if isPermExist: - RET=RET +''+itmname+' (' + item + ') is Protected.
'+perm+'
[android:exported=true]info A'+ad+' '+itmname+' is found to be exported, but is protected by permission.' + prot = "" + if node.getAttribute("android:permission") in PERMISSION_DICT: + prot = "
protectionLevel: " + PERMISSION_DICT[node.getAttribute("android:permission")] + RET=RET +''+itmname+' (' + item + ') is Protected by a permission.
'+perm+prot+'
[android:exported=true]info A'+ad+' '+itmname+' is found to be exported, but is protected by permission.' else: if (itmname =='Activity' or itmname=='Activity-Alias'): EXPORTED.append(item) @@ -1300,11 +1333,14 @@ def ManifestAnalysis(mfxml,mainact): item=node.getAttribute("android:name") if node.getAttribute("android:permission"): #permission exists - perm = 'PERMISSION: '+node.getAttribute("android:permission") + perm = 'Permission: '+node.getAttribute("android:permission") isPermExist = True if item!=mainact: if isPermExist: - RET=RET +''+itmname+' (' + item + ') is Protected.
'+perm+'
[android:exported=true]info A'+ad+' '+itmname+' is found to be exported, but is protected by permission.' + prot = "" + if node.getAttribute("android:permission") in PERMISSION_DICT: + prot = "
protectionLevel: " + PERMISSION_DICT[node.getAttribute("android:permission")] + RET=RET +''+itmname+' (' + item + ') is Protected by a permission.
'+perm+prot+'
[android:exported=true]info A'+ad+' '+itmname+' is found to be exported, but is protected by permission.' else: if (itmname =='Activity' or itmname=='Activity-Alias'): EXPORTED.append(item) @@ -1646,7 +1682,7 @@ def CodeAnalysis(APP_DIR,MD5,PERMS,TYP): #Security Code Review Description dg={'d_sensitive' : "Files may contain hardcoded sensitive informations like usernames, passwords, keys etc.", - 'd_ssl': 'Insecure Implementation of SSL. Trusting all the certificates or accepting self signed certificates is a critical Security Hole.', + 'd_ssl': 'Insecure Implementation of SSL. Trusting all the certificates or accepting self signed certificates is a critical Security Hole. This application is vulnerable to MITM attacks', 'd_sqlite': 'App uses SQLite Database and execute raw SQL query. Untrusted user input in raw SQL queries can cause SQL Injection. Also sensitive information should be encrypted and written to the database.', 'd_con_world_readable':'The file is World Readable. Any App can read from the file', 'd_con_world_writable':'The file is World Writable. Any App can write to the file', @@ -1655,7 +1691,7 @@ def CodeAnalysis(APP_DIR,MD5,PERMS,TYP): 'd_extstorage': 'App can read/write to External Storage. Any App can read data written to External Storage.', 'd_tmpfile': 'App creates temp file. Sensitive information should never be written into a temp file.', 'd_jsenabled':'Insecure WebView Implementation. Execution of user controlled code in WebView is a critical Security Hole.', - 'd_webviewdisablessl':'Insecure WebView Implementation. WebView ignores SSL Certificate Errors.', + 'd_webviewdisablessl':'Insecure WebView Implementation. WebView ignores SSL Certificate errors and accept any SSL Certificate. This application is vulnerable to MITM attacks', 'd_webviewdebug':'Remote WebView debugging is enabled.', 'dex_debug': 'DexGuard Debug Detection code to detect wheather an App is debuggable or not is identified.', 'dex_debug_con':'DexGuard Debugger Detection code is identified.', @@ -2095,19 +2131,23 @@ def BinaryAnalysis(SRC,TOOLS_DIR,APP_DIR): print "[INFO] Running otool against the Binary" #Libs Used LIBS='' - args=['otool','-L',BIN_PATH] + if len(settings.OTOOL_BINARY) > 0 and isFileExists(OTOOL_BINARY): + OTOOL = settings.OTOOL_BINARY + else: + OTOOL = "otool" + args=[OTOOL,'-L',BIN_PATH] dat=subprocess.check_output(args) dat=escape(dat.replace(BIN_DIR + "/","")) LIBS=dat.replace("\n","
") #PIE - args=['otool','-hv',BIN_PATH] + args=[OTOOL,'-hv',BIN_PATH] dat=subprocess.check_output(args) if "PIE" in dat: PIE= "fPIE -pie flag is FoundSecureApp is compiled with Position Independent Executable (PIE) flag. This enables Address Space Layout Randomization (ASLR), a memory protection mechanism for exploit mitigation." else: PIE="fPIE -pie flag is not FoundInsecureApp is not compiled with Position Independent Executable (PIE) flag. So Address Space Layout Randomization (ASLR) is missing. ASLR is a memory protection mechanism for exploit mitigation." #Stack Smashing Protection & ARC - args=['otool','-Iv',BIN_PATH] + args=[OTOOL,'-Iv',BIN_PATH] dat=subprocess.check_output(args) if "stack_chk_guard" in dat: SSMASH="fstack-protector-all flag is FoundSecureApp is compiled with Stack Smashing Protector (SSP) flag and is having protection against Stack Overflows/Stack Smashing Attacks." @@ -2172,14 +2212,15 @@ def BinaryAnalysis(SRC,TOOLS_DIR,APP_DIR): x=list(set(x)) x=', '.join(x) if len(x)>1: - DBG="Binary calls ptrace Function for anti-debugging.SecureThe binary may use ptrace function. It is used to detect and prevent debuggers." - else: - DBG="Binary does not call ptrace Function for anti-debugging.WarningThe binary does not use ptrace function. It is used to detect and prevent debuggers." + DBG="Binary calls ptrace Function for anti-debugging.warningThe binary may use ptrace function. It can be used to detect and prevent debuggers. Ptrace is not a public API and Apps that use non-public APIs will be rejected from AppStore. " CDUMP='' WVIEW='' try: print "[INFO] Running class-dump-z against the Binary" - CLASSDUMPZ_BIN=os.path.join(TOOLS_DIR,'class-dump-z') + if len(settings.CLASSDUMPZ_BINARY) > 0 and isFileExists(settings.CLASSDUMPZ_BINARY): + CLASSDUMPZ_BIN = settings.CLASSDUMPZ_BINARY + else: + CLASSDUMPZ_BIN = os.path.join(TOOLS_DIR,'class-dump-z') subprocess.call(["chmod", "777", CLASSDUMPZ_BIN]) dat=subprocess.check_output([CLASSDUMPZ_BIN,BIN_PATH]) CDUMP=dat @@ -2237,7 +2278,7 @@ def iOS_Source_Analysis(SRC,MD5): #Code Analysis EmailnFile='' URLnFile='' - c = {key: [] for key in ('i_buf','webv','i_log','net','i_sqlite','fileio')} + c = {key: [] for key in ('i_buf','webv','i_log','net','i_sqlite','fileio','ssl_bypass','ssl_uiwebview','path_traversal')} for dirName, subDir, files in os.walk(SRC): for jfile in files: if jfile.endswith(".m"): @@ -2262,13 +2303,20 @@ def iOS_Source_Analysis(SRC,MD5): c['fileio'].append(jfile_path.replace(SRC,'')) if (re.findall("WebView|UIWebView",dat)): c['webv'].append(jfile_path.replace(SRC,'')) - #CODE-ISSUES + + #SECURITY ANALYSIS if (re.findall("strcpy|memcpy|strcat|strncat|strncpy|sprintf|vsprintf|gets",dat)): c['i_buf'].append(jfile_path.replace(SRC,'')) if (re.findall("NSLog",dat)): c['i_log'].append(jfile_path.replace(SRC,'')) if (re.findall("sqlite3_exec",dat)): c['i_sqlite'].append(jfile_path.replace(SRC,'')) + if re.findall('canAuthenticateAgainstProtectionSpace|continueWithoutCredentialForAuthenticationChallenge|kCFStreamSSLAllowsExpiredCertificates|kCFStreamSSLAllowsAnyRoot|kCFStreamSSLAllowsExpiredRoots|allowInvalidCertificates\s*=\s*(YES|yes)',dat): + c['ssl_bypass'].append(jfile_path.replace(SRC,'')) + if re.findall('setAllowsAnyHTTPSCertificate:YES|allowsAnyHTTPSCertificateForHost|loadingUnvalidatedHTTPSPage\s*=\s*(YES|yes)',dat): + c['ssl_uiwebview'].append(jfile_path.replace(SRC,'')) + if "NSTemporaryDirectory()," in dat: + c['path_traversal'].append(jfile_path.replace(SRC,'')) fl=jfile_path.replace(SRC,'') base_fl=ntpath.basename(fl) @@ -2312,19 +2360,24 @@ def iOS_Source_Analysis(SRC,MD5): dg={'i_buf' : 'The App may contain banned API(s). These API(s) are insecure and must not be used.', 'i_log' : 'The App logs information. Sensitive information should never be logged.', 'i_sqlite' : 'App uses SQLite Database. Sensitive Information should be encrypted.', + 'ssl_bypass' : 'App allows self signed or invalid SSL certificates. App is vulnerable to MITM attacks.', + 'ssl_uiwebview' : 'UIWebView in App ignore SSL errors and accept any SSL Certificate. App is vulnerable to MITM attacks.', + 'path_traversal' : 'Untrusted user input to "NSTemporaryDirectory()"" will result in path traversal vulnerability.', } dang='' spn_dang='high' spn_info='info' spn_sec='secure' + spn_warn='warning' for k in dg: if c[k]: link='' if (re.findall('i_sqlite',k)): hd=''+dg[k]+''+spn_info+'' + elif (re.findall('path_traversal',k)): + hd=''+dg[k]+''+spn_warn+'' else: hd=''+dg[k]+''+spn_dang+'' - for ll in c[k]: link+=""+escape(ntpath.basename(ll))+" " diff --git a/clean.sh b/clean.sh index ed6a0a3d09..1fe37ea9bc 100755 --- a/clean.sh +++ b/clean.sh @@ -5,29 +5,16 @@ read -p "Are you sure? " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]] then - echo "Deleting all PYC" - find . -name "*.pyc" -exec rm -rf {} \; - echo "Deleting all .DS_Store" - find . -name ".DS_Store" -exec rm -rf {} \; echo "Deleting all Uploads" rm -rf uploads/* echo "Deleting all Downloads" - rm -rf static/downloads/* - echo "Deleting Screen Cache" - rm -rf static/screen/screen.png + rm -rf downloads/* echo "Deleting temp and log files" rm -rf logs/* rm -rf classes-error.zip echo "Deleting DB" rm -rf db.sqlite3 - echo "Migrating DB changes" - python manage.py migrate - echo "Deleting App Secret File" - rm -rf MobSF/secret - echo "Creating certs directory" - mkdir logs/certs - echo "Creating Placeholders" - echo > logs/certs/PLACEHOLDER - echo > uploads/PLACEHOLDER - echo > static/downloads/PLACEHOLDER + echo "Deleting Secret File" + rm -rf secret + echo "Done" fi \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index 5cc9b57f42..0000000000 Binary files a/db.sqlite3 and /dev/null differ diff --git a/logs/certs/PLACEHOLDER b/logs/certs/PLACEHOLDER deleted file mode 100644 index 8b13789179..0000000000 --- a/logs/certs/PLACEHOLDER +++ /dev/null @@ -1 +0,0 @@ - diff --git a/mass_static_analysis.py b/mass_static_analysis.py new file mode 100644 index 0000000000..9fa333d138 --- /dev/null +++ b/mass_static_analysis.py @@ -0,0 +1,140 @@ +#Mass Static Analysis +import tornado.httpclient, subprocess, os, urllib2, argparse, mimetypes, urlparse, re, json, hashlib, urllib +from threading import Thread + +def HTTP_GET_Request(url): + response = None + http_client = tornado.httpclient.HTTPClient() + try: + response = http_client.fetch(url) + except tornado.httpclient.HTTPError as e: + pass + except Exception as e: + print ("[ERROR] HTTP GET Request Error: "+ str(e)) + http_client.close() + return response + + +def isServerUp(url): + try: + response=urllib2.urlopen(url,timeout=5) + return True + except urllib2.URLError as err: pass + return False + +def getCSRF(url): + resp = HTTP_GET_Request(url) + return resp.headers['Set-Cookie'].split(";")[0].split("=")[1] + + +def encode_multipart_formdata(fields, files): + """ + fields is a sequence of (name, value) elements for regular form fields. + files is a sequence of (name, filename, value) elements for data to be uploaded as files + Return (content_type, body) ready for httplib.HTTP instance + """ + BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' + CRLF = '\r\n' + L = [] + for (key, value) in fields: + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"' % key) + L.append('') + L.append(value) + for (key, filename, value) in files: + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) + L.append('Content-Type: %s' % get_content_type(filename)) + L.append('') + L.append(value) + L.append('--' + BOUNDARY + '--') + L.append('') + body = CRLF.join(L) + content_type = 'multipart/form-data; boundary=%s' % BOUNDARY + return content_type, body + +def get_content_type(filename): + return mimetypes.guess_type(filename)[0] or 'application/octet-stream' + +def genMD5(app): + + BLOCKSIZE = 65536 + hasher = hashlib.md5() + with open(app, 'rb') as afile: + buf = afile.read(BLOCKSIZE) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(BLOCKSIZE) + return (hasher.hexdigest()) + +def doScan(app, server_url): + print "\nUploading : " + app + UPLOAD_URL = server_url + "/Upload/" + CSRF = getCSRF(server_url) + APP_NAME = os.path.basename(app) + + fields = [("csrfmiddlewaretoken", CSRF)] + files = [("file", APP_NAME, open(app, "rb").read())] + + http_client = tornado.httpclient.HTTPClient() + content_type, body = encode_multipart_formdata(fields, files) + headers = {"Content-Type": content_type, 'content-length': str(len(body)), 'Cookie': 'csrftoken='+CSRF} + request = tornado.httpclient.HTTPRequest(UPLOAD_URL, "POST", headers=headers, body=body, validate_cert=False) + response = http_client.fetch(request) + if response.code == 200: + r = json.loads(response.body) + if r["status"] == "success": + MD5 = genMD5(app) + SCAN_DB[MD5] = APP_NAME + #Start Scan + START_SCAN_URL = server_url + "/" + r["url"].replace(APP_NAME,urllib.quote(APP_NAME)) + SCAN_URLS.append(START_SCAN_URL) + elif r["description"]: + print r["description"] + return SCAN_DB, SCAN_URLS + +def startScan(directory,server_url): + SCAN_URLS = [] + SCAN_DB = {} + print "\nLooking for Android/iOS binaries or source code in : " + directory + for root, directories, filenames in os.walk(directory): + for filename in filenames: + scan_file = os.path.join(root,filename) + abs_filename, file_extension = os.path.splitext(scan_file) + if re.findall("apk|ipa|zip",file_extension): + SCAN_DB, SCAN_URLS = doScan(scan_file,server_url) + if len(SCAN_URLS) > 0: + print "\nFiles Uploaded " + print "======================================================================" + print "MD5 | App " + print "======================================================================" + for key, val in SCAN_DB.items(): + print key + " | " + val + print "\nInvoking Scan Request. This takes time depending on the number of apps to be scanned." + for url in SCAN_URLS: + t = Thread(target=HTTP_GET_Request, args=(url,)) + t.start() + print "Please wait while MobSF is performing Static Analysis. Once the scan is completed, you can get the report by searching for the MD5 checksum" + print "Exiting the Script..." + +parser = argparse.ArgumentParser() +parser.add_argument("-d", "--directory", help="Path to the directory that contains mobile app binary/zipped source code") +parser.add_argument("-s", "--ipport", help="IP address and Port number of a running MobSF Server. (ex: 127.0.0.1:8000)") + +args = parser.parse_args() + +SCAN_DB = dict() +SCAN_URLS = list() + +if args.directory and args.ipport: + SERVER = args.ipport + DIRECTORY = args.directory + SERVER_URL = "http://"+SERVER + if isServerUp(SERVER_URL) == False: + print "MobSF Server is not running at " + SERVER_URL + print "Exiting....." + exit(0) + #MobSF is running, start scan + startScan(DIRECTORY,SERVER_URL) +else: + parser.print_help() \ No newline at end of file diff --git a/static/css/dropzone.css b/static/css/dropzone.css new file mode 100644 index 0000000000..0494d1ccf4 --- /dev/null +++ b/static/css/dropzone.css @@ -0,0 +1,388 @@ +/* + * The MIT License + * Copyright (c) 2012 Matias Meno + */ +@-webkit-keyframes passing-through { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); } + 30%, 70% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); } + 100% { + opacity: 0; + -webkit-transform: translateY(-40px); + -moz-transform: translateY(-40px); + -ms-transform: translateY(-40px); + -o-transform: translateY(-40px); + transform: translateY(-40px); } } +@-moz-keyframes passing-through { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); } + 30%, 70% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); } + 100% { + opacity: 0; + -webkit-transform: translateY(-40px); + -moz-transform: translateY(-40px); + -ms-transform: translateY(-40px); + -o-transform: translateY(-40px); + transform: translateY(-40px); } } +@keyframes passing-through { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); } + 30%, 70% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); } + 100% { + opacity: 0; + -webkit-transform: translateY(-40px); + -moz-transform: translateY(-40px); + -ms-transform: translateY(-40px); + -o-transform: translateY(-40px); + transform: translateY(-40px); } } +@-webkit-keyframes slide-in { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); } + 30% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); } } +@-moz-keyframes slide-in { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); } + 30% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); } } +@keyframes slide-in { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); } + 30% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); } } +@-webkit-keyframes pulse { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + 10% { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } + 20% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } +@-moz-keyframes pulse { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + 10% { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } + 20% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } +@keyframes pulse { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + 10% { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } + 20% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } +.dropzone, .dropzone * { + box-sizing: border-box; } + +.dropzone { + min-height: 150px; + border: 2px solid rgba(0, 0, 0, 0.3); + background: white; + padding: 20px 20px; } + .dropzone.dz-clickable { + cursor: pointer; } + .dropzone.dz-clickable * { + cursor: default; } + .dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * { + cursor: pointer; } + .dropzone.dz-started .dz-message { + display: none; } + .dropzone.dz-drag-hover { + border-style: solid; } + .dropzone.dz-drag-hover .dz-message { + opacity: 0.5; } + .dropzone .dz-message { + text-align: center; + margin: 2em 0; } + .dropzone .dz-preview { + position: relative; + display: inline-block; + vertical-align: top; + margin: 16px; + min-height: 100px; } + .dropzone .dz-preview:hover { + z-index: 1000; } + .dropzone .dz-preview:hover .dz-details { + opacity: 1; } + .dropzone .dz-preview.dz-file-preview .dz-image { + border-radius: 20px; + background: #999; + background: linear-gradient(to bottom, #eee, #ddd); } + .dropzone .dz-preview.dz-file-preview .dz-details { + opacity: 1; } + .dropzone .dz-preview.dz-image-preview { + background: white; } + .dropzone .dz-preview.dz-image-preview .dz-details { + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; } + .dropzone .dz-preview .dz-remove { + font-size: 14px; + text-align: center; + display: block; + cursor: pointer; + border: none; } + .dropzone .dz-preview .dz-remove:hover { + text-decoration: underline; } + .dropzone .dz-preview:hover .dz-details { + opacity: 1; } + .dropzone .dz-preview .dz-details { + z-index: 20; + position: absolute; + top: 0; + left: 0; + opacity: 0; + font-size: 13px; + min-width: 100%; + max-width: 100%; + padding: 2em 1em; + text-align: center; + color: rgba(0, 0, 0, 0.9); + line-height: 150%; } + .dropzone .dz-preview .dz-details .dz-size { + margin-bottom: 1em; + font-size: 16px; } + .dropzone .dz-preview .dz-details .dz-filename { + white-space: nowrap; } + .dropzone .dz-preview .dz-details .dz-filename:hover span { + border: 1px solid rgba(200, 200, 200, 0.8); + background-color: rgba(255, 255, 255, 0.8); } + .dropzone .dz-preview .dz-details .dz-filename:not(:hover) { + overflow: hidden; + text-overflow: ellipsis; } + .dropzone .dz-preview .dz-details .dz-filename:not(:hover) span { + border: 1px solid transparent; } + .dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span { + background-color: rgba(255, 255, 255, 0.4); + padding: 0 0.4em; + border-radius: 3px; } + .dropzone .dz-preview:hover .dz-image img { + -webkit-transform: scale(1.05, 1.05); + -moz-transform: scale(1.05, 1.05); + -ms-transform: scale(1.05, 1.05); + -o-transform: scale(1.05, 1.05); + transform: scale(1.05, 1.05); + -webkit-filter: blur(8px); + filter: blur(8px); } + .dropzone .dz-preview .dz-image { + border-radius: 20px; + overflow: hidden; + width: 120px; + height: 120px; + position: relative; + display: block; + z-index: 10; } + .dropzone .dz-preview .dz-image img { + display: block; } + .dropzone .dz-preview.dz-success .dz-success-mark { + -webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + -moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + -ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + -o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); } + .dropzone .dz-preview.dz-error .dz-error-mark { + opacity: 1; + -webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + -moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + -ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + -o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); } + .dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark { + pointer-events: none; + opacity: 0; + z-index: 500; + position: absolute; + display: block; + top: 50%; + left: 50%; + margin-left: -27px; + margin-top: -27px; } + .dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg { + display: block; + width: 54px; + height: 54px; } + .dropzone .dz-preview.dz-processing .dz-progress { + opacity: 1; + -webkit-transition: all 0.2s linear; + -moz-transition: all 0.2s linear; + -ms-transition: all 0.2s linear; + -o-transition: all 0.2s linear; + transition: all 0.2s linear; } + .dropzone .dz-preview.dz-complete .dz-progress { + opacity: 0; + -webkit-transition: opacity 0.4s ease-in; + -moz-transition: opacity 0.4s ease-in; + -ms-transition: opacity 0.4s ease-in; + -o-transition: opacity 0.4s ease-in; + transition: opacity 0.4s ease-in; } + .dropzone .dz-preview:not(.dz-processing) .dz-progress { + -webkit-animation: pulse 6s ease infinite; + -moz-animation: pulse 6s ease infinite; + -ms-animation: pulse 6s ease infinite; + -o-animation: pulse 6s ease infinite; + animation: pulse 6s ease infinite; } + .dropzone .dz-preview .dz-progress { + opacity: 1; + z-index: 1000; + pointer-events: none; + position: absolute; + height: 16px; + left: 50%; + top: 50%; + margin-top: -8px; + width: 80px; + margin-left: -40px; + background: rgba(255, 255, 255, 0.9); + -webkit-transform: scale(1); + border-radius: 8px; + overflow: hidden; } + .dropzone .dz-preview .dz-progress .dz-upload { + background: #333; + background: linear-gradient(to bottom, #666, #444); + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 0; + -webkit-transition: width 300ms ease-in-out; + -moz-transition: width 300ms ease-in-out; + -ms-transition: width 300ms ease-in-out; + -o-transition: width 300ms ease-in-out; + transition: width 300ms ease-in-out; } + .dropzone .dz-preview.dz-error .dz-error-message { + display: block; } + .dropzone .dz-preview.dz-error:hover .dz-error-message { + opacity: 1; + pointer-events: auto; } + .dropzone .dz-preview .dz-error-message { + pointer-events: none; + z-index: 1000; + position: absolute; + display: block; + display: none; + opacity: 0; + -webkit-transition: opacity 0.3s ease; + -moz-transition: opacity 0.3s ease; + -ms-transition: opacity 0.3s ease; + -o-transition: opacity 0.3s ease; + transition: opacity 0.3s ease; + border-radius: 8px; + font-size: 13px; + top: 130px; + left: -10px; + width: 140px; + background: #be2626; + background: linear-gradient(to bottom, #be2626, #a92222); + padding: 0.5em 1.2em; + color: white; } + .dropzone .dz-preview .dz-error-message:after { + content: ''; + position: absolute; + top: -6px; + left: 64px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #be2626; } diff --git a/static/downloads/PLACEHOLDER b/static/downloads/PLACEHOLDER deleted file mode 100644 index 8b13789179..0000000000 --- a/static/downloads/PLACEHOLDER +++ /dev/null @@ -1 +0,0 @@ - diff --git a/static/favicon.ico b/static/favicon.ico index 7573b4a1dd..f72a51073c 100644 Binary files a/static/favicon.ico and b/static/favicon.ico differ diff --git a/static/img/MobSF_Logo_small.png b/static/img/MobSF_Logo_small.png new file mode 100644 index 0000000000..e782868fda Binary files /dev/null and b/static/img/MobSF_Logo_small.png differ diff --git a/static/screen/loading.jpg b/static/img/loading.jpg similarity index 100% rename from static/screen/loading.jpg rename to static/img/loading.jpg diff --git a/static/img/logo-head.png b/static/img/logo-head.png deleted file mode 100644 index 7892f7ee16..0000000000 Binary files a/static/img/logo-head.png and /dev/null differ diff --git a/static/js/dropzone.js b/static/js/dropzone.js new file mode 100644 index 0000000000..895b055a8f --- /dev/null +++ b/static/js/dropzone.js @@ -0,0 +1,1767 @@ + +/* + * + * More info at [www.dropzonejs.com](http://www.dropzonejs.com) + * + * Copyright (c) 2012, Matias Meno + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +(function() { + var Dropzone, Emitter, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without, + __slice = [].slice, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + noop = function() {}; + + Emitter = (function() { + function Emitter() {} + + Emitter.prototype.addEventListener = Emitter.prototype.on; + + Emitter.prototype.on = function(event, fn) { + this._callbacks = this._callbacks || {}; + if (!this._callbacks[event]) { + this._callbacks[event] = []; + } + this._callbacks[event].push(fn); + return this; + }; + + Emitter.prototype.emit = function() { + var args, callback, callbacks, event, _i, _len; + event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + this._callbacks = this._callbacks || {}; + callbacks = this._callbacks[event]; + if (callbacks) { + for (_i = 0, _len = callbacks.length; _i < _len; _i++) { + callback = callbacks[_i]; + callback.apply(this, args); + } + } + return this; + }; + + Emitter.prototype.removeListener = Emitter.prototype.off; + + Emitter.prototype.removeAllListeners = Emitter.prototype.off; + + Emitter.prototype.removeEventListener = Emitter.prototype.off; + + Emitter.prototype.off = function(event, fn) { + var callback, callbacks, i, _i, _len; + if (!this._callbacks || arguments.length === 0) { + this._callbacks = {}; + return this; + } + callbacks = this._callbacks[event]; + if (!callbacks) { + return this; + } + if (arguments.length === 1) { + delete this._callbacks[event]; + return this; + } + for (i = _i = 0, _len = callbacks.length; _i < _len; i = ++_i) { + callback = callbacks[i]; + if (callback === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + return Emitter; + + })(); + + Dropzone = (function(_super) { + var extend, resolveOption; + + __extends(Dropzone, _super); + + Dropzone.prototype.Emitter = Emitter; + + + /* + This is a list of all available events you can register on a dropzone object. + + You can register an event handler like this: + + dropzone.on("dragEnter", function() { }); + */ + + Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete"]; + + Dropzone.prototype.defaultOptions = { + url: null, + method: "post", + withCredentials: false, + parallelUploads: 2, + uploadMultiple: false, + maxFilesize: 256, + paramName: "file", + createImageThumbnails: true, + maxThumbnailFilesize: 10, + thumbnailWidth: 120, + thumbnailHeight: 120, + filesizeBase: 1000, + maxFiles: null, + params: {}, + clickable: true, + ignoreHiddenFiles: true, + acceptedFiles: null, + acceptedMimeTypes: null, + autoProcessQueue: true, + autoQueue: true, + addRemoveLinks: false, + previewsContainer: null, + hiddenInputContainer: "body", + capture: null, + renameFilename: null, + dictDefaultMessage: "Drop files here to upload", + dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", + dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.", + dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.", + dictInvalidFileType: "You can't upload files of this type.", + dictResponseError: "Server responded with {{statusCode}} code.", + dictCancelUpload: "Cancel upload", + dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", + dictRemoveFile: "Remove file", + dictRemoveFileConfirmation: null, + dictMaxFilesExceeded: "You can not upload any more files.", + accept: function(file, done) { + return done(); + }, + init: function() { + return noop; + }, + forceFallback: false, + fallback: function() { + var child, messageElement, span, _i, _len, _ref; + this.element.className = "" + this.element.className + " dz-browser-not-supported"; + _ref = this.element.getElementsByTagName("div"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + if (/(^| )dz-message($| )/.test(child.className)) { + messageElement = child; + child.className = "dz-message"; + continue; + } + } + if (!messageElement) { + messageElement = Dropzone.createElement("
"); + this.element.appendChild(messageElement); + } + span = messageElement.getElementsByTagName("span")[0]; + if (span) { + if (span.textContent != null) { + span.textContent = this.options.dictFallbackMessage; + } else if (span.innerText != null) { + span.innerText = this.options.dictFallbackMessage; + } + } + return this.element.appendChild(this.getFallbackForm()); + }, + resize: function(file) { + var info, srcRatio, trgRatio; + info = { + srcX: 0, + srcY: 0, + srcWidth: file.width, + srcHeight: file.height + }; + srcRatio = file.width / file.height; + info.optWidth = this.options.thumbnailWidth; + info.optHeight = this.options.thumbnailHeight; + if ((info.optWidth == null) && (info.optHeight == null)) { + info.optWidth = info.srcWidth; + info.optHeight = info.srcHeight; + } else if (info.optWidth == null) { + info.optWidth = srcRatio * info.optHeight; + } else if (info.optHeight == null) { + info.optHeight = (1 / srcRatio) * info.optWidth; + } + trgRatio = info.optWidth / info.optHeight; + if (file.height < info.optHeight || file.width < info.optWidth) { + info.trgHeight = info.srcHeight; + info.trgWidth = info.srcWidth; + } else { + if (srcRatio > trgRatio) { + info.srcHeight = file.height; + info.srcWidth = info.srcHeight * trgRatio; + } else { + info.srcWidth = file.width; + info.srcHeight = info.srcWidth / trgRatio; + } + } + info.srcX = (file.width - info.srcWidth) / 2; + info.srcY = (file.height - info.srcHeight) / 2; + return info; + }, + + /* + Those functions register themselves to the events on init and handle all + the user interface specific stuff. Overwriting them won't break the upload + but can break the way it's displayed. + You can overwrite them if you don't like the default behavior. If you just + want to add an additional event handler, register it on the dropzone object + and don't overwrite those options. + */ + drop: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragstart: noop, + dragend: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragenter: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragover: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragleave: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + paste: noop, + reset: function() { + return this.element.classList.remove("dz-started"); + }, + addedfile: function(file) { + var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; + if (this.element === this.previewsContainer) { + this.element.classList.add("dz-started"); + } + if (this.previewsContainer) { + file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); + file.previewTemplate = file.previewElement; + this.previewsContainer.appendChild(file.previewElement); + _ref = file.previewElement.querySelectorAll("[data-dz-name]"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + node.textContent = this._renameFilename(file.name); + } + _ref1 = file.previewElement.querySelectorAll("[data-dz-size]"); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + node = _ref1[_j]; + node.innerHTML = this.filesize(file.size); + } + if (this.options.addRemoveLinks) { + file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); + file.previewElement.appendChild(file._removeLink); + } + removeFileEvent = (function(_this) { + return function(e) { + e.preventDefault(); + e.stopPropagation(); + if (file.status === Dropzone.UPLOADING) { + return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() { + return _this.removeFile(file); + }); + } else { + if (_this.options.dictRemoveFileConfirmation) { + return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() { + return _this.removeFile(file); + }); + } else { + return _this.removeFile(file); + } + } + }; + })(this); + _ref2 = file.previewElement.querySelectorAll("[data-dz-remove]"); + _results = []; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + removeLink = _ref2[_k]; + _results.push(removeLink.addEventListener("click", removeFileEvent)); + } + return _results; + } + }, + removedfile: function(file) { + var _ref; + if (file.previewElement) { + if ((_ref = file.previewElement) != null) { + _ref.parentNode.removeChild(file.previewElement); + } + } + return this._updateMaxFilesReachedClass(); + }, + thumbnail: function(file, dataUrl) { + var thumbnailElement, _i, _len, _ref; + if (file.previewElement) { + file.previewElement.classList.remove("dz-file-preview"); + _ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + thumbnailElement = _ref[_i]; + thumbnailElement.alt = file.name; + thumbnailElement.src = dataUrl; + } + return setTimeout(((function(_this) { + return function() { + return file.previewElement.classList.add("dz-image-preview"); + }; + })(this)), 1); + } + }, + error: function(file, message) { + var node, _i, _len, _ref, _results; + if (file.previewElement) { + file.previewElement.classList.add("dz-error"); + if (typeof message !== "String" && message.error) { + message = message.error; + } + _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + _results.push(node.textContent = message); + } + return _results; + } + }, + errormultiple: noop, + processing: function(file) { + if (file.previewElement) { + file.previewElement.classList.add("dz-processing"); + if (file._removeLink) { + return file._removeLink.textContent = this.options.dictCancelUpload; + } + } + }, + processingmultiple: noop, + uploadprogress: function(file, progress, bytesSent) { + var node, _i, _len, _ref, _results; + if (file.previewElement) { + _ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + if (node.nodeName === 'PROGRESS') { + _results.push(node.value = progress); + } else { + _results.push(node.style.width = "" + progress + "%"); + } + } + return _results; + } + }, + totaluploadprogress: noop, + sending: noop, + sendingmultiple: noop, + success: function(file) { + if (file.previewElement) { + return file.previewElement.classList.add("dz-success"); + } + }, + successmultiple: noop, + canceled: function(file) { + return this.emit("error", file, "Upload canceled."); + }, + canceledmultiple: noop, + complete: function(file) { + if (file._removeLink) { + file._removeLink.textContent = this.options.dictRemoveFile; + } + if (file.previewElement) { + return file.previewElement.classList.add("dz-complete"); + } + }, + completemultiple: noop, + maxfilesexceeded: noop, + maxfilesreached: noop, + queuecomplete: noop, + addedfiles: noop, + previewTemplate: "
\n
\n
\n
\n
\n
\n
\n
\n
\n \n Check\n \n \n \n \n \n
\n
\n \n Error\n \n \n \n \n \n \n \n
\n
" + }; + + extend = function() { + var key, object, objects, target, val, _i, _len; + target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + for (_i = 0, _len = objects.length; _i < _len; _i++) { + object = objects[_i]; + for (key in object) { + val = object[key]; + target[key] = val; + } + } + return target; + }; + + function Dropzone(element, options) { + var elementOptions, fallback, _ref; + this.element = element; + this.version = Dropzone.version; + this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, ""); + this.clickableElements = []; + this.listeners = []; + this.files = []; + if (typeof this.element === "string") { + this.element = document.querySelector(this.element); + } + if (!(this.element && (this.element.nodeType != null))) { + throw new Error("Invalid dropzone element."); + } + if (this.element.dropzone) { + throw new Error("Dropzone already attached."); + } + Dropzone.instances.push(this); + this.element.dropzone = this; + elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {}; + this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); + if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { + return this.options.fallback.call(this); + } + if (this.options.url == null) { + this.options.url = this.element.getAttribute("action"); + } + if (!this.options.url) { + throw new Error("No URL provided."); + } + if (this.options.acceptedFiles && this.options.acceptedMimeTypes) { + throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); + } + if (this.options.acceptedMimeTypes) { + this.options.acceptedFiles = this.options.acceptedMimeTypes; + delete this.options.acceptedMimeTypes; + } + this.options.method = this.options.method.toUpperCase(); + if ((fallback = this.getExistingFallback()) && fallback.parentNode) { + fallback.parentNode.removeChild(fallback); + } + if (this.options.previewsContainer !== false) { + if (this.options.previewsContainer) { + this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer"); + } else { + this.previewsContainer = this.element; + } + } + if (this.options.clickable) { + if (this.options.clickable === true) { + this.clickableElements = [this.element]; + } else { + this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable"); + } + } + this.init(); + } + + Dropzone.prototype.getAcceptedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getRejectedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (!file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getFilesWithStatus = function(status) { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === status) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getQueuedFiles = function() { + return this.getFilesWithStatus(Dropzone.QUEUED); + }; + + Dropzone.prototype.getUploadingFiles = function() { + return this.getFilesWithStatus(Dropzone.UPLOADING); + }; + + Dropzone.prototype.getAddedFiles = function() { + return this.getFilesWithStatus(Dropzone.ADDED); + }; + + Dropzone.prototype.getActiveFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.init = function() { + var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1; + if (this.element.tagName === "form") { + this.element.setAttribute("enctype", "multipart/form-data"); + } + if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { + this.element.appendChild(Dropzone.createElement("
" + this.options.dictDefaultMessage + "
")); + } + if (this.clickableElements.length) { + setupHiddenFileInput = (function(_this) { + return function() { + if (_this.hiddenFileInput) { + _this.hiddenFileInput.parentNode.removeChild(_this.hiddenFileInput); + } + _this.hiddenFileInput = document.createElement("input"); + _this.hiddenFileInput.setAttribute("type", "file"); + if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) { + _this.hiddenFileInput.setAttribute("multiple", "multiple"); + } + _this.hiddenFileInput.className = "dz-hidden-input"; + if (_this.options.acceptedFiles != null) { + _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles); + } + if (_this.options.capture != null) { + _this.hiddenFileInput.setAttribute("capture", _this.options.capture); + } + _this.hiddenFileInput.style.visibility = "hidden"; + _this.hiddenFileInput.style.position = "absolute"; + _this.hiddenFileInput.style.top = "0"; + _this.hiddenFileInput.style.left = "0"; + _this.hiddenFileInput.style.height = "0"; + _this.hiddenFileInput.style.width = "0"; + document.querySelector(_this.options.hiddenInputContainer).appendChild(_this.hiddenFileInput); + return _this.hiddenFileInput.addEventListener("change", function() { + var file, files, _i, _len; + files = _this.hiddenFileInput.files; + if (files.length) { + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _this.addFile(file); + } + } + _this.emit("addedfiles", files); + return setupHiddenFileInput(); + }); + }; + })(this); + setupHiddenFileInput(); + } + this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL; + _ref1 = this.events; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + eventName = _ref1[_i]; + this.on(eventName, this.options[eventName]); + } + this.on("uploadprogress", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("removedfile", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("canceled", (function(_this) { + return function(file) { + return _this.emit("complete", file); + }; + })(this)); + this.on("complete", (function(_this) { + return function(file) { + if (_this.getAddedFiles().length === 0 && _this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) { + return setTimeout((function() { + return _this.emit("queuecomplete"); + }), 0); + } + }; + })(this)); + noPropagation = function(e) { + e.stopPropagation(); + if (e.preventDefault) { + return e.preventDefault(); + } else { + return e.returnValue = false; + } + }; + this.listeners = [ + { + element: this.element, + events: { + "dragstart": (function(_this) { + return function(e) { + return _this.emit("dragstart", e); + }; + })(this), + "dragenter": (function(_this) { + return function(e) { + noPropagation(e); + return _this.emit("dragenter", e); + }; + })(this), + "dragover": (function(_this) { + return function(e) { + var efct; + try { + efct = e.dataTransfer.effectAllowed; + } catch (_error) {} + e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy'; + noPropagation(e); + return _this.emit("dragover", e); + }; + })(this), + "dragleave": (function(_this) { + return function(e) { + return _this.emit("dragleave", e); + }; + })(this), + "drop": (function(_this) { + return function(e) { + noPropagation(e); + return _this.drop(e); + }; + })(this), + "dragend": (function(_this) { + return function(e) { + return _this.emit("dragend", e); + }; + })(this) + } + } + ]; + this.clickableElements.forEach((function(_this) { + return function(clickableElement) { + return _this.listeners.push({ + element: clickableElement, + events: { + "click": function(evt) { + if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) { + _this.hiddenFileInput.click(); + } + return true; + } + } + }); + }; + })(this)); + this.enable(); + return this.options.init.call(this); + }; + + Dropzone.prototype.destroy = function() { + var _ref; + this.disable(); + this.removeAllFiles(true); + if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) { + this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); + this.hiddenFileInput = null; + } + delete this.element.dropzone; + return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); + }; + + Dropzone.prototype.updateTotalUploadProgress = function() { + var activeFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref; + totalBytesSent = 0; + totalBytes = 0; + activeFiles = this.getActiveFiles(); + if (activeFiles.length) { + _ref = this.getActiveFiles(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + totalBytesSent += file.upload.bytesSent; + totalBytes += file.upload.total; + } + totalUploadProgress = 100 * totalBytesSent / totalBytes; + } else { + totalUploadProgress = 100; + } + return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); + }; + + Dropzone.prototype._getParamName = function(n) { + if (typeof this.options.paramName === "function") { + return this.options.paramName(n); + } else { + return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : ""); + } + }; + + Dropzone.prototype._renameFilename = function(name) { + if (typeof this.options.renameFilename !== "function") { + return name; + } + return this.options.renameFilename(name); + }; + + Dropzone.prototype.getFallbackForm = function() { + var existingFallback, fields, fieldsString, form; + if (existingFallback = this.getExistingFallback()) { + return existingFallback; + } + fieldsString = "
"; + if (this.options.dictFallbackText) { + fieldsString += "

" + this.options.dictFallbackText + "

"; + } + fieldsString += "
"; + fields = Dropzone.createElement(fieldsString); + if (this.element.tagName !== "FORM") { + form = Dropzone.createElement("
"); + form.appendChild(fields); + } else { + this.element.setAttribute("enctype", "multipart/form-data"); + this.element.setAttribute("method", this.options.method); + } + return form != null ? form : fields; + }; + + Dropzone.prototype.getExistingFallback = function() { + var fallback, getFallback, tagName, _i, _len, _ref; + getFallback = function(elements) { + var el, _i, _len; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )fallback($| )/.test(el.className)) { + return el; + } + } + }; + _ref = ["div", "form"]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + tagName = _ref[_i]; + if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { + return fallback; + } + } + }; + + Dropzone.prototype.setupEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.addEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.removeEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.removeEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.disable = function() { + var file, _i, _len, _ref, _results; + this.clickableElements.forEach(function(element) { + return element.classList.remove("dz-clickable"); + }); + this.removeEventListeners(); + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + _results.push(this.cancelUpload(file)); + } + return _results; + }; + + Dropzone.prototype.enable = function() { + this.clickableElements.forEach(function(element) { + return element.classList.add("dz-clickable"); + }); + return this.setupEventListeners(); + }; + + Dropzone.prototype.filesize = function(size) { + var cutoff, i, selectedSize, selectedUnit, unit, units, _i, _len; + selectedSize = 0; + selectedUnit = "b"; + if (size > 0) { + units = ['TB', 'GB', 'MB', 'KB', 'b']; + for (i = _i = 0, _len = units.length; _i < _len; i = ++_i) { + unit = units[i]; + cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10; + if (size >= cutoff) { + selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i); + selectedUnit = unit; + break; + } + } + selectedSize = Math.round(10 * selectedSize) / 10; + } + return "" + selectedSize + " " + selectedUnit; + }; + + Dropzone.prototype._updateMaxFilesReachedClass = function() { + if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + if (this.getAcceptedFiles().length === this.options.maxFiles) { + this.emit('maxfilesreached', this.files); + } + return this.element.classList.add("dz-max-files-reached"); + } else { + return this.element.classList.remove("dz-max-files-reached"); + } + }; + + Dropzone.prototype.drop = function(e) { + var files, items; + if (!e.dataTransfer) { + return; + } + this.emit("drop", e); + files = e.dataTransfer.files; + this.emit("addedfiles", files); + if (files.length) { + items = e.dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry != null)) { + this._addFilesFromItems(items); + } else { + this.handleFiles(files); + } + } + }; + + Dropzone.prototype.paste = function(e) { + var items, _ref; + if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) { + return; + } + this.emit("paste", e); + items = e.clipboardData.items; + if (items.length) { + return this._addFilesFromItems(items); + } + }; + + Dropzone.prototype.handleFiles = function(files) { + var file, _i, _len, _results; + _results = []; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _results.push(this.addFile(file)); + } + return _results; + }; + + Dropzone.prototype._addFilesFromItems = function(items) { + var entry, item, _i, _len, _results; + _results = []; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { + if (entry.isFile) { + _results.push(this.addFile(item.getAsFile())); + } else if (entry.isDirectory) { + _results.push(this._addFilesFromDirectory(entry, entry.name)); + } else { + _results.push(void 0); + } + } else if (item.getAsFile != null) { + if ((item.kind == null) || item.kind === "file") { + _results.push(this.addFile(item.getAsFile())); + } else { + _results.push(void 0); + } + } else { + _results.push(void 0); + } + } + return _results; + }; + + Dropzone.prototype._addFilesFromDirectory = function(directory, path) { + var dirReader, errorHandler, readEntries; + dirReader = directory.createReader(); + errorHandler = function(error) { + return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0; + }; + readEntries = (function(_this) { + return function() { + return dirReader.readEntries(function(entries) { + var entry, _i, _len; + if (entries.length > 0) { + for (_i = 0, _len = entries.length; _i < _len; _i++) { + entry = entries[_i]; + if (entry.isFile) { + entry.file(function(file) { + if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { + return; + } + file.fullPath = "" + path + "/" + file.name; + return _this.addFile(file); + }); + } else if (entry.isDirectory) { + _this._addFilesFromDirectory(entry, "" + path + "/" + entry.name); + } + } + readEntries(); + } + return null; + }, errorHandler); + }; + })(this); + return readEntries(); + }; + + Dropzone.prototype.accept = function(file, done) { + if (file.size > this.options.maxFilesize * 1024 * 1024) { + return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); + } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { + return done(this.options.dictInvalidFileType); + } else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles)); + return this.emit("maxfilesexceeded", file); + } else { + return this.options.accept.call(this, file, done); + } + }; + + Dropzone.prototype.addFile = function(file) { + file.upload = { + progress: 0, + total: file.size, + bytesSent: 0 + }; + this.files.push(file); + file.status = Dropzone.ADDED; + this.emit("addedfile", file); + this._enqueueThumbnail(file); + return this.accept(file, (function(_this) { + return function(error) { + if (error) { + file.accepted = false; + _this._errorProcessing([file], error); + } else { + file.accepted = true; + if (_this.options.autoQueue) { + _this.enqueueFile(file); + } + } + return _this._updateMaxFilesReachedClass(); + }; + })(this)); + }; + + Dropzone.prototype.enqueueFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + this.enqueueFile(file); + } + return null; + }; + + Dropzone.prototype.enqueueFile = function(file) { + if (file.status === Dropzone.ADDED && file.accepted === true) { + file.status = Dropzone.QUEUED; + if (this.options.autoProcessQueue) { + return setTimeout(((function(_this) { + return function() { + return _this.processQueue(); + }; + })(this)), 0); + } + } else { + throw new Error("This file can't be queued because it has already been processed or was rejected."); + } + }; + + Dropzone.prototype._thumbnailQueue = []; + + Dropzone.prototype._processingThumbnail = false; + + Dropzone.prototype._enqueueThumbnail = function(file) { + if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { + this._thumbnailQueue.push(file); + return setTimeout(((function(_this) { + return function() { + return _this._processThumbnailQueue(); + }; + })(this)), 0); + } + }; + + Dropzone.prototype._processThumbnailQueue = function() { + if (this._processingThumbnail || this._thumbnailQueue.length === 0) { + return; + } + this._processingThumbnail = true; + return this.createThumbnail(this._thumbnailQueue.shift(), (function(_this) { + return function() { + _this._processingThumbnail = false; + return _this._processThumbnailQueue(); + }; + })(this)); + }; + + Dropzone.prototype.removeFile = function(file) { + if (file.status === Dropzone.UPLOADING) { + this.cancelUpload(file); + } + this.files = without(this.files, file); + this.emit("removedfile", file); + if (this.files.length === 0) { + return this.emit("reset"); + } + }; + + Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) { + var file, _i, _len, _ref; + if (cancelIfNecessary == null) { + cancelIfNecessary = false; + } + _ref = this.files.slice(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) { + this.removeFile(file); + } + } + return null; + }; + + Dropzone.prototype.createThumbnail = function(file, callback) { + var fileReader; + fileReader = new FileReader; + fileReader.onload = (function(_this) { + return function() { + if (file.type === "image/svg+xml") { + _this.emit("thumbnail", file, fileReader.result); + if (callback != null) { + callback(); + } + return; + } + return _this.createThumbnailFromUrl(file, fileReader.result, callback); + }; + })(this); + return fileReader.readAsDataURL(file); + }; + + Dropzone.prototype.createThumbnailFromUrl = function(file, imageUrl, callback, crossOrigin) { + var img; + img = document.createElement("img"); + if (crossOrigin) { + img.crossOrigin = crossOrigin; + } + img.onload = (function(_this) { + return function() { + var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3; + file.width = img.width; + file.height = img.height; + resizeInfo = _this.options.resize.call(_this, file); + if (resizeInfo.trgWidth == null) { + resizeInfo.trgWidth = resizeInfo.optWidth; + } + if (resizeInfo.trgHeight == null) { + resizeInfo.trgHeight = resizeInfo.optHeight; + } + canvas = document.createElement("canvas"); + ctx = canvas.getContext("2d"); + canvas.width = resizeInfo.trgWidth; + canvas.height = resizeInfo.trgHeight; + drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); + thumbnail = canvas.toDataURL("image/png"); + _this.emit("thumbnail", file, thumbnail); + if (callback != null) { + return callback(); + } + }; + })(this); + if (callback != null) { + img.onerror = callback; + } + return img.src = imageUrl; + }; + + Dropzone.prototype.processQueue = function() { + var i, parallelUploads, processingLength, queuedFiles; + parallelUploads = this.options.parallelUploads; + processingLength = this.getUploadingFiles().length; + i = processingLength; + if (processingLength >= parallelUploads) { + return; + } + queuedFiles = this.getQueuedFiles(); + if (!(queuedFiles.length > 0)) { + return; + } + if (this.options.uploadMultiple) { + return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength)); + } else { + while (i < parallelUploads) { + if (!queuedFiles.length) { + return; + } + this.processFile(queuedFiles.shift()); + i++; + } + } + }; + + Dropzone.prototype.processFile = function(file) { + return this.processFiles([file]); + }; + + Dropzone.prototype.processFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.processing = true; + file.status = Dropzone.UPLOADING; + this.emit("processing", file); + } + if (this.options.uploadMultiple) { + this.emit("processingmultiple", files); + } + return this.uploadFiles(files); + }; + + Dropzone.prototype._getFilesWithXhr = function(xhr) { + var file, files; + return files = (function() { + var _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.xhr === xhr) { + _results.push(file); + } + } + return _results; + }).call(this); + }; + + Dropzone.prototype.cancelUpload = function(file) { + var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref; + if (file.status === Dropzone.UPLOADING) { + groupedFiles = this._getFilesWithXhr(file.xhr); + for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) { + groupedFile = groupedFiles[_i]; + groupedFile.status = Dropzone.CANCELED; + } + file.xhr.abort(); + for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) { + groupedFile = groupedFiles[_j]; + this.emit("canceled", groupedFile); + } + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", groupedFiles); + } + } else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) { + file.status = Dropzone.CANCELED; + this.emit("canceled", file); + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", [file]); + } + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + resolveOption = function() { + var args, option; + option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + if (typeof option === 'function') { + return option.apply(this, args); + } + return option; + }; + + Dropzone.prototype.uploadFile = function(file) { + return this.uploadFiles([file]); + }; + + Dropzone.prototype.uploadFiles = function(files) { + var file, formData, handleError, headerName, headerValue, headers, i, input, inputName, inputType, key, method, option, progressObj, response, updateProgress, url, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + xhr = new XMLHttpRequest(); + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.xhr = xhr; + } + method = resolveOption(this.options.method, files); + url = resolveOption(this.options.url, files); + xhr.open(method, url, true); + xhr.withCredentials = !!this.options.withCredentials; + response = null; + handleError = (function(_this) { + return function() { + var _j, _len1, _results; + _results = []; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr)); + } + return _results; + }; + })(this); + updateProgress = (function(_this) { + return function(e) { + var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results; + if (e != null) { + progress = 100 * e.loaded / e.total; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + file.upload = { + progress: progress, + total: e.total, + bytesSent: e.loaded + }; + } + } else { + allFilesFinished = true; + progress = 100; + for (_k = 0, _len2 = files.length; _k < _len2; _k++) { + file = files[_k]; + if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) { + allFilesFinished = false; + } + file.upload.progress = progress; + file.upload.bytesSent = file.upload.total; + } + if (allFilesFinished) { + return; + } + } + _results = []; + for (_l = 0, _len3 = files.length; _l < _len3; _l++) { + file = files[_l]; + _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent)); + } + return _results; + }; + })(this); + xhr.onload = (function(_this) { + return function(e) { + var _ref; + if (files[0].status === Dropzone.CANCELED) { + return; + } + if (xhr.readyState !== 4) { + return; + } + response = xhr.responseText; + if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { + try { + response = JSON.parse(response); + } catch (_error) { + e = _error; + response = "Invalid JSON response from server."; + } + } + updateProgress(); + if (!((200 <= (_ref = xhr.status) && _ref < 300))) { + return handleError(); + } else { + return _this._finished(files, response, e); + } + }; + })(this); + xhr.onerror = (function(_this) { + return function() { + if (files[0].status === Dropzone.CANCELED) { + return; + } + return handleError(); + }; + })(this); + progressObj = (_ref = xhr.upload) != null ? _ref : xhr; + progressObj.onprogress = updateProgress; + headers = { + "Accept": "application/json", + "Cache-Control": "no-cache", + "X-Requested-With": "XMLHttpRequest" + }; + if (this.options.headers) { + extend(headers, this.options.headers); + } + for (headerName in headers) { + headerValue = headers[headerName]; + if (headerValue) { + xhr.setRequestHeader(headerName, headerValue); + } + } + formData = new FormData(); + if (this.options.params) { + _ref1 = this.options.params; + for (key in _ref1) { + value = _ref1[key]; + formData.append(key, value); + } + } + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + this.emit("sending", file, xhr, formData); + } + if (this.options.uploadMultiple) { + this.emit("sendingmultiple", files, xhr, formData); + } + if (this.element.tagName === "FORM") { + _ref2 = this.element.querySelectorAll("input, textarea, select, button"); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + input = _ref2[_k]; + inputName = input.getAttribute("name"); + inputType = input.getAttribute("type"); + if (input.tagName === "SELECT" && input.hasAttribute("multiple")) { + _ref3 = input.options; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + option = _ref3[_l]; + if (option.selected) { + formData.append(inputName, option.value); + } + } + } else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) { + formData.append(inputName, input.value); + } + } + } + for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) { + formData.append(this._getParamName(i), files[i], this._renameFilename(files[i].name)); + } + return this.submitRequest(xhr, formData, files); + }; + + Dropzone.prototype.submitRequest = function(xhr, formData, files) { + return xhr.send(formData); + }; + + Dropzone.prototype._finished = function(files, responseText, e) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.SUCCESS; + this.emit("success", file, responseText, e); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("successmultiple", files, responseText, e); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + Dropzone.prototype._errorProcessing = function(files, message, xhr) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.ERROR; + this.emit("error", file, message, xhr); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("errormultiple", files, message, xhr); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + return Dropzone; + + })(Emitter); + + Dropzone.version = "4.3.0"; + + Dropzone.options = {}; + + Dropzone.optionsForElement = function(element) { + if (element.getAttribute("id")) { + return Dropzone.options[camelize(element.getAttribute("id"))]; + } else { + return void 0; + } + }; + + Dropzone.instances = []; + + Dropzone.forElement = function(element) { + if (typeof element === "string") { + element = document.querySelector(element); + } + if ((element != null ? element.dropzone : void 0) == null) { + throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone."); + } + return element.dropzone; + }; + + Dropzone.autoDiscover = true; + + Dropzone.discover = function() { + var checkElements, dropzone, dropzones, _i, _len, _results; + if (document.querySelectorAll) { + dropzones = document.querySelectorAll(".dropzone"); + } else { + dropzones = []; + checkElements = function(elements) { + var el, _i, _len, _results; + _results = []; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )dropzone($| )/.test(el.className)) { + _results.push(dropzones.push(el)); + } else { + _results.push(void 0); + } + } + return _results; + }; + checkElements(document.getElementsByTagName("div")); + checkElements(document.getElementsByTagName("form")); + } + _results = []; + for (_i = 0, _len = dropzones.length; _i < _len; _i++) { + dropzone = dropzones[_i]; + if (Dropzone.optionsForElement(dropzone) !== false) { + _results.push(new Dropzone(dropzone)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i]; + + Dropzone.isBrowserSupported = function() { + var capableBrowser, regex, _i, _len, _ref; + capableBrowser = true; + if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { + if (!("classList" in document.createElement("a"))) { + capableBrowser = false; + } else { + _ref = Dropzone.blacklistedBrowsers; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + regex = _ref[_i]; + if (regex.test(navigator.userAgent)) { + capableBrowser = false; + continue; + } + } + } + } else { + capableBrowser = false; + } + return capableBrowser; + }; + + without = function(list, rejectedItem) { + var item, _i, _len, _results; + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + item = list[_i]; + if (item !== rejectedItem) { + _results.push(item); + } + } + return _results; + }; + + camelize = function(str) { + return str.replace(/[\-_](\w)/g, function(match) { + return match.charAt(1).toUpperCase(); + }); + }; + + Dropzone.createElement = function(string) { + var div; + div = document.createElement("div"); + div.innerHTML = string; + return div.childNodes[0]; + }; + + Dropzone.elementInside = function(element, container) { + if (element === container) { + return true; + } + while (element = element.parentNode) { + if (element === container) { + return true; + } + } + return false; + }; + + Dropzone.getElement = function(el, name) { + var element; + if (typeof el === "string") { + element = document.querySelector(el); + } else if (el.nodeType != null) { + element = el; + } + if (element == null) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element."); + } + return element; + }; + + Dropzone.getElements = function(els, name) { + var e, el, elements, _i, _j, _len, _len1, _ref; + if (els instanceof Array) { + elements = []; + try { + for (_i = 0, _len = els.length; _i < _len; _i++) { + el = els[_i]; + elements.push(this.getElement(el, name)); + } + } catch (_error) { + e = _error; + elements = null; + } + } else if (typeof els === "string") { + elements = []; + _ref = document.querySelectorAll(els); + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + el = _ref[_j]; + elements.push(el); + } + } else if (els.nodeType != null) { + elements = [els]; + } + if (!((elements != null) && elements.length)) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those."); + } + return elements; + }; + + Dropzone.confirm = function(question, accepted, rejected) { + if (window.confirm(question)) { + return accepted(); + } else if (rejected != null) { + return rejected(); + } + }; + + Dropzone.isValidFile = function(file, acceptedFiles) { + var baseMimeType, mimeType, validType, _i, _len; + if (!acceptedFiles) { + return true; + } + acceptedFiles = acceptedFiles.split(","); + mimeType = file.type; + baseMimeType = mimeType.replace(/\/.*$/, ""); + for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) { + validType = acceptedFiles[_i]; + validType = validType.trim(); + if (validType.charAt(0) === ".") { + if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { + return true; + } + } else if (/\/\*$/.test(validType)) { + if (baseMimeType === validType.replace(/\/.*$/, "")) { + return true; + } + } else { + if (mimeType === validType) { + return true; + } + } + } + return false; + }; + + if (typeof jQuery !== "undefined" && jQuery !== null) { + jQuery.fn.dropzone = function(options) { + return this.each(function() { + return new Dropzone(this, options); + }); + }; + } + + if (typeof module !== "undefined" && module !== null) { + module.exports = Dropzone; + } else { + window.Dropzone = Dropzone; + } + + Dropzone.ADDED = "added"; + + Dropzone.QUEUED = "queued"; + + Dropzone.ACCEPTED = Dropzone.QUEUED; + + Dropzone.UPLOADING = "uploading"; + + Dropzone.PROCESSING = Dropzone.UPLOADING; + + Dropzone.CANCELED = "canceled"; + + Dropzone.ERROR = "error"; + + Dropzone.SUCCESS = "success"; + + + /* + + Bugfix for iOS 6 and 7 + Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios + based on the work of https://github.com/stomita/ios-imagefile-megapixel + */ + + detectVerticalSquash = function(img) { + var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy; + iw = img.naturalWidth; + ih = img.naturalHeight; + canvas = document.createElement("canvas"); + canvas.width = 1; + canvas.height = ih; + ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + data = ctx.getImageData(0, 0, 1, ih).data; + sy = 0; + ey = ih; + py = ih; + while (py > sy) { + alpha = data[(py - 1) * 4 + 3]; + if (alpha === 0) { + ey = py; + } else { + sy = py; + } + py = (ey + sy) >> 1; + } + ratio = py / ih; + if (ratio === 0) { + return 1; + } else { + return ratio; + } + }; + + drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { + var vertSquashRatio; + vertSquashRatio = detectVerticalSquash(img); + return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); + }; + + + /* + * contentloaded.js + * + * Author: Diego Perini (diego.perini at gmail.com) + * Summary: cross-browser wrapper for DOMContentLoaded + * Updated: 20101020 + * License: MIT + * Version: 1.2 + * + * URL: + * http://javascript.nwbox.com/ContentLoaded/ + * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE + */ + + contentLoaded = function(win, fn) { + var add, doc, done, init, poll, pre, rem, root, top; + done = false; + top = true; + doc = win.document; + root = doc.documentElement; + add = (doc.addEventListener ? "addEventListener" : "attachEvent"); + rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); + pre = (doc.addEventListener ? "" : "on"); + init = function(e) { + if (e.type === "readystatechange" && doc.readyState !== "complete") { + return; + } + (e.type === "load" ? win : doc)[rem](pre + e.type, init, false); + if (!done && (done = true)) { + return fn.call(win, e.type || e); + } + }; + poll = function() { + var e; + try { + root.doScroll("left"); + } catch (_error) { + e = _error; + setTimeout(poll, 50); + return; + } + return init("poll"); + }; + if (doc.readyState !== "complete") { + if (doc.createEventObject && root.doScroll) { + try { + top = !win.frameElement; + } catch (_error) {} + if (top) { + poll(); + } + } + doc[add](pre + "DOMContentLoaded", init, false); + doc[add](pre + "readystatechange", init, false); + return win[add](pre + "load", init, false); + } + }; + + Dropzone._autoDiscoverFunction = function() { + if (Dropzone.autoDiscover) { + return Dropzone.discover(); + } + }; + + contentLoaded(window, Dropzone._autoDiscoverFunction); + +}).call(this); diff --git a/templates/about.html b/templates/about.html index f89e60fea5..6e106a3027 100644 --- a/templates/about.html +++ b/templates/about.html @@ -12,6 +12,7 @@

About Mobile Security Framework

Mobile Security Framework (MobSF) is an intelligent, all-in-one open source mobile application (Android/iOS) automated pen-testing framework capable of performing static and dynamic analysis. It can be used for effective and fast security analysis of Android and iOS Applications and supports both binaries (APK & IPA) and zipped source code. MobSF can also perform Web API Security testing with it's API Fuzzer.

+

Developer: Ajin Abraham

diff --git a/templates/dynamic_analysis.html b/templates/dynamic_analysis.html index edd1924a69..0d30ce0698 100644 --- a/templates/dynamic_analysis.html +++ b/templates/dynamic_analysis.html @@ -90,13 +90,13 @@

Downloads

@@ -418,7 +418,7 @@

EXPORTED ACTIVI {% for img, desc in expacttest.items %} - {{desc}} + {{desc}} {{ desc }} {% endfor %} @@ -446,7 +446,7 @@

ACTIVITY TESTER {% for img, desc in acttest.items %} - + {% endfor %} @@ -476,7 +476,7 @@

SCREENSHOTS

{% for i in imgs %} - Screenshot + Screenshot {% endfor %} diff --git a/templates/index.html b/templates/index.html index 1df158c303..14453b1b25 100644 --- a/templates/index.html +++ b/templates/index.html @@ -24,10 +24,11 @@ - + + + -
@@ -35,27 +36,22 @@
-
-

Mobile Security Framework

- +
+

- -
+
+ +
+ + +
+ {% csrf_token %} + + +
+ + +
+
+
{% csrf_token %}
Upload & Analyze -

- - +

+ +
+ +
+ + +
-

© Mobile Security Framework {% now "Y" %} . All Rights Reserved

+
Recent Scans | About

+

© {% now "Y" %} Mobile Security Framework - MobSF {{ version }}. All Rights Reserved

@@ -123,6 +138,52 @@

function _(el){ return document.getElementById(el); } +function responseHandler (json, isbutton) { + + if (json.status==='error') + { + _("status").innerHTML = json.description; + } + else if (json.status==='success') + { + + i=1; + setInterval(function () { + dot=''; + for (x=1;x<=i;x++) + { + dot+='.'; + } + _("status").innerHTML = "Analyzing" +dot; + i+=1; + if(i==5) + { + i=1; + } + }, 2000); + + window.location.href=window.location.href+json.url; + } +} + + + Dropzone.options.uploadWidget = { + paramName: 'file', + createImageThumbnails: false, + maxFilesize: 50, // MB + maxFiles: 8, + dictDefaultMessage: 'Drag files here or click Upload & Analyze', + acceptedFiles: '.apk,.ipa,.zip', + + init: function() { + this.on('success', function( file, resp ){ + //console.log( file ); + console.log( resp ); + responseHandler (resp); + }); + }, + +}; $(document).ready(function () @@ -181,31 +242,7 @@

{ var json= JSON.parse(event.target.responseText); - if (json.status==='error') - { - _("status").innerHTML = json.description; - } - else if (json.status==='success') - { - - i=1; - setInterval(function () { - dot=''; - for (x=1;x<=i;x++) - { - dot+='.'; - } - _("status").innerHTML = "Analyzing" +dot; - i+=1; - if(i==5) - { - i=1; - } - }, 2000); - - window.location.href=window.location.href+json.url; - _("progressBar").value = 100; - } + responseHandler(json); } function errorHandler(event){ _("status").innerHTML = "Upload Failed!"; diff --git a/templates/start_test.html b/templates/start_test.html index db577d0c20..390f745909 100644 --- a/templates/start_test.html +++ b/templates/start_test.html @@ -38,7 +38,7 @@
{{desc}}{{desc}} {{ desc }}