0


第30天:安全开发-JS 应用&NodeJS 指南&原型链污染&Express 框架&功能实现&审计0

时间轴:

演示案例:

环境搭建-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

  1. //express_demo.js 文件
  2. var express = require('express');
  3. var app = express();
  4. app.get('/', function (req, res) {
  5. res.send('Hello World');
  6. })
  7. var server = app.listen(3000, function () {
  8. var host = server.address().address
  9. var port = server.address().port
  10. console.log("应用实例,访问地址为 http://%s:%s", host, port)
  11. })

使用node .\sql.js进行运行:

无法运行时需要安装express库。

npm i express (会得到node modules)

运行结果:(文字路径不能太多)

网页源代码:

只显示了Hello World,没有显示visual studio中的代码

看到结果是运行结果而不是源代码

功能实现-NodeJS-数据库&文件&执行

登录操作

1、Express 开发

2、实现用户登录

3、加入数据库操作

1.先创建一个sql.html:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>后台登录</title>
  6. <style>
  7. body {
  8. background-color: #f1f1f1;
  9. }
  10. .login {
  11. width: 400px;
  12. margin: 100px auto;
  13. background-color: #fff;
  14. border-radius: 5px;
  15. box-shadow: 0 0 10px rgba(0,0,0,0.3);
  16. padding: 30apx;
  17. }
  18. .login h2 {
  19. text-align: center;
  20. font-size: 2em;
  21. margin-bottom: 30px;
  22. }
  23. .login label {
  24. display: block;
  25. margin-bottom: 20px;
  26. font-size: 1.2em;
  27. }
  28. .login input[type="text"], .login input[type="password"] {
  29. width: 100%;
  30. padding: 10px;
  31. border: 1px solid #ccc;
  32. border-radius: 5px;
  33. font-size: 1.2em;
  34. margin-bottom: 20px;
  35. }
  36. .login input[type="submit"] {
  37. background-color: #2ecc71;
  38. color: #fff;
  39. border: none;
  40. padding: 10px 20px;
  41. border-radius: 5px;
  42. font-size: 1.2em;
  43. cursor: pointer;
  44. }
  45. .login input[type="submit"]:hover {
  46. background-color: #27ae60;
  47. }
  48. </style>
  49. </head>
  50. <body>
  51. <div class="login" >
  52. <h2>后台登录</h2>
  53. <form action="http://127.0.0.1:3000/login" method="POST">
  54. <label for="username">用户名:</label>
  55. <input type="text" name="username" id="username" class="user" >
  56. <label for="password">密码:</label>
  57. <input type="password" name="password" id="password" class="pass" >
  58. <button>登录</button>
  59. </form>
  60. </div>

2.创建一个sql.js(覆盖掉之前那一个)

sql.js:

  1. const express= require('express');
  2. const app=express();
  3. //get路由
  4. app.get('/login',function(req,res){ //req访问,res结果
  5. res.send('<hr>登录页面</hr>');
  6. })
  7. app.get('/',function(req,res){
  8. //res.send('<hr>首页页面</hr>');
  9. res.sendFile(__dirname+'/'+'sql.html'); //当前运行下的/sql.html(渲染为此页面)
  10. })
  11. const server = app.listen(3000,function(){
  12. console.log('web的3000端口已启动!');
  13. })

运行结果:

以get路由来传参:

对sql.html进行修改:

  1. <div class="login" >
  2. <h2>后台登录</h2>
  3. <form action="http://127.0.0.1:3000/login" method="GET">
  4. <label for="username">用户名:</label>
  5. <input type="text" name="username" id="username" class="user" >
  6. <label for="password">密码:</label>
  7. <input type="password" name="password" id="password" class="pass" >
  8. <button>登录</button>
  9. </form>
  10. </div>

对sql.js进行修改:

  1. const express= require('express');
  2. const app=express();
  3. //get路由
  4. app.get('/login',function(req,res){
  5. const u = req.query.username //获取sql.html中的username
  6. const p = req.query.password //获取sql.html中的password
  7. console.log(u);
  8. console.log(p)
  9. if(u == 'admin' && p == '123456'){
  10. res.send("欢迎进入后台管理页面")
  11. }else{
  12. res.send('登录用户或密码错误!');
  13. }
  14. })
  15. app.get('/',function(req,res){
  16. //res.send('<hr>首页页面</hr>');
  17. res.sendFile(__dirname+'/'+'sql.html');
  18. })
  19. const server = app.listen(3000,function(){
  20. console.log('web的3000端口已启动!');
  21. })

运行结果:(使用console.log()来查看是否定义成功)

以post路由来传参:

