Files
Mobile-Security-Framework-M…/mobsf/DynamicAnalyzer/views/common/device.py
T
Ajin Abraham 62563ca429 [HOTFIX][Security] Security Updates July 5 2026 (#2627)
Security Fixes

[GHSA-8j49-mmcx-4mp5] Arbitrary File Read via Path Traversal in ZIP/APK Icon Extraction
The android:icon attribute from an APK manifest was interpolated into file paths without validation, allowing a crafted APK to read arbitrary files from the server. Fixed by adding is_path_traversal() and is_safe_path() guards in find_icon_path_zip. Dependency bump included.

[GHSA-3p54-567p-2wpr] CSRF Checks Not Enforced After Django Middleware Migration
The migration from the deprecated MIDDLEWARE_CLASSES to MIDDLEWARE omitted CsrfViewMiddleware, SecurityMiddleware, and XFrameOptionsMiddleware, leaving CSRF, HSTS, and clickjacking protections silently disabled. All three have been restored to the active MIDDLEWARE tuple. The now-dead MIDDLEWARE_CLASSES block has been removed to prevent future confusion.

[GHSA-x768-8642-mmq9] Zip Bomb Denial of Service via Per-File Size Limit Bypass
The per-file size check in ZIP extraction logged a warning on oversized members but was missing a continue, allowing files exceeding ZIP_MAX_UNCOMPRESSED_FILE_SIZE to be extracted anyway as long as the aggregate limit was not reached. Fixed by adding the missing continue.

[GHSA-95px-34x5-p37h] SSRF Port Restriction Bypass in assetlinks_check
valid_host() was called with only the bare hostname, allowing android:port to be appended afterward without going through port validation, bypassing the HTTP/HTTPS-only restriction. Fixed with a two-layer approach: primary port allowlist check before URL assembly in get_browsable_activities, and a defence-in-depth re-check in _check_url.

Hardening

The hand-rolled safe_extract used os.path.abspath (symlink-blind) instead of os.path.realpath, creating a TOCTOU window where a symlink could redirect extraction outside the destination directory. Replaced with Python 3.12's tarfile.extractall(filter='data') (PEP 706), which rejects symlinks, hardlinks, absolute paths, and traversal members per-member before extraction. A robust realpath-based fallback is included for older Python versions.
Both download_app_data call sites in the iOS dynamic analyser now wrap app_container and tarfile with shlex.quote when building the SSH tar command, as defensive coding hygiene.
Refactoring

is_path_traversal, is_safe_path, clean_filename, cmd_injection_check, is_pipe_or_link, and is_attack_pattern were scattered across utils.py and shared.py. All have been moved to mobsf/MobSF/security.py as the single authoritative location for security primitives.
Developer Tooling

AGENTS.md / CLAUDE.md - AI agent guidelines
Added a coding-standards document for AI coding agents (Cursor, Claude, Codex) covering MobSF-specific secure-by-default patterns: path traversal guards, archive extraction safety, Django middleware active-tuple hygiene, split-validation anti-patterns (SSRF port bypass), guard completeness (continue/return/raise after every security check), and Django-specific security features (form validators, decorators, middleware). CLAUDE.md is a symlink to AGENTS.md.
2026-07-05 16:55:49 -07:00

105 lines
3.2 KiB
Python

# -*- coding: utf_8 -*-
"""Dynamic Analyzer Reporting."""
import logging
import ntpath
from pathlib import Path
from django.conf import settings
from django.shortcuts import render
from django.utils.html import escape
from mobsf.MobSF.views.authentication import (
login_required,
)
from mobsf.MobSF.security import is_safe_path
from mobsf.MobSF.utils import (
is_md5,
print_n_send_error_response,
read_sqlite,
)
from plistlib import (
FMT_XML,
dumps,
)
logger = logging.getLogger(__name__)
@login_required
def view_file(request, api=False):
"""View File in app data directory."""
logger.info('Viewing File')
try:
typ = ''
rtyp = ''
dat = ''
sql_dump = {}
if api:
fil = request.POST['file']
md5_hash = request.POST['hash']
typ = request.POST['type']
else:
fil = request.GET['file']
md5_hash = request.GET['hash']
typ = request.GET['type']
if not is_md5(md5_hash):
return print_n_send_error_response(
request,
'Invalid Parameters',
api)
src = Path(settings.UPLD_DIR) / md5_hash / 'DYNAMIC_DeviceData'
sfile = src / fil
src = src.as_posix()
if not is_safe_path(src, sfile.as_posix(), fil):
err = 'Path Traversal Attack Detected'
return print_n_send_error_response(request, err, api)
# Check if it's a binary plist first
if fil.endswith('.plist'):
# Read as bytes to check for binary plist
raw_data = sfile.read_bytes()
if raw_data.startswith(b'bplist0'):
# It's a binary plist, load it properly
from plistlib import loads
try:
plist_data = loads(raw_data)
dat = dumps(plist_data, fmt=FMT_XML).decode('utf-8', 'ignore')
except Exception as e:
logger.warning('Failed to parse binary plist: %s', e)
dat = raw_data.decode('utf-8', 'ignore')
else:
# It's a text plist, read as text
dat = raw_data.decode('utf-8', 'ignore')
else:
dat = sfile.read_text('ISO-8859-1')
if fil.endswith(('.xml', '.plist')) and typ in ['xml', 'plist']:
rtyp = 'xml'
elif typ == 'db':
dat = None
sql_dump = read_sqlite(sfile.as_posix())
rtyp = 'asciidoc'
elif typ == 'others':
rtyp = 'asciidoc'
else:
err = 'File type not supported'
return print_n_send_error_response(request, err, api)
fil = escape(ntpath.basename(fil))
context = {
'title': fil,
'file': fil,
'data': dat,
'sqlite': sql_dump,
'type': rtyp,
'version': settings.MOBSF_VER,
}
template = 'general/view.html'
if api:
return context
return render(request, template, context)
except Exception:
logger.exception('Viewing File')
return print_n_send_error_response(
request,
'Error Viewing File',
api)