diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js b/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js index 79e59ed192..2d9be8d5e1 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js @@ -18,37 +18,51 @@ function app_env_info() { send('App Document Path: ' + getPath(9)); send('App Library Path: ' + libPath); send('App Cache Path: ' + getPath(13)); - - // try { - // //Credit: https://github.com/iddoeldor/frida-snippets#find-ios-application-uuid - // var mainBundleContainerPathIdentifier = ""; - // var bundleIdentifier = String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier')); - // var path_prefix = "/var/mobile/Containers/Data/Application/"; - // var plist_metadata = "/.com.apple.mobile_container_manager.metadata.plist"; - // var folders = ObjC.classes.NSFileManager.defaultManager().contentsOfDirectoryAtPath_error_(path_prefix, NULL); - // if (!folders){ - // send('Unable to identify App Container Path.') - // } else { - // for (var i = 0, l = folders.count(); i < l; i++) { - // var uuid = folders.objectAtIndex_(i); - // var metadata = path_prefix + uuid + plist_metadata; - // var dict = ObjC.classes.NSMutableDictionary.alloc().initWithContentsOfFile_(metadata); - // var enumerator = dict.keyEnumerator(); - // var key; - // while ((key = enumerator.nextObject()) !== null) { - // if (key == 'MCMMetadataIdentifier') { - // var appId = String(dict.objectForKey_(key)); - // if (appId.indexOf(bundleIdentifier) != -1) { - // mainBundleContainerPathIdentifier = uuid; - // break; - // } - // } - // } - // } - // send("App Container Path: /var/mobile/Containers/Data/Application/" + mainBundleContainerPathIdentifier + "/"); - // } - // } catch (e){ - // } - } app_env_info() + + + + + + + + + + + + + + +// try { +// //Credit: https://github.com/iddoeldor/frida-snippets#find-ios-application-uuid +// var mainBundleContainerPathIdentifier = ""; +// var bundleIdentifier = String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier')); +// var path_prefix = "/var/mobile/Containers/Data/Application/"; +// var plist_metadata = "/.com.apple.mobile_container_manager.metadata.plist"; +// var folders = ObjC.classes.NSFileManager.defaultManager().contentsOfDirectoryAtPath_error_(path_prefix, NULL); +// if (!folders){ +// send('Unable to identify App Container Path.') +// } else { +// for (var i = 0, l = folders.count(); i < l; i++) { +// var uuid = folders.objectAtIndex_(i); +// var metadata = path_prefix + uuid + plist_metadata; +// var dict = ObjC.classes.NSMutableDictionary.alloc().initWithContentsOfFile_(metadata); +// var enumerator = dict.keyEnumerator(); +// var key; +// while ((key = enumerator.nextObject()) !== null) { +// if (key == 'MCMMetadataIdentifier') { +// var appId = String(dict.objectForKey_(key)); +// if (appId.indexOf(bundleIdentifier) != -1) { +// mainBundleContainerPathIdentifier = uuid; +// break; +// } +// } +// } +// } +// send("App Container Path: /var/mobile/Containers/Data/Application/" + mainBundleContainerPathIdentifier + "/"); +// } +// } catch (e){ +// } + + diff --git a/mobsf/DynamicAnalyzer/views/android/environment.py b/mobsf/DynamicAnalyzer/views/android/environment.py index c48b85a1ea..1840a3ca13 100644 --- a/mobsf/DynamicAnalyzer/views/android/environment.py +++ b/mobsf/DynamicAnalyzer/views/android/environment.py @@ -191,9 +191,8 @@ def configure_proxy(self, project, request): """HTTPS Proxy.""" self.install_mobsf_ca('install') proxy_port = settings.PROXY_PORT - logger.info('Starting HTTPs Proxy on %s', proxy_port) - httptools_url = get_http_tools_url(request) - stop_httptools(httptools_url) + logger.info('Starting HTTPS Proxy on %s', proxy_port) + stop_httptools(get_http_tools_url(request)) start_proxy(proxy_port, project) def install_mobsf_ca(self, action): diff --git a/mobsf/DynamicAnalyzer/views/android/tests_common.py b/mobsf/DynamicAnalyzer/views/android/tests_common.py index e1bf12327e..2925de356f 100644 --- a/mobsf/DynamicAnalyzer/views/android/tests_common.py +++ b/mobsf/DynamicAnalyzer/views/android/tests_common.py @@ -145,8 +145,7 @@ def download_data(request, api=False): 'message': 'App details not found in database'} return send_response(data, api) apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/') - httptools_url = get_http_tools_url(request) - stop_httptools(httptools_url) + stop_httptools(get_http_tools_url(request)) files_loc = '/data/local/' logger.info('Archiving files created by app') env.adb_command(['tar', '-cvf', files_loc + package + '.tar', diff --git a/mobsf/DynamicAnalyzer/views/ios/analysis.py b/mobsf/DynamicAnalyzer/views/ios/analysis.py index e171e16a13..c916bf0ab0 100644 --- a/mobsf/DynamicAnalyzer/views/ios/analysis.py +++ b/mobsf/DynamicAnalyzer/views/ios/analysis.py @@ -30,25 +30,29 @@ def get_screenshots(checksum, download_dir): return screenshots -def get_logs_data(app_dir): +def get_logs_data(app_dir, bundle_id): """Get Data for analysis.""" data = [] dump_file = Path(app_dir) / 'mobsf_dump_file.txt' fd_log_file = Path(app_dir) / 'mobsf_frida_out.txt' + flows = Path.home() / '.httptools' / 'flows' + web_file = flows / f'{bundle_id}.flow.txt' if dump_file.exists(): data.append(dump_file.read_text('utf-8', 'ignore')) if fd_log_file.exists(): data.append(fd_log_file.read_text('utf-8', 'ignore')) + if web_file.exists(): + data.append(web_file.read_text('utf-8', 'ignore')) return '\n'.join(data) -def run_analysis(app_dir, checksum): +def run_analysis(app_dir, bundle_id, checksum): """Run Dynamic File Analysis.""" analysis_result = {} logger.info('Dynamic File Analysis') domains = {} # Collect Log data - data = get_logs_data(app_dir) + data = get_logs_data(app_dir, bundle_id) urls = re.findall(URL_REGEX, data.lower()) if urls: urls = list(set(urls)) diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py index 073f443c53..20f9159b91 100644 --- a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py @@ -1,6 +1,7 @@ # -*- coding: utf_8 -*- """Corellium APIs.""" import logging +import os from copy import deepcopy from socket import gethostname @@ -72,9 +73,12 @@ def get_authorized_keys(self): def add_authorized_key(self, key): """Add SSH public key to the Project.""" logger.info('Adding SSH public key to Corellium project') + extras = '' + if os.getenv('MOBSF_PLATFORM') == 'docker': + extras = ' - (docker)' data = { 'kind': 'ssh', - 'label': f'MobSF SSH Key - {gethostname()}', + 'label': f'MobSF SSH Key - {gethostname()}{extras}', 'key': key, } r = requests.post( @@ -85,6 +89,15 @@ def add_authorized_key(self, key): return r.json()['identifier'] return False + def delete_authorized_key(self, key_id): + """Delete SSH public key from the Project.""" + r = requests.delete( + f'{self.api}/projects/{self.project_id}/keys/{key_id}', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return OK + return False + def get_instances(self): """Get Instances.""" logger.info('Getting iOS instances') diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py index 6a9c793807..1b8d11e60e 100644 --- a/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py @@ -3,6 +3,7 @@ import logging import re import os +import shutil import time from base64 import b64encode from pathlib import Path @@ -25,6 +26,10 @@ strict_package_check, ) from mobsf.DynamicAnalyzer.forms import UploadFileForm +from mobsf.DynamicAnalyzer.tools.webproxy import ( + get_http_tools_url, + stop_httptools, +) from mobsf.DynamicAnalyzer.views.common.shared import ( invalid_params, send_response, @@ -707,26 +712,35 @@ def download_app_data(ci, checksum): @require_http_methods(['POST']) -def download_data(request, checksum, api=False): +def download_data(request, bundle_id, api=False): """Download Application Data from Device.""" logger.info('Downloading application data') data = { 'status': 'failed', 'message': 'Failed to Download application data'} try: - if not is_md5(checksum): - # Additional Check for REST API - data['message'] = 'Invalid Hash' - return send_response(data, api) instance_id = request.POST['instance_id'] failed = common_check(instance_id) if failed: return send_response(failed, api) + if not strict_package_check(bundle_id): + data['message'] = 'Invalid iOS Bundle id' + return send_response(data, api) apikey = getattr(settings, 'CORELLIUM_API_KEY', '') ci = CorelliumInstanceAPI(apikey, instance_id) + checksum = get_md5(bundle_id.encode('utf-8')) # App Container download logger.info('Downloading app container data') download_app_data(ci, checksum) + # Stop HTTPS Proxy + stop_httptools(get_http_tools_url(request)) + # Move HTTP raw logs to download directory + flows = Path.home() / '.httptools' / 'flows' + webf = flows / f'{bundle_id}.flow.txt' + dwd = Path(settings.DWD_DIR) + dweb = dwd / f'{checksum}-web_traffic.txt' + if webf.exists(): + shutil.copyfile(webf, dweb) # Pcap download logger.info('Downloading network capture') pcap = ci.download_network_capture() diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py b/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py index d4c1467fb9..69df0cef5a 100644 --- a/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py @@ -23,8 +23,10 @@ # Modified for MobSF. import logging -import socketserver import select +import socket +import socketserver +from threading import Thread from pathlib import Path import paramiko @@ -78,6 +80,7 @@ def parse_ssh_string(ssh): return ssh_dict +# Local Port Forward class ForwardServer(socketserver.ThreadingTCPServer): daemon_threads = True allow_reuse_address = True @@ -148,6 +151,57 @@ class SubHander(Handler): logger.info('Port Forwarding Already in place') +# Remote Port Forward +def handler(chan, host, port): + sock = socket.socket() + try: + sock.connect((host, port)) + except ConnectionRefusedError: + # Proxy server is stopped + return + except Exception: + logger.info('Forwarding request to %s:%d failed', host, port) + return + try: + while True: + r, w, x = select.select([sock, chan], [], []) + if sock in r: + data = sock.recv(1024) + if len(data) == 0: + break + chan.send(data) + if chan in r: + data = chan.recv(1024) + if len(data) == 0: + break + sock.send(data) + except ConnectionResetError: + pass + finally: + chan.close() + sock.close() + + +def reverse_forward_tunnel(server_port, remote_host, remote_port, transport): + try: + transport.request_port_forward('', server_port) + while True: + chan = transport.accept(1000) + if chan is None: + continue + Thread( + target=handler, + args=(chan, remote_host, remote_port), + daemon=True).start() + except paramiko.SSHException as exp: + if 'forwarding request denied' in str(exp): + # Handle TCP forwarding request denied + # Happens if already forwarding port + pass + else: + logger.exception('SSH Remote Port Forward Exception') + + def ssh_jump_host(ssh_string): """Connect to SSH over a bastion.""" ssh_dict = parse_ssh_string(ssh_string) @@ -160,7 +214,8 @@ def ssh_jump_host(ssh_string): generate_keypair_if_not_exists(home) keyf = home / 'ssh_key.private' jumpbox = paramiko.SSHClient() - jumpbox.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + jumpbox.load_system_host_keys() + jumpbox.set_missing_host_key_policy(paramiko.WarningPolicy()) jumpbox.connect( bastion_host, username=bastion_user, @@ -173,7 +228,8 @@ def ssh_jump_host(ssh_string): 'direct-tcpip', dest_addr, src_addr) target = paramiko.SSHClient() - target.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + target.load_system_host_keys() + target.set_missing_host_key_policy(paramiko.WarningPolicy()) target.connect( private_ip, username=user, @@ -194,8 +250,20 @@ def ssh_jumphost_port_forward(ssh_string): forward_port, target.get_transport(), ssh_string) - # target close() - # jumpbox close() + + +def ssh_jumphost_reverse_port_forward(ssh_string): + """SSH over Jump Host and Remote Port Forward.""" + target, _jumpbox = ssh_jump_host(ssh_string) + # HTTPS proxy port + port = settings.PROXY_PORT + remote_host = '127.0.0.1' + reverse_forward_tunnel( + port, + remote_host, + port, + target.get_transport(), + ) def ssh_execute_cmd(target, cmd): diff --git a/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py b/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py index 9d4ec20bf3..326f5635f8 100644 --- a/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py +++ b/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py @@ -1,7 +1,9 @@ # -*- coding: utf_8 -*- """iOS Dynamic Analysis.""" import logging +import os from pathlib import Path +from threading import Thread from django.conf import settings from django.shortcuts import render @@ -18,8 +20,17 @@ from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import ( generate_keypair_if_not_exists, ) +from mobsf.DynamicAnalyzer.tools.webproxy import ( + get_http_tools_url, + start_proxy, + stop_httptools, +) from mobsf.DynamicAnalyzer.views.ios.corellium_apis import ( CorelliumAPI, + CorelliumInstanceAPI, +) +from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import ( + ssh_jumphost_reverse_port_forward, ) logger = logging.getLogger(__name__) @@ -97,6 +108,9 @@ def dynamic_analyzer(request, api=False): app_dir = Path(settings.UPLD_DIR) / bundle_hash if not app_dir.exists(): app_dir.mkdir() + apikey = getattr(settings, 'CORELLIUM_API_KEY', '') + ci = CorelliumInstanceAPI(apikey, instance_id) + configure_proxy(request, bundleid, ci) context = { 'hash': bundle_hash, 'instance_id': instance_id, @@ -122,6 +136,7 @@ def setup_ssh_keys(c): location = Path(settings.UPLD_DIR).parent _prv, pub = generate_keypair_if_not_exists(location) add_keys = False + is_docker = os.getenv('MOBSF_PLATFORM') == 'docker' if not pkeys: # No SSH Keys associated with the project # let's add one @@ -132,12 +147,17 @@ def setup_ssh_keys(c): pub_key_exists = False for pkey in pkeys: if pkey['project'] == c.project_id: - ckey = get_md5(pkey['key'].encode('utf-8')) - lkey = get_md5(pub) - if ckey == lkey: + rkey = get_md5(pkey['key'].encode('utf-8')) + if rkey == get_md5(pub): pub_key_exists = True break - # Out key is not asscoiated with the project, let's add it + if is_docker and pkey['label'].endswith('(docker)'): + # Delete all docker generated keys + # This is done to avoid multiple stale keys being + # added on each run. + logger.info('Removing old stale SSH public key') + c.delete_authorized_key(pkey['identifier']) + # Our key is not asscoiated with the project, let's add it if not pub_key_exists: add_keys = True if add_keys: @@ -146,3 +166,16 @@ def setup_ssh_keys(c): logger.error('Failed to add SSH Key to Corellium project') return logger.info('Added SSH Key to Corellium project') + + +def configure_proxy(request, project, ci): + """Configure HTTPS Proxy.""" + proxy_port = settings.PROXY_PORT + logger.info('Starting HTTPS Proxy on %s', proxy_port) + stop_httptools(get_http_tools_url(request)) + start_proxy(proxy_port, project) + # Remote Port forward for HTTPS Proxy + logger.info('Starting Remote Port Forward over SSH') + Thread(target=ssh_jumphost_reverse_port_forward, + args=(ci.get_ssh_connection_string(),), + daemon=True).start() diff --git a/mobsf/DynamicAnalyzer/views/ios/report.py b/mobsf/DynamicAnalyzer/views/ios/report.py index c14791138c..1c22e8f307 100644 --- a/mobsf/DynamicAnalyzer/views/ios/report.py +++ b/mobsf/DynamicAnalyzer/views/ios/report.py @@ -16,11 +16,12 @@ from mobsf.MobSF.utils import ( base64_decode, common_check, - is_md5, + get_md5, key, pretty_json, print_n_send_error_response, replace, + strict_package_check, ) @@ -31,22 +32,23 @@ register.filter('base64_decode', base64_decode) -def ios_view_report(request, checksum, api=False): +def ios_view_report(request, bundle_id, api=False): """Dynamic Analysis Report Generation.""" logger.info('iOS Dynamic Analysis Report Generation') try: - if not is_md5(checksum): - # We need this check since checksum is not validated - # in REST API - return print_n_send_error_response( - request, - 'Invalid Hash', - api) instance_id = request.GET.get('instance_id') if instance_id and not common_check(instance_id): dev = instance_id else: dev = '' + if not strict_package_check(bundle_id): + # We need this check since bundleid + # is not validated in REST API + return print_n_send_error_response( + request, + 'Invalid iOS Bundle id', + api) + checksum = get_md5(bundle_id.encode('utf-8')) app_dir = Path(settings.UPLD_DIR) / checksum download_dir = settings.DWD_DIR tools_dir = settings.TOOLS_DIR @@ -56,8 +58,8 @@ def ios_view_report(request, checksum, api=False): 'for this app. Perform Dynamic Analysis ' 'and generate the report.') return print_n_send_error_response(request, msg, api) - dynamic_dump = ios_api_analysis(app_dir) - dump_analaysis = run_analysis(app_dir, checksum) + api_analysis = ios_api_analysis(app_dir) + dump_analaysis = run_analysis(app_dir, bundle_id, checksum) trk = Trackers.Trackers(app_dir, tools_dir) trackers = trk.get_trackers_domains_or_deps( dump_analaysis['domains'], None) @@ -67,11 +69,12 @@ def ios_view_report(request, checksum, api=False): 'version': settings.MOBSF_VER, 'title': 'iOS Dynamic Analysis Report', 'instance_id': dev, + 'bundleid': bundle_id, 'trackers': trackers, 'screenshots': screenshots, 'frida_logs': frida_log.exists(), } - context.update(dynamic_dump) + context.update(api_analysis) context.update(dump_analaysis) template = 'dynamic_analysis/ios/dynamic_report.html' if api: diff --git a/mobsf/MobSF/urls.py b/mobsf/MobSF/urls.py index bfd4e37cda..8a921c865f 100755 --- a/mobsf/MobSF/urls.py +++ b/mobsf/MobSF/urls.py @@ -262,7 +262,7 @@ re_path(r'^ios/system_logs/$', instance.system_logs, name='ios_system_logs'), - re_path(r'^ios/download_data/(?P[0-9a-f]{32})$', + re_path(r'^ios/download_data/(?P([\w]*\.)+[\w]{2,155})$', instance.download_data, name='ios_download_data'), re_path(r'^ios/instrument/$', @@ -274,7 +274,7 @@ re_path(r'^ios/get_script/$', ios_tests_frida.ios_get_script, name='ios_get_script'), - re_path(r'^ios/view_report/(?P[0-9a-f]{32})$', + re_path(r'^ios/view_report/(?P([\w]*\.)+[\w]{2,155})$', ios_view_report.ios_view_report, name='ios_view_report'), diff --git a/mobsf/MobSF/utils.py b/mobsf/MobSF/utils.py index d600baf2ee..b4c645ffcc 100755 --- a/mobsf/MobSF/utils.py +++ b/mobsf/MobSF/utils.py @@ -609,9 +609,9 @@ def strict_package_check(user_input): For android package and ios bundle id """ - pat = re.compile(r'^([\w]*\.)+[\w]*$') + pat = re.compile(r'^([\w]*\.)+[\w]{2,155}$') resp = re.match(pat, user_input) - if not resp: + if not resp or '..' in user_input: logger.error('Invalid package name/bundle id/class name') return resp diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html b/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html index d72311e0a3..6a1b4aa261 100644 --- a/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html @@ -43,7 +43,7 @@
Corellium Project ID: {{ project_id }}
VM VERSION STATE - META + PROGRESS ACTIONS @@ -60,8 +60,15 @@
Corellium Project ID: {{ project_id }}
{{i.state}} - -
 {{ i.restoreStatus}}
+ + {% if 'progress' in i.restoreStatus %} +
+
{{i.restoreStatus.progress| floatformat:"0"}}%
+
+ {% endif %} + {% if 'stage' in i.restoreStatus %} +
 {{ i.restoreStatus.stage}}
+ {% endif %}

@@ -133,7 +140,7 @@

Apps Available

Upload & Install

- View Report + View Report

{% endif %} @@ -337,6 +344,8 @@ // List installed apps function list_apps(){ $('#in_device tbody tr').remove(); + $('#in_device thead').remove(); + dynamic_loader(); $.ajax({ url: '{% url 'list_apps' %}', type : 'POST', @@ -377,13 +386,15 @@ ${escapeHtml(type)}

Start Dynamic Analysis - View Report + View Report Uninstall

`); } + stop_loader(); } else { + stop_loader(); Swal.fire( 'Listing apps failed', 'Is VM ready?: ' + json.message, @@ -392,6 +403,7 @@ } }, error : function(xhr,errmsg,err) { + stop_loader(); console.log(errmsg) } }); diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html index 95f758df79..a9731e1e1a 100644 --- a/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html @@ -1138,11 +1138,11 @@ stop_network_capture(); // Download app data print_status("Downloading application data."); - action('{% url 'ios_download_data' checksum=hash %}', {instance_id: '{{ instance_id }}'}, function(json) { + action('{% url 'ios_download_data' bundle_id=bundle_id %}', {instance_id: '{{ instance_id }}'}, function(json) { if (json.status==="ok"){ // Generate Report print_status("Generating report"); - location.href = '{% url 'ios_view_report' checksum=hash %}?instance_id={{ instance_id }}'; + location.href = '{% url 'ios_view_report' bundle_id=bundle_id %}?instance_id={{ instance_id }}'; } else { print_status("Failed to download application data"); } diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_report.html b/mobsf/templates/dynamic_analysis/ios/dynamic_report.html index e6ece564fe..dda59045df 100644 --- a/mobsf/templates/dynamic_analysis/ios/dynamic_report.html +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_report.html @@ -269,13 +269,16 @@
- +
@@ -297,9 +300,11 @@
{% if frida_logs %} Frida Logs View {% endif %} + Start HTTPTools

Raw Logs

+ HTTP(S) Traffic Network Pcap

diff --git a/mobsf/templates/general/recent.html b/mobsf/templates/general/recent.html index c85900e6b8..9a91b445f5 100644 --- a/mobsf/templates/general/recent.html +++ b/mobsf/templates/general/recent.html @@ -71,7 +71,9 @@

Recent Scans

{% if '.apk' == e.FILE_NAME|slice:"-4:" or '.xapk' == e.FILE_NAME|slice:"-5:" or '.apks' == e.FILE_NAME|slice:"-5:"%} Dynamic Report {% elif '.ipa' == e.FILE_NAME|slice:"-4:" %} - Dynamic Report + {% if e.PACKAGE_NAME %} + Dynamic Report + {% endif %} {% endif %}

{% else %}