Apple 2025年7月安全更新与Python取证脚本实战

本文详细分析了Apple 2025年7月发布的89个安全漏洞,涵盖iOS、macOS等多平台,并提供了一个实用的Python取证脚本,用于快速扫描和匹配文件中的关键词,提升调查效率。

Apple 2025年7月安全更新

Apple于2025年7月29日发布了iOS、iPadOS、macOS、watchOS、tvOS和visionOS的更新。此次更新虽为功能发布,但包含了重要的安全补丁,共修复了89个不同的漏洞。目前尚未发现这些漏洞被利用的情况。

Apple的漏洞描述通常较为简略。大多数漏洞可能导致拒绝服务(DoS)问题,造成系统或子系统崩溃。本次更新中还修复了一些权限提升和沙箱逃逸漏洞。被标记为内存损坏或堆损坏的漏洞可能导致代码执行,但由于Apple提供的信息有限,具体影响范围难以确定。

部分值得关注的漏洞包括:

  • CVE-2025-43217:麦克风或摄像头访问的隐私指示器可能无法正确显示。这很可能指的是控制中心旁边的绿点,而非某些Apple笔记本上的物理LED指示灯。
  • CVE-2025-43240:下载来源可能被错误关联。可能是一个“Web标记”问题?Apple使用扩展文件属性来处理此问题。遗憾的是,没有详细信息来审查现有下载。

对于macOS,可追溯到Ventura(macOS 13)的版本都有安全更新。对于iOS/iPadOS,18和17版本均有更新可用。

受影响版本

  • iOS 18.6 and iPadOS 18.6
  • iPadOS 17.7.9
  • macOS Sequoia 15.6
  • macOS Sonoma 14.7.7
  • macOS Ventura 13.7.7
  • watchOS 11.6
  • tvOS 18.6
  • visionOS 2.6

部分漏洞详情

CVE编号 描述 影响组件
CVE-2025-24119 应用可能能够在其沙箱外执行任意代码或获得某些提升的权限 Finder
CVE-2025-24188 处理恶意制作的网页内容可能导致Safari意外崩溃 Safari
CVE-2025-24220 应用可能能够读取持久设备标识符 沙箱配置文件
CVE-2025-24224 远程攻击者可能能够导致意外系统终止 内核
CVE-2025-31229 密码可能被VoiceOver朗读出来 辅助功能
CVE-2025-31243 应用可能能够获得root权限 AppleMobileFileIntegrity
CVE-2025-31273 处理恶意制作的网页内容可能导致内存损坏 WebKit
CVE-2025-31275 沙箱进程可能能够启动任何已安装的应用 MediaRemote
CVE-2025-31276 即使“加载远程图像”设置关闭,远程内容仍可能被加载 邮件草稿
CVE-2025-31278 处理恶意制作的网页内容可能导致内存损坏 WebKit

(此处省略部分漏洞详情,完整列表参见原文)


取证分析:Python脚本加速数据筛选

在需要快速分析大量数据时,关键的一步是筛选(Triage)。在取证调查中,这一步骤至关重要,因为它允许调查人员从海量数据中快速识别、优先处理和隔离最相关或高价值的证据,确保有限的时间和资源集中在最可能揭示事件关键事实的工件上。有时,一个简单的脚本就足以加速这一任务。

今天,我正在处理一个包含超过20,000个混合文件的目录。其中有很多ZIP压缩包(主要是Office文档),这些压缩包内也包含大量文件。目标是扫描所有这些文件(包括ZIP压缩包)以查找某些关键词。我编写了一个快速的Python脚本,该脚本将使用嵌入的YARA规则扫描所有文件,如果找到匹配项,则将原始文件复制到目标目录。

以下是脚本代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#
# Quick Python triage script
# Copy files matching a YARA rule to another directory
#
import yara
import os
import shutil
import zipfile
import io

