mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-11 22:50:09 +00:00
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.
105 lines
3.2 KiB
Python
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)
|