node.js
什么是node.js
Node.js是JavaScript语言的服务器运行环境
个人理解就是可以将 javascript 作为后端语言运行的一个环境
服务器 – 后端
浏览器 – 前端
个人思考过的问题
javascript和java有关系吗?
以下是chatgpt的回答
JavaScript和Java虽然在名称上很相似,但是它们是两种完全不同的编程语言,并且没有直接的关系。
JavaScript是一种解释性的脚本语言,它的语法规则比较灵活,允许开发人员在编写代码时更加自由地表达自己的思想。
Java则是一种编译型的语言,它的语法规则比较严格,不允许开发人员在语言方面有太多的自由度。
至于他们为什么这么相似,以下是chatgpt的回答
总结:javascript是蹭热度的
没有什么概念定义可以讲的,看题吧
CTFSHOW_node.js模块
1.web334(toUpperCase函数)
先在首页面下载附件,附件中含有两个js文件
在user.js附件中的代码是
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};
在 Node.js 中,每个模块都是一个独立的作用域,它们的变量和函数默认都是私有的,无法被其他模块访问。为了使得模块的内部内容能够被其他模块访问和使用,Node.js 提供了一个特殊的对象 module.exports
,用于将模块的某个变量或函数导出给其他模块使用。
在 Node.js 中,每个模块都有一个 module
对象,该对象包含了模块的相关信息和方法。其中,module.exports
是一个属性,用于将模块的内容导出。
具体到这段代码来说,这段代码是一个 Node.js 模块,它定义了一个名为 items
的数组,其中包含了一个对象,该对象具有 username
和 password
两个属性。此外,该模块使用 module.exports
将该数组导出,使得其他模块可以引用和使用该数组。
在其他模块中引用该模块时,可以使用 require
函数来获取该模块导出的内容,例如:
javascriptCopy codeconst myModule = require('./myModule.js');
console.log(myModule.items);
这里的 myModule
是导入的模块对象,可以通过 myModule.items
访问到该模块中导出的 items
数组。
然后再看一下另一个js文件,也就是实现主要功能的文件
var express = require('express');
var router = express.Router();//
var users = require('../modules/user').items;
var findUser = function(name, password)
//定义了一个名为 `findUser` 的函数,该函数接受两个参数 `name` 和 `password`,用于表示用户输入的用户名和密码。
{
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};
/* GET home page. */
router.post('/', function(req, res, next)
//req(request):封装了HTTP请求的所有信息,包括请求头、请求体、请求参数等等,通过访问req对象的属性和方法可以获取这些信息。
//res(response):封装了HTTP响应的所有信息,包括响应头、响应体、状态码等等,通过访问res对象的属性和方法可以设置这些信息。
{
res.type('html');
var flag='flag_here';
var sess = req.session;
var user = findUser(req.body.username, req.body.password);
//接受请求体中的username,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});
});
}else{
res.json({ret_code: 1, ret_msg: '账号或密码错误'});
}
});
module.exports = router;
主要实现检测功能的代码是
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};
-
return users.find(function(item){})
:在users
数组中调用find
方法,查找符合条件的用户。find
方法接受一个回调函数,该函数会被依次传入数组中的每个元素item
,如果该函数返回值为true
,则find
方法会返回该元素,否则继续查找直到数组末尾。因此,在这个回调函数中需要实现对输入的用户名和63密码的匹配逻辑。return name !=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
:这是回调函数的实现,具体解释如下:
name!=='CTFSHOW'
:这是 JavaScript 中的一种比较运算符,表示严格不等于,如果输入的用户名不等于字符串'CTFSHOW'
,则返回true
,表示该元素匹配成功。item.username === name.toUpperCase()
:这也是一个判断条件,如果输入的用户名经过转换为大写字母后与该元素的用户名相等,则返回true
,表示该元素匹配成功。item.password === password
:同上,如果输入的密码与该元素的密码相等,则返回true
,表示该元素匹配成功。- 如果以上三个判断条件均返回
true
,则说明该元素是符合要求的用户,find
方法会将该元素作为返回值返回给findUser
函数,否则继续查找。如果数组中没有符合要求的元素,find
方法会返回undefined
,此时findUser
函数也会返回undefined
。
所以理一下思路,现在的目标是使传入的name参数在经过toUpperCase函数后变为“CTFSHOW”,然后传入的name在一开始不是大写的“CTFSHOW”即可
传入小写ctfshow和密码即可
一开始看粗略看wp时以为本题和toUpperCase的特性有关系,但是并没有,不过这里记录一下
toUpperCase():
ı(这是土耳其语中的字母“i”(小写)) ==>I
ſ (这个字符是历史上使用的拉丁字母“s”的一种形式,称为长s) ==>S
toLowerCase():
İ(土耳其语和阿塞拜疆语等一些土耳其语族语言中的一个字母) ==>i
K ==> k
K的话看不出什么特别的,好像粗一点
这关可能是想让用这个特性绕过,那样的话应该是把小写的ctfshow ban了 不太理解
2.web335(spawnSync函数执行系统命令)
源码中提示了含有get传参,参数名为eval
那应该就是传入一个命令使用js中的eval函数执行了
payload(显示当前目录下的文件和文件夹列表)
require('child_process').spawnSync('ls',['.']).stdout.toString()
解释一下这串代码:
require
是 Node.js 中用来引入模块的函数,在这里引用了Node.js的child_process模块
child_process
模块是 Node.js 标准库中的一个模块,它提供了创建子进程的功能,可以通过它执行系统命令、shell 脚本等。其中包含有spawnSync()方法,spawnSync函数可以用来执行系统命令
spawnSync(‘ls’,[‘.’]) 前面代表命令,后面是一个参数,这里的 . 代表着当前目录, … 代表上一级目录 …/… 代表上上级目录
这中间用于连接的两个点是用来访问对象属性的
'stdout’是一个缓冲区,它包含了子进程的标准输出,也就是说输出的内容在这里
toString()转换为字符串
payload执行成功后,页面回显
发现了里面有fl00g,txt,应该就是存放flag的文件了
于是可构建出payload
require('child_process').spawnSync('cat',['fl00g.txt']).stdout.toString()
3.web336(黑名单绕过)
和上一题一样样的,只是将文件名由fl00g.txt改为了fl001g.txt,不再赘述
这里看了别的师傅的wp,尝试了三种命令执行,后面两种都被ban了
所以这里是考察了下黑名单
require('child_process').spawnSync('ls',['.']).stdout.toString()
require('child_process').execSync('ls').toString()
global.process.mainModule.constructor._load('child_process').execSync('ls',['.']).toString()
/?eval=require(‘fs’).readFileSync(‘/app/routes/index.js’,‘utf-8’)用这个payload可以看到index源码,但是不清楚它怎么知道的路径
这是源码
将exec,load给ban掉了
4.web337(MD5绕过)
题目界面给出一段源码
var express = require('express');
var router = express.Router();
var crypto = require('crypto');
function md5(s) {
return crypto.createHash('md5')
.update(s)
.digest('hex');
}
/* GET home page. */
router.get('/', function(req, res, next) {
res.type('html');
var flag='xxxxxxx';
var a = req.query.a;
var b = req.query.b;
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag);
}else{
res.render('index',{ msg: 'tql'});
}
});
module.exports = router;
看起来像是MD5绕过,要求两个参数值长度一样,MD5加密后值一样
这里是强类型比较,不能用0e开头的那几个数
采取数组(bushi)绕过,得到flag
这里是一开始的一个误区,认为这个是数组了
有wp上给了一串如下的代码
在jsp中,上面是属于数据类型“对象”,下面是属于数据类型“数组”
代码运行结果得到的是
也就是说输入对象是两个 ”对象“ 类型时,在经过加法运算后的值是一样的(那么MD5加密后的值也是一样的)
所以按照以下payload传入值是正确的
这样的话第一个payload中传入的值就不是数组类型了,问chatgpt
所以前面说的”数组绕过“我不认为是对的,我认为叫对象绕过更贴切,虽然有点别扭
5.web338(原型链污染)
先了解一下什么是原型链污染
在此之前,先了解什么是原型链
原型链:原型链是实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个prototype属性,指向原型对象。原型对象都包含一个指向构造函数的指针(constructor)。把构造函数的prototype属性修改成另一个构造函数的实例,此时原型对象就将包含指向另一个原型的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是原型链的概念。
举个例子(chatgpt举得)
// 定义一个构造函数
function Animal(name) {
this.name = name;
}
// 在构造函数的原型对象上定义一个方法
Animal.prototype.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
// 定义一个子构造函数
function Dog(name, breed) {
Animal.call(this, name); //将子类实例的 this 对象作为参数传递给父类的构造函数,并传递一个 name 参数。这种方式被称为使用构造函数继承,它的作用是在子类中创建一个父类的实例对象,并将其绑定到子类的 this 上,从而实现子类对父类属性和方法的继承。
}
// 子构造函数通过原型链继承父构造函数的方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 在子构造函数的原型对象上定义一个方法
Dog.prototype.bark = function() {
console.log('Woof, woof!');
};
// 创建一个子类的实例对象
var myDog = new Dog('Buddy', 'Golden Retriever');
// 调用父构造函数和子构造函数继承的方法
myDog.sayHello(); // Hello, I am Buddy
myDog.bark(); // Woof, woof!
在上面的例子中,Animal
是父构造函数,Dog
是子构造函数。Animal
构造函数的原型对象上定义了一个 sayHello
方法,Dog
子构造函数的原型对象上定义了一个 bark
方法。子构造函数通过 Object.create
方法继承了父构造函数的原型对象,从而继承了父构造函数的方法。最终,myDog
实例对象可以访问父构造函数和子构造函数继承的方法。
这就是一个简单的原型链,说起来复杂其实还好
那什么是原型链污染?
简单来说,就是当原型对象被修改时,对应的所有实例化对象都会被影响。
通过篡改原型链,使得原本不应存在的属性或方法被添加到对象中,从而对对象产生意想不到的影响。
举个例子
假设我们有一个名为Person
的构造函数,它有一个name
属性和一个greet
方法:
codefunction Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
}
现在假设我们的应用程序包含一个从未被信任的源加载的脚本,它在全局作用域中定义了一个名为Person
的对象:
// 危险代码来自不受信任的源
Person = {
name: 'Evil Hacker',
greet: function() {
console.log('I am going to hack you!');
}
};
如果我们现在创建一个Person
实例并调用greet
方法,将调用不受信任的源中定义的方法而不是我们预期的Person.prototype.greet
方法:
const person = new Person('Alice');
person.greet(); // 输出"I am going to hack you!",而不是"Hello, my name is Alice"
这就是一个简单的原型污染示例。通过修改全局Person
对象的原型,不受信任的代码覆盖了我们的Person
构造函数的原型,使其定义的所有实例都受到影响。
跟反序列化异曲同工
说了这么多 看题吧
routes/login.js中源码
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;
可以看到输出flag的条件是 secert.ctfshow===‘36dboy’
发现下面的copy函数,可能会用到,查看一下来源
在utils/common.js中源码
module.exports = {
copy:copy
};
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
这段代码实现了一个深拷贝函数,将object2的属性和值拷贝到object1中。如果object2中的属性是一个对象,就递归调用copy函数拷贝该对象的属性和值。如果object1中已经存在相同的属性,就将object2的属性值覆盖掉object1的属性值。
所以这里的思路就是利用 utils.copy(user,req.body);这串代码了,从代码中可得知,接收的参数是POST请求的数据,会保存在req.body中,然后copy函数将其中的值赋予给user
secret
对象直接继承了Object.prototype,所以污染Object.prototype即可
构建payload:
"__proto__":{"ctfshow":"36dboy"}
这样的话Object.prototype的原型链就被污染了,添加了一个叫ctfshow的类,值为36dboy,于是后面检测 secert.ctfshow===‘36dboy’ 时,secret找不到ctfshow这个类,就按照原型链去找,就在object的原型中找到了
抓包修改post参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AGReb9zK-1681607370825)(D:\cybersec\typoraimage\image-20230411194223249.png)]
后面的就比较困难了,后续对node.js有更多了解后再去尝试做吧
y(user,req.body);这串代码了,从代码中可得知,接收的参数是POST请求的数据,会保存在req.body中,然后copy函数将其中的值赋予给user
secret
对象直接继承了Object.prototype,所以污染Object.prototype即可
构建payload:
"__proto__":{"ctfshow":"36dboy"}
这样的话Object.prototype的原型链就被污染了,添加了一个叫ctfshow的类,值为36dboy,于是后面检测 secert.ctfshow===‘36dboy’ 时,secret找不到ctfshow这个类,就按照原型链去找,就在object的原型中找到了
抓包修改post参数
后面的就比较困难了,后续对node.js有更多了解后再去尝试做吧