1.问题
const run = () =>{ const auditCommand = 'npm audit --registry=https://r.cnpmjs.org/ --audit-level=high --production --json'; const execOptions = { maxBuffer: 10 * 1024 * 1024 }; exec(auditCommand, execOptions, function (error, stdout, stderr) { if (error !== null) { console.log('exec error: ' + error); return; } if (stdout) { console.log(stdout); } }); }
exec error: Error: Command failed: npm audit --registry=https://r.cnpmjs.org/ --audit-level=high --production --json
2.解决问题
如果想要自定义实现一套自己的npm audit,需要解决哪些问题呢?我觉得有如下几个问题需要解决:
- 如何获取漏洞库?
- 从package.json中解析一级依赖。
- 根据package-lock.json解析并生成依赖树。
- 从依赖树中生成依赖链。
- 判断当前引用版本是否存在问题。
2.1 漏洞库获取
同npm audit一样,我们使用Security advisoriesd的漏洞库,该漏洞库可以直接通过相关的接口获取,只是在header头中需要设置“’x-spiferack’: ‘1’”,这里不再赘述,部分代码如下。
const result = await axios.get(base_url + '?page=' + pageNum.toString() + '&perPage=' + perPageNum.toString(), { headers: { 'x-requested-with': 'XMLHttpRequest', 'x-spiferack': '1', }, }); const data_json = result.data; total_num = data_json.advisoriesData.total; const objects = data_json.advisoriesData.objects;
2.2 package.json解析
由于package.json是一个json文件,可以直接读取文件内容,然后通过JSON.parse()方法获取相关的json数据,并从dependencies,devDependencies中提取出相关的一级依赖。
2.3 由package-lock.json构建依赖树
熟悉package-lock.json文件结构的应该清楚,该文件会列出项目所有的依赖项,包括间接依赖,直至该相关的依赖项不再有依赖项为止,因此该文件对构建项目的整体依赖树非常便利。首先定义依赖树的节点,如下所示,节点中各数据的含义解释见注释。
function DenpendencyNode(name, version, vulIndex=-1, isDev=false) {
// 节点的唯一标示,方便增加节点时快速寻找父节点 this.identify = getMd5(Math.random().toString());
// 当前节点的深度 this.deep = 0;
// 依赖包的名称 this.name = name;
// 依赖包的版本号 this.version = version;
// 与2.1的漏洞库对应,方便判断该节点是否存在漏洞 this.vulIndex = vulIndex;
// 是否为测试依赖项目 this.isDev = isDev;
// 父节点 this.parent = null;
// 子节点列表 this.children = []; }
然后在具体定义树的结构及相关添加元素、遍历等方法,如下所示,相关说明见注解。
// 一般通过一个空的根节点初始化整颗树
function DenpendencyTree(name, version) { let denpendencyNode = new DenpendencyNode(name, version); this._root = denpendencyNode; }
// 深度优先遍历,可以通过定义callback函数来执行特定操作,如提取漏洞依赖链 DenpendencyTree.prototype.traverseDF = function (callback) { (function recurse(currentNode) { for (let i = 0, { length } = currentNode.children; i < length; i++) { recurse(currentNode.children[i]); } callback(currentNode); })(this._root); };
// 检测是否包含某个节点 DenpendencyTree.prototype.contains = function (callback, traversal) { traversal.call(this, callback); };
// 添加元素,通过节点的唯一标识identify来找到父节点 DenpendencyTree.prototype.add = function (name, version, vulIndex, isDev, toIdentify, traversal) { let child = new DenpendencyNode(name, version, vulIndex, isDev); let parent = null; let callback = function (node) { if (node.identify === toIdentify) { parent = node; } }; this.contains(callback, traversal); if (parent) { parent.children.push(child); child.parent = parent; child.deep = parent.deep + 1; } else { throw new Error('Cannot add node to a non-existent parent.'); } return [child.identify, child.deep]; };
在构建树的过程中有一点必须注意,就是对树的深度进行限制,当某个节点超过限定的深度时,则停止添加子节点,如果不进行限制则可能造成死循环,根据实际测试的时间,建议深度设置为3。
2.4 生成依赖链
生成依赖链可以通过traverseDF方法中定义的callback函数实现,对树进行遍历,当某个节点的vulIndex大于 -1 时,表明该节点存在漏洞,则遍历获取该节点的父节点,直至父节点为跟节点为止,从而构建出整条依赖链。
deConstructDenpendencyTree(results) {
// 所有的依赖链 const dependency_lists = []; const denpendencyTree = results['denpendencyTree']; denpendencyTree.traverseDF(node => { if (node.vulIndex > -1) { let _list = []; while(node.parent) { _list.push(node); node = node.parent; } dependency_lists.push(_list); } }); results['dependencyLists'] = dependency_lists; }
2.5 漏洞版本判断
漏洞的判断直接将依赖的版本与漏洞版本进行判断,这个判断个人觉得大多是是正确的,但是仍然有小部分判断存在问题,所以如果大家有更好的判断方法,欢迎告知。
const vulnerable_version = '>=0.2.1 <1.0.0 || >=1.2.3'; console.log(innerJudge('0.0.8', vulnerable_version)); function innerJudge(pkVersion, vulnerable_version) { const v_lists = vulnerable_version.split('||').map((key) => { const ii_list = key.trim().split(' ').map((key) => { return key.trim(); }); return ii_list; }); let final_is_vul = false; const single_symbol = ['>', '>=', '<', '<=', '~']; for (const v_list of v_lists) { let judege_list = []; let last_index = 0; for (let i=0; i<v_list.length; i++) { const _item = v_list[i]; if ((_item != '*') && (single_symbol.indexOf(_item.substring(0, 2)) == -1) && (single_symbol.indexOf(_item.substring(0, 1)) == -1) && (_item.trim() != '')) { let _sst = ''; for (let j=last_index; j<=i; j++) { _sst = `${_sst.trim()}${v_list[j].trim()}`; } judege_list.push(_sst); last_index = i + 1; } else if (single_symbol.indexOf(_item) == -1 && _item.trim() != '') { judege_list.push(_item); } else if (_item == '*' && _item.trim() != '') { judege_list.push(_item); } } if (judege_list.length == 0) { judege_list = v_list; } // console.log(v_list); // console.log(judege_list); // console.log('------------'); let is_vul = false; switch (judege_list.length) { case 1: is_vul = singleJudge(pkVersion, judege_list[0]); break; case 2: is_vul = singleJudge(pkVersion, judege_list[0]) && singleJudge(pkVersion, judege_list[1]); break; default: console.log('Impossible array length.', judege_list); break; } if (is_vul) { final_is_vul = is_vul; break; } } return final_is_vul; } function singleJudge(pkVersion, _v) { let is_vul = false; if (_v == '*') { is_vul = true; } else if (_v.indexOf('~') == 0) { if (pkVersion.indexOf(_v.substring(1)) == 0) { is_vul = true; } } else if (_v.indexOf('<=') == 0) { if (pkVersion <= _v.substring(2)) { is_vul = true; } } else if (_v.indexOf('<') == 0) { if (pkVersion < _v.substring(1)) { is_vul = true; } } else if (_v.indexOf('>=') == 0) { if (pkVersion >= _v.substring(2)) { is_vul = true; } } else if (_v.indexOf('>') == 0) { if (pkVersion > _v.substring(1)) { is_vul = true; } } return is_vul; }
3.测试示例
Start pull security advisories. Data pull progress: 6.997% Data pull progress: 13.99% Data pull progress: 20.99% Data pull progress: 27.99% Data pull progress: 34.98% Data pull progress: 41.98% Data pull progress: 48.98% Data pull progress: 55.98% Data pull progress: 62.98% Data pull progress: 69.97% Data pull progress: 76.97% Data pull progress: 83.97% Data pull progress: 90.97% Data pull progress: 97.97% Data pull progress: 100% End. Security advisories size 1429. Consume time: 18.734s. Start get base dependencies by package.json. File path: /path/to/package.json End. Consume time: 18.879s. Start construct dependency tree by package-lock.json. Lock file path: /path/to/package-lock.json Max dependency deep is 3 End. Consume time: 0.9029999999999987s. Start generate dependency lists. End. Generate 223 dependency list. Consume time 0s --------------------------------------------------------- Result Size : 223 --------------------------------------------------------- --------------------------------------------------------- Severity : low Package : minimist Version : 0.0.8 VulnerableVersion: <0.2.1 || >=1.0.0 <1.2.3 PatchedVsersion : >=0.2.1 <1.0.0 || >=1.2.3 DependencyPath : mkdirp > minimist Dev : false MoreInfo : https://www.npmjs.com/advisories/1179 --------------------------------------------------------- .......
4.问题及后续优化
目前该工具基本实现了npm audit的功能,当遍历深度不深时,时间是还是可以接受的,但是算法的总体效率还是偏低,后续将对整个依赖树构建算法进行针对性优化。
来源:freebuf.com 2020-08-25 18:29:11 by: nightmarelee
请登录后发表评论
注册