『安全开发』JS安全开发(二)NodeJS
NodeJS基本概念
-
概念
NodeJS是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,允许开发者在服务器端运行 JavaScript。它没有浏览器的 DOM 或 BOM,而是提供了与文件系统、网络请求、进程管理等相关的 API,主要用于构建服务器端应用程序。
注意:nodejs不是一门新的编程语言,nodejs是在服务端运行javascript的运行环境
-
使用文档
-
第三方库
- express:Express是一个简洁而灵活的node.js Web应用框架
- body-parser:node.js中间件,用于处理 JSON, Raw, Text和URL编码的数据。
- cookie-parser:一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。
- multer:node.js中间件,用于处理 enctype=“multipart/form-data”(设置表单的MIME编码)的表单数据。
- mysql:Node.js来连接MySQL专用库,并对数据库进行操作。
NodeJS的安装使用
-
安装
在cmd中输入
node -v
确定自己是否已经安装过没有安装过的话,参考:
-
使用方法
初始化项目并安装Express框架
1
2npm init -y # 初始化 Node.js 项目,生成 package.json 文件
npm install express # 安装 Express这会在你的项目中安装 Express 库,并在
node_modules
目录下存放依赖。在终端中输入以下命令来运行demo:
1
node app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 引入 Express 框架
const express = require('express');
// 创建 Express 应用程序实例
const app = express();
// 处理 '/login' 路径的 GET 请求,返回简单的登录页面
app.get('/login', function(req, res) {
res.send('<hr>登录页面</hr>');
});
// 处理根路径的 GET 请求,发送名为 'sql.html' 的文件
app.get('/', function(req, res) {
// res.sendFile(__dirname + '/' + 'sql.html');
res.send("hello word");
});
// 启动服务器,监听端口 3001
const server = app.listen(3001, function() {
console.log('Web 服务器已经启动,监听端口 3001!');
});
基本语法
GET/POST请求
req.query
:用于处理 URL 查询字符串参数**GET请求
**
req.body
:用于处理 **POST 请求
**中的表单数据。
回调函数
-
如何理解回调函数
-
用法
在JavaScript中,回调函数通常是作为参数传递给其他函数,然后由那个函数在适当的时机调用。这种方式使得代码更加灵活和可扩展。
回调函数通常与异步操作一起使用,比如读取文件、发送网络请求或等待用户输入。由于这些操作可能需要一些时间来完成,所以通常会使用回调函数来在操作完成后执行某些任务。
-
基本语法
下面的延时执行函数中的匿名函数就是回调函数,表示2秒之后执行这个匿名函数
1 | setTimeout(function() { |
-
如何判断目标函数是否使用了回调函数
回调函数并不是指函数内部必须明确命名为
callback
。在 JavaScript 中,回调函数的核心概念是:它是作为参数传递给另一个函数,并且在该函数执行时被调用。所以,不管这个函数的名字是什么,只要它是被传递到其他函数中,并在那个函数内部执行,就可以算作回调函数。
比如上面的
setTimeout
函数就是一个回调函数,虽然它没有显式地命名为callback
,因为:- 它作为参数传递给了
setTimeout
函数。 - 在指定的时间延迟后,
setTimeout
会调用这个函数。
- 它作为参数传递给了
功能实现
用户登录
-
功能:前端输入用户名和密码,在数据库中匹配验证
-
环境
下载解析GET/POST请求体的中间件
body-parser
1
npm i body-parser
-
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83// 引入 Express 框架
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql');
// 创建 Express 应用程序实例
const app = express();
//创建一个用于解析 URL 编码的 bodyParser 中间件实例
const urlencodedParser = bodyParser.urlencoded({ extended: false });
// 处理 '/login' 路径的 GET 请求,返回简单的登录页面
app.get('/login', function(req, res) {
// 从请求中获取用户名和密码
const u = req.query.username;
const p = req.query.password;
console.log(u);
console.log(p);
// 检查用户名和密码是否为 admin 和 123456
if (u === 'admin' && p === '123456') {
res.send('欢迎进入后台管理页面');
} else {
res.send('登录用户或密码错误');
}
});
// 处理 '/login' 路径的 POST 请求,使用 bodyParser 解析表单数据
app.post('/login', urlencodedParser, function(req, res) {
// 从请求中获取表单提交的用户名和密码
const u = req.body.username;
const p = req.body.password;
// 输出获取到的用户名和密码,用于调试
console.log(u);
console.log(p);
// 创建与 MySQL 数据库的连接
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'root',
database : 'dome01'
});
// 建立数据库连接
connection.connect();
// 构建 SQL 查询,检查数据库中是否存在匹配的用户名和密码
const sql = 'select * from admin where username="'+u+'" and password="'+p+'"';
console.log(sql);
// 执行 SQL 查询
connection.query(sql, function(error, data){
// 检查查询执行中是否存在错误
if(error){
console.log('数据库连接失败!');
}
try {
// 检查用户名和密码是否匹配数据库中的数据
if(u == data[0]['username'] && p == data[0]['password']){
// 如果匹配,发送欢迎消息到前端
res.send('欢迎进入后台管理页面');
}
} catch {
// 捕获异常,如果没有匹配的数据或其他错误,发送错误消息到前端
res.send('错误');
};
});
})
// 处理根路径的 GET 请求,发送名为 'sql.html' 的文件
app.get('/', function(req, res) {
res.sendFile(__dirname + '/' + 'sql.html');
});
// 启动服务器,监听端口 3001
const server = app.listen(3001, function() {
console.log('Web 服务器已经启动,监听端口 3001!');
});
文件管理
-
功能:目录读取
-
环境
下载相关文件管理依赖
fs
1
npm i fs
-
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 引入文件系统和 Express 框架
const fs = require('fs');
const express = require('express');
const app = express();
// 处理 '/file' 路径的 GET 请求
app.get('/file', function (req, res) {
// 从请求中获取目录参数
const dir = req.query.dir;
console.log(dir);·
// 调用文件管理函数,传递目录参数
filemanage(dir);
});
// 启动 Express 应用监听在3000端口
var server = app.listen(3000, function () {
console.log('Web应用已启动在3000端口!');
});
// 文件管理函数,接收一个目录参数
function filemanage(dir) {
// 使用 fs.readdir 读取目录下的文件
fs.readdir(dir, function (error, files) {
// 打印目录中的文件列表
console.log(files);
});
}调用文件管理函数,传递目录参数
命令执行
-
功能:执行系统命令、将字符串当作代码解析
-
环境
下载子进程管理模块
child_process
1
npm i child_process
-
代码
1
2
3
4
5
6
7
8
9
10
11
12// 引入child_process模块
const rce = require('child_process');
// 使用exec方法调用系统命令'notepad',打开记事本
rce.exec('notepad');
// 使用spawnSync方法调用系统命令'calc',打开计算器
rce.spawnSync('calc');
// 使用eval调用代码命令执行,将字符串当做代码解析
// 请注意:避免在生产环境中使用eval,可能存在安全风险
eval('require("child_process").exec("calc");');
原型链污染
什么是原型链
因为 JavaScript 没有类似 Java 中的 Class 概念,所以继承都是由原型链来实现的。JavaScript 中每个对象都有一个内部属性 [[Prototype]]
,这个属性指向另一个对象。通过这个原型对象,当前对象可以继承其上定义的属性和方法。
-
每个对象都有一个原型:当你创建一个对象时,它会有一个指向其构造函数的原型对象。
-
查找过程:当你访问一个对象的属性时,JavaScript 引擎首先会检查这个对象自身是否有该属性。如果没有,它会通过原型链查找,依次查找对象的原型及其原型的原型,直到找到该属性或者原型链的尽头(通常是
null
)为止。 -
例子
下面的代码中,
foo.bar
输出为1而不是x,因为引擎会首先检查该对象自身是否有该属性。zoo作为继承自Object的对象,同样也继承了其bar值,为x。1
2
3
4
5
6
7
8let foo = {bar:1}
console.log(foo.bar) // 输出为1
foo.__proto__.bar = 'x' // 修改原型
console.log(foo.bar) // 输出为 1,因为查找顺序的原因
let zoo = {}
console.log(zoo.bar) // 输出为 'x',因为zoo的原型指向foo.__proto__,即最底层的对象Object -
原型链结构图
Object.prototype
是 JavaScript 中所有对象的原型对象,它是 所有对象的原型链 的终极基类。几乎在 JavaScript 中创建的所有对象,都会继承Object.prototype
中的方法和属性。如果你访问一个对象的某个属性或方法时,JavaScript 会沿着原型链从当前对象开始查找,直到它找到Object.prototype
为止。如果Object.prototype
中没有该属性或方法,JavaScript 会返回undefined
。以下面的图为例:
person.__proto__
是与Person.prototype
等价的,而Person.prototype.__proto__
是指向Object.prototype
的,再往下Object.prototype.__proto__
指向null
。
原型链污染的原理
在 JavaScript 中,对象通过原型链继承属性和方法。如果攻击者能够通过某些方式修改对象的原型(即 prototype
),构造函数和原型属性,就可能导致整个对象模型的行为发生异常。同时,所有继承了被污染原型的对象都会受到影响。原型链污染通常会导致拒绝服务、篡改程序执行流程、导致远程执行代码等漏洞。
原型链污染的利用
原型链污染配合RCE:
有原型链污染的前提之下,我们可以控制基类的成员,赋值为一串恶意代码,从而造成代码注入。
例如下面的代码:通过原型链bar属性执行命令打开计算器
1 | // 创建一个包含属性 bar 的对象 foo,并将 bar 设置为 1 |
实际案例
因为原型链污染的条件苛刻,一般只在CTF比赛中作为考题出现,现实中比较少
-
CTF
-
CTFshow的Web334-344
-
CTF中的nodejs考点:ctf-nodejs之一些小知识
-
-
yapi token注入漏洞