# YARA rule
yara_rule = """
rule case_xxxxxx_search_1
{
    strings:
        $s1 = "string1" nocase wide ascii
        $s2 = "string2" nocase wide ascii
        $s3 = "string3" nocase wide ascii
        $s4 = "string4" nocase wide ascii
        $s5 = "string5" nocase wide ascii
    condition:
        any of ($s*)
}
"""

source_dir = "Triage"
dest_dir = "MatchedFiles"
os.makedirs(dest_dir, exist_ok=True)
rules = yara.compile(source=yara_rule)

def is_zip_file(filepath):
    """
    Check ZIP archive magic bytes.
    """
    try:
        with open(filepath, "rb") as f:
            sig = f.read(4)
            return sig in (b"PK\x03\x04", b"PK\x05\x06", b"PK\x07\x08")
    except Exception:
        return False

def safe_extract_path(member_name):
    """
    Returns a safe relative path inside the destination folder (Prevent .. in paths).
    """
    return os.path.normpath(member_name).replace("..", "_")

def scan_file(filepath, file_bytes=None, inside_zip=False, zip_name=None, member_name=None):
    """
    Scan a file with YARA.
    """
    try:
        if file_bytes is not None:
            matches = rules.match(data=file_bytes)
        else:
            matches = rules.match(filepath)

        if matches:
            if inside_zip:
                print("[MATCH] {member_name} (inside {zip_name})")
                rel_path = os.path.relpath(zip_name, source_dir)
                filepath = os.path.join(source_dir, rel_path)
                dest_path = os.path.join(dest_dir, rel_path)
            else:
                print("[MATCH] {filepath}")
                rel_path = os.path.relpath(filepath, source_dir)
                dest_path = os.path.join(dest_dir, rel_path)
            
            # Save a copy
            os.makedirs(os.path.dirname(dest_path), exist_ok=True)
            shutil.copy2(filepath, dest_path)
    except Exception as e:
        print(e)
        pass

# Main
for root, dirs, files in os.walk(source_dir):
    for name in files:
        filepath = os.path.join(root, name)
        if is_zip_file(filepath):
            try:
                with zipfile.ZipFile(filepath, 'r') as z:
                    for member in z.namelist():
                        if member.endswith("/"):  # Skip directories
                            continue
                        try:
                            file_data = z.read(member)
                            scan_file(member, file_bytes=file_data, inside_zip=True, zip_name=filepath, member_name=member)
                        except Exception:
                            pass
            except zipfile.BadZipFile:
                pass
        else:
            scan_file(filepath)

运行脚本后,您可以看到匹配结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[MATCH] docProps/app.xml (inside Triage\xxxxxxx.xlsx)
[MATCH] xl/sharedStrings.xml (inside Triage\xxxxx.xlsx)
[MATCH] xl/sharedStrings.xml (inside Triage\xxxxxxxxxxxxxxxxxxxx.xlsx)
[MATCH] ppt/slides/slide3.xml (inside Triage\xxxxxxxxxxxxxxxxxxxxxx.pptx)
[MATCH] ppt/slides/slide12.xml (inside Triage\xxxxxxxxxxxxxxxxxxxxxx.pptx)
[MATCH] ppt/slides/slide14.xml (inside Triage\xxxxxxxxxxxxxxxxxxxxxx.pptx)
[MATCH] ppt/slides/slide15.xml (inside Triage\xxxxxxxxxxxxxxxxxxxxxx.pptx)
[MATCH] xl/sharedStrings.xml (inside Triage\xxxxxxxx.xlsx)
[MATCH] Triage\xxxxxxxxxxxxxxxxxxxxxxx.pdf
[MATCH] Triage\xxxxxxxxxxxxxxxxxxx.xls
[MATCH] xl/sharedStrings.xml (inside Triage\xxxxxxxxxxxxxxxx.xlsx)
[MATCH] Triage\xxxxxxxxxxxxxxxxxxxxxxxxxx.xls

通过几行Python代码,您可以加速调查中的筛选阶段。请注意,该脚本是为处理我当前的文件集而编写的,并不适用于更广泛的用途(例如处理受密码保护的压缩包或其他类型的压缩包)。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计