伪静态注入与空格绕过:Web安全攻防中的SQL注入高级技巧
1. 项目概述当“伪静态”遇上“空格限制”在Web渗透测试的实战中我们常常会遇到一些“看起来很美”的防护措施。比如很多开发者认为只要把URL从传统的?id1改成/news/1.html这样的伪静态形式就能有效隐藏参数增加攻击难度。再比如在输入过滤层面简单粗暴地过滤或限制空格字符也被视为一种常见的“加固”手段。然而安全从来都是一个攻防对抗的动态过程。今天要聊的“伪静态注入”及其相关的“空格限制绕过”正是攻击者针对这两种常见防护思路的精准打击。简单来说伪静态注入的本质是将传统的动态参数隐藏在看似静态的URL路径中但其后端处理逻辑依然是动态查询数据库。攻击者需要识别出URL中哪一部分是实际传递给后端程序的参数并尝试对其进行注入。而空格限制绕过则是在后端对用户输入中的空格进行过滤、替换或限制时寻找功能等效的替代字符以构造出能够被数据库正确解析的恶意SQL语句。理解这两个技术点对于安全从业者而言具有双重意义。对于防御方它能让你看清那些“表面功夫”下的真实风险从而实施更有效的代码层防护。对于学习者和CTF选手这是提升手工注入技巧、理解WAFWeb应用防火墙绕过原理的绝佳案例。接下来我们将从原理到实战一步步拆解这两个“拦路虎”。2. 伪静态注入的原理与识别2.1 伪静态技术的工作机制伪静态顾名思义就是“伪装成静态”。它通常通过Web服务器如Apache的mod_rewrite模块、Nginx的rewrite规则的URL重写功能实现。其核心目的是提升URL的美观度和SEO友好度而非安全性。一个典型的转换过程如下原始动态URLhttp://target.com/news.php?id123重写规则示例将/news/(\d).html重写为/news.php?id$1用户访问的伪静态URLhttp://target.com/news/123.html从用户和浏览器的角度看访问的是一个静态页面。但从服务器端看news.php脚本依然被调用并且参数id的值123通过重写规则被提取并传递。安全风险就藏在这里开发者容易因为URL形态的改变而放松对参数id的过滤和校验误以为它不再是一个可控的输入点。2.2 如何识别伪静态注入点识别伪静态注入点的关键在于观察和测试URL的规律。1. 观察URL模式存在明显的数字ID序列如/article/1.html,/product/55.html。URL路径中存在看似是目录但实际可能是参数的部分如/category/books/item/1024这里的1024很可能就是item_id参数。文件扩展名可能是.html,.htm甚至无扩展名但页面内容明显是动态生成的如包含用户评论、时间戳等。2. 基础测试方法数字型参数测试尝试修改URL中的数字部分。将/news/123.html改为/news/124.html如果页面内容如文章标题、正文随之改变则基本确定该数字是参数。错误触发测试这是判断注入类型的关键。将数字改为一个可能引发错误的Payload。尝试/news/123或/news/123\。如果页面返回数据库错误如MySQL的You have an error in your SQL syntax则说明存在字符型注入且未正确过滤引号。尝试/news/123 and 11和/news/123 and 12。如果两个页面返回结果不同11正常12异常或空白则极可能存在数字型或未闭合的数字型注入。但在伪静态环境下空格可能被URL编码或触发重写规则错误直接这样测试可能失败这就需要用到后续的绕过技巧。3. 实战中的技巧有时参数可能不是简单的数字而是经过编码或具有特定格式。例如URL为/news/abc-123.html。你需要猜测abc-123的整体是参数还是只有123是参数。可以尝试将abc-123整体替换为123进行测试。如果网站使用类似/date/2024-05-15/news.html的格式那么2024-05-15很可能就是一个直接拼接到SQL语句WHERE date2024-05-15中的参数。注意在测试伪静态注入点时务必注意URL的完整性。错误的测试可能破坏重写规则导致404错误。这不一定代表没有注入可能只是你的Payload格式不符合重写规则预期的正则表达式。例如规则可能只匹配数字(\d)你传入123包含非数字字符直接被重写模块拒绝根本到不了后端PHP脚本。此时你需要先确保Payload符合规则例如先保证是数字再通过其他方式如注释符来构造语句。3. 空格限制的常见方式与绕过原理在注入测试中空格是分隔SQL关键字和语句成分的重要字符。例如UNION SELECT username, password FROM users。如果空格被过滤这条语句将无法被数据库解析。防御方通常会采用以下几种方式处理空格直接删除将输入中的空格字符 ,%20全部移除。替换为其他字符如将空格替换为下划线_或空字符串。限制空格数量只允许出现一个空格或多个空格被压缩为一个。WAF/过滤规则拦截检测到特定关键字组合中间存在空格时进行阻断。针对这些限制攻击者发展出了一系列功能等效的“空白符”替代方案。其核心原理是在SQL语法中某些位置的空格并非必需可以用其他字符或方式实现相同的分隔效果。3.1 绕过空格限制的替代字符以下字符在大多数数据库管理系统中如MySQL、MariaDB都可以作为空格的替代品用于分隔SQL关键字替代字符URL编码说明与示例/**/(多行注释)%2F%2A%2A%2F最常用、最可靠。MySQL将/**/视为一个可被忽略的注释块完美充当空格。UNION/**/SELECT%09(水平制表符 TAB)%09在HTTP请求中TAB符也是空白符。UNION%09SELECT%0a(换行符 LF)%0a换行符在某些上下文中可作分隔。UNION%0aSELECT%0b(垂直制表符)%0b一个不常见的空白符。UNION%0bSELECT%0c(换页符)%0c另一个不常见的空白符。UNION%0cSELECT%0d(回车符 CR)%0d回车符。UNION%0dSELECT()(括号)%28%29括号可以包裹子查询或表达式有时能创造分隔效果但更常用于绕过特定关键字检测。(加号)%2b注意仅在URL参数或GET请求中加号会被服务器解码为空格。在POST正文或MySQL原生语句中是加法运算符不能替代空格。例如在URL中?id1unionselect。实操心得/**/是首选兼容性极佳且因为它是注释有时能绕过一些简单的关键字匹配过滤过滤规则可能只匹配UNION SELECT而不匹配UNION/**/SELECT。在测试时应使用Burp Suite的Intruder或Repeater模块系统地遍历这些替代字符观察响应差异。一个重要区别%20是URL编码的空格而上述列表是空格的“替代品”。如果服务器只是简单过滤 和%20那么使用%09、/**/等就能轻松绕过。如果服务器进行了更全面的空白符过滤可能需要组合使用或寻找更偏门的技巧。3.2 高级绕过利用SQL语法特性当所有常见的空白符替代都被过滤时就需要深入理解SQL语法寻找无需空格也能正确解析的语句构造方式。1. 利用括号和注释巧解FROM子句在UNION SELECT注入中FROM关键字后通常需要空格。如果空格被过滤可以尝试UNION SELECT 1,2,3 FROM users可以尝试变形为UNION SELECT 1,2,3 FROM(users)或者利用注释将FROM和表名连在一起但让数据库能解析UNION SELECT 1,2,3 FROM/**/users如果/**/也被过滤可以尝试用括号包裹表名但这依赖于数据库版本和模式并非总是有效。2. 内联注释/*! ... */的妙用MySQL特有的内联注释其中的代码会被MySQL执行而其他数据库会视其为普通注释。它内部的空格有时能逃过过滤。UNION/*!SELECT*/1,2,3甚至可以在内联注释中指定MySQL版本实现更精细的绕过/*!50001UNION SELECT*/表示在MySQL 5.00.01及以上版本才执行其中的语句。3. 反引号包裹在MySQL中反引号用于包裹数据库名、表名、字段名尤其是当它们与关键字冲突时。在极端情况下可以尝试UNION SELECT username,password FROM users虽然关键字之间仍需分隔但反引号能帮助解析器区分边界有时与无空格语句结合能产生奇效但这属于非常规技巧成功率不高。4. 利用字符串连接符在某些情境下可以通过构造字符串连接来绕过对特定函数空格的限制但这通常用于绕过union select这类固定短语的检测而非纯粹的空格过滤。核心要点空格绕过不是机械地替换字符而是理解SQL解析器如何识别语句的“词元”。我们的目标是构造一个能被数据库正确解析为合法词元序列的字符串至于词元之间是空格、制表符还是注释解析器并不关心。4. 实战演练伪静态注入与空格绕过组合拳理论说得再多不如一次实战。我们假设一个目标http://target.com/blog/123.html。我们怀疑它是一个伪静态页面对应blog.php?id123并且服务器过滤了空格。4.1 第一步确认注入点与类型基础访问访问http://target.com/blog/123.html页面正常显示某篇博客。参数探测访问http://target.com/blog/124.html内容变化确认124是参数。错误法测类型访问http://target.com/blog/123.html。如果页面报SQL语法错误说明是字符型注入且单引号未过滤。原SQL可能类似SELECT * FROM blog WHERE id123。如果上一步无错误访问http://target.com/blog/123 and 11.html。但这里有个陷阱and 11中间有空格可能被过滤或导致重写规则失效规则可能只匹配(\d)页面直接404。这说明我们需要同时处理伪静态规则和空格过滤。4.2 第二步构造符合重写规则且绕过空格的Payload假设重写规则是^/blog/(\d)\.html$重写为/blog.php?id$1。这意味着我们传入的路径中blog/和.html之间的部分必须是一个或多个数字否则规则不匹配返回404。我们的Payload必须满足是“数字”这个形式。如何做到利用SQL注释符对于MySQL--后面有个空格或#是行注释符。在URL中#需要编码为%23。原语句可能是SELECT ... FROM ... WHERE id123我们构造123 and 11最终SQLSELECT ... FROM ... WHERE id123 and 11恒真。但and前后需要分隔。我们不能用空格用/**/替代。关键点Payload整体123/**/and/**/11不是纯数字会破坏重写规则。怎么办技巧将注释放在参数值内部利用注释使后面的部分被SQL引擎忽略但重写规则只看到数字。构造123--.html传递给重写规则的部分是123纯数字规则通过。传递给blog.php的id参数值是123--。最终SQLSELECT ... FROM ... WHERE id123-- 。--后面的单引号被注释掉了语句合法。访问http://target.com/blog/123-- .html(注意--后有一个空格在URL中为%20但可能被过滤所以常用#即%23替代)更优构造http://target.com/blog/123%23.html(%23是#的URL编码)重写规则看到123。id参数收到123#。SQLSELECT ... FROM ... WHERE id123##之后的所有内容被注释。如果页面正常返回说明注入存在。为了验证可以构造一个恒假条件对比恒真http://target.com/blog/123%23.html(SQL:id123#)恒假http://target.com/blog/123 and 12%23.html(但and需要空格)绕过空格构造恒假http://target.com/blog/123/**/and/**/12%23.html这里/**/在重写规则看来是非数字字符导致404。此路不通。我们需要一个既能充当空格又能被重写规则认为是数字一部分的字符。实际上没有这种字符。所以我们必须调整思路先保证Payload能通过重写规则即开头是数字然后立即用注释符截断将真正的注入Payload放在注释符后面不行注释符后面的内容SQL不执行。解决方案将注入Payload全部放在注释符之前但确保整个字符串以数字开头。尝试123and11(无空格)SQL:id123and11。这取决于SQL解析器的容错性。在某些情况下MySQL能解析123and11将and前后的字符串比较结果进行逻辑与操作。但这不稳定。更可靠的方案使用括号和运算。构造123and(1)or(12(这是一个故意构造的畸形语句用于探测不展开)这变得非常复杂且不通用。因此在伪静态空格过滤的复合限制下最实用的初步测试方法是先测试注释符是否生效123%23。如果页面正常说明单引号闭合注释符起效注入点存在。要测试布尔条件需要更精巧的、无需空格的SQL语句或者寻找其他未被过滤的分隔符如%0b。假设%23注释成功。我们已确认注入。下一步是联合查询。4.3 第三步进行联合查询Union Select获取信息联合查询需要union和select两个关键字且通常需要空格。我们使用/**/绕过。判断列数使用order byorder by也需要空格。构造123/**/order/**/by/**/1%23访问http://target.com/blog/123/**/order/**/by/**/1%23.html逐渐增加数字直到页面错误.../by/**/5%23.html错误则列数为4。这里/**/在重写规则看来是非数字字符再次导致404我们遇到了核心矛盾。终极矛盾与解决方案伪静态的重写规则要求路径片段是纯数字而注入Payload需要引入非数字字符如、union、select、/**/。除非规则设计有严重缺陷比如允许非数字否则无法直接在路径中注入。那么伪静态注入如何发生通常有以下几种情况规则缺陷重写规则过于宽松例如^/blog/(.*)\.html$匹配了任何字符。这样123就能通过。多参数伪静态URL形如/blog/123-title.html。规则可能提取123作为idtitle作为另一个参数。注入点可能在title部分而该部分允许更多字符。其他注入点虽然主ID参数被伪静态保护但页面可能存在其他未伪静态化的参数如/blog/123.html?sortdate注入点在那里。假设我们遇到的是情况1规则宽松。那么后续步骤为执行联合查询确定列数后例如4列构造联合查询Payload。123/**/union/**/select/**/1,2,3,4%23访问对应URL查看页面中哪个位置显示了2或3数字被替换为查询结果的位置。获取数据库信息假设第2、3位可显示。查询当前数据库123/**/union/**/select/**/1,database(),user(),4%23查询表名123/**/union/**/select/**/1,group_concat(table_name),3,4 from information_schema.tables where table_schemadatabase()%23注意from前后也需要/**/分隔。如果from被过滤可以尝试from/**/information_schema.tables。查询字段名123/**/union/**/select/**/1,group_concat(column_name),3,4 from information_schema.columns where table_nameusers%23最终拖取数据123/**/union/**/select/**/1,group_concat(username,:,password),3,4 from users%234.4 使用SQLMap进行自动化测试对于伪静态注入SQLMap需要特殊处理。直接跑sqlmap -u http://target.com/blog/123.html是没用的因为SQLMap不认识这个URL是动态的。你需要用*标记注入点sqlmap -u http://target.com/blog/123*.html --tamperspace2comment*告诉SQLMap123这个位置是注入点。--tamperspace2comment加载一个绕过脚本自动将空格替换为/**/。如果网站有复杂的重写规则可能需要指定--prefix和--suffix选项来帮助SQLMap构造正确的Payload格式或者使用--eval选项在每次请求时用Python代码处理URL。这需要对目标站点的重写规则有深入了解手工测试往往更灵活。5. 防御之道从根源上杜绝注入理解了攻击手法防御思路就清晰了。伪静态和过滤空格都是“表面防护”治标不治本。1. 使用预编译语句Prepared Statements这是唯一从根本上解决SQL注入的方法。无论是PHP的PDO、MySQLi还是Java的PreparedStatement、Python的cursor.execute()其原理都是将SQL语句的结构模板与数据参数分开发送给数据库。数据库先编译语句结构再将参数作为纯数据处理无论参数中包含什么、union、/**/都不会改变原语句的语义。// PHP PDO 示例 $stmt $pdo-prepare(SELECT * FROM blog WHERE id ?); $stmt-execute([$id]); // $id 来自 /blog/123.html 中提取的“123” $result $stmt-fetchAll();2. 严格的输入验证与类型转换对于id这类明确是数字的参数在进入SQL查询前必须进行强制类型转换。$id (int)$_GET[id]; // 或 intval($_GET[id]) // 或者从伪静态路径中提取后 if (!is_numeric($extracted_id)) { die(Invalid input); } $id (int)$extracted_id;这样即使攻击者传入123 union select 1,2,3#$id也会被转换为整数123后面的Payload被丢弃。3. 最小化数据库权限应用程序连接数据库的账户不应具有DROP、FILE、GRANT等高级权限。通常只赋予SELECT、INSERT、UPDATE、DELETE等必要权限。这样即使发生注入损害也能被限制。4. 安全的伪静态实现在重写规则中就进行严格的输入限制。例如只允许数字# Nginx 示例 location ~ ^/blog/(\d)\.html$ { try_files $uri $uri/ /blog.php?id$1; } # Apache .htaccess 示例 RewriteRule ^blog/([0-9])\.html$ blog.php?id$1 [L]确保规则只匹配数字[0-9]或\d这样非数字的Payload在到达PHP脚本前就被Nginx/Apache拒之门外提供了第一道防线。5. 不要依赖黑名单过滤过滤空格、union、select等关键字是徒劳的。攻击者有无数种绕过方式大小写、双写、编码、注释分割、等价函数替换。安全的核心是“白名单”思维只允许已知好的、预期的输入格式。6. 常见问题与排查技巧实录Q1: 测试伪静态注入时总是返回404错误怎么办A1: 这很可能是因为你的Payload不符合服务器的URL重写规则。解决步骤先确认正常页面的URL格式确保你的测试URL在结构上与之一模一样。尝试最基础的参数变化如数字递增确认参数位置。使用最简单的Payload测试如仅添加一个单引号并确保其URL编码%27后整个路径片段仍符合规则例如如果规则要求数字123%27就不符合。考虑注入点可能不在路径中而在其他位置如Header、Cookie、POST数据。Q2: 使用/**/绕过空格但网站还是拦截了请求为什么A2: 可能原因WAF/过滤规则升级有些WAF已经能识别/**/作为空格的替代。可以尝试其他替代符如%09、%0a等。关键字被单独过滤可能union和select这两个词本身被过滤了与空格无关。需要尝试关键字绕过如UnIoN大小写混淆、uniunionon双写绕过、%75nionURL编码等。长度或特殊字符限制请求可能因包含过多特殊字符或长度异常被整体拒绝。Q3: 联合查询时页面没有显示预期的数字位是哪里出错了A3: 可能原因及排查列数不对重新用order by精确判断列数。数据类型不匹配联合查询前后两个SELECT语句对应列的数据类型必须兼容。如果原查询第一列是字符串而你union select 1,2,3...的第一列是数字1可能导致查询失败或结果显示异常。尝试将数字改为字符串union select a,b,c。显示位置不在当前页面查询结果可能被插入到HTML的隐藏标签、JavaScript变量或注释中。查看网页源代码进行搜索。有错误但被屏蔽开启数据库错误显示通常有助于调试但在生产环境不现实。可以尝试构造一个必然出错的Payload如union select 1,concat(0x7e,version(),0x7e),3看页面是否空白或异常间接判断。Q4: 在DVWA、Pikachu等靶场练习时伪静态环境如何搭建A4: 这些靶场默认通常不是伪静态。要练习你需要手动配置。以DVWA为例假设使用Apache确保Apache启用了mod_rewrite模块。在DVWA根目录创建或编辑.htaccess文件添加规则RewriteEngine On RewriteRule ^vulnerabilities/sqli/(\d)/?$ vulnerabilities/sqli/index.php?id$1 [L,NC]这条规则将/vulnerabilities/sqli/1映射到/vulnerabilities/sqli/index.php?id1。将DVWA SQL注入关卡改为使用$_GET[id]并修改源码使其从伪静态URL中获取ID可能需要修改includes/dvwaPage.inc.php或相关文件。这是一个进阶的练习能让你更深刻地理解伪静态的实现与风险。手工注入是理解SQL注入本质的最佳途径它能锻炼你面对各种奇怪过滤和限制时的思维灵活性。而真正保障安全永远始于开发者笔下严谨的代码。