尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CTF SQL注入进阶:从171到213题绕过技巧与实战解析

CTF SQL注入进阶:从171到213题绕过技巧与实战解析 1. 先说清楚这道题区间到底考什么ctfshow的SQL注入题目从171到213这一长串数字看起来是个大工程但你要是真的顺着刷下来会发现它其实是一条非常清晰的进阶路线。前面几道题还在考最基础的联合查询、报错注入中间开始加入各种过滤和绕过到后面基本上就是拿真实环境里的骚操作在虐你了。很多人在刷到这一段的时候会出现一个很典型的状态前面一两道题做得很顺后面突然就卡住了尤其是遇到空格被过滤、逗号被过滤、关键字被替换这种题一下子不知道从哪里下手。我当初刷这段的时候也一样最惨的一次是一道题卡了两个多小时最后发现就是一个很简单的绕过姿势没转过弯来。所以这篇文章我想换个角度不按题号一道一道写writeup而是把171到213这40多道题涉及的核心知识点拆开讲清楚配合我实际做题时用到的payload和踩过的坑给你一套能复用的解题思路。不管你是刚刷完前170道、正准备进入这个区间的新手还是已经刷到一半、想回头整理思路的老手这篇文章应该都能帮你把这块拼图补完整。先说个总体的判断这段题目的难度曲线是阶梯式上升的不是平滑过渡。大概可以分成四个阶段——基础注入巩固、报错与盲注专项、过滤绕过的花样、以及综合型的堆叠注入和读写文件操作。每个阶段之间有明显的跳变如果你感觉某道题突然变难了大概率就是进入了下一个阶段。2. 注入点识别与类型判断做对后面的所有事2.1 三步确认注入点的存在不管题目怎么变第一步永远是确认注入点。很多新手在这一步就翻车是因为过于依赖工具或者经验一上来直接丢sqlmap跑半天没结果就慌了。我习惯的做法是先手工确认三个问题参数是否可控、是否能影响SQL语句、以及回显是否可见。拿最常见的GET型注入来举例假设有个URL是这样的http://target.com/news.php?id1第一步是改动参数值看页面是否跟着变。把id改成2如果页面内容变了说明这个参数大概率是拼接进了SQL语句并影响到了查询结果如果页面完全不变那要么是静态页面要么参数压根没被后端使用。第二步是加一个单引号观察报错或行为差异。http://target.com/news.php?id1如果页面直接报SQL语法错误或者在加引号前后页面呈现完全不同的形态比如原来是正常内容现在变成了空页面那基本可以断定这里存在SQL注入。如果页面和原来一模一样也别急着放弃有可能后端做了转义或者整型参数根本没有被引号包裹单引号只是作为一个普通字符被处理了。第三步是试试用运算表达式探测。http://target.com/news.php?id2-1如果页面内容和id1时一样那说明这个注入点很可能是数字型参数值被直接用于运算后再拼入SQL。如果页面内容和id2-1这个字面值对应的结果一致比如直接当成字符串2-1去查询了那就是字符型。对ctfshow 171这道题前面的基础题目来说这套流程基本能快速锁定注入点。有一道题我记得是用POST方式提交id但判断逻辑一模一样只是换了个传参位置很多人被这个假象迷惑了。2.2 判断列数与回显位置的几条经验确认注入点后接下来的标准动作是order by判断列数然后union select找回显位。这个流程本身不难但我总结几个非常关键的经验order by的数字从1开始递增试探直到报错为止。用单引号配合注释符时要小心不同的数据库对注释符的支持不一样MySQL里可以使用--或者#或者--%20其中--中的加号在URL里代表空格如果你是在Burp Suite里直接改包而不是用浏览器访问要注意URL编码的问题。如果order by 3正常、order by 4报错说明是三列。数字型注入不需要闭合引号字符型注入需要在参数值后面先闭合掉前面的引号再接union。回显位的确认最稳妥的方法是把每一列都输出一遍特定标识数字。比如select 1,2,3观察哪个位置出现了数字那个位置就是可回显的。有些题目只有部分列回显到页面其他列是盲的。ctfshow这个区间的早期题目有一个特点直接给你无过滤环境联查就完事了。但你不要只求做出题扎实走完判断流程对后面的过滤题帮助巨大因为后面所有的绕过思路都是建立在“我知道这里本来应该填什么”的基础上的。3. 过滤绕过这个区间的重头戏过了基础题之后从大概180多题开始题目就变得有意思了。你会发现专门有一批题目在折腾你的绕过滤能力各种你想得到想不到的关键字符都被ban了。这块我拆开细说。3.1 空格被过滤时的三种替代方案空格被过滤是ctfshow里非常经典的一个考点。你不能在SQL语句里写空格了但这不代表没法拼出完整语句思路就是找替代空格的字符。在MySQL里面注释符/**/可以直接替代空格这是最常见也最稳定的方案。比如select/**/database()/**/from/**/information_schema.tables不需要额外说明就是把所有空格换成/**/语句照样能跑。第二个替代方案是用括号。MySQL允许函数名和括号之间不加空格查询条件也可以用括号来消除空格。比如用union注入时id1/**/union/**/select/**/1,2,3%23如果你后面遇到了空格和注释符同时被过滤的情况那就得考虑用%09Tab、%0a换行这类URL编码字符来替代。这类控制字符在MySQL的语法解析里同样可以充当空白分隔符。实际做题时有时候不算特别稳定但是确实有一些题目专门在考这个点。这里有个非常关键的实操经验很多题目是在后端直接用正则过滤你传入的字符串的所以/*和*/如果分别被过滤了你就没法用注释符。但是如果没有过滤注释里的内容你甚至可以把payload本身作为注释的一部分传进去让后端正则无法匹配这个技巧在长payload的时候特别好用。3.2 关键字过滤大小写、双写、内联注释关键字过滤是过滤题的另一大主力考法。常见的过滤列表大概包括select、union、where、from、information_schema这些。第一种绕过思路是大小写混合。如果后端是直接做字符串包含判断并且用了区分大小写的方式通常用正则的/i修饰符时会转为不区分但很多自定义过滤函数返回的是php的stripos那SeLeCt这种写法就能直接绕过。ctfshow这个区间有一部分题目就是这么设计的给你过滤了但过滤写得不严谨。第二种思路是双写。原理非常巧妙如果后端把select替换成空字符串并且只替换一次那么你传selselectect经过替换后剩下的正好是select。这是ctfshow里非常高频的考法一路上会遇到好几道题。你只需要写一个简单的小脚本把payload里的关键词都重复一遍就能自动化解决。第三种思路是利用MySQL的内联注释语法。MySQL支持一种特殊的可执行注释/*!50000select*//*!后面跟数字再跟SQL语句这种形式的注释在MySQL里会被当作真实代码来执行但在其他数据库里就是普通注释。用这个可以绕过一些简单的关键字匹配因为中间的关键字被包裹在注释结构里正则很难覆盖到所有情况。还要提一下information_schema这个高频被过滤的词。ctfshow里很多题把这个表名过滤了但其实MySQL里还有几个系统库也能查到类似信息比如mysql.innodb_table_stats和sys.schema_auto_increment_columns。如果你在真实环境里做题这几个表都是可用的备用方案。在ctfshow里有一两道题你直接用sys.schema_auto_increment_columns就能拿到表名但很多人只知道information_schema卡在那里出不来。3.3 逗号与等号过滤的高阶玩法逗号被过滤是最让人头疼的情况之一。union select的列之间需要逗号limit参数需要逗号substr函数的参数需要逗号。你没法写逗号就得想别的办法。union注入里逗号被过滤时可以用join语法来代替列的罗列select 1 union select * from (select 1)a join (select 2)b join (select 3)c这样就不需要逗号了。这个写法在ctfshow的某道题里有奇效。另外substr函数的逗号被过滤也有替代方案。用substr的语法substr(str from 1 for 1)这种写法也就是用from和for关键字来替代逗号非常实用substr((select database()) from 1 for 1)等号被过滤的题我印象里也遇到过。比如where id1这种没法写等号的情况可以用where id like 1或者where id in(1)来代替。有些场景下还可以用大于小于号来构造范围只要你能找到等价条件就行。4. 盲注的两种形态布尔与时间的实战取舍4.1 怎样快速判断一道题是不是盲注当你执行union select后页面没有任何变化说明当前注入点很可能不返回查询结果给页面。这时候就需要盲注了。ctfshow这个区间的盲注题算是比较友好的因为每道题的过滤环境都很明确你很容易判断用布尔还是时间。先看一个最直观的判断方法传一个会改变布尔结果的payload如果页面出现不同的渲染结果真值页面有内容假值页面是空或者只有固定文案那就是布尔盲注可以用二分法逐步跑数据。如果无论真假页面都完全一样那就得用时间盲注通过sleep函数延时来制造可观测的差异。布尔盲注的核心是逻辑判断和条件构造。比如判断当前数据库名的第一个字符是不是aid1 and if(substr(database(),1,1)a,1,0)%23如果页面正常说明第一个字符确实是a。这类判断逐位跑下去就能拼出所有数据。速度上建议写脚本跑而不要手工猜因为几十位字符手工猜是撑不住的。4.2 写一个简单的自动化判断脚本相比手工会快得多。我参考之前解题时用过的思路写了个非常简单的Python逻辑框架流程是这样的import requests url http://target.com/query.php chars abcdefghijklmnopqrstuvwxyz0123456789_,.- def test(payload): data {id: payload} res requests.post(url, datadata) return 某个成功标记 in res.text result for i in range(1, 50): for c in chars: payload f1 and ascii(substr((select database()),{i},1)){ord(c)}%23 if test(payload): result c break else: break print(result)实际使用时要按照页面的成功标记来调整判断函数。如果你判断的条件里用了ascii()函数和等于号而题目过滤了等号就要换成like或者用大于小于的二分比较法来绕过这个点很关键。时间盲注相对烦一些因为它需要等延迟。一个比较稳的写法是id1 and if(ascii(substr(database(),1,1))97,sleep(3),0)%23执行正常情况下会等待3秒才返回页面异常情况下瞬间返回。但你在脚本里要根据响应时间设置阈值比如超过2秒算真、否则算假。这里有个特别容易踩的坑如果你把sleep(3)写在if条件为真时而网速本身很慢可能会导致误判。建议在脚本里分别请求一次已知真值和已知假值先校准好阈值再开始跑数据。4.3 盲注脚本里的几个不常见但有效的加速技巧盲注跑起来很慢尤其是时间盲注一秒一秒地熬。我总结过几个优化方式用二分法而不是逐字符穷举。确定一个字符的ASCII范围后用二分查找可以大幅减少请求次数。把多个字符的bit位一次判断出来。通过ord函数转换成数字后对每一位bit值单独判断一个字符只需要8次请求比50个候选字符一个个试要快得多。如果后端支持多语句或者可以拼接多个if判断可以考虑在一次请求里同时判断多个位置。这个技巧在真实环境中不太容易扩展但在ctfshow里部分题目完全没有限制可以用来加速。不过实话实说ctfshow这个区间多数盲注题目本身的数据量就不大表名库名的字符数量有限程序跑个十几秒就出结果了。你真正需要的不是赛跑而是写脚本时逻辑不要出错不然前功尽弃。5. 报错注入与堆叠注入实战思路5.1 updatexml和extractvalue的原理与细节报错注入在ctfshow这个区间占据了相当大的比重。它的原理很简单让数据库在计算过程中触发一个语法或函数错误错误信息被直接输出到页面而这些错误信息里包含我们想要的数据。MySQL中最常用的两个函数是updatexml和extractvalue。updatexml的用法是接收三个参数第二个参数要求是合法的XPath路径如果传入的数据不符合XPath格式MySQL就会爆出错误错误信息中包含你传参的内容。利用方式如下id1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)%23这里的0x7e是波浪号~的十六进制表示作用是让XPath报错时信息和正常文本明显区分开。concat把数据库名和波浪号拼接成一个整体由于波浪号不是合法XPath字符会在报错信息里原样返回。extractvalue的用法和updatexml类似只是参数位置不同id1 and extractvalue(1,concat(0x7e,(select database()),0x7e))%23报错注入最大的好处是不需要回显位只要页面上能显示报错信息你就能直接把数据取出来。ctfshow在很多过滤题里有意保留报错输出就是为了让解题者用这个方式。这里有几个容易踩的坑。第一个是concat出的结果如果超出32个字符MySQL会截断报错信息导致拿到的数据不全解决办法是把数据分段提取比如用substr每次取30个字符。第二个是有些题目会把updatexml和extractvalue直接过滤掉这时候就需要从其他报错触发点想办法比如exp()函数利用数学溢出报错不过它对版本有要求5.5.5以上通常可用。还有floor(rand(0)*2)配合group by触发主键冲突报错的经典方式但语法相对复杂id1 and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)%23这种payload会报主键重复的错误错误内容就是我们拼进去的数据。应对思路就是把能试的报错函数轮流试一遍谁让页面动了就用谁。5.2 堆叠注入的含义与CTF环境下的特殊价值堆叠注入指的是在同一连接中执行多条SQL语句通常用分号来分割。它在真实Web渗透中遇到的概率不算特别高因为很多数据库驱动和中间件默认不允许多语句执行。但CTF里这是一个常驻考点尤其是ctfshow的后期题几乎每道都在问你“能不能同时跑两条以上语句”。检测是否支持堆叠注入的方法很简单在原参数后面加分号加一条新语句观察页面表现。id1;select 1%23如果页面在原本查询结果后面出现了额外的内容或者产生了和之前不同的行为就说明支持堆叠执行。但实际上很多堆叠注入的题目页面不会把第二条语句的结果显示出来它只是让第二条语句在数据库里执行了而已。这个特性在CTF里的价值非常大尤其是配合修改数据或文件操作。比如id1;update users set password123456 where id1%23你没法查看数据但你可以改数据然后通过正常的业务逻辑来验证修改是否生效。ctfshow有几道题考的就是这个思路——通过堆叠注入修改管理员密码然后登录后台。还有一类考法是堆叠注入配合预处理语句来绕过对select的过滤id1;set sqlconcat(sel,ect database());prepare stmt from sql;execute stmt;%23动态拼接SQL字符串再执行这招在处理大量关键字过滤的堆叠注入题目时非常有效。6. 工具辅助与手工验证的取舍6.1 sqlmap在过滤题上到底能不能用、怎么用说到SQL注入sqlmap几乎是绕不开的工具。但在ctfshow的过滤题里直接默认参数跑往往效果很差原因很简单题目故意设置的过滤规则会拦掉sqlmap默认生成的payload。不过sqlmap并不是不能用它提供了tamper参数来加载绕过脚本把要绕过的特性交给脚本去处理比如空格被过滤可以用space2comment脚本、关键字被过滤可以用doubleup脚本、特殊字符用charencode等。我当时遇到一道过滤空格和多关键字的题用的是这样的命令格式sqlmap -u http://target.com/query.php?id1 --tamperspace2comment --batch --dbs执行时sqlmap会先尝试注入标准payload如果被过滤导致返回异常它会自动套用tamper脚本里的变换规则把请求体改写成绕过之后的形态。实测下来对一些只过滤了固定字符串的题非常管用。但要提醒一个观点ctfshow的过滤题不建议一上来就跑sqlmap。原因有两点。第一题目的过滤方式可能很刁钻比如只过滤了select而不过滤别名、或者只过滤了information_schema而其他系统表没过滤这种场景sqlmap的全自动检测反而容易混乱。第二手工绕一次能让你真正理解过滤的边界在哪里而工具跑出来你什么都没学到。我建议的做法是先手工确认注入点和基本绕过方案再决定要不要用sqlmap去辅助拉数据。6.2 手工与工具配合时的两条重要经验经验一sqlmap跑不出结果时先检查URL编码和请求头。ctfshow的很多题目对UA头或者Referer头有要求甚至有的会校验Cookiesqlmap默认的请求头有时候会被后端拒绝。用--headers参数补齐请求头经常能解决这个问题。经验二手工构造的payload先放在Burp Suite里验证再交给脚本。你不要直接拿一个没验证过的思路去写自动化脚本那样排错会很痛苦。最稳的做法是先在Burp里手工发送请求确认payload能够生效再把完整的请求格式搬到脚本或者工具里批量执行。7. 常见问题与排查技巧实录题目刷多了以后你会发现反复踩的坑其实就那么几个。我整理了一份基于常见情况的问题排查表按严重程度排了个序现象可能原因排查方向payload在Burp里能用浏览器里不行URL编码问题特殊字符被浏览器转换在Burp里改包发送不要用浏览器直接访问拼接后的URL页面一直返回500注释符没生效或引号没有闭合检查是否使用了#但在URL中没有编码成%23检查单引号闭合位置sqlmap跑很久没有结果过滤规则或自定义WAF干扰先用Burp手工确认存在注入再考虑调整tamper报错注入看不到报错信息函数被过滤或报错被统一处理尝试其他报错函数用堆叠注入预处理绕过盲注脚本判断全部为假条件比较方式错了比如等号被过滤把条件改用like或二分法比较updatexml返回数据不全报错信息被截断用substr分段取数据每次不超过30个字符双写绕过失效过滤系统可能做了二次替换尝试大小写混合或内联注释/*!50000select*/堆叠注入不生效后端/数据库驱动禁止多语句执行检查能不能用预处理语句实在不行放弃堆叠思路再补充几个容易出问题的细节。第一在MySQL 8.0的环境下select的解析顺序和5.7有一些差异尤其是涉及group by和limit的时候payload的写法可能会稍有不同。ctfshow搭建的靶场多数还是5.x版本但如果你在自己本地环境测试时发现payload不报错要想到环境版本差异的可能。第二关于information_schema.tables里的table_schema字段如果拿到的是一个十六进制或者编码后的库名记得转回明文。有些题目的库名带了特殊字符比如单引号就会导致你后续拼接payload失败。第三注意请求的频率。时间盲注每来一次sleep等待时间会拉长整体做题时间建议脚本里做好超时控制不要让某些断连状态卡住整个循环。8. 从171到213刷完我总结的几条做题心法这段题刷完之后我最大的感受是SQL注入的知识点虽然多但终归是围绕那么几个核心问题的排列组合——参数怎么传递、数据怎么截取、条件怎么判断、输出怎么获取。ctfshow这个题区间的价值在于它把每个知识点都单独拆出来出了一组题让你被迫把每种手法都练到肌肉记忆。我给后面刷这段题的人几个建议第一不要只追求做出题。ctfshow的题目允许你做出来之后再看其他人的writeup但就算做出来了也要试着想想这道题的过滤规则还能怎么破有没有第二种、第三种解法。我至少遇到过三题网上主流的解法和我的解法完全不同但都对。第二把常用的payload写进自己的备忘录里。你不需要记住每一个payload的每一个字符但你需要记住“什么场景下用什么思路”比如遇到空格过滤就想到注释符和换行符遇到逗号过滤就想到join和from-for写法遇到关键字过滤就想到双写和内联注释。思路在线临时查语法都来得及。第三多写脚本。手工注入练的是手感脚本注入练的是系统性思维。你写脚本时会逼自己去理解每一步请求的格式、判断条件和循环逻辑这个理解过程比刷十道题都有用。最后再分享一个我做这整段题目时的小习惯每道题做完后我会在本地记录三样东西——注入类型、过滤规则、最终payload。刷到后面你会发现同一个类型的题反复出现时翻一下自己之前的记录一分钟就能回到状态。这个习惯帮我省下了大量重复摸索的时间你如果正卡在这个区间可以试试。
返回列表