在搭建网站的过程中,数据库安全常常被忽视。很多开发者只关注功能实现,却没意识到一个简单的输入框就可能成为攻击入口。比如,通过SQL注入枚举表名,攻击者可以一步步摸清你数据库的结构,进而获取敏感信息。
什么是SQL注入枚举表名
当网站没有对用户输入做过滤时,攻击者可以在参数中插入恶意SQL语句。以常见的登录查询为例:
SELECT * FROM users WHERE username = 'admin' AND password = '123456'
如果后端直接拼接字符串,攻击者输入用户名 admin' OR 1=1 --,语句就变成了:
SELECT * FROM users WHERE username = 'admin' OR 1=1 --' AND password = ''
此时查询条件恒真,注释符绕过了密码验证。但这只是第一步,更危险的是利用系统表来枚举数据库中的所有表名。
如何通过注入获取表名
在MySQL中,information_schema.tables 存储了所有数据库的表信息。攻击者可以通过联合查询(UNION SELECT)把表名列出来。例如,在URL参数中输入:
' UNION SELECT table_name,2 FROM information_schema.tables WHERE table_schema = database() --
如果页面回显第一个字段的内容,就能看到当前数据库下所有的表名,比如 users、orders、config 等。一旦知道了表名,下一步就可以猜测字段名,进一步提取数据。
在PostgreSQL中,类似的查询会用到 pg_tables 或 information_schema:
' UNION SELECT tablename,2 FROM pg_tables WHERE schemaname = 'public' --
真实场景中的风险
设想你做了一个小型电商站,后台用PHP+MySQL,商品列表通过 ?id=1 获取数据。如果没做预处理,攻击者访问:
?id=1' UNION SELECT table_name,2 FROM information_schema.tables WHERE table_schema = database() --
页面上突然出现了“user_info”、“payment_log”这些不该被看到的名字。攻击者立刻意识到这里有用户和支付数据,接下来就是针对这些表构造更精确的查询。
如何有效防御
最根本的方法是使用参数化查询(预编译语句)。以PHP的PDO为例:
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$id]);
这样无论用户输入什么,都会被当作数据处理,而不是SQL代码的一部分。另外,限制数据库账户权限也很关键。Web应用连接数据库时,不要用root账号,只赋予最基本的操作权限。
还可以关闭错误信息的详细输出。调试阶段开启报错没问题,但上线后应该记录到日志,而不是显示给用户。因为错误信息往往会暴露表名、字段名甚至SQL结构。
定期检查代码中的拼接逻辑,尤其是老项目。有些早期写的代码为了省事直接用字符串拼接SQL,这类地方最容易出问题。