FreeBuf|手把手教你自定义实现一个npm audit


代码库地址:https://github.com/blackarbiter/node-dep-audit
npm包地址:https://www.npmjs.com/package/node-dep-audit
1.问题npm audit命令可以帮助检测项目的依赖包是否存在已知的漏洞 , 漏洞库来源:Security advisories 。 当希望将依赖组件漏洞纳入SAST漏洞扫描范围时 , 通常的想法是通过执行npm audit命令以获取相关的结果 。
construn ==>{constauditCommand ='npm audit --registry=https://r.cnpmjs.org/ --audit-level=high --production --json';constexecOptions = {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);}});}但是执行的时候往往或出现下面的错误 。
execerror: Error: Command failed: npm audit --registry=https://r.cnpmjs.org/ --audit-level=high --production --json而且使用npm audit不能控制依赖的深度 , 返回的结果为所有深度的依赖项目 。
2.解决问题如果想要自定义实现一套自己的npm audit , 需要解决哪些问题呢?我觉得有如下几个问题需要解决:
如何获取漏洞库?
从package.json中解析一级依赖 。
根据package-lock.json解析并生成依赖树 。
从依赖树中生成依赖链 。
判断当前引用版本是否存在问题 。
2.1 漏洞库获取同npm audit一样 , 我们使用Security advisoriesd的漏洞库 , 该漏洞库可以直接通过相关的接口获取 , 只是在header头中需要设置“’x-spiferack’: ‘1’” , 这里不再赘述 , 部分代码如下 。
constresult =awaitaxios.get(base_url +'?page='+ pageNum.toString +'&perPage='+ perPageNum.toString,},});constdata_json = result.data;total_num = data_json.advisoriesData.total;constobjects = 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) {letdenpendencyNode =newDenpendencyNode(name, version);this._root = denpendencyNode;}// 深度优先遍历 , 可以通过定义callback函数来执行特定操作 , 如提取漏洞依赖链DenpendencyTree.prototype.traverseDF =function(callback){(functionrecurse(currentNode){for(leti =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){letchild =newDenpendencyNode(name, version, vulIndex, isDev);letparent =null;letcallback =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{thrownewError('Cannot add node to a non-existent parent.');}return[child.identify, child.deep];};在构建树的过程中有一点必须注意 , 就是对树的深度进行限制 , 当某个节点超过限定的深度时 , 则停止添加子节点 , 如果不进行限制则可能造成死循环 , 根据实际测试的时间 , 建议深度设置为3 。
2.4 生成依赖链生成依赖链可以通过traverseDF方法中定义的callback函数实现 , 对树进行遍历 , 当某个节点的vulIndex大于 -1 时 , 表明该节点存在漏洞 , 则遍历获取该节点的父节点 , 直至父节点为根节点为止 , 从而构建出整条依赖链 。
deConstructDenpendencyTree(results) {// 所有的依赖链constdependency_lists = ;constdenpendencyTree = 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 漏洞版本判断漏洞的判断直接将依赖的版本与漏洞版本进行判断 , 这个判断个人觉得大多是是正确的 , 但是仍然有小部分判断存在问题 , 所以如果大家有更好的判断方法 , 欢迎告知 。
constvulnerable_version ='>=0.2.1 <1.0.0 || >=1.2.3';console.log(innerJudge('0.0.8', vulnerable_version));functioninnerJudge(pkVersion, vulnerable_version){constv_lists = vulnerable_version.split('||').map((key) =>{constii_list = key.trim.split(' ').map((key) =>{returnkey.trim;});returnii_list;});letfinal_is_vul =false;constsingle_symbol = ['>','>=','<','<=','~'];for(constv_listofv_lists) {letjudege_list = ;letlast_index =0;for(leti=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(letj=last_index; j<=i; j++) {_sst =`${_sst.trim}${v_list[j].trim}`;}judege_list.push(_sst);last_index = i +1;}elseif(single_symbol.indexOf(_item) ==-1&& _item.trim !='') {judege_list.push(_item);}elseif(_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('');letis_vul =false;switch(judege_list.length) {case1:is_vul = singleJudge(pkVersion, judege_list[0]);break;case2:is_vul = singleJudge(pkVersion, judege_list[0]) && singleJudge(pkVersion, judege_list[1]);default:console.log('Impossible array length.', judege_list);break;}if(is_vul) {final_is_vul = is_vul;break;}}returnfinal_is_vul;}functionsingleJudge(pkVersion, _v){letis_vul =false;if(_v =='*') {is_vul =true;}elseif(_v.indexOf('~') ==0) {if(pkVersion.indexOf(_v.substring(1)) ==0) {is_vul =true;}}elseif(_v.indexOf('<=') ==0) {if(pkVersion <= _v.substring(2)) {is_vul =true;}}elseif(_v.indexOf('<') ==0) {if(pkVersion < _v.substring(1)) {is_vul =true;}}elseif(_v.indexOf('>=') ==0) {if(pkVersion >= _v.substring(2)) {is_vul =true;}}elseif(_v.indexOf('>') ==0) {if(pkVersion > _v.substring(1)) {is_vul =true;}}returnis_vul;}3.测试示例Startpullsecurityadvisories.Datapull progress:6.997%Datapull progress:13.99%Datapull progress:20.99%Datapull progress:27.99%Datapull progress:34.98%Datapull progress:41.98%Datapull progress:48.98%Datapull progress:55.98%Datapull progress:62.98%Datapull progress:69.97%Datapull progress:76.97%Datapull progress:83.97%Datapull progress:90.97%Datapull progress:97.97%Datapull progress:100%End.Securityadvisoriessize1429\. Consumetime:18.734s.Startgetbase dependenciesbypackage.json.Filepath: /path/to/package.jsonEnd. Consumetime:18.879s.Startconstruct dependency treebypackage-lock.json.Lockfilepath: /path/to/package-lock.jsonMaxdependency deepis3End. Consumetime:0.9029999999999987s.Startgenerate dependency lists.End. Generate223dependency list. Consumetime0sResultSize:223Version:0.0.8VulnerableVersion: <0.2.1|| >=1.0.0<1.2.3PatchedVsersion : >=0.2.1<1.0.0|| >=1.2.3DependencyPath : mkdirp > minimistDev :falseMoreInfo : https://www.npmjs.com/advisories/1179.......4.问题及后续优化目前该工具基本实现了npm audit的功能 , 当遍历深度不深时 , 时间是还是可以接受的 , 但是算法的总体效率还是偏低 , 后续将对整个依赖树构建算法进行针对性优化 。
FreeBuf|手把手教你自定义实现一个npm audit
本文插图


FreeBuf|手把手教你自定义实现一个npm audit
本文插图
【FreeBuf|手把手教你自定义实现一个npm audit】


    推荐阅读