├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── lib └── serialize.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - '0.11' 6 | - '0.12' 7 | - '4' 8 | - '5' 9 | - '6' 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Zihua Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-serialize 2 | 3 | Serialize a object including it's function into a JSON. 4 | 5 | [![Build Status](https://travis-ci.org/luin/serialize.png?branch=master)](https://travis-ci.org/luin/serialize) 6 | 7 | ## SECURITY WARNING 8 | 9 | This module provides a way to unserialize strings into executable JavaScript code, so that it may lead security vulnerabilities if the original strings can be modified by untrusted third-parties (aka hackers). For instance, the following attack example provided by [ajinabraham](https://github.com/luin/serialize/issues/4) shows how to achieve arbitrary code injection with an IIFE: 10 | 11 | ```javascript 12 | var serialize = require('node-serialize'); 13 | var x = '{"rce":"_$$ND_FUNC$$_function (){console.log(\'exploited\')}()"}' 14 | serialize.unserialize(x); 15 | ``` 16 | 17 | To avoid the security issues, at least one of the following methods should be taken: 18 | 19 | 1. Make sure to send serialized strings internally, isolating them from potential hackers. For example, only sending the strings from backend to fronend and always using HTTPS instead of HTTP. 20 | 21 | 2. Introduce public-key cryptosystems (e.g. RSA) to ensure the strings not being tampered with. 22 | 23 | 24 | ## Install 25 | 26 | ``` 27 | npm install node-serialize 28 | ``` 29 | 30 | ## Usage 31 | 32 | ```javascript 33 | var serialize = require('node-serialize'); 34 | ``` 35 | 36 | Serialize an object including it's function: 37 | 38 | 39 | ```javascript 40 | var obj = { 41 | name: 'Bob', 42 | say: function() { 43 | return 'hi ' + this.name; 44 | } 45 | }; 46 | 47 | var objS = serialize.serialize(obj); 48 | typeof objS === 'string'; 49 | serialize.unserialize(objS).say() === 'hi Bob'; 50 | ``` 51 | 52 | Serialize an object with a sub object: 53 | 54 | ```javascript 55 | var objWithSubObj = { 56 | obj: { 57 | name: 'Jeff', 58 | say: function() { 59 | return 'hi ' + this.name; 60 | } 61 | } 62 | }; 63 | 64 | var objWithSubObjS = serialize.serialize(objWithSubObj); 65 | typeof objWithSubObjS === 'string'; 66 | serialize.unserialize(objWithSubObjS).obj.say() === 'hi Jeff'; 67 | ``` 68 | 69 | Serialize a circular object: 70 | 71 | ```javascript 72 | var objCircular = {}; 73 | objCircular.self = objCircular; 74 | 75 | var objCircularS = serialize.serialize(objCircular); 76 | typeof objCircularS === 'string'; 77 | typeof serialize.unserialize(objCircularS).self.self.self.self === 'object'; 78 | ``` 79 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.export = require('./lib/serialize'); 2 | -------------------------------------------------------------------------------- /lib/serialize.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var FUNCFLAG = '_$$ND_FUNC$$_'; 3 | var CIRCULARFLAG = '_$$ND_CC$$_'; 4 | var KEYPATHSEPARATOR = '_$$.$$_'; 5 | var ISNATIVEFUNC = /^function\s*[^(]*\(.*\)\s*\{\s*\[native code\]\s*\}$/; 6 | 7 | var getKeyPath = function(obj, path) { 8 | path = path.split(KEYPATHSEPARATOR); 9 | var currentObj = obj; 10 | path.forEach(function(p, index) { 11 | if (index) { 12 | currentObj = currentObj[p]; 13 | } 14 | }); 15 | return currentObj; 16 | }; 17 | 18 | serialize = function(obj, ignoreNativeFunc, outputObj, cache, path) { 19 | path = path || '$'; 20 | cache = cache || {}; 21 | cache[path] = obj; 22 | outputObj = outputObj || {}; 23 | 24 | var key; 25 | for(key in obj) { 26 | if(obj.hasOwnProperty(key)) { 27 | if(typeof obj[key] === 'object' && obj[key] !== null) { 28 | var subKey; 29 | var found = false; 30 | for(subKey in cache) { 31 | if (cache.hasOwnProperty(subKey)) { 32 | if (cache[subKey] === obj[key]) { 33 | outputObj[key] = CIRCULARFLAG + subKey; 34 | found = true; 35 | } 36 | } 37 | } 38 | if (!found) { 39 | outputObj[key] = serialize(obj[key], ignoreNativeFunc, outputObj[key], cache, path + KEYPATHSEPARATOR + key); 40 | } 41 | } else if(typeof obj[key] === 'function') { 42 | var funcStr = obj[key].toString(); 43 | if(ISNATIVEFUNC.test(funcStr)) { 44 | if(ignoreNativeFunc) { 45 | funcStr = 'function() {throw new Error("Call a native function unserialized")}'; 46 | } else { 47 | throw new Error('Can\'t serialize a object with a native function property. Use serialize(obj, true) to ignore the error.'); 48 | } 49 | } 50 | outputObj[key] = FUNCFLAG + funcStr; 51 | } else { 52 | outputObj[key] = obj[key]; 53 | } 54 | } 55 | } 56 | 57 | return (path === '$') ? JSON.stringify(outputObj) : outputObj; 58 | }; 59 | 60 | unserialize = function(obj, originObj) { 61 | var isIndex; 62 | if (typeof obj === 'string') { 63 | obj = JSON.parse(obj); 64 | isIndex = true; 65 | } 66 | originObj = originObj || obj; 67 | 68 | var circularTasks = []; 69 | var key; 70 | for(key in obj) { 71 | if(obj.hasOwnProperty(key)) { 72 | if(typeof obj[key] === 'object') { 73 | obj[key] = unserialize(obj[key], originObj); 74 | } else if(typeof obj[key] === 'string') { 75 | if(obj[key].indexOf(FUNCFLAG) === 0) { 76 | obj[key] = eval('(' + obj[key].substring(FUNCFLAG.length) + ')'); 77 | } else if(obj[key].indexOf(CIRCULARFLAG) === 0) { 78 | obj[key] = obj[key].substring(CIRCULARFLAG.length); 79 | circularTasks.push({obj: obj, key: key}); 80 | } 81 | } 82 | } 83 | } 84 | 85 | if (isIndex) { 86 | circularTasks.forEach(function(task) { 87 | task.obj[task.key] = getKeyPath(originObj, task.obj[task.key]); 88 | }); 89 | } 90 | return obj; 91 | }; 92 | 93 | 94 | var objectToExports = { 95 | serialize: serialize, 96 | unserialize: unserialize 97 | }; 98 | 99 | // Node.js 100 | if (typeof module === 'object' && module.exports) { 101 | module.exports = objectToExports; 102 | } 103 | // AMD / RequireJS 104 | else if (typeof define === 'function' && define.amd) { 105 | define(function () { return objectToExports; }); 106 | } 107 | // included directly via