修改后的sql.js:

  1. const express= require('express');
  2. const bodyParser = require('body-parser');
  3. const app=express();
  4. // 创建 application/x-www-form-urlencoded 编码解析
  5. var urlencodedParser = bodyParser.urlencoded({ extended: false })
  6. //get路由
  7. app.get('/login',function(req,res){
  8. const u = req.query.username //query适用于get
  9. const p = req.query.password
  10. console.log(u);
  11. console.log(p)
  12. if(u == 'admin' && p == '123456'){
  13. res.send("欢迎进入后台管理页面")
  14. }else{
  15. res.send('登录用户或密码错误!');
  16. }
  17. })
  18. //post路由
  19. app.post('/login',urlencodedParser,function(req,res){
  20. const u = req.body.username; //body适用于post
  21. const p = req.body.password;
  22. console.log(u);
  23. console.log(p);
  24. if(u == 'admin' && p == '123456'){
  25. res.send("欢迎进入后台管理页面")
  26. }else{
  27. res.send('登录用户或密码错误!');
  28. }
  29. })
  30. app.get('/',function(req,res){
  31. //res.send('<hr>首页页面</hr>');
  32. res.sendFile(__dirname+'/'+'sql.html');
  33. })
  34. const server = app.listen(3000,function(){
  35. console.log('web的3000端口已启动!');
  36. })

在前面安装一个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连接代码:

  1. var connection = mysql.createConnection({
  2. host : 'localhost',
  3. user : 'root',
  4. password : 'root',
  5. database : 'demo01'
  6. });
  7. connection.connect();
  8. const sql = 'select * from admin ';
  9. console.log(sql);
  10. connection.query(sql,function(error,data){
  11. if(error){
  12. console.log('数据库连接失败!');
  13. }
  14. console.log(data);
  15. })

效果展示:

与数据库相同

数据库与登录逻辑结合:
  1. //post路由
  2. app.post('/login',urlencodedParser,function(req,res){
  3. const u = req.body.username;
  4. const p = req.body.password;
  5. console.log(u);
  6. console.log(p);
  7. var connection = mysql.createConnection({
  8. host : 'localhost',
  9. user : 'root',
  10. password : 'root',
  11. database : 'demo01'
  12. });
  13. connection.connect();
  14. const sql = 'select * from admin where username="'+u+'" and password="'+p+'"';
  15. console.log(sql);
  16. connection.query(sql,function(error,data){
  17. if(error){
  18. console.log('数据库连接失败!');
  19. }
  20. try{ //报错测试
  21. if(u==(data[0]['username']) && p==data[0]['password']){
  22. //data[0]的意思是取第一行数据['username']是取里面的username值
  23. res.send('欢迎进入后台管理页面');
  24. }
  25. }catch{
  26. res.send('错误');
  27. };
  28. })
  29. })
  30. app.get('/',function(req,res){
  31. //res.send('<hr>首页页面</hr>');
  32. res.sendFile(__dirname+'/'+'sql.html');
  33. })
  34. const server = app.listen(3000,function(){
  35. console.log('web的3000端口已启动!');
  36. })

效果展示:

使用永“真”语句注入的话,无论前面是什么都会全部回显。(or)

在页面注入:

测试下来后没有往后面运行:

证明前面的代码if(u==(data[0]['username']) && p==data[0]['password'])对其进行了过滤:

