ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

CTF靶场SQL注入实战:从信息收集到数据提取的完整流程

CTF靶场SQL注入实战:从信息收集到数据提取的完整流程 1. 靶场环境与目标分析拿到一个CTF题目尤其是像“LoveSQL”这种名字基本可以确定是SQL注入的靶场。题目来自“极客大挑战 2019”这类比赛题目通常不会设置过于复杂的绕过核心是考察对SQL注入基础流程的掌握以及信息收集、手工注入的技巧。很多新手一看到登录框就想着用万能密码‘ or ‘1’‘1去试但更专业的做法是先摸清整个靶场的“地形”。首先我们得明确目标。这是一个Web靶场通常访问给出的URL题目一般会提供一个链接会看到一个登录页面。我们的最终目标是通过SQL注入漏洞获取到隐藏在数据库中的关键信息也就是所谓的“flag”。这个flag可能是一串字符串藏在某个数据库的某个表的某个字段里。整个挑战的过程本质上就是一次未经授权的数据库信息探测和提取。在开始“注入”之前有几步准备工作是必须的。第一使用浏览器开发者工具F12查看页面源码看看有没有注释掉的提示、隐藏的表单或者引用的JS文件这些地方有时会泄露数据库结构或查询逻辑。第二用Burp Suite这类工具拦截一下登录请求观察请求参数是如何传递的GET还是POST参数名是什么比如常见的username和password。第三对任何输入点进行基础的模糊测试比如在用户名和密码框里输入一个单引号‘看看页面的回显是否有变化是否出现了数据库报错信息。报错信息是黄金线索它能直接或间接地告诉我们后端使用的数据库类型MySQL、SQL Server、PostgreSQL等以及查询语句的拼接方式。对于这道题根据经验它很可能是一个基于MySQL的注入并且登录逻辑存在漏洞。我们接下来的所有操作都将围绕“如何利用这个漏洞一步步从数据库里掏出我们想要的东西”来展开。这个过程就像开一个没有钥匙的锁我们需要通过试探锁芯的结构数据库结构来制作一把合适的钥匙注入Payload。2. 漏洞点探测与初步信息收集实战开始。假设我们访问靶场地址看到一个典型的登录界面。第一步不是盲注而是先进行“友好”的试探。在用户名框输入一个单引号‘密码框随意输入比如123然后点击登录。注意输入单引号后要仔细观察页面反应。如果页面直接白屏或者显示了一串包含“You have an error in your SQL syntax”的报错信息那基本就坐实了存在SQL注入漏洞并且是报错型注入。如果页面只是提示“登录失败”或“用户名或密码错误”没有具体报错那可能是盲注布尔盲注或时间盲注。从“LoveSQL”这个题目的普遍解法来看它大概率是报错型注入这会让我们的过程顺利很多。假设我们收到了类似下面的报错You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near at line 1这个报错非常经典。它告诉我们几件事1. 后端数据库是MySQL。2. 我们的单引号破坏了SQL语句的语法。3. 错误信息被直接回显到了前端这属于报错注入。从错误信息near ‘’’’’可以推断我们输入的单引号被直接拼接进了SQL语句。原始的查询语句可能长这样SELECT * FROM users WHERE username ‘我们输入的内容’ AND password ‘我们输入的内容’当我们输入‘作为用户名时语句变成了SELECT * FROM users WHERE username ‘’’ AND password ‘123’这就导致了单引号不匹配的语法错误。接下来我们需要确认注入点是否真的可利用并尝试闭合这个语句。常用的探测Payload是‘ or ‘1’‘1。但这里有个小技巧为了更清晰地看到语句是如何闭合的我们可以使用注释符。在MySQL中#和--注意--后面有个空格是行注释符。我们尝试在用户名框输入‘ or 11 #密码框随意。这个Payload的意图是用单引号‘闭合掉username字段原本的开头引号然后添加一个恒真条件or 11再用#将后面原本的AND password...全部注释掉。如果登录成功说明注入点存在且可利用。如果成功了我们不仅绕过了登录还验证了这是一个基于单引号字符型的注入点。但我们的目标不是登录而是获取数据。所以在确认漏洞存在后我们需要进行更深入的信息收集。这里要用到MySQL的内置函数和系统数据库。一个非常关键的步骤是判断当前查询返回的字段数。这是为了后续使用UNION SELECT联合查询做准备。UNION操作要求前后两个SELECT语句的列数必须相同。常用的方法是使用ORDER BY子句。我们在用户名框输入‘ order by 1 #然后‘ order by 2 #‘ order by 3 #……依次递增数字。ORDER BY n表示根据第n列进行排序。如果n超过了实际列数数据库就会报错。当我们输入‘ order by 4 #时页面报错而‘ order by 3 #时页面正常可能是返回登录失败页面但不报语法错误那么就说明当前查询的列数是3。假设我们测出来是3列。接下来我们还需要确认哪些列的回显位置在页面上是可见的方便我们之后把想要的数据“显示”出来。这需要用到UNION SELECT。我们构造Payload‘ union select 1,2,3 #。注意因为原查询可能未返回数据比如错误的用户名导致查询结果为空所以前面要用‘闭合并让原查询结果为空例如‘ and 12然后接上union。更常见的写法是‘ and 12 union select 1,2,3 #。and 12确保前半部分查询不返回任何结果这样页面显示的内容就完全来自我们union后面的select 1,2,3。提交后观察页面。如果页面的某个位置出现了数字“2”和“3”通常不会显示“1”可能被用于其他逻辑比如在登录后的欢迎信息、错误提示框或者页面源码的某个角落这就说明第2和第3列的数据会被输出到页面上。我们记下这些位置它们就是我们后续“显示”数据库信息的屏幕。3. 数据库结构探查与数据提取知道了回显点我们就可以开始“查户口”了。首先获取当前连接的数据库名。MySQL中database()函数返回当前数据库名称。我们使用Payload‘ and 12 union select 1, database(), 3 #。提交后在刚才数字“2”显示的位置应该就会变成实际的数据库名假设它返回了geek。拿到数据库名后下一步是获取这个数据库里有哪些表。MySQL中存储表结构信息的元数据存储在名为information_schema的系统数据库中其中的TABLES表记录了所有表的信息。我们查询geek数据库下的所有表名‘ and 12 union select 1, group_concat(table_name), 3 from information_schema.tables where table_schema‘geek’ #。这里解释一下几个关键点group_concat(): 这是一个聚合函数它把查询到的多行结果多个表名连接成一个字符串用逗号分隔。这样我们就能在一个回显点里看到所有表名而不是只能看到第一行。information_schema.tables: 这是系统表。where table_schema‘geek’: 这个条件限定了只查询数据库名为geek的表。假设查询结果返回了geekuser, l0ve1ysq1两个表名。通常CTF的flag会放在一个名字看起来比较奇怪的表里l0ve1ysq1这个表名就非常可疑符合“LoveSQL”的主题。当然geekuser可能是存放用户凭证的表。接下来我们需要查看可疑表l0ve1ysq1有哪些列字段。同样查询information_schema数据库这次是COLUMNS表‘ and 12 union select 1, group_concat(column_name), 3 from information_schema.columns where table_schema‘geek’ and table_name‘l0ve1ysq1’ #。假设返回了id, username, password三个列名。这看起来像一个标准的用户表结构但flag可能就藏在某个字段里。我们需要把这张表的数据全部“拖”出来看看。使用最终的查询Payload‘ and 12 union select 1, group_concat(username, ‘:’, password), 3 from geek.l0ve1ysq1 #。这个Payload做了几件事union select联合查询利用已知的回显位置第2列。from geek.l0ve1ysq1指定从geek数据库的l0ve1ysq1表查询。group_concat(username, ‘:’, password)将username和password字段的值用冒号连接起来然后所有行再合并成一个字符串。这样我们就能一次性看到所有数据。提交这个Payload后在页面的回显位置我们期望看到类似这样的输出admin:admin123, test:test456, ... flag:flag{th1s_is_a_fake_fl4g}我们需要在这些数据中寻找格式类似flag{...}的字符串那就是本题的答案。4. 手工注入过程中的细节陷阱与技巧整个流程听起来很顺但实际操作中会遇到各种小坑。第一个坑是关于注释符。在URL中传递参数时#号通常被浏览器认为是锚点不会发送到服务器。所以如果注入点是GET请求参数在URL里直接写#可能会失效。解决方法有两种一是使用URL编码后的#即%23二是使用--两个减号一个空格在URL中空格需要编码为或%20所以Payload会变成‘ or 11 --。这个号在Burp Suite里可以直接打它代表空格。第二个坑是关于group_concat()的长度限制。group_concat()函数有一个默认的最大长度1024字节。如果表里的数据非常多超过了这个限制查询结果就会被截断导致我们看不到完整数据可能恰好就把flag给截没了。解决方法是在查询前修改这个限制或者分批次查询。可以先用count(*)看看表里有多少行数据如果太多可以结合limit子句分批查询例如‘ union select 1, username, password from l0ve1ysq1 limit 0,1 #查询第1行然后limit 1,1查询第2行以此类推。第三个坑是数据回显位置可能不直观。有时数字2,3并不会直接显示在页面的文本中而是隐藏在HTML标签的属性里比如input value“2”或者作为某个JS变量的一部分。因此在测试回显点时一定要右键“查看页面源代码”CtrlU在完整的HTML源码里搜索1,2,3这些数字才能准确定位。第四个技巧是关于效率。在不知道表名的时候我们是通过information_schema.tables来查的。但有些CTF题目会过滤information_schema这个关键词。这时就需要用到MySQL的另一些特性。在MySQL 5.6及以上版本我们可以尝试查询sys.schema_auto_increment_columns等视图或者使用无列名注入技术。对于这道2019年的题通常不会设置这么强的过滤但知道这些备选方案是资深选手的素养。最后一个非常重要的习惯保存你的Payload和每一步的返回结果。可以用文本文件或者Burp Suite的Logger功能。这样当你在复杂的注入过程中迷失时可以回溯检查。尤其是在尝试判断列数、回显点时把order by X和union select 1,2,3...的请求与响应一一对应记录下来能帮你快速理清思路。整个“LoveSQL”的解题过程本质上就是一套标准的手工SQL注入流程探测注入点 - 判断类型和列数 - 寻找回显位置 - 获取数据库名 - 获取表名 - 获取列名 - 提取数据。这道题没有设置WAFWeb应用防火墙过滤没有奇怪的编码考察的就是最基础、最核心的链条是否清晰。把这套流程走通、走熟遇到更复杂的变形题目时你才能知道基础环节哪里可能被加固又该如何绕过。真正的内功往往就体现在对这些基础步骤的深刻理解和灵活运用上而不是死记硬背几个Payload。
返回列表