return error怎么定义_手把手教你自定义实现一个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命令以获取相关的结果。

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

而且使用npm audit不能控制依赖的深度,返回的结果为所有深度的依赖项目。

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      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.jsonEnd. Consume time: 18.879s.Start construct dependency tree by package-lock.json. Lock file path: /path/to/package-lock.jsonMax dependency deep is 3End. 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的功能,当遍历深度不深时,时间是还是可以接受的,但是算法的总体效率还是偏低,后续将对整个依赖树构建算法进行针对性优化。

556d44ac6150cffaa4e71513287d09f9.gif

精彩推荐

7663be717058cfd0c40f97862fd6c399.png 0b4c890391e3a8436c70afea095c74ed.png 286274a5d523508a1455c31a899dfa25.png

2b21ba56003dd6cb37e32a7e1f426ef5.png

2fac2106b4392bbc3a2d164ba94e1c4a.png

98bc1f7c778b1bcb7b72b2cf056008bf.png

ca0b7606e92ebd07d73471cd475d0248.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值