由于这几个用户请求参数非常方便,开发者常常会偷懒,直接透传这些参数到 Model 层,直接作为 Model 层参数创建数据对象,然后保存的数据库。示例代码如下:
ctx.model.Post.create(ctx.request.body);
ctx.model.Post.update(ctx.request.query.id, ctx.request.body);
ctx.model.Post.destroy(ctx.request.params);
这样看起来代码很简单优雅,实际隐含着非常容易被攻击的可能性。如这 create 的时候,攻击者可以给 body 设置 id,强制修改你以为的自增长 id。
或者修改 user_id, role 之类的重要字段,导致漏洞
这种模式违反了一个安全原则:不能信任一切用户输入的参数。
借鉴 Rails 的 StrongParameters 思路,如果这些参数都是基于统一的 Parameters 参数类生产的参数对象,那么就可以这 Model 层的 create,update 和 destroy 各种数据操作方法里面,实现对参数判断,确保这些参数都不能够是 Parameters 类的对象实例。
Model.prototype.create = function(params) {
if (params instanceof Parameters) throw new TypeError('wrong params');
// ...
};
或者判断 params.permitted,总之需要给 Model 层知道参数是否原始用户参数,还是经过开发者处理过的参数。
cc @huacnlee
一般入参不是都要校验的吗
@popomore 校验了,但是多传的参数,你不会去校验。
可以参考最早 Rails Strong Parameters 发布时候的公告:
Rails 里面实现是 if (!params.permitted?) { throw new error }
当调用过 params.permit 以后会改变状态,params 依然是 Parameters 的 Instance,这样的好处是 Parameters 里面的其他方法(它比标准的 Hash 带有更多实用功能,还能扩展)还能继续使用,例如 merge
@huacnlee 可以返回 StrongParameters 类,它是 Parameters 的子类,Parameters 的其他方法都继承了。
使用ts的话,应该是一个以 model 形式的 interface 来限制传参,多余的参数被过滤掉,这样可以避免
@huacnlee StrongParameters 类不行,看来还是得判断是否明确被 permit 执行过。
主要是你拦不住别人,或你自己大意的时候,这么使了一把:
yield ctx.model.Topic.create(ctx.request.body)
// 或者更复杂的场景
const topicParam = Object.assign(ctx.request.query, ctx.request.body);
topicParam = Object.assign({
// ... 一些其他复杂的额外参数
}, topicParam)
// ... 好多行代码
// 然后后面这行你就一眼看不出毛病了
// 或者一开始前面是 _.pick 出来的,Review 通过了
// 某天不知道被谁改了,变成直接用 ctx.request.body,Diff 上你看不到后面是直接塞 topicParam 到 Model 的
yield ctx.model.Topic.create(topicParam);
@fengmk2
Active Model (Rails Model 的基础)里面是这样:
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/forbidden_attributes_protection.rb
@huacnlee 核心原则就是要让 Model 感知参数是否经过开发者处理。
另外,不应该直接过滤,而是应该跑异常,让开发者在实现过程中就能发现问题。
确保开发者是显示的提取参数的。
可以用 json schema 来验证,不会有多余值
ts 或者 json schema 都可以做到,但是架不住人偷懒。最终还是人性问题。
For example:
https://github.com/eggjs/examples/blob/master/sequelize-example/app/controller/post.js#L20
https://github.com/eggjs/examples/blob/master/sequelize-example/app/service/post.js#L36
以及其他的用户
https://github.com/malun666/hamkd/blob/45a3e83716853092dba368dd71a73e8257fe97ae/app/controller/user.js#L50
https://github.com/jjj201200/blog/blob/28b4d719e910ad2eb8639c0ae5c141d400f23f7b/app/service/blog.js#L78
这不是懒、意识的原因。
而是某些时候你在写这行的时候,你不会想到那么多细节,尤其是事情多,项目复杂,功能多的时候
Strong Parameters 的机制就是你必须制定要传递的属性,否则代码跑不通。
开发者不用时刻注意各种各样的细节,有问题框架会提示出来。
人都会偷懒,确实拦不住,但是可以设置强校验,每一个字段都【恶心】开发者 Permit 一下的话,至少可以有一道防线拦一下或者给开发者敲一下钟
是不是可以增加一个类似 Laravel 的 fillable (可被批量赋值的属性设置) / guarded (不可被批量赋值的属性设置) ,来对创建、更新的数据进行过滤限制呢?
公司业务的代码我会检查 body 中的参数,但是确实没有找到 best practice。写 sequelize-example 的时候确实是偷懒了,赞成这个。
是否可以给 Controller 基类定义 permit 函数
结合使用 egg-validate
class BaseController {
permit(validateSchema, requestObj) {
const ctx = this.ctx;
ctx.validate(obj, requestObj);
const targetObj = {};
for (const key in requestObj) {
targetObj[key] = requestObj[key];
}
return targetObj;
}
}
// 业务 Controller
* index() {
const createParam = this.permit({
type: { type: 'string' },
}, ctx.request.body);
console.log(createParam.type);
....
}
使用ts的话,应该是一个以 model 形式的 interface 来限制传参,多余的参数被过滤掉,这样可以避免
这是一个很好的办法,学习了!
Most helpful comment
https://github.com/eggjs/egg-parameters
已实现