详细的报错会泄露三样东西:SQL 怎么写的、代码放在哪、跑的是什么版本。 Demo 里详细版正文 294 字节、三项全泄露;笼统版 39 字节、一项不泄露。 做法是:给用户一句笼统的话,给日志一份详细记录。
上一节泄露的是文件,这一节泄露的是「内部结构」——而结构恰恰是构造下一次攻击最需要的信息。
一、报错会说出什么
一条没处理好的异常,可能带出:异常类型和消息、SQL 语句原文、文件绝对路径和行号、框架版本、 数据库版本、甚至连接串。 这些单看不起眼,凑起来正好是一张地图:知道是 MySQL,就知道用哪套注入语法; 知道框架版本,就能查对应的已知漏洞;知道文件路径,就能猜目录结构。 Demo 里详细版打出了四行:错误消息、SQL 语句、代码文件路径、Python 版本。 笼统版只有一句「服务暂时不可用」。 三行泄露项逐条判定:SQL 语句结构、程序文件路径、解释器版本,详细版全是「泄露」, 笼统版全是「没泄露」。
二、报错注入:靠错误一步步问
有一类 SQL 注入专门利用报错:故意让语句出错,从错误消息里读出数据。 比如让数据库把查询结果拼进错误信息里。 所以「关掉报错显示」不只是好看,它直接掐掉了一条数据外带通道。 同样的道理适用于所有「把内部细节返回给用户」的地方:调试页、堆栈、框架的详细异常页。 开发环境开着是方便,生产环境开着就是把图纸贴在门口。
三、两句不同的话
正确的做法是把两件事拆开:用户看到的一句话和日志里的一份记录。
用户看到的是「服务暂时不可用,请稍后再试」,加一个可以报给客服的编号;
日志里记完整的堆栈,带上那个编号,出问题时按编号一查就能对上。
还有一条容易漏:状态码也要一致。不管内部是「参数错」还是「数据库挂了」,对外都回同一个 500。
否则状态码本身就成了一条线索。
最后一句提醒:Server 首部、X-Powered-By 这类「报家门」的首部也该去掉。
6.5.7 测过,它们能把服务器软件和版本直接告诉对方。
四、动手 Demo:详细报错 294 字节,笼统报错 39 字节
两个服务端,同一个处理逻辑,只是一个把异常原样返回、一个只回一句笼统的话。 各喂一个带 SQL 片段的参数,对比返回内容。
except Exception:
if verbose:
msg = (u'查询失败:%s\nSQL: SELECT * FROM users WHERE id = %s\n'
u'文件:%s\nPython: %s' % (err, uid, __file__, sys.version))
return 500, msg
return 500, u'服务暂时不可用,请稍后再试'
实跑输出:
--------------------------------------------------------------------
错误消息:报得越详细,泄露得越多
--------------------------------------------------------------------
请求参数:id=1 UNION SELECT pw FROM users
【详细报错】状态 500,正文 294 字节
查询失败:invalid literal for int() with base 10: '1 UNION SELECT pw FROM users'
SQL: SELECT * FROM users WHERE id = 1 UNION SELECT pw FROM users
文件:I:\Desktop\自我教学\.workbuddy\scripts\ch37_dev\_d37w.py
Python: 3.13.14 (main, Jun 11 2026, 04:04:46) [MSC v.1944 64 bit (AMD64)]
【笼统报错】状态 500,正文 39 字节
服务暂时不可用,请稍后再试
泄露项 详细报错 笼统报错
SQL 语句结构 泄露 没泄露
程序文件路径 泄露 没泄露
解释器版本 泄露 没泄露
详细报错那一版,攻击者一次就拿到了三段情报:SQL 怎么写的、代码放在哪个目录、跑的是什么版本。
这些信息单看不起眼,凑起来正好够他构造下一次注入——「报错 → 调整 → 再报错」这个循环,就是所谓「报错注入」。
所以线上环境的原则很简单:**给用户一句笼统的话,给日志一份详细的记录**。两句话分开写,别混在一起。
顺带一句:状态码也要一致。不管内部是「参数错」还是「数据库挂了」,对外都给同一个 500,别让状态码本身成为线索。
详细版正文 294 字节,四行把错误原因、SQL 结构、文件路径、解释器版本全交代了。
笼统版正文 39 字节,就一句话。两个都回 500,看不出内部差别。
泄露项那一张三行全是「泄露/没泄露」的对照,这也说明了「泄露」不是概率问题,
是开关问题——把开关关掉,就一点不漏。
参数里那句 UNION SELECT pw FROM users 其实没被执行(在这段代码里它只是一个转不成整数的字符串),
但它被原样打进了错误消息——这正是攻击者想要的:看到自己输入被怎么处理。
30 秒实验
- 在浏览器里故意请求一个不存在的接口,看返回的是 JSON 还是整页 HTML 堆栈。
- 用
curl -I看一个网站的响应首部,找Server和X-Powered-By。 - 想一想:为什么给用户一个「错误编号」比给一句详细解释更有用?
常见误区
- 「报错详细一点方便排障」——排障看日志,不该给用户看。
- 「只有内网才显示堆栈,外网关了」——内网也会被横向移动。
- 「关掉报错就安全了」——它只是减少了信息泄露,注入本身还在。
自测三问(点开对答案)
1. 详细报错通常会泄露哪三样东西? —— SQL 或语句结构、代码文件路径、框架或解释器版本。 2. 为什么「报错注入」能读到数据? —— 攻击者故意构造错误,让数据库把数据拼进错误消息里带出来。 3. 正确的错误处理方式是什么? —— 用户看到一句笼统的话加一个编号,完整详情写进日志。文件和信息都会泄露。还有一种漏洞,是「你的站点帮别人跳转」。
- ☐ (本节完,下一节 11.3.3 开放重定向)