防住内网SQL注入的核心是参数化查询+白名单校验+最小权限:所有用户输入必须通过占位符绑定,动态表名列名须严格白名单控制,数据库账号按功能隔离权限,禁用高危操作,关闭详细错误并记录可疑查询。

内网不等于安全,企业内部管理系统反而更常因“信任内网”而放松SQL注入防护——攻击者一旦突破边界(如钓鱼邮件、跳板机、第三方集成漏洞),就能直面无WAF保护、权限宽松的后台服务。防住内网注入,核心不是加更多拦截,而是让恶意输入根本无法变成可执行代码。
用参数化查询替代所有字符串拼接
哪怕只有一处 query = "SELECT * FROM " + table_name,就可能被构造出 table_name = "users; DROP TABLE logs; --" 这类堆叠注入。内网数据库往往支持多语句执行,风险比外网更高。
- 所有查询必须用占位符:Python 用
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)),Java 用PreparedStatement,PHP 用PDO::prepare() - 动态表名/列名不能参数化,此时必须走白名单校验:
if table_name not in ["users", "orders", "logs"]: raise ValueError("非法表名") - ORM 框架(如 Django ORM、SQLAlchemy)默认启用参数化,但要禁用
raw()、extra()等绕过接口
数据库账号按功能严格隔离权限
内网系统常共用一个高权限 DBA 账号,一旦该账号凭证泄露或被劫持,攻击者可直接删库。最小权限不是“建议”,是内网第一道隔离墙。
- 登录模块只配
SELECT权限,且仅限users表 - 报表导出模块若需聚合,额外授予
SELECT+EXECUTE(仅限特定存储过程),禁止CREATE TEMP TABLE - 绝对禁用
app_user账号的DROP、ALTER、TRUNCATE、LOAD DATA权限 - PostgreSQL 中用
REVOKE ALL ON DATABASE xxx FROM app_user显式收回,再逐项GRANT
关闭详细错误信息并记录可疑查询
内网调试习惯常开启 show_errors = true 或打印完整 SQL 异常堆栈,这会让攻击者直接看到表结构、字段名甚至数据库版本。
- 生产环境 Web 层统一捕获
SQLException,返回泛化提示如“操作失败,请联系管理员”,不暴露psycopg2.errors.UndefinedTable这类错误码 - 数据库日志开启
log_statement = 'mod'(PostgreSQL)或general_log = OFF+slow_query_log = ON(MySQL),重点监控含UNION SELECT、EXTRACTVALUE、SLEEP(的慢查询 - 在应用层加轻量日志钩子:当检测到输入含
' OR '、;--、/*等模式时,记录 IP、时间、原始参数(脱敏后),不阻断但告警
最易被忽略的一点:内网服务常通过 Redis、MQ 或配置中心下发 SQL 片段(比如“自定义报表SQL”),这类动态 SQL 几乎无法参数化。遇到这种设计,必须强制要求前端传入的是字段名列表、过滤条件 JSON,后端用白名单映射生成语句——而不是接收任意 SQL 字符串。

















