热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Lodash严重安全漏洞背后:你不得不知道的JavaScript知识

作者|LucasHC来源|https:zhuanlan.zhihu.comp73186974可能有信息敏感的同学已经了解到:Lodash库爆出严重安全漏洞࿰

640?wx_fmt=jpeg

作者 | Lucas HC

来源 | https://zhuanlan.zhihu.com/p/73186974


可能有信息敏感的同学已经了解到:Lodash 库爆出严重安全漏洞,波及 400万+ 项目。这个漏洞使得 lodash “连夜”发版以解决潜在问题,并强烈建议开发者升级版本。

我们在忙着“看热闹”或者“”升级版本”的同时,静下心来想:真的有理解这个漏洞产生的原因,明白漏洞修复背后的原理了吗?

这篇短文将从原理层面分析这一事件,相信“小白”读者会有所收获。

漏洞原因

其实漏洞很简单,举一个例子:lodash 中 defaultsDeep 方法

_.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } })

输出:

{ 'a': { 'b': 2, 'c': 3 } }

如上例,该方法:

分配来源对象(该方法的第二个参数)的可枚举属性到目标对象(该方法的第一个参数)所有解析为 undefined 的属性上

这样的操作存在的隐患:

const payload = '{"constructor": {"prototype": {"toString": true}}}'_.defaultsDeep({}, JSON.parse(payload))

如此一来,就触发了原型污染。原型污染是指:

攻击者通过某种手段修改 Javascript 对象的原型(prototype)

对应上例,Object.prototype.toString 就会非常不安全了。

详解原型污染

理解原型污染,需要读者理解 Javascript 当中的原型、原型链的知识。我们先来看一个例子:

// person 是一个简单的 Javascript 对象let person = {name: 'lucas'}
// 输出 lucas
console.log(person.name)
// 修改 person 的原型
person.__proto__.name = 'messi'
// 由于原型链顺序查找的原因,person.name 仍然是 lucas
console.log(person.name)
// 再创建一个空的 person2 对象
let person2 = {}
// 查看 person2.name,输出 messi
console.log(person2.name)

把危害扩大化:

let person = {name: 'lucas'}console.log(person.name)person.__proto__.toString = () => {alert('evil')}console.log(person.name)let person2 = {}console.log(person2.toString())

这段代码执行将会 alert 出 evil 文字。同时 Object.prototype.toString 这个方法会在隐式转换以及类型判断中经常被用到:

Object.prototype.toString 方法返回一个表示该对象的字符串

每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。

默认情况下,toString() 方法被每个 Object 对象继承。

如果此方法在自定义对象中未被覆盖,toString() 返回 [object type],其中 type 是对象的类型。

如果 Object 原型上的 toString 被污染,后果可想而知。以此为例,可见 lodash 这次漏洞算是比较严重了。

再谈原型污染(NodeJS 漏洞案例)

由上分析,我们知道原型污染并不是什么新鲜的漏洞,它“随时可见”,“随处可见”。在 Nullcon HackIM 比赛中就有一个类似的 hack 题目:

'use strict';const express = require('express');
const bodyParser = require('body-parser')const COOKIEParser = require('COOKIE-parser');const path = require('path');const isObject = obj => obj && obj.constructor && obj.constructor === Object;function merge(a, b) { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a}function clone(a) { return merge({}, a);}
// Constantsconst PORT = 8080;const HOST = '0.0.0.0';const admin = {};
// Appconst app = express();
app.use(bodyParser.json())app.use(COOKIEParser());app.use('/', express.static(path.join(__dirname, 'views')));app.post('/signup', (req, res) => { var body = JSON.parse(JSON.stringify(req.body)); var copybody = clone(body) if (copybody.name) { res.COOKIE('name', copybody.name).json({ "done": "COOKIE set" }); } else { res.json({ "error": "COOKIE not set" }) }});app.get('/getFlag', (req, res) => { var аdmin = JSON.parse(JSON.stringify(req.COOKIEs)) if (admin.аdmin == 1) { res.send("hackim19{}"); } else { res.send("You are not authorized"); }});app.listen(PORT, HOST);console.log(`Running on http://${HOST}:${PORT}`);

这段代码的漏洞就在于 merge 函数上,我们可以这样攻击:

curl -vv --header 'Content-type: application/json' -d '{"__proto__": {"admin": 1}}' 'http://0.0.0.0:4000/signup'; curl -vv 'http://0.0.0.0:4000/getFlag'

首先请求 /signup 接口,在 NodeJS 服务中,我们调用了有漏洞的 merge 方法,并通过 proto 为 Object.prototype(因为 {}.proto === Object.prototype) 添加上一个新的属性 admin,且值为 1。

再次请求 getFlag 接口,条件语句 admin.аdmin == 1 为 true,服务被攻击。

攻击案例出自:Prototype pollution attacks in NodeJS applications

这样的漏洞在 jQuery $.extend 中也经常见到:

  • jQuery 修复原型污染 PR:https://github.com/jquery/jquery/pull/4333

  • jQuery prototype pollution vulnerability:https://snyk.io/blog/after-three-years-of-silence-a-new-jquery-prototype-pollution-vulnerability-emerges-once-again/


对于 jQuery:如果担心安全问题,建议升级至最新版本 jQuery 3.4.0,如果还在使用 jQuery 的 1.x 和 2.x 版本,那么你的应用程序和网站仍有可能遭受攻击。

防范原型污染

了解了漏洞潜在问题以及攻击手段,那么如何防范呢?

在 lodash “连夜”发版的修复中:

640?wx_fmt=jpeg

我们可以清晰的看到,在遍历 merge 时,当遇见 constructor 以及 proto 敏感属性,则退出程序。

那么作为业务开发者,我们需要注意些什么,防止攻击出现呢?总结一下有:

冻结 Object.prototype,使原型不能扩充属性

我们可以采用 Object.freeze 达到目的:

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

看代码:

Object.freeze(Object.prototype);Object.prototype.toString = 'evil'consoel.log(Object.prototype.toString)ƒ toString() { [native code] }

对比:

Object.prototype.toString = 'evil'console.log(Object.prototype.toString)"evil"

  • 建立 JSON schema在解析用户输入内容是,通过 JSON schema 过滤敏感键名。

  • 规避不安全的递归性合并这一点类似 lodash 修复手段,完善了合并操作的安全性,对敏感键名跳过处理

  • 使用无原型对象,在创建对象时,不采用字面量方式,而是使用 Object.create(null):


Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto

Object.create(null) 的返回值不会链接到 Object.prototype:

let foo = Object.create(null)
console.log(foo.__proto__)
// undefined

这样一来,无论如何扩充对象,都不会干扰到原型了。

采用新的 Map 数据类型,代替 Object 类型

Map 对象保存键/值对,是键/值对的集合。任何值(对象或者原始值)都可以作为一个键或一个值。使用 Map 数据结构,不会存在 Object 原型污染状况。

这里总结一下 Map 和 Object 不同点::

  • Object 的键只支持 String 或者 Symbols 两种类型,Map 的键可以是任意值,包括函数、对象、基本类型

  • Map 中的键值是有序的,而 Object 中的键则不是

  • 具体 API 上的差异:比如,通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值无法获取;再比如迭代一个 Map 和 Object 差异也比较明显

  • Map 在频繁增删键值对的场景下会有些性能优势

补充:V8,chromium 的小机灵

同样存在风险的是我们常用的 JSON.parse 方法,但是如果你运行:

JSON.parse('{ "a":1, "__proto__": { "b": 2 }}')

你会发现返回的结果如图:

640?wx_fmt=jpeg

复写 Object.prototype 失败了,proto 属性还是我们熟悉的那个有安全感的 __proto__ 。这是因为:

V8 ignores keys named proto in JSON.parse

这个相关讨论 Doug Crockford,Brendan Eich,反正 chromium 和 JS 发明人讨论过很多次。相关 issue 和 PR:

  • chromium 讨论

  • chromium 讨论


相关 ES 语言设计的讨论:ES 语言设计的讨论:proto-and-json


在上面链接中,你能发现 Javascript 发明人等一众大佬哦~


总之你可以记住,V8 默认使用 JSON.parse 时候会忽略 proto,原因当然是之前分析的安全性了。

总结

通过分析 lodash 的漏洞,以及解决方案,我们了解了原型污染的方方面面。涉及到的知识点包括但不限于:

  • Object 原型

  • 原型、原型链

  • NodeJS 相关问题

  • Object.create 方法

  • Object.freeze 方法

  • Map 数据结构

  • 深拷贝

  • 以及其他问题


这么来看,全是基础知识。也正是基础,构成了前端知识体系的方方面面。



640?wx_fmt=jpeg

640?wx_fmt=jpeg


推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 人脸检测 pyqt+opencv+dlib
    一、实验目标绘制PyQT界面,调用摄像头显示人脸信息。在界面中,用户通过点击不同的按键可以实现多种功能:打开和关闭摄像头, ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • NotSupportedException无法将类型“System.DateTime”强制转换为类型“System.Object”
    本文介绍了在使用LINQ to Entities时出现的NotSupportedException异常,该异常是由于无法将类型“System.DateTime”强制转换为类型“System.Object”所导致的。同时还介绍了相关的错误信息和解决方法。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • node.jsrequire和ES6导入导出的区别原 ... [详细]
  • 本文介绍了如何使用MATLAB调用摄像头进行人脸检测和识别。首先需要安装扩展工具,并下载安装OS Generic Video Interface。然后使用MATLAB的机器视觉工具箱中的VJ算法进行人脸检测,可以直接调用CascadeObjectDetector函数进行检测。同时还介绍了如何调用摄像头进行人脸识别,并对每一帧图像进行识别。最后,给出了一些相关的参考资料和实例。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了源码分析--ConcurrentHashMap与HashTable(JDK1.8)相关的知识,希望对你有一定的参考价值。  Concu ... [详细]
  • 学习笔记17:Opencv处理调整图片亮度和对比度
    一、理论基础在数学中我们学过线性理论,在图像亮度和对比度调节中同样适用,看下面这个公式:在图像像素中其中:参数f(x)表示源图像像素。参数g(x)表示输出图像像素。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了VoLTE端到端业务详解|VoLTE用户注册流程相关的知识,希望对你有一定的参考价值。书籍来源:艾怀丽 ... [详细]
  • Linux线程的同步和互斥
    目录1、线程的互斥2、可重入VS线程安全3、线程的同步1、线程的互斥 ... [详细]
author-avatar
小辛牛牛123牛牛小辛321
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有