CVE-2021-23419 open-graph 原型链污染分析

 

CVE-2021-23419 open-graph 原型链污染分析

文章首发于安全客:CVE-2021-23419 open-graph 原型链污染分析

0x00 前言

CVE-2021-23419是Vulnerability DB在2021 年 8 月 5 日披露的出现在 node.js open-graph 包中的一个原型链污染漏洞,CVSS 分数为7.3,攻击难度低,下面是笔者的分析过程,以此来学习 open graph 协议与 JavaScript 原型链污染形成原理。

0x01 open graph 简介

开放图谱 (OG) 协议是 Facebook 于 2010 年创建的,旨在使网页链接成为具有与 Facebook 上发布的其他内容相似的功能和外观的丰富对象。 如果您已经在社交媒体上分享了一个链接,并且看到该平台甚至在您点击 Post 之前就自动向您显示了您想要分享的链接的大图片、标题、描述和 URL。

Open Graph 元标记在 HTML 页面的<head>内容中,用一系列以“og”为前缀的属性进行标识。

例如:

<html prefix="og: https://ogp.me/ns#">
<head>
<title>The Rock (1996)</title>
<meta property="og:title" content="The Rock" />
<meta property="og:type" content="video.movie" />
<meta property="og:url" content="https://www.imdb.com/title/tt0117500/" />
<meta property="og:image" content="https://ia.media-imdb.com/images/rock.jpg" />
...
</head>
...
</html>

OG 元标记还可用于根据其共享平台自定义网页的外观。 例如,Twitter 基于OG 协议对其进行了自定义实现,下面的代码告诉 Twitter 显示大图像网页预览。

<meta name="twitter:card" content="summary_large_image" />
<meta
    name="twitter:image"
    content="https://example.com/image.png"
/>

在twitter中查看源码并搜索 og: 就可以看到 open graph 的标签。

image-20210812221325417

open graph 中定义的元数据有很多种类型:

基本元数据:

  • og:title - 应该出现在图表中的对象的标题。
  • og:type-对象的类型,例如”video.movie”。
  • og:image - 一个图像 URL。
  • og:url - 这一个 url 将用作该图片的永久 ID。

示例:

<html prefix="og: https://ogp.me/ns#">
<head>
<title>The Rock (1996)</title>
<meta property="og:title" content="The Rock" />
<meta property="og:type" content="video.movie" />
<meta property="og:url" content="https://www.imdb.com/title/tt0117500/" />
<meta property="og:image" content="https://ia.media-imdb.com/images/rock.jpg" />
...
</head>
...
</html>

可选元数据:

  • og:audio :伴随该对象的音频文件的 URL。
  • og:description: 对此对象的描述。
  • og:determiner:语法中的限定词。
  • og:locale :语言 ,默认为en_US.
  • og:locale:alternate :其他可以代替的语言,一个数组。
  • og:site_name:站点名词,如果您的对象是一个大型网站的一部分,则应为整个网站显示的名称。
  • og:video :补充此对象的视频文件的 URL。

元数据的属性:

某些元数据可以进一步补充其属性,以og:image为例,可以补充如下的属性:

  • og:image:type:此图像的 MIME 类型。
  • og:image:width :像素宽度。
  • og:image:height : 高像素数。
  • og:image:alt:对图像内容的描述(不是标题)。
  • og:image:url:与og:image完全相同.
  • og:image:secure_url :网页需要 HTTPS 时使用的备用网址。

示例如下:

<meta property="og:audio" content="https://example.com/bond/theme.mp3" />
<meta property="og:description" 
  content="Sean Connery found fame and fortune as the
           suave, sophisticated British agent, James Bond." />
<meta property="og:determiner" content="the" />
<meta property="og:locale" content="en_GB" />
<meta property="og:locale:alternate" content="fr_FR" />
<meta property="og:locale:alternate" content="es_ES" />
<meta property="og:site_name" content="IMDb" />
<meta property="og:video" content="https://example.com/bond/trailer.swf" /

0x02 open-graph 包简介

open-graph 是 Node.js 的 Open Graph 实现。

用法如下:

const og = require('open-graph');

const url = 'http://github.com/samholmes/node-open-graph/raw/master/test.html';

