免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。
自动化检测
自动化检测的灵感来源自GitHub项目:GitHub - HoLyVieR/prototype-pollution-nsec18: Content released at NorthSec 2018 for my talk on prototype pollution,我在他的基础上做了批量检查的改进。
基本原理
首先明确检测思路就是,假定JavaScript中所有的库方法都存在原型链污染,那么我们提取出库中所有的方法,依次传入payload进行调用,然后检查Object函数是否被污染,就可以实现自动化的检测JavaScript原型链污染漏洞。我们可以细化每一步:
-
首先导入库文件:
var lib = require(pkgName);
2. 当然,为了更加自动化,我们可以自动下载并导入库文件:
// 使用npm install命令下载指定版本的包,或者默认下载最新版本
try {
execSync(`npm install ${packageWithVersion}`, { stdio: 'inherit' , timeout: 60000});
} catch (err) {
console.error(`Error installing ${packageWithVersion}:`, err);
continue;
}
3. 随后,我们可以提取出库中的所有方法,使用for遍历出所有的属性和方法,使用typeof把方法筛选出来:
for (var k in lib) {
if (k == "abort") continue;
if (k == "__proto__") continue;
if (+k == k) continue;
console.log(k);
if (typeof lib[k] == "function"){
...
}
4. 提取出方法后,我们需要调用方法来污染原型链,但因为JavaScript不支持反射,我们无从得知这个方法的调用规则是什么,我们只能准备一份最常见的函数signature表
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)"
}]
5. 有了这个表之后,我们就可以依次调用方法了:
function run(fnct, sig, name, totest) {
// Reinitialize to avoid issue if the previous function changed attributes.
let BAD_JSON = JSON.parse('{"__proto__":{"test":123}}');
if (typeof totest !== 'function') {
console.warn(`Trouble: ${totest} is not a function in run() with name=${name}`);
return;
}
try {
fnct(totest);
} catch (e) {}
if (check()) {
console.log("Detected : " + name + " (" + sig + ")");
}
}
6. 运行了之后,我们怎么验证?其实很简单,如果原型链遭到污染,那么Object属性中就大概率会有我们输入的特殊属性,直接判断即可
function check() {
if ({}.test == "123" || {}.test == 123) {
delete Object.prototype.test;
return true;
}
return false;
}
批量刷洞
针对单个库文件的扫描程序我们已经写好了,但是作为一个成熟的脚本小子,我们当然不满足于一个库一个库的检测漏洞,一键批量刷洞才是我们的终极目标。
-
首先我们需要获得大量的库文件,npm有一个库all-the-package-names,已经帮我们实现:
const names = require("all-the-package-names")
names.includes('superagent')
// => true
// Check if a given package name exists
names.includes('crazy-new-package-name')
// => false
names.length
// => 286289
names.filter(name => name.includes('banana'))
// => [ 'banana', 'banana-banana', 'banana-split', ...]
2. 我们只需要其filter功能,筛选出我们想要的库名
let filterArray = names.filter(name => name.includes(packageName));
3. 然后对筛选出的库名进行循环检测
let filterArray = names.filter(name => name.includes(packageName));
write_log("Filter " + filterArray.length + " Packages.");
for (let pkgName of filterArray) {
exploreLib(lib, packageWithVersion, 5);
}
4. 但是检测前,我们需要先下载对应的库,而在大量的检测中,往往因为下载的库过多大量的占用存储空间,所以我们可以下载后检测,检测后直接卸载,不断循环
let filterArray = names.filter(name => name.includes(packageName));
write_log("Filter " + filterArray.length + " Packages.");
for (let pkgName of filterArray) {
if (pkgName == "@deepcase/android-lodash-fix"){
continue;
}
// 如果没有指定版本号,就使用最新版本
var packageWithVersion = version ? `${pkgName}@${version}` : pkgName;
write_log("Checking: " + packageWithVersion);
console.log("Downloading: " + packageWithVersion);
// 使用npm install命令下载指定版本的包,或者默认下载最新版本
try {
execSync(`npm install ${packageWithVersion}`, { stdio: 'inherit' , timeout: 60000});
} catch (err) {
console.error(`Error installing ${packageWithVersion}:`, err);
continue;
}
console.log("Exec: " + packageWithVersion);
try {
var lib = require(pkgName);
} catch (e) {
console.log("Missing library : " + packageWithVersion );
try {
execSync(`npm uninstall ${pkgName}`, { stdio: 'inherit' });
console.log(`Successfully uninstalled ${pkgName}`);
} catch (err) {
console.error(`Error uninstalling ${pkgName}:`, err);
}
continue;
}
//exploreLib(lib, packageWithVersion, 5);
let startTime = new Date().getTime();
let maxDuration = 1500; // 在这里设置你的超时时间(毫秒)
while(new Date().getTime() - startTime < maxDuration) {
try {
exploreLib(lib, packageWithVersion, 5, startTime, maxDuration);
break;
} catch(e) {
if(e.message !== 'RangeError') throw e; // 如果是其他错误,则抛出
}
}
// 执行完毕后,卸载这个包
try {
execSync(`npm uninstall ${pkgName}`, { stdio: 'inherit' });
console.log(`Successfully uninstalled ${pkgName}`);
} catch (err) {
console.error(`Error uninstalling ${pkgName}:`, err);
}
// 设置为 null 以便于垃圾收集
pkgName = null;
packageWithVersion = null;
lib = null;
}
5. ok,所有逻辑都处理完毕,可以愉快地刷洞了!代码已上传至GitHub:https://github.com/fatmo666/Proton-pollution-check
总结
本文我们首先讲解了原型链污染的原理,列举了问题代码段,并讲解了漏洞CVE-2019-10744。最后引入了采用动态检测的方式批量刷原型链污染漏洞的方法。原型链作为JavaScript实现继承的替代方案,在JavaScript代码中广泛存在,一旦发生原型链污染,将导致严重后果(可以实现拒绝服务攻击甚至是远程代码执行)。实际上,原型链污染的思想同时存在于其他编程语言中,比如Python的继承机制就有可能导致类似JS原型链污染的问题,值得我们进一步探索。自动化刷洞的方法已经放在Github,点击原文链接可以直达。
原文始发于微信公众号(赛博安全狗):原型链污染:从原理分析到批量刷洞(四)—— 批量刷洞方法与源码
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论