u==(data[0]['username']是用来判断键值是否相同,正常应该是数据库取出的行数进行判断,而不是data中取的值。

安全方法:

使用sql预编译进行写比较安全(secguide-main)

文件管理功能:

创建file.js:(记得npm i fs)

  1. const fs=require('fs');
  2. const express = require('express');
  3. const app = express();
  4. app.get('/file', function (req, res) {
  5. const dir=req.query.dir; //接受dir
  6. console.log(dir); //调试dir
  7. filemanage(dir); //执行dir
  8. })
  9. var server = app.listen(3000, function () {
  10. console.log('web应用3000端口已启动!')
  11. })
  12. function filemanage(dir){
  13. fs.readdir(dir,function(error,files){
  14. console.log(files);
  15. })};

运行结果:使用node .\file.js(在网址后面加上/file?dir=)

命令执行功能:

文件操作

1、Express开发
2、实现自录读取
3、加入传参接受
一命令执行(RCE)
1、eval
2、exec & spawnSyn

创建一个rce.js:(npm i child_process)

  1. const rce=require('child_process');
  2. //nodejs 调用系统命令执行
  3. //rce.exec('notepad');
  4. //rce.spawnSync('calc');
  5. //nodejs 调用代码命令执行 把字符串当做代码解析
  6. eval('require("child_process").exec("calc");');

node.js判断:

安全问题-NodeJs-注入&RCE&原型链

1、SQL注入&文件操作
2、RCE执行&原型链污染
2、NodeJS黑盒无代码分析

实战测试NodeJs安全
判断:参考前期的信息收集

黑盒:通过对各种功能和参数进行payload测试

白盒:通过对代码中写法安全进行审计分析

原型链污染

如果攻击者控制·并修改了一个对象的原型,(proto)那么将可以影响所有和这个对象来自同一个类、父祖类的对象。

  1. // // foo是一个简单的JavaScript对象
  2. // let foo = {bar: 1} //解释:1=1 0 __proto__= x
  3. // // 原型链污染
  4. // // foo.bar 此时为1
  5. // console.log(foo.bar)
  6. // // 修改foo的原型(即Object)
  7. // foo.__proto__.bar = 'x'
  8. // // // 由于查找顺序的原因,foo.bar仍然是1
  9. // console.log(foo.bar)
  10. // // // 此时再用Object创建一个空的zoo对象
  11. // let zoo = {}
  12. // // 查看zoo.bar,此时bar为2
  13. // console.log(zoo.bar)
  14. let foo = {bar: 1};
  15. console.log(foo.bar);
  16. foo.__proto__.bar = 'require(\'child_process\').execSync(\'calc\');'
  17. console.log(foo.bar);
  18. let zoo = {};
  19. 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:

  1. //login.js
  2. var express = require('express'); //引入各个模块
  3. var router = express.Router();
  4. var users = require('../modules/user').items; //引入用户模块(user.js)
  5. var findUser = function(name, password){ //定义函数
  6. return users.find(function(item){
  7. return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  8. }); //如果name不等于CTFSHOW,并且将name都转为大写与item.name(CTFSHOW)相同,password=123456。则findUser返回true //toUpperCase()是javascript中将小写转换成大写的函数。
  9. };
  10. /* GET home page. */
  11. router.post('/', function(req, res, next) { //POST请求的处理函数
  12. res.type('html'); //设置响应(res)的内容类型为html
  13. var flag='flag_here';
  14. var sess = req.session;
  15. var user = findUser(req.body.username, req.body.password);
  16. if(user){
  17. req.session.regenerate(function(err) {
  18. if(err){
  19. return res.json({ret_code: 2, ret_msg: '登录失败'});
  20. }
  21. req.session.loginUser = user.username;
  22. res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag}); //登录成功返回flag
  23. });
  24. }else{
  25. res.json({ret_code: 1, ret_msg: '账号或密码错误'});
  26. }
  27. });
  28. module.exports = router; //通过module.exports将该路由模块导出,以便在其他文件中引入和使用

user.js:

  1. //user.js
  2. module.exports = {
  3. items: [
  4. {username: 'CTFSHOW', password: '123456'}
  5. ]
  6. };
  7. //这段代码是一个模块文件,通过`module.exports`将一个对象导出。
  8. //在这个模块中,导出的对象是一个包含一个属性`items`的对象。`items`属性是一个数组,包含了一个用户对象。这个用户对象有两个属性:`username`表示用户名为"CTFSHOW",`password`表示密码为"123456"。
  9. //通过这种方式,其他文件可以引入该模块并访问`items`数组中的用户对象,用于验证用户的登录信息。

突破点:

1.2 nodejs语言的缺点

1.2.1 大小写特性

toUpperCase()
toLowerCase()

对于toUpperCase(): 字符

  1. "ı"

  1. "ſ"

经过toUpperCase处理后结果为

  1. "I"

  1. "S"

对于toLowerCase(): 字符

  1. "K"

经过toLowerCase处理后结果为

  1. "k"

(这个K不是K)

  1. var findUser = function(name, password){ //定义函数
  2. return users.find(function(item){
  3. return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  4. }); //如果name不等于CTFSHOW,并且将name都转为大写与item.name(CTFSHOW)相同,password=123456。则findUser返回true //toUpperCase()是javascript中将小写转换成大写的函数。
  5. };

payload:

name:ctfshow

password:123456

result:


2、YApi管理平台漏洞
https://blog.csdn.net/weixin_42353842/article/details/127960229

开发指南-NodeJs-安全SecGuide项目:

https://github.com/Tencent/secguide

本文章由李豆豆喵和番薯小羊卷~共同完成。


本文转载自: https://blog.csdn.net/m0_74930529/article/details/144095283
版权归原作者 李豆豆喵 所有, 如有侵权,请联系我们删除。

“第30天:安全开发-JS 应用&NodeJS 指南&原型链污染&Express 框架&功能实现&审计0”的评论:

还没有评论