⚠️ 重要声明:本教程仅用于教育和授权的安全测试目的,未经授权对任何网站进行攻击是非法行为,可能导致严重的法律后果,请务必在获得明确书面授权的环境(例如你自己的靶场或CTF比赛)中实践这些技术。

php手动注入教程
(图片来源网络,侵删)

第一部分:理解基础

在开始之前,我们需要了解几个核心概念:

  1. 什么是SQL注入? SQL注入(SQL Injection,简称SQLi)是一种代码注入技术,攻击者通过在应用程序的输入字段(如用户名、密码、搜索框、URL参数等)中插入恶意的SQL语句,来欺骗服务器执行非预期的命令,如果应用程序没有对这些输入进行充分的过滤和验证,攻击者就可能操作数据库,比如窃取数据、修改数据、删除数据,甚至获取服务器的控制权。

  2. 为什么PHP容易受到SQL注入攻击? 在PHP中,开发者常常使用字符串拼接的方式来构建SQL查询。

    // 危险的代码示例
    $username = $_POST['username'];
    $password = $_POST['password'];
    $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
    $result = mysqli_query($conn, $sql);

    如果攻击者在用户名字段输入 admin' --,那么最终执行的SQL语句就变成了:

    php手动注入教程
    (图片来源网络,侵删)
    SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'whatever'

    在SQL中, 是注释符,它会把后面的内容都当成注释忽略掉,这样,密码验证部分就被绕过了,攻击者只需知道用户名admin就能登录。


第二部分:注入前的准备

你需要一些工具来辅助注入:

  1. 浏览器: 任何现代浏览器(Chrome, Firefox)都可以,用于查看页面源码、提交表单和查看响应。
  2. 代理工具(强烈推荐): Burp Suite 是进行Web应用渗透测试的瑞士军刀,它可以拦截你浏览器和服务器之间的所有HTTP/S请求,让你可以查看、修改和重放这些请求,社区版是免费的,足以进行SQL注入学习。
  3. 数据库知识: 你需要了解基本的SQL语法,如 SELECT, INSERT, UPDATE, DELETE, AND, OR, , , 等。

第三部分:手动SQL注入实战步骤

我们将按照一个经典的流程来进行手动注入:信息收集 -> 判断注入点 -> 确定列数 -> 确定回显位置 -> 猜解数据类型 -> 猜解数据

假设我们有一个存在漏洞的登录页面 login.php,它接收一个 username 参数。

php手动注入教程
(图片来源网络,侵删)

步骤 1:寻找并判断注入点

这是最关键的一步,我们需要找到一个可以输入数据并影响SQL查询的地方。

  • GET请求注入: URL参数,如 .../profile.php?id=1
  • POST请求注入: 登录表单、搜索框、留言板等。
  • HTTP头注入: User-Agent, Referer, Cookie 等(较高级)。

