时间轴:
演示案例:
环境搭建-NodeJS-解析安装&库安装
功能实现-NodeJS-数据库&文件&执行
安全问题-NodeJS-注入&RCE&原型链
案例分析-NodeJS-CTF 题目&源码审计
开发指南-NodeJS-安全 SecGuide 项目、
环境搭建-NodeJS-解析安装&库安装
node.js是运行在服务端的JavaScript。
0 、文档参考:
https://www.w3cschool.cn/nodejs/
1 、 Nodejs 安装
https://nodejs.org/en
作者安装的是18.16.0.安装好后重启电脑,让环境进行配置。 (只有这样才能使npm,node这些生效)
2 、三方库安装
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 专用库,并对数据库进行操作。
安装命令:
npm i express
npm i body-parser
npm i cookie-parser
npm i multer
npm i mysql
案例导入:
在js文件中创建一个sql.js,将端口创建为3000
javascript
//express_demo.js 文件
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World');
})
var server = app.listen(3000, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
使用node .\sql.js进行运行:
无法运行时需要安装express库。
npm i express (会得到node modules)
运行结果:(文字路径不能太多)
网页源代码:
只显示了Hello World,没有显示visual studio中的代码
看到结果是运行结果而不是源代码
功能实现-NodeJS-数据库&文件&执行
登录操作
1 、 Express 开发
2 、实现用户登录
3 、加入数据库操作
1.先创建一个sql.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>后台登录</title>
<style>
body {
background-color: #f1f1f1;
}
.login {
width: 400px;
margin: 100px auto;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
padding: 30apx;
}
.login h2 {
text-align: center;
font-size: 2em;
margin-bottom: 30px;
}
.login label {
display: block;
margin-bottom: 20px;
font-size: 1.2em;
}
.login input[type="text"], .login input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 1.2em;
margin-bottom: 20px;
}
.login input[type="submit"] {
background-color: #2ecc71;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 5px;
font-size: 1.2em;
cursor: pointer;
}
.login input[type="submit"]:hover {
background-color: #27ae60;
}
</style>
</head>
<body>
<div class="login" >
<h2>后台登录</h2>
<form action="http://127.0.0.1:3000/login" method="POST">
<label for="username">用户名:</label>
<input type="text" name="username" id="username" class="user" >
<label for="password">密码:</label>
<input type="password" name="password" id="password" class="pass" >
<button>登录</button>
</form>
</div>
2.创建一个sql.js(覆盖掉之前那一个)
sql.js:
javascript
const express= require('express');
const app=express();
//get路由
app.get('/login',function(req,res){ //req访问,res结果
res.send('<hr>登录页面</hr>');
})
app.get('/',function(req,res){
//res.send('<hr>首页页面</hr>');
res.sendFile(__dirname+'/'+'sql.html'); //当前运行下的/sql.html(渲染为此页面)
})
const server = app.listen(3000,function(){
console.log('web的3000端口已启动!');
})
运行结果:
以get路由来传参:
对sql.html进行修改:
javascript
<div class="login" >
<h2>后台登录</h2>
<form action="http://127.0.0.1:3000/login" method="GET">
<label for="username">用户名:</label>
<input type="text" name="username" id="username" class="user" >
<label for="password">密码:</label>
<input type="password" name="password" id="password" class="pass" >
<button>登录</button>
</form>
</div>
对sql.js进行修改:
javascript
const express= require('express');
const app=express();
//get路由
app.get('/login',function(req,res){
const u = req.query.username //获取sql.html中的username
const p = req.query.password //获取sql.html中的password
console.log(u);
console.log(p)
if(u == 'admin' && p == '123456'){
res.send("欢迎进入后台管理页面")
}else{
res.send('登录用户或密码错误!');
}
})
app.get('/',function(req,res){
//res.send('<hr>首页页面</hr>');
res.sendFile(__dirname+'/'+'sql.html');
})
const server = app.listen(3000,function(){
console.log('web的3000端口已启动!');
})
运行结果:(使用console.log()来查看是否定义成功)
以post路由来传参:
修改后的sql.js:
javascript
const express= require('express');
const bodyParser = require('body-parser');
const app=express();
// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false })
//get路由
app.get('/login',function(req,res){
const u = req.query.username //query适用于get
const p = req.query.password
console.log(u);
console.log(p)
if(u == 'admin' && p == '123456'){
res.send("欢迎进入后台管理页面")
}else{
res.send('登录用户或密码错误!');
}
})
//post路由
app.post('/login',urlencodedParser,function(req,res){
const u = req.body.username; //body适用于post
const p = req.body.password;
console.log(u);
console.log(p);
if(u == 'admin' && p == '123456'){
res.send("欢迎进入后台管理页面")
}else{
res.send('登录用户或密码错误!');
}
})
app.get('/',function(req,res){
//res.send('<hr>首页页面</hr>');
res.sendFile(__dirname+'/'+'sql.html');
})
const server = app.listen(3000,function(){
console.log('web的3000端口已启动!');
})
在前面安装一个const bodyParser = require('body-parser');
在终端使用npm i body-parser安装库
使用:
// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false })是为了使得在network里看到的数据username =111&password =111有一个规范的格式。
数据库管理:
数据库连接:
mysql连接代码:
javascript
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'root',
database : 'demo01'
});
connection.connect();
const sql = 'select * from admin ';
console.log(sql);
connection.query(sql,function(error,data){
if(error){
console.log('数据库连接失败!');
}
console.log(data);
})
效果展示:
与数据库相同
数据库与登录逻辑结合:
javascript
//post路由
app.post('/login',urlencodedParser,function(req,res){
const u = req.body.username;
const p = req.body.password;
console.log(u);
console.log(p);
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'root',
database : 'demo01'
});
connection.connect();
const sql = 'select * from admin where username="'+u+'" and password="'+p+'"';
console.log(sql);
connection.query(sql,function(error,data){
if(error){
console.log('数据库连接失败!');
}
try{ //报错测试
if(u==(data[0]['username']) && p==data[0]['password']){
//data[0]的意思是取第一行数据['username']是取里面的username值
res.send('欢迎进入后台管理页面');
}
}catch{
res.send('错误');
};
})
})
app.get('/',function(req,res){
//res.send('<hr>首页页面</hr>');
res.sendFile(__dirname+'/'+'sql.html');
})
const server = app.listen(3000,function(){
console.log('web的3000端口已启动!');
})
效果展示:
使用永"真"语句注入的话,无论前面是什么都会全部回显。(or)
在页面注入:
测试下来后没有往后面运行:
证明前面的代码if(u==(data[0]['username']) && p==data[0]['password'])对其进行了过滤:
u==(data[0]['username']是用来判断键值是否相同,正常应该是数据库取出的行数进行判断,而不是data中取的值。
安全方法:
使用sql预编译进行写比较安全(secguide-main)
文件管理功能:
创建file.js:(记得npm i fs)
javascript
const fs=require('fs');
const express = require('express');
const app = express();
app.get('/file', function (req, res) {
const dir=req.query.dir; //接受dir
console.log(dir); //调试dir
filemanage(dir); //执行dir
})
var server = app.listen(3000, function () {
console.log('web应用3000端口已启动!')
})
function filemanage(dir){
fs.readdir(dir,function(error,files){
console.log(files);
})};
运行结果:使用node .\file.js(在网址后面加上/file?dir=)
命令执行功能:
文件操作
1、Express开发
2、实现自录读取
3、加入传参接受
一命令执行(RCE)
1、eval
2、exec & spawnSyn
创建一个rce.js:(npm i child_process)
javascript
const rce=require('child_process');
//nodejs 调用系统命令执行
//rce.exec('notepad');
//rce.spawnSync('calc');
//nodejs 调用代码命令执行 把字符串当做代码解析
eval('require("child_process").exec("calc");');
node.js判断:
安全问题-NodeJs-注入&RCE&原型链
1、SQL注入&文件操作
2、RCE执行&原型链污染
2、NodeJS黑盒无代码分析
实战测试NodeJs安全
判断:参考前期的信息收集
黑盒:通过对各种功能和参数进行payload测试
白盒:通过对代码中写法安全进行审计分析
原型链污染
如果攻击者控制·并修改了一个对象的原型,(proto)那么将可以影响所有和这个对象来自同一个类、父祖类的对象。
javascript
// // foo是一个简单的JavaScript对象
// let foo = {bar: 1} //解释:1=1 0 __proto__= x
// // 原型链污染
// // foo.bar 此时为1
// console.log(foo.bar)
// // 修改foo的原型(即Object)
// foo.__proto__.bar = 'x'
// // // 由于查找顺序的原因,foo.bar仍然是1
// console.log(foo.bar)
// // // 此时再用Object创建一个空的zoo对象
// let zoo = {}
// // 查看zoo.bar,此时bar为2
// console.log(zoo.bar)
let foo = {bar: 1};
console.log(foo.bar);
foo.__proto__.bar = 'require(\'child_process\').execSync(\'calc\');'
console.log(foo.bar);
let zoo = {};
console.log(eval(zoo.bar));
运行结果:
案例分析-NodeJS-CTF题目&源码审计
1、CTFSHOW几个题日
https://ctfshow/Web334-344
https://f1veseven.github.io/2022/04/03/ctf-nodejs-zhi-yi-xie-xiao-zhi-shi/
CTFSHOW-334:
1.打开zip发现是pk开头:
证明是zip文件:使用winzip或者别的解压软件打开(Bandzip):
打开后由login.js和user.js:
(引用文章:Ctfshow web入门 nodejs篇 web334-web344-阿里云开发者社区)
login.js:
javascript
//login.js
var express = require('express'); //引入各个模块
var router = express.Router();
var users = require('../modules/user').items; //引入用户模块(user.js)
var findUser = function(name, password){ //定义函数
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
}); //如果name不等于CTFSHOW,并且将name都转为大写与item.name(CTFSHOW)相同,password=123456。则findUser返回true //toUpperCase()是javascript中将小写转换成大写的函数。
};
/* GET home page. */
router.post('/', function(req, res, next) { //POST请求的处理函数
res.type('html'); //设置响应(res)的内容类型为html
var flag='flag_here';
var sess = req.session;
var user = findUser(req.body.username, req.body.password);
if(user){
req.session.regenerate(function(err) {
if(err){
return res.json({ret_code: 2, ret_msg: '登录失败'});
}
req.session.loginUser = user.username;
res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag}); //登录成功返回flag
});
}else{
res.json({ret_code: 1, ret_msg: '账号或密码错误'});
}
});
module.exports = router; //通过module.exports将该路由模块导出,以便在其他文件中引入和使用
user.js:
javascript
//user.js
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};
//这段代码是一个模块文件,通过`module.exports`将一个对象导出。
//在这个模块中,导出的对象是一个包含一个属性`items`的对象。`items`属性是一个数组,包含了一个用户对象。这个用户对象有两个属性:`username`表示用户名为"CTFSHOW",`password`表示密码为"123456"。
//通过这种方式,其他文件可以引入该模块并访问`items`数组中的用户对象,用于验证用户的登录信息。
突破点:
1.2 nodejs语言的缺点
1.2.1 大小写特性
toUpperCase()
toLowerCase()
对于toUpperCase(): 字符"ı"
、"ſ"
经过toUpperCase处理后结果为 "I"
、"S"
对于toLowerCase(): 字符"K"
经过toLowerCase处理后结果为"k"
(这个K不是K)
javascript
var findUser = function(name, password){ //定义函数
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
}); //如果name不等于CTFSHOW,并且将name都转为大写与item.name(CTFSHOW)相同,password=123456。则findUser返回true //toUpperCase()是javascript中将小写转换成大写的函数。
};
payload:
name:ctfshow
password:123456
result:
2、YApi管理平台漏洞
https://blog.csdn.net/weixin_42353842/article/details/127960229
开发指南-NodeJs-安全SecGuide项目:
https://github.com/Tencent/secguide
本文章由李豆豆喵和番薯小羊卷~共同完成。