基础
语法
在Nodejs中,全局变量定义方法为
var PATH=...
局部变量则依旧使用 let xxx=...
全局变量
名称 | 描述 | 示例 |
---|---|---|
__filename | 当前模块的绝对路径(包括文件名) | console.log(__filename); 输出当前文件的完整路径 |
__dirname | 当前模块的目录名(不包括文件名) | console.log(__dirname); 输出当前文件所在目录的路径 |
exports | 当前模块的导出对象,用于暴露模块中的功能 | exports.sayHello = function() { console.log('Hello'); }; |
module | 当前模块的对象,包含模块的相关信息(如 exports , filename 等) | console.log(module); 输出模块的详细信息 |
require | 用于加载其他模块的函数 | const fs = require('fs'); 加载内建的 fs 模块 |
global | Node.js 的全局对象,类似浏览器中的 window ,用于存储全局变量 | global.myVar = 'Hello'; console.log(myVar); |
process | 提供关于当前 Node.js 进程的信息和控制方法 | console.log(process.argv); 输出命令行参数 |
Buffer | 用于处理二进制数据的类 | const buf = Buffer.from('Hello'); console.log(buf.toString()); |
setTimeout() | 设置一个延时执行的定时器函数 | setTimeout(() => { console.log('Hello'); }, 2000); |
setInterval() | 设置一个定期执行的定时器函数 | setInterval(() => { console.log('Every second'); }, 1000); |
clearTimeout() | 清除由 setTimeout() 设置的定时器 | const id = setTimeout(() => {}, 2000); clearTimeout(id); |
clearInterval() | 清除由 setInterval() 设置的定时器 | const id = setInterval(() => {}, 1000); clearInterval(id); |
console | 提供用于输出调试信息的对象,包含如 log() , error() , warn() 等方法 | console.log('Debug message'); |
require.main | 返回启动 Node.js 应用程序时的主模块,用于检查当前模块是否是主入口 | if (require.main === module) { console.log('Main module'); } |
模块
模块引用方法:
const module = require('module-name');
使用npm安装包时默认安装在当前项目下的 node_modules
文件夹中,若要安装在全局下则需要使用 npm install -g ...
要证明程序运行在函数内,就使用
console.log(arguments)
,arguments参数只在函数体里面有,arguments.callee
返回的是当前的函数是谁arguments.callee+''
把这个对象变为字符串,输出完整的函数内容
exports
module.exports
是一个非常重要的概念,它用于将模块的功能暴露给外部,使得其他文件可以通过 require()
导入和使用
module.exports
的作用: 每个模块在 Node.js 中都有一个 exports
对象,module.exports
代表模块的导出内容。通过它,你可以指定当前模块对外提供的功能。默认情况下,module.exports
和 exports
是指向同一个对象,但你可以通过修改 module.exports
来完全控制模块导出的内容。
//module1.js
module.exports = {
add: function (a, b) {
return a + b;
},
substract: function (a, b) {
return a - b;
}
}
//这里使用匿名函数直接返回计算结果
var module1 = require('./module1.js');
console.log(module1.add(623,126));
console.log(module1.substract(623,126));
/* 输出
749
497
export
工作原理: exports
本质上是 module.exports
的引用。也就是说,在模块的加载过程中,exports
和 module.exports
默认指向同一个对象。这就意味着,当你在 exports
上添加属性或方法时,module.exports
也会反映出这些变化。 将以上例子改写:
//module1.js
exports.add = function (a, b) {
return a + b;
}
exports.substract = function (a, b) {
return a - b;
}
它们两个是等价的
module.exports
和 exports
的区别? 首先需要知道node中的引用是怎么样的: 值类型传递
function modifyValue(x) { x = 100; } let a = 10; modifyValue(a); console.log(a); // 输出: 10
引用类型传递
function modifyObject(obj) { obj.name = 'Charlie'; }
let person = { name: 'Alice' };
modifyObject(person); c
onsole.log(person.name);
// 输出: 'Charlie'
对于一个引用出的对象(exports)来说,=
代表的是对引用对象的修改。对 exports
的修改会影响 module.exports
,反之亦然。
如果对 exports
直接赋值,则会丢失对 module.exports
的引用:
exports = { name: 'Alice', sayHello: function() { console.log('Hello ' + this.name); } };
//这种不行🙅♂️
函数
来自菜鸟教程
声明:
function greet(name) {
console.log(`Hello, ${name}!`);
}
默认参数 在函数声明时为参数提供默认值。
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, Guest!
greet('Alice'); // Hello, Alice!
剩余参数
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
解构参数 从对象或数组中提取数据并将其赋值给变量
function getUserInfo({ name, age }) {
console.log(`Name: ${name}, Age: ${age}`);
}
const user = { name: 'Alice', age: 30 };
getUserInfo(user); // Name: Alice, Age: 30
箭头函数:
const greet = (name) => {
console.log(`Hello, ${name}!`);
};
例子:
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob']
这里的 user => user.name
相当于
function(user) {
return user.name;
}
也就是说箭头左边的是输入的参数
异步处理
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去; 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其 他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
这里跳过使用回调函数实现异步处理的方法
Promises
什么是 Promise?
一个 Promise
是一个对象,表示异步操作的最终完成(或失败)及其结果值的表示。Promise
是一个占位符,它会在未来某个时刻被 resolve
(成功完成)或 reject
(失败)赋予一个值。
Promise 的三种状态
- Pending(等待中):初始状态,表示异步操作还在进行中。
- Fulfilled(已完成):表示异步操作成功完成,并返回了一个结果值。
- Rejected(已拒绝):表示异步操作失败,返回了一个错误。
这三种状态的转换是不可逆的。Promise
在一旦完成或拒绝后,就不能再进入其他状态。
Promise 的基本用法
创建一个 Promise
对象时,你需要提供一个执行器函数(executor function)。这个执行器函数有两个参数:resolve
和 reject
,分别用于处理成功和失败的结果。
const myPromise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("操作成功!");
} else {
reject("操作失败!");
}
});
使用 then
和 catch
一旦 Promise
被解决,你可以使用 then()
和 catch()
方法来处理结果。
then()
用于处理 Promise 成功的结果。catch()
用于处理 Promise 失败的错误。
myPromise
.then(result => {
console.log(result); // 如果 Promise 成功,输出: "操作成功!"
})
.catch(error => {
console.error(error); // 如果 Promise 失败,输出: "操作失败!"
});
链式调用
Promise
允许链式调用,即你可以在 then()
中返回一个新的 Promise,这样就能继续处理异步操作。
const myPromise = new Promise((resolve, reject) => {
resolve("数据获取成功");
});
myPromise
.then(result => {
console.log(result); // 输出: "数据获取成功"
return new Promise((resolve) => resolve("更多的数据"));
})
.then(result => {
console.log(result); // 输出: "更多的数据"
})
.catch(error => {
console.error("错误:", error);
});
静态方法
Promise.all()
并行,接受多个promise,全部执行则解决
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 100, "foo"));
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // 输出: [3, 42, "foo"]
});
Promise.allSettled()
Promise.allSettled()
方法会等待所有 Promise 完成,不论是成功还是失败,它都会返回每个 Promise 的结果。
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject("错误");
Promise.allSettled([promise1, promise2]).then((results) => {
console.log(results);
// 输出:
// [
// { status: "fulfilled", value: 3 },
// { status: "rejected", reason: "错误" }
// ]
});
Promise.race()
Promise.race()
返回第一个完成(无论是成功或失败)的 Promise 的结果,其他 Promise 将被忽略
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, "快速"));
const promise2 = new Promise((resolve) => setTimeout(resolve, 1000, "慢速"));
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // 输出: "快速"
});
Promise.resolve()
& Promise.reject()
Promise.resolve(value)
返回一个已解决的 Promise。Promise.reject(reason)
返回一个已拒绝的 Promise。
Promise.resolve(42).then(value => console.log(value)); // 输出: 42
Promise.reject('错误').catch(error => console.log(error)); // 输出: "错误"
async/await
async和await是 ES2017
被引用的语法糖,底层依然是通过promises实现的,它可以使得异步函数看起来像同步函数一样编写
以下是异步请求API的一个例子:
function fetchdata(url) {
return new Promise((resolve, reject) =>{
fetch(url) //fetch会返回一个Promise
.then(response => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then(data => {
resolve(data);
})
.catch(error => {
error => reject(error);
});
});
}
async function getData() {
try {
console.log("Fetching data...");
const data = await fetchdata("https://jsonplaceholder.typicode.com/todos/1");
//这里的await用于等待fetchData的结果
console.log("Data fetched:",data);
}catch (error) {
console.log("Error fetching data:", error);
}
}
getData();
修改 getData()
使用 Promise.all
实现并行:
async function getData() {
try {
console.log("Fetching data...");
const [data1, data2] = await Promise.all([
fetchdata("https://jsonplaceholder.typicode.com/posts/1"),
fetchdata("https://jsonplaceholder.typicode.com/posts/2")
])
console.log("Data fetched:", data1, data2);
}catch (error) {
console.log("Error fetching data:", error);
}
}
[[Media/3463ed5338e44aca89b591652c708bba_MD5.jpeg|Open: Pasted image 20250207165253.png]]
Nodejs服务器
路由
const http = require('http');
const server = http.createServer((req, res) => {
const { url, method } = req;
if (url === '/ping' && method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('pong');
} else if (url === '/hello' && method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
})
server.listen(8082, () => {
console.log('Server is running on port 8082');
})
参数
使用URL包
const myUrl = new URL("http://localhost:8888/start?foo=bar&hello=world");
// 提取路径名
console.log(myUrl.pathname); // 输出: /start
// 提取查询参数
console.log(myUrl.searchParams.get("foo")); // 输出: bar
console.log(myUrl.searchParams.get("hello")); // 输出: world
NodeJs 安全
Node.Js安全分析 - Boogiepop Doesn’t Laugh 写的太全了,直接看这个好了😭
RCE
这里RCE一是获得JS命令执行,再是系统命令的执行
JS命令执行
eval 可以直接运行JS命令字符串,和php的eval差不多:eval("console.log('orxiain')")
setInterval 这个函数的第一个参数接受的是方法,可以用箭头函数简化 setInterval(()=>console.log('hack'),2000)
每两秒运行一次给定函数
setTimeout setTimeout(()=>console.log('hack'), 2000);
两秒后运行一次
Function Function("console.log('HelloWolrd')")();
系统命令执行
exec 使用 require
获取 child_process
模块,然后调用 exec
实现 eval("require(child_process).exec('calc')")
spawn 使用条件是具备 {shell:true}
require('child_process').spawn('calc',{shell:true});
这里如果没有
{shell:true}
计算器依然可以正常弹出: 若没有{shell:true}
,Nodejs会去找可执行的应用,而不是作为一个shell命令来执行,也就是说可以用来执行可执行文件,有{shell:true}
的话就会作为shell命令执行。
execFile 专门用来执行可执行文件的,配合上传打
execFile('calc', [], (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`输出:\n${stdout}`);
});
Fork fork
是 child_process
模块中特别用于创建新的 Node.js 进程的函数。它基于 spawn
方法,专门用于启动一个新的 Node.js 脚本,并且在父子进程之间建立一个通信通道,可通过 send
方法进行消息传递。就是能执行指定的JS文件,也能配合上传打
const { fork } = require('child_process');
const child = fork('path/to/childScript.js');
在 childScript.js
里赛exec就行了
弱类型比较
搬了直接
代码 | 输出 | 说明 |
---|---|---|
1 == '1' | true | 数字 1 和字符串 '1' 进行弱类型比较时,字符串 '1' 会被强制转换为数字 1。 |
1 > '2' | false | 数字 1 和字符串 '2' 比较时,字符串 '2' 会被转换为数字 2,因此 1 > 2 为 false 。 |
'1' < '2' | true | 字符串比较基于 ASCII 码的字符顺序,字符 '1' 的 ASCII 值小于字符 '2' 。 |
111 > '3' | true | 数字 111 和字符串 '3' 比较时,字符串 '3' 会被强制转换为数字 3,因此 111 > 3 为 true 。 |
'111' > '3' | false | 字符串 '111' 和字符串 '3' 比较时,基于 ASCII 顺序 '111' 被认为小于 '3' 。 |
'asd' > 1 | false | 字符串 'asd' 与数字 1 比较时,字符串 'asd' 被转换为 NaN ,导致比较结果为 false 。 |
[] == [] | false | 空数组与空数组进行比较时,它们是不同的对象,因此为 false 。 |
[] > [] | false | 空数组与空数组进行比较,空数组转换为 false ,所以结果是 false 。 |
[6, 2] > [5] | true | 数组比较时,比的是数组的第一个元素,6 > 5 ,所以结果为 true 。 |
[100, 2] < 'test' | true | 数组 [100, 2] 会被转换为字符串 '100,2' ,与字符串 'test' 比较,'100,2' < 'test' 为 true 。 |
[1, 2] < '2' | true | 数组 [1, 2] 会被转换为字符串 '1,2' ,与字符串 '2' 比较,'1,2' < '2' 为 true 。 |
[11, 16] < "10" | false | 数组 [11, 16] 会被转换为字符串 '11,16' ,与字符串 '10' 比较,'11,16' > '10' 为 false 。 |
null == undefined | true | null 与 undefined 进行弱类型比较时,它们相等。 |
null === undefined | false | null 和 undefined 在严格比较时不相等。 |
NaN == NaN | false | NaN 不等于任何值,包括它自己,因此 NaN == NaN 为 false 。 |
NaN === NaN | false | NaN 在严格比较时也不等于任何值,包括它自己。 |
- 数字与数字字符串比较时:数字字符串会被强制转换为数字后进行比较。
- 字符串与字符串比较:基于字符的 ASCII 码顺序。
- 数组比较:空数组在与其他类型进行比较时,通常会被视为
false
。数组会按第一个元素进行比较,且如果数组含有非数值型元素,会以字符串形式与其进行比较。 - 特殊值:
null
与undefined
在 弱类型比较 下相等,但 严格比较 不相等。NaN
在 弱类型比较 和 严格比较 下都不等于任何值,包括它自己。
其他
javascript大小写特性
来自文章 - Node.js 常见漏洞学习与总结 - 先知社区
在javascript中有几个特殊的字符需要记录一下
对于toUpperCase():
字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"
对于toLowerCase():
字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)
在绕一些规则的时候就可以利用这几个特殊字符进行绕过
Node.Js原型链污染
Links
- https://blog.csdn.net/qq_26442553/article/details/78729793
- https://www.bilibili.com/video/BV1WP4y187Tu
- Node.Js - Boogiepop Doesn’t Laugh
- Node.Js安全分析 - Boogiepop Doesn’t Laugh
- 文章 - Node.js 常见漏洞学习与总结 - 先知社区
- Artsploit:[demo.paypal.com] Node.js 代码注入 (RCE)
- 深入理解 JavaScript Prototype 污染攻击 | 离别歌
- Node.js 文件系统 | 菜鸟教程