方法:

  1. 使用单引号 ('):在输入框中输入一个单引号,然后提交,观察服务器返回的错误信息。

    • 正常情况: 页面显示正常或提示“用户不存在”。
    • 注入点存在: 页面返回一个SQL语法错误,
      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 '' LIMIT 0,1' at line 1

      这个错误明确告诉我们,我们的单引号被拼进了SQL查询中,并且没有经过转义,这几乎可以断定存在SQL注入漏洞。

  2. 使用双引号 ("):如果应用使用的是双引号包裹字符串(不常见于MySQL,但可能在其他数据库或代码逻辑中),可以尝试双引号。

  3. 使用逻辑运算符:输入 ' OR '1'='1,如果登录成功或返回了所有用户的数据,那么存在布尔盲注或基于错误的注入。

如果页面返回SQL错误,我们就找到了一个基于错误的SQL注入点。

步骤 2:判断数据库中的列数

知道了SQL语句被拼接,下一步就是搞清楚查询结果返回了多少列,这通常使用 ORDER BY 子句来完成。

ORDER BY 子句用于根据指定的列对结果集进行排序,如果指定的列名存在,排序正常;如果不存在,就会报错。

  • 构造Payload: 在原始参数后面加上 ORDER BY [数字]

    • 原始URL是 .../profile.php?id=1,我们尝试: .../profile.php?id=1' ORDER BY 1 -- - .../profile.php?id=1' ORDER BY 2 -- - .../profile.php?id=1' ORDER BY 3 -- - ( 是MySQL的注释符,用于注释掉SQL语句后面的内容,防止语法错误)
  • 测试过程:

    1. .../profile.php?id=1' ORDER BY 10 -- - -> 页面报错,说明列数小于10。
    2. 逐渐减小数字,直到页面不再报错。
    3. 假设我们尝试到 ORDER BY 3 时页面正常,而 ORDER BY 4 时报错,那么我们就知道这个查询返回的结果集有 3列

步骤 3:确定回显位置

知道了列数,我们需要知道哪一列的数据可以显示在页面上,这通常使用 UNION SELECT 语句来完成。UNION 用于合并两个或多个 SELECT 语句的结果集。

  • 构造Payload: UNION SELECT [列数个占位符]

    • 因为我们知道有3列,所以payload是 .../id=1' UNION SELECT 1,2,3 -- -
  • 测试过程:

    1. 将这个payload提交到页面。
    2. 观察页面的返回内容,如果页面正常显示,并且你能找到数字 1, 2, 3 出现在页面的某个位置(比如表格里、文本里),那么你就成功确定了回显位置。
    3. 假设数字 2 出现在用户名的位置,数字 3 出现在邮箱的位置,我们就可以用这两个位置来回显我们想要的信息。

注意: UNION SELECT 要求两个 SELECT 语句返回的列数必须相同。

步骤 4:猜解数据库、表、列名

现在我们已经有了回显点,可以开始获取数据库信息了。

  • 获取当前数据库名:

    • Payload: .../id=1' UNION SELECT 1,database(),3 -- -
    • 说明: database() 是一个MySQL函数,用于返回当前数据库名称,这个名称会显示在第二步中确定的位置(比如数字2的位置)。
  • 获取数据库中的表名:

    • 我们需要查询 information_schema 数据库中的 tables 表。
    • Payload: .../id=1' UNION SELECT 1,table_name,3 FROM information_schema.tables WHERE table_schema='[数据库名]' LIMIT 1,1 -- -
    • 说明:
      • [数据库名] 替换成上一步获取的数据库名。
      • information_schema.tables 存储了所有数据库的表信息。
      • WHERE table_schema='...' 用于筛选指定数据库的表。
      • LIMIT 1,1 用于分页获取,第一次获取第一个表名,第二次获取第二个...以此类推。
      • 每次提交,观察回显位置,就能逐个得到表名(如 users, admins, products 等)。
  • 获取表中的列名:

    • 假设我们怀疑 users 表里存着用户信息,想获取它的列名(如 username, password, email)。
    • Payload: .../id=1' UNION SELECT 1,column_name,3 FROM information_schema.columns WHERE table_schema='[数据库名]' AND table_name='users' LIMIT 1,1 -- -
    • 说明:
      • information_schema.columns 存储了所有表的列信息。
      • WHERE table_schema='...' AND table_name='users' 用于筛选指定数据库和指定表的列。
      • 同样使用 LIMIT 来逐个获取列名。

步骤 5:获取数据

最后一步,就是从我们找到的表中提取出具体的数据。

  • Payload: .../id=1' UNION SELECT 1,username,password FROM users LIMIT 1,1 -- -
  • 说明:
    • 这条语句直接从 users 表中选取 usernamepassword 列。
    • LIMIT 1,1 表示获取第一条记录(从第0条开始,取1条)。
    • 提交后,如果页面正常显示,你就能在回显位置看到第一个用户的用户名和密码(密码可能是明文或哈希值)。

第四部分:注入类型补充

除了上面介绍的基于错误和UNION联合查询的注入,还有其他类型:

  1. 布尔盲注:

    • 场景: 页面没有SQL错误,也没有数据回显,但页面内容会根据SQL语句的真假而改变(登录成功/失败,显示“欢迎”/“访问被拒绝”)。
    • 方法: 使用 AND, OR 等逻辑运算符来构造真/假条件,通过观察页面变化来猜解信息。
    • 示例: .../id=1' AND (SELECT LENGTH(database())) > 10 -- -

      如果数据库名长度大于10,页面A显示;否则,页面B显示,通过这种方式可以一点点猜出数据库名的长度、每个字符的ASCII码。

  2. 时间盲注:

    • 场景: 页面内容完全不变,无论SQL语句真假,返回结果都一样。
    • 方法: 使用 SLEEP()BENCHMARK() 函数,如果SQL语句为真,就让数据库“睡觉”几秒钟。
    • 示例: .../id=1' AND IF((SELECT LENGTH(database())) > 10, SLEEP(5), 0) -- -

      如果数据库名长度大于10,页面会延迟5秒才加载,通过这种方式可以判断条件的真假,从而猜解信息。

  3. 堆叠查询注入:

    • 场景: 应用在执行完第一个查询后,没有关闭数据库连接,且允许使用分号 来分隔多个SQL语句。
    • 方法: 在第一个查询后加上 再跟一个新的SQL语句。
    • 示例: .../id=1'; DROP TABLE users; -- -
    • 说明: 这种注入非常危险,可以直接执行写操作,如删表、删库,但并非所有环境都支持。

第五部分:如何防御SQL注入

了解攻击是为了更好地防御,防御SQL注入的核心思想是永远不要信任用户的输入

  1. 使用预处理语句: 这是最推荐、最有效的方法,它将SQL命令和数据分开处理,从根本上杜绝了SQL注入。

    • PDO (PHP Data Objects) 示例:
      $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
      $stmt->bindParam(':username', $_POST['username']);
      $stmt->bindParam(':password', $_POST['password']);
      $stmt->execute();
      $user = $stmt->fetch();
    • MySQLi 示例:
      $stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
      $stmt->bind_param("ss", $_POST['username'], $_POST['password']); // "ss" 表示两个字符串
      $stmt->execute();
      $result = $stmt->get_result();
      $user = $result->fetch_assoc();
  2. 输入验证和过滤:

    • 白名单验证: 只允许特定格式的输入,如果用户名只能是字母和数字,就使用正则表达式 /^[a-zA-Z0-9]+$/ 来验证。
    • 黑名单过滤: 过滤已知的危险字符(如 , , , , , ),但黑名单很容易被绕过,不推荐作为主要防御手段
  3. 最小权限原则:

    • 为Web应用连接数据库的账户设置尽可能低的权限,如果应用只需要查询数据,就不要给它 INSERT, UPDATE, DELETE, DROP 等权限,即使发生注入,攻击者能造成的损害也会被限制。
  4. 转义特殊字符:

    • 如果无法使用预处理语句,可以使用数据库自带的转义函数(如 mysqli_real_escape_string()),但这不是100%安全,且代码容易出错,仅作为备选方案

手动SQL注入是一个需要耐心和逻辑推理的过程,从寻找一个单引号引发的错误开始,到利用 ORDER BY 探索结构,再到用 UNION SELECT 窃取数据,每一步都建立在对SQL和Web应用的深刻理解之上,请务必将这份知识用于正途,在合法的范围内学习和提升自己的安全技能。