前段时间包括当下,打CTF的时候有遇到NodeJS的题目,但没怎么学过,看到题比较懵。所以决定去学习一下,包括要学JavaScript语法之类的,花点时间,一边做题一边学习了JavaScript的基础。
NodeJs基础
简单介绍
Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行环境
但是它是由C++开发的,它只是一个JavaScript语言解释器。
REPL环境运行JavaScript的代码
在浏览器的控制台或者node的运行环境都属于REPL运行环境,均可以运行JS代码。
在NodeJS中分为三个模块,分别是:核心模块、自定义模块、第三方模块。
这里提一点,JS代码在编程时,如果需要使用某个模块的功能,那么就需要提前将其导入,与Python类似,只不过在Python中使用import关键字,而JS中使用require关键字。
读取文件操作
文件系统模块就是核心模块
1 | var fs = require('fs');//导入fs模块 |
读取文件的操作,下面会在CTF例题中用到。显示了读取文件的各种姿势。
在这里了解更多读取文件的函数和使用。
同步和异步
区别:
同步方法:等待每个操作完成,然后只执行下一个操作
异步方式:从不等待每个操作完成,而是只在第一步执行所有操作
看到一个比较有趣的描述:
同步:可以拿吃饭和看电视来举例子,同步就是先吃完饭,吃完饭后再看电视,不能边看边吃,这就是同步
异步:同样拿上边的例子来说,异步就是边吃饭边看电视,看电视和吃饭同时进行,这样举例就应该很清楚了
还用上面的代码做例子,readFile()是异步操作,所以其运行结果为
可以很明显的看出来下面阻塞代码程序是正常的同步加载,代码由上到下执行。上面这个异步(非阻塞)代码程序会先输出下面的console.log()然后才执行回掉函数里的代码。
全局变量
__dirname
:当前模块的目录名。__filename
:当前模块的文件名。这是当前的模块文件的绝对路径(符号链接会被解析)。exports
变量是默认赋值给module.exports,它可以被赋予新值,它会暂时不会绑定到module.exports。module
:在每个模块中, module 的自由变量是对表示当前模块的对象的引用。为方便起见,还可以通过全局模块的 exports 访问 module.exports。module 实际上不是全局的,而是每个模块本地的require
模块就不多说了,用于引入模块、 JSON、或本地文件。可以从 node_modules 引入模块。
1 | // 引入 JSON 文件: |
自行设置:
1 | global.param=1234; |
经常使用的全局变量是__dirname
、__filename
。
HTTP服务
新建一个测试的js文件,用于开启http服务
1 | //引入http核心模块 |
启动运行该文件,访问指定端口,HTTP服务的网页就显示出来了。
child_process(创建子进程)
child_process
提供了几种创建子进程的方式
异步方式:spawn、exec、execFile、fork
同步方式:spawnSync、execSync、execFileSync
经过上面的同步和异步思想的理解,创建子进程的同步异步方式应该不难理解。
在异步创建进程时,spawn
是基础,其他的fork、exec、execFile都是基于spawn来生成的。
同步创建进程可以使用child_process.spawnSync()
、child_process.execSync()
和 child_process.execFileSync()
,同步的方法会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到子进程退出。
JavaScript原型链
原型和原型链
首先要知道,JavaScript没有父类和子类这个概念,也没有类和实例的区分,而JavaScript中的继承关系则是靠一种很奇怪的“原型链”模式来实现继承。
在次之前,要先搞清楚对象和函数有什么区别和联系。
对象是由函数创建的,而函数又是另一种对象。
JavaScript中的对象
在JavaScript中几乎所有的事物都是对象,如下代码:
1 | var a = { |
其中访问对像的属性,可以有两种方式:
1 | //例如 |
原型的定义和继承
原型的定义:
任何对象都有一个原型对象,这个原型对象由对象的内置属性proto指向它的构造函数的prototype指向的对象,即任何对象都是由一个构造函数创建的
1 | function a(name,age){ |
a函数内容是a类的构造函数,其中this.name
、this.age
就是a类的属性。
在JavaScript中,声明了一个函数a,然后浏览器就自动在内存中创建一个对象b,a函数默认有一个属性prototype指向了这个对象b,b就是函数a的原型对象,简称原型。同时,对象b默认有属性constructor指向函数a。
创建一个对象a,对象a会默认有一个属性proto指向构造函数A的原型对象b
这里A.prototype就指向函数的原型B。则a.proto是实例化的对象a的一个属性。
在javascript中,一切都是对象,他也只有对象这一种结构。而对象和对象间又存在继承关系。
1 | var test = { |
每个实例对象(object)都有一个私有属性(proto)指向它的构造函数的原型对象(prototype),每个实例对象还有一个属性(constructor)指向原型的构造函数。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
经过不断调用,最终的原型对象会调用到null,这将作为该原型链的最后一个环节,与之对应的,作为终点的 null 自然也是没有原型对象的。
原型链定义及如何污染
原型链的核心就是依赖对象proto的指向,当访问的属性在该对象不存在时,就会向上从该对象构造函数的prototype的进行查找,直至查找到Object的原型null为止。
由于对象之间存在继承关系,所以当我们要使用或者输出一个变量就会通过原型链向上搜索,当上层没有就会再向上上层搜索,直到指向 null,若此时还未找到就会返回 undefined
图中的原型链是 cat->Cat.protype->Object.prototype->null
原型链污染就是修改其构造函数中的属性值,使其他通过该构造函数实例化出的对象也具有这个属性的值。
由于对象是无序的,当使用第二种方式访问对象时,只能使用指明下标的方式去访问。
因此我们可以通过 a [“proto“] 的方式去访问其原型对象。
调用对象属性时, 会查找属性,如果本身没有,则会去proto中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有proto,那么会去proto的显式原型中查找,一直到null。
p神的文章中提到JavaScript原型链继承
CTF题目实战
NodeJS简单类型的题目以及常见绕过
搜集了一下,做个总结,方便自己以后查阅。前面的题目没有涉及到原型链污染,不过也是学到了许多知识。