og(url, function (err, meta) {
  console.log(meta);
});

Outputs:

{
  title: 'OG Testing',
  type: 'website',
  url: 'http://github.com/samholmes/node-open-graph/raw/master/test.html',
  site_name: 'irrelavent',
  description: 'This is a test bed for Open Graph protocol.',
  image: {
    url: 'http://google.com/images/logo.gif',
    width: '100',
    height: '100'
  }
}

open-graph 向外提供 og 函数,主要功能可以分为两个部分:

  • 根据 url 获得 mata 标签内容。
  • 很具 open graph 协议解析 meta 标签的内容并存入一个 Object 对象中。

其中第二步涉及到了对象的递归赋值,因此造成原型链污染漏洞。

0x03 漏洞原理分析

open-graph 库在 8月5日被披露存在原型链污染漏洞,CVE号为 CVE-2021-23419,目前最新版本为 0.2.6,漏洞已修复,复现分析原理时可以安装 0.2.4 版本。

poc 如下:

分为server.js 与 client.js。

server.js,其实也就是一个回显 meta 标签的 web 服务。

const http = require('http');

http.createServer((req, res) => {
    res.write('<meta property="og:__proto__:foo" content="polluted" />');
    res.end();
}).listen(8080);

client.js,访问 meta 标签内容,即可看到

const og = require('open-graph');

og('http://127.0.0.1:8080', () => {
    console.log(({}).foo); // polluted
});

下面结合这个poc进行进一步分析。

打开源码 node_modules\open-graph\index.js,源码比较短,主要有两个函数,getHTML 与 parse。

getHTML 没有什么可以说的,向某个页面发起请求,解析页面内容,cb 是一个回调函数,在 client.js 调用 og 函数时没有指定,这里就无需关注。

exports.getHTML = function(url, cb){
	var purl = require('url').parse(url);

	if (!purl.protocol)
		purl = require('url').parse("http://"+url);

	url = require('url').format(purl);

	request({
			url: url,
			encoding: 'utf8',
			gzip: true,
			jar: true
		},
		function(err, res, body) {
			if (err) return cb(err);

			if (res.statusCode === 200) {
				cb(null, body);
			}
			else {
				cb(new Error("Request failed with HTTP status code: "+res.statusCode));
			}
		})
}

parse 函数用于将 open graph 内容解析为一个Object 对象。

我们可以直接下断到这一行。

image-20210812233050062

可以看到 mata 是一个 object 对象,mataTags 用于存放所有的 mata 标签内容。然后对所有 mata 标签进行遍历。其中:

  • namespace 内容为 ‘og’。var property = propertyAttr.substring(namespace.length+1),是将 poc 中的 og:__proto__:foo截取 og:后面的内容,因此 property 的内容为'__proto__:foo'
  • keys 为 property 根据 :切分而来,因此内容为一个数组:(2) ['__proto__', 'foo']
  • meta 赋值给了 ptr,因此 ptr 是一个 object 对象。

后面这块代码为递归进行赋值,第一个循环的结尾处 ptr = ptr[key]; ,ptr 即被赋值成了 object 类的原型。

image-20210812233617820

由于只有一个键值对,只会循环一次,跳出循环后对 ptr 进行,由于前面获取到了原型链,此处key=foo,content=polluted所以 ptr[key] = content;造成了原型链污染。

image-20210813091923929

0x04 漏洞修复

在 open-graph 0.2.6 中已经修复该漏洞,修复的方式是加入一个黑名单,并且在循环键名时进行判断。

var keyBlacklist = [
	'__proto__',
	'constructor',
	'prototype'
]
...
		while (keys.length > 1) {
			key = keys.shift();

			if (keyBlacklist.includes(key)) continue

			if (Array.isArray(ptr[key])) {
				// the last index of ptr[key] should become
				// the object we are examining.
				tmp = ptr[key].length-1;
				ptr = ptr[key];
				key = tmp;
			}

			if (typeof ptr[key] === 'string') {
				// if it's a string, convert it
				ptr[key] = { '': ptr[key] };
			} else if (ptr[key] === undefined) {
				// create a new key
				ptr[key] = {};
			}

			// move our pointer to the next subnode
			ptr = ptr[key];
		}

参考资料