行业资讯
📅 2026/8/28 23:09:36
SQL注入实战:从手工探测到数据库信息提取的完整流程
1. 靶场环境与目标解析拿到一个CTF题目尤其是Web方向的第一步永远是信息收集和环境理解。这道题目标题是“【CTF_SQL】[极客大挑战 2019]LoveSQL 1”结合相关热词核心指向非常明确这是一道关于SQL注入的CTF题目并且很可能基于一个名为“LoveSQL”的登录或查询界面。题目年份是2019年这意味着它可能不会涉及太新的数据库特性或防护手段但作为经典题型其考察的注入原理和手工绕过技巧是永恒的。从网络热词中频繁出现的“sql注入万能密码”、“mariadb”等词汇我们可以合理推测靶场环境。很多CTF平台和线下赛题喜欢使用MariaDB或MySQL作为后端数据库因为其语法普及且默认配置下可能存在的安全特性如sql_mode为注入提供了更多可能性。题目编号为“1”通常意味着这是一个系列题的开端难度不会设置得过于变态旨在考察基础的联合查询注入流程。我们的核心目标是什么CTF中的SQL注入最终目的几乎都是获取一个被称为“flag”的特定字符串。这个flag可能藏在数据库的某个表、某个字段里。因此解题思路通常遵循一个标准流程判断注入点 - 判断数据库类型和结构 - 获取数据库名 - 获取表名 - 获取字段名 - 获取数据即flag。整个过程我们都需要与网页进行交互通过精心构造的输入让数据库执行我们期望的SQL语句并将结果回显在页面上。2. 手工注入探测与信息收集面对一个疑似存在SQL注入的Web界面比如一个登录框、搜索框或传参点我们不能一上来就“狂轰滥炸”。有策略、有步骤的探测是成功的关键。2.1 初步试探与注入类型判断首先我们需要找到注入点。假设题目是一个登录页面有用户名username和密码password两个输入框。后端SQL语句可能类似SELECT * FROM users WHERE username$username AND password$password我们的输入会被代入到$username和$password的位置。经典的试探方法是使用单引号‘来尝试闭合原有的SQL字符串观察页面反应。单引号测试在用户名框输入一个单引号‘然后随意输入密码如123。点击登录。如果页面返回了数据库错误信息如“You have an error in your SQL syntax...”这强烈暗示存在SQL注入漏洞并且错误信息可能暴露数据库类型如MySQL/MariaDB。如果页面只是提示“登录失败”或“用户名不存在”这并不代表没有注入可能只是被应用层优雅地处理了。我们需要进一步测试。逻辑测试这是判断注入是否存在以及类型的核心。永真条件测试尝试输入admin‘ or ‘1’‘1。代入到SQL语句中会变成SELECT * FROM users WHERE usernameadmin or 11 AND password$password由于‘1’‘1‘永远为真只要username’admin‘这部分能匹配上或者整个OR条件成立就可能绕过密码验证。如果使用‘ or 11 --注意最后的空格和注释符--效果更彻底它会注释掉后面的AND password部分。永假条件测试输入admin‘ and 12 --。如果页面正常返回“用户不存在”或登录失败而永真条件却能返回不同结果比如跳转到其他页面这基本确认存在基于字符型的SQL注入并且我们可以控制查询逻辑。注意注释符在不同数据库中不同。MySQL/MariaDB中--后面有个空格、#、/*...*/都是有效的。在URL或表单中#有时会被当作锚点所以--用加号代表空格或--%20URL编码的空格是更稳妥的选择。2.2 确定字段数与回显位确认存在注入后下一步是使用ORDER BY和UNION SELECT来确定当前查询语句选取的字段数量以及哪些字段的内容会被回显在页面上。这是后续获取数据的基础。ORDER BY猜解字段数ORDER BY子句用于根据指定列排序。如果ORDER BY 5表示根据第5列排序如果查询结果只有4列数据库就会报错。我们可以利用这个特性来猜解。 在注入点输入‘ order by 1 --页面正常。 然后尝试‘ order by 2 --‘ order by 3 --‘ order by 4 --... 直到某个数字N使得页面报错或显示异常那么字段数就是 N-1。 假设order by 4正常order by 5报错那么字段数就是4。UNION SELECT联合查询与确定回显位 得知字段数假设为4后我们构造联合查询。UNION操作符用于合并两个SELECT语句的结果集前提是列数必须相同。 输入‘ union select 1,2,3,4 --这个语句会尝试将我们自定义的(1,2,3,4)结果集与原有查询结果集合并。如果页面某处原本显示数据的地方变成了数字1,2,3,4中的某一个或几个那就说明该位置是“回显位”。这些数字可以被替换为我们想查询的数据库信息。 例如如果页面某处显示了2和3那么第2和第3个字段就是回显位。我们后续的查询比如database()version()就需要放在这两个位置上像这样‘ union select 1, database(), version(), 4 --。3. 数据库结构探秘与信息提取确定了回显位我们就拿到了从数据库读取信息的“通道”。接下来就是标准的“拖库”流程先看当前数据库再看有哪些表然后看目标表有哪些列最后把数据读出来。3.1 获取基础信息与数据库名利用回显位我们可以一次性获取多条有用信息‘ union select 1, database(), version(), user() --database(): 返回当前数据库名称。这是我们的首要目标。version(): 返回数据库版本信息。可以确认是MySQL还是MariaDB以及具体版本对于后续利用某些特性如报错注入函数有帮助。user(): 返回当前数据库用户。了解权限有时高权限用户如root能访问更多系统表。假设返回结果中database()显示为geek。那么我们的目标数据库就是geek。3.2 枚举数据库中的表名在MySQL和MariaDB中数据库的元数据如表名、列名信息存储在名为information_schema的默认数据库中。其中TABLES表存储了所有表的信息。 关键字段是TABLE_SCHEMA: 数据库名TABLE_NAME: 表名我们要查询geek数据库下的所有表。因为回显位可能有限比如只有2个而一个数据库里可能有几十张表我们需要用group_concat()函数将所有表名合并成一个字符串返回。‘ union select 1, group_concat(table_name), 3, 4 from information_schema.tables where table_schema‘geek’ --这条语句的意思是从information_schema.tables中选取table_schema为geek的所有记录的table_name字段并用group_concat()合并成一个字符串在回显位2显示出来。 返回结果可能像这样geekuser, l0ve1ysq1, news, admin...等等。我们需要从中寻找可能存储flag的表。表名有时会给出提示比如l0ve1ysq1love sql的变体就非常可疑flag,secret,users也值得关注。3.3 枚举目标表的字段名假设我们怀疑l0ve1ysq1这个表。下一步是查看它有哪些列字段。这需要查询information_schema.COLUMNS表。 关键字段TABLE_SCHEMA: 数据库名TABLE_NAME: 表名COLUMN_NAME: 字段名输入‘ union select 1, group_concat(column_name), 3, 4 from information_schema.columns where table_schema‘geek’ and table_name‘l0ve1ysq1’ --返回结果可能像id, username, password。如果看到像flag,value,secret这样的字段名那很可能就是目标。3.4 最终数据提取与Flag获取知道了表名和字段名最后一步就是直接查询数据。如果字段是id, username, password我们可以直接查询‘ union select 1, username, password, 4 from l0ve1ysq1 --如果字段太多或者我们只想看某个字段可以指定‘ union select 1, 2, 3, 4 from l0ve1ysq1 --先看结构 或者更直接地如果怀疑flag在password字段里‘ union select 1, group_concat(password), 3, 4 from l0ve1ysq1 --通常flag会以特定格式出现比如flag{xxxx-xxxx-xxxx}CTF{...}等。在返回的结果中仔细查找这样的字符串那就是本题的答案。4. 实战技巧、常见问题与深度防御思考手工注入的过程看似线性但实战中会遇到各种“小惊喜”。下面分享一些我踩过坑后总结的技巧和常见问题的排查思路。4.1 绕过过滤与编码技巧大小写绕过如果代码简单过滤了SELECT、UNION等关键词可以尝试SeLeCt、UnIoN。MySQL默认是不区分关键字大小写的。双写绕过如果过滤方式是删除关键词比如将select替换为空可以尝试selselectect删除中间的select后剩下的字符正好拼成select。注释符内联/*!SELECT*/这种格式在MySQL中是一种内联注释其中的代码会被执行。可以用来包裹关键词如/*!UNION*/ /*!SELECT*/。十六进制编码将字符串转换成十六进制。例如‘geek‘的十六进制是0x6765656b。在注入时可以用0x6765656b来代替‘geek‘有时可以绕过对单引号的过滤。‘ union select 1,2,3 from test where name0x6765656b --。URL编码在GET请求传参时特殊字符需要URL编码。空格是%20单引号是%27井号#是%23。--中的在URL中代表空格。4.2 无回显注入盲注的初步感知这道“LoveSQL 1”很可能是有回显的联合查询注入属于最简单的一类。但你要知道很多题目会关闭错误显示并且UNION查询的结果不会输出到页面即页面无论对错看起来都一样。这就是盲注。 盲注主要分两类布尔盲注页面会根据注入的SQL语句执行结果的真假返回不同的内容比如“存在”或“不存在”。我们通过and length(database())4、and substr(database(),1,1)‘g‘这样的语句像猜密码一样一位一位地判断。时间盲注页面无论真假都返回相同内容。我们通过and sleep(5)这样的语句如果页面响应延迟了5秒就说明条件为真。通过判断延迟来逐位获取信息。 虽然本题可能用不到但这是SQL注入知识体系的重要部分。4.3 常见错误与排查清单UNION语句前后列数不一致这是最常见的错误。务必先用ORDER BY精确确定字段数。数据类型不匹配如果原查询的某个字段是字符串类型而你在UNION的对应位置放了个数字1可能引发错误。稳妥起见可以用null或者‘a‘来占位。单引号被转义或过滤如果输入的单引号被前置了反斜杠\‘那么它会被转义失去闭合作用。可以尝试宽字节注入如果数据库使用GBK等编码或者寻找不需要单引号的注入点如数字型注入。关键词被过滤尝试上述的绕过技巧。或者使用代替!like代替等。information_schema被禁用在极少数高版本MySQL或特定配置下可能无法访问information_schema。这时需要了解MySQL的替代方案如通过sys.schema_table_statistics等视图或者利用innodb引擎的特性进行盲猜但这属于高阶技巧。4.4 从攻击者视角到防御者视角作为学习者我们通过CTF题目理解攻击原理。但更重要的是要建立起防御的意识。针对这类联合查询注入防御措施包括最小权限原则数据库连接账户不应拥有FILE、PROCESS等高权限且只授予其访问必要数据库和表的权限。预编译语句Prepared Statements这是最有效、最根本的防御手段。使用参数化查询将用户输入的数据始终视为“数据”而非“代码”从原理上杜绝了SQL注入的可能。几乎所有现代编程语言和框架都支持。输入验证与过滤虽然不能作为主要防御手段但可以作为一种辅助。对输入进行严格的类型检查比如ID必须是数字、长度限制、使用安全的允许字符白名单。禁用详细错误信息避免将数据库的原始错误信息直接展示给用户防止泄露数据库结构。使用Web应用防火墙WAF可以拦截常见的注入攻击特征但存在被绕过的风险不能依赖。手工完成一道SQL注入题就像是完成了一次对目标数据库的“外科手术式”探查。从试探、判断、确定路径到一步步深入最终获取目标数据整个过程要求你对SQL语法、数据库结构有清晰的认识并且要足够细心和耐心。这道“LoveSQL 1”作为入门题完美地串联了这些基础步骤。当你熟练掌握这套流程后面对更复杂的盲注、二次注入、堆叠注入等变形题目时你便有了坚实的分析基础和解题框架。记住工具如sqlmap可以提升效率但手工理解每一步的原理才是你安全能力成长的基石。在真实环境中复杂的过滤和防护往往需要你灵活运用这些基础知识进行手工测试和绕过这是自动化工具无法完全替代的。