📘 第十一章 Web的攻击技术
📄 一句话概括

浏览器发出去的东西全都由客户端决定:隐藏字段、下拉框选项、禁用的按钮、JS 校验,一样都拦不住。 Demo 里把价格从 5900 改成 1,朴素的服务端就按 1 分钱成交了。

📝 本节和前面几节的关系

上一节讲「路上能改」,这一节讲更省事的:根本不用上路,用户自己在他的浏览器里就能改。

一、浏览器里的一切都能改

页面上那个「不可见」的隐藏字段,在开发者工具里就是一个属性,想改什么改什么。 下拉框的选项也一样——它只是 HTML 里的几行 <option>。 disabled 的按钮?把属性删掉就能点。readonly 的输入框?改一行就能输入。JS 校验? 把那段脚本删掉,或者干脆自己发请求。 最彻底的办法是绕开页面:用 curl、用 Postman、写几行脚本,直接对着接口发请求。 页面上的一切约束这时候统统不存在。 所以「前端校验」的定位必须摆正:它是为了用户体验(少一次往返就告诉你填错了),不是安全措施。 安全校验必须在服务端再做一遍。

二、经典案例:价格来自客户端

Demo 里那个朴素商城,下单请求长这样:id=a&name=图解 HTTP&price=5900。 服务端把它读出来,直接用 price 算应付金额。 正常下单,应付 5900 分;把 price 改成 1,应付 1 分。 两次服务端都回了 200,都记了一笔成交。 修正版只改一处:价格不信客户端,只用 id 去服务端查表。 同一个被改过的请求,修正版算出来还是 5900 分——改价格没用了。 这类漏洞在各行各业都出现过:金额、数量、折扣、角色、用户 ID。 判断方法很简单——凡是决定「钱」或者「权限」的值,都不能来自请求。

三、还有哪些「客户端说了算」的坑

越权:把 URL 里的 /order/1001 改成 /order/1002,如果服务端不查「这个订单是不是你的」, 就直接看到了别人的。这类叫 IDOR(不安全的直接对象引用),比注入更常见。 角色:注册时多传一个 role=admin,服务端如果直接入库,你就成了管理员。 角色只能由服务端根据可信信息决定。 文件类型:上传时只检查扩展名,把 shell.php 改成 shell.jpg 就过去了。 真正的检查要看文件内容,而且上传目录不能能被当脚本执行。

四、动手 Demo:把价格改成 1 分

两个商城服务端:一个直接用请求里的 price,一个只用 id 查表。 各接收两次下单——一次正常,一次把价格改成 1 分。

# 朴素版:价格来自客户端
price = int(q.get(u'price', [u'0'])[0])
return u'应付 %d 分' % price

# 修正版:价格来自服务端的表
name, price = GOODS[pid]
return u'应付 %d 分' % price

实跑输出:

--------------------------------------------------------------------
客户端能改的东西比你想的多
--------------------------------------------------------------------
  朴素版,正常下单           下单成功:图解 HTTP,单价 5900 分,应付 5900 分
  朴素版,改价格之后         下单成功:图解 HTTP,单价 1 分,应付 1 分
  修正版,正常下单           下单成功:图解 HTTP,单价 5900 分,应付 5900 分
  修正版,改价格之后         下单成功:图解 HTTP,单价 5900 分,应付 5900 分

  第三、四行是关键。同一个请求,朴素版照单全收,修正版只信 `id`,价格自己查表——所以改价格没用。
  结论只有一句:**客户端提交的一切都不可信**。隐藏字段、下拉框的选项、禁用的按钮、JS 校验,全都能绕过。

朴素版两次分别算出 5900 和 1。同一个商品、同一个 id,只因请求里那个数字变了。 修正版两次都是 5900。被改过的请求它照样收到,但那个 price 参数压根没被读——它只认 id。 这两行输出就是整节的核心:客户端提交的一切都不可信。隐藏字段、禁用按钮、JS 校验,全都能绕。 顺带说一句:id 也不完全可信。上面修正版没查「这个 id 存不存在」,我给它加了一个判断; 真实系统里还要再查「这个用户能不能买这个商品」。

30 秒实验

  1. 把一个 disabled 的按钮在开发者工具里改回可点,看能不能提交。
  2. 用 curl -X POST -d 'a=1' 直接打一个只允许页面提交的接口,看服务端认不认。
  3. 想一想:为什么「前端校验」只能算体验优化,不能算安全措施?

常见误区

  1. 「隐藏字段用户看不到所以安全」——在开发者工具里它就是一行文本。
  2. 「把按钮禁用了他就提交不了」——绕开页面直接调接口更省事。
  3. 「JS 里加了校验就拦住了」——删掉脚本或自己发请求,校验就不存在了。
自测三问(点开对答案) 1. 为什么隐藏字段不安全? —— 它只是 HTML 的一个属性,客户端可以任意修改或直接构造请求。 2. 防「改价格」最直接的办法是什么? —— 价格由服务端按商品 id 查表决定,不读客户端传来的金额。 3. 改 URL 里的订单号就能看别人的订单,这类漏洞叫什么? —— IDOR,不安全的直接对象引用,属于越权。

单个请求能改,那攻击者的手法还能分成几类?下一节给一张分类表。

相关笔记