mirror of
https://github.com/mvt-project/mvt.git
synced 2026-07-28 07:08:49 +02:00
* fix(ios): scan Safari profile databases for history and browser state Safari profiles (iOS 17 and later) keep their own databases under Library/Safari/Profiles/<UUID>/, but SafariHistory and SafariBrowserState only ever looked at the default profile's Library/Safari/History.db and Library/Safari/BrowserState.db. On a device where browsing happens inside a profile, MVT silently skipped that history and still reported no detections, so an indicator only ever visited within a profile went unnoticed. Both modules now also match Library/Safari/Profiles/*/ in backups and in filesystem dumps. No helper changes were needed: the Manifest.db lookup already translates "*" into a SQL LIKE wildcard, and the filesystem lookup already globs. Found while examining an encrypted iOS 26.5.2 backup that contained 14 per-profile History.db files under AppDomain-com.apple.mobilesafari::Library/Safari/Profiles/<UUID>/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ios): scope Safari redirects to history database --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Janik Besendorf <janik@besendorf.org>
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
# Mobile Verification Toolkit (MVT)
|
|
# Copyright (c) 2021-2023 The MVT Authors.
|
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
|
# https://license.mvt.re/1.1/
|
|
|
|
import logging
|
|
import shutil
|
|
|
|
import pytest
|
|
|
|
from mvt.common.indicators import Indicators
|
|
from mvt.common.module import run_module
|
|
from mvt.ios.modules.mixed.safari_browserstate import SafariBrowserState
|
|
|
|
from ..utils import add_backup_manifest_entry, get_ios_backup_folder
|
|
|
|
# fileID of HomeDomain::Library/Safari/BrowserState.db in the test backup.
|
|
DEFAULT_BROWSER_STATE_FILE_ID = "3a47b0981ed7c10f3e2800aa66bac96a3b5db28e"
|
|
PROFILE_UUID = "00000000-0000-4000-A000-000000000001"
|
|
PROFILE_BROWSER_STATE_FILE_ID = "bb00000000000000000000000000000000000001"
|
|
|
|
|
|
@pytest.fixture
|
|
def backup_with_safari_profile(tmp_path):
|
|
"""An iTunes backup where a Safari profile has its own browser state."""
|
|
backup_path = tmp_path / "backup"
|
|
shutil.copytree(get_ios_backup_folder(), backup_path)
|
|
|
|
profile_db = (
|
|
backup_path / PROFILE_BROWSER_STATE_FILE_ID[:2] / PROFILE_BROWSER_STATE_FILE_ID
|
|
)
|
|
profile_db.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(
|
|
backup_path / DEFAULT_BROWSER_STATE_FILE_ID[:2] / DEFAULT_BROWSER_STATE_FILE_ID,
|
|
profile_db,
|
|
)
|
|
add_backup_manifest_entry(
|
|
backup_path,
|
|
PROFILE_BROWSER_STATE_FILE_ID,
|
|
"AppDomain-com.apple.mobilesafari",
|
|
f"Library/Safari/Profiles/{PROFILE_UUID}/BrowserState.db",
|
|
)
|
|
|
|
return str(backup_path)
|
|
|
|
|
|
class TestSafariBrowserStateModule:
|
|
def test_parsing(self):
|
|
m = SafariBrowserState(target_path=get_ios_backup_folder())
|
|
m.is_backup = True
|
|
run_module(m)
|
|
assert len(m.results) == 1
|
|
assert len(m.timeline) == 1
|
|
assert len(m.alertstore.alerts) == 0
|
|
|
|
def test_parsing_backup_with_profile(self, backup_with_safari_profile):
|
|
m = SafariBrowserState(target_path=backup_with_safari_profile)
|
|
m.is_backup = True
|
|
run_module(m)
|
|
|
|
# Both the default profile and the named profile are extracted.
|
|
assert len(m.results) == 2
|
|
assert len({result["safari_browser_state_db"] for result in m.results}) == 2
|
|
|
|
def test_detection(self, indicator_file):
|
|
m = SafariBrowserState(target_path=get_ios_backup_folder())
|
|
m.is_backup = True
|
|
ind = Indicators(log=logging.getLogger())
|
|
ind.parse_stix2(indicator_file)
|
|
# Adds a file that exists in the manifest.
|
|
ind.ioc_collections[0]["domains"].append("en.wikipedia.org")
|
|
m.indicators = ind
|
|
run_module(m)
|
|
assert len(m.alertstore.alerts) == 1
|
|
assert len(m.results) == 1
|
|
assert m.results[0]["tab_url"] == "https://en.wikipedia.org/wiki/NSO_Group"
|