目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. Archived elephant(Unsolved)
    2. 2.2. hackjs(复现)
  3. 3. Misc
    1. 3.1. checkin

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

VCTF2024

2024/3/16 CTF线上赛 Nodejs
  |     |   总文章阅读量:

前言

依旧是爆了零的web呢

官方wp:https://github.com/ChaMd5Team/Venom-WP/tree/main/2024VenomCTF

Web

Archived elephant(Unsolved)

java代码审计 + Ueditor存在JSON注入隐患 + fastjson<1.2.68 common-io任意文件写 + beetl模板白名单绕过

题目给的Dockerfile里中科大源寄了,自己换了个源(虽然后面附件更新了)

可用源的地址:https://launchpad.net/ubuntu/+archivemirrors0

FROM tomcat:8-jdk8
COPY files /tmp/files/
RUN mv /tmp/files/flag.sh / && \
    mv /tmp/files/start.sh / && \
    mv /tmp/files/db.sql / && \
    chmod +x /flag.sh /start.sh && \
    sed -i 's@//.*archive.ubuntu.com@//mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list && \
    sed -i 's@//.*deb.debian.org@//mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list && \
    apt-get update -y && \
    apt-get install libaio1 libnuma1 psmisc libmecab2 libatomic1 libncurses6 libsasl2-2 perl -y && \
    DEBIAN_FRONTEND=noninteractive dpkg -i /tmp/files/mysql-common_5.7.29-1debian10_amd64.deb && \
    DEBIAN_FRONTEND=noninteractive dpkg -i /tmp/files/mysql-community-client_5.7.29-1debian10_amd64.deb && \
    DEBIAN_FRONTEND=noninteractive dpkg -i /tmp/files/mysql-client_5.7.29-1debian10_amd64.deb && \
    DEBIAN_FRONTEND=noninteractive dpkg -i /tmp/files/mysql-community-server_5.7.29-1debian10_amd64.deb && \
    DEBIAN_FRONTEND=noninteractive dpkg -i /tmp/files/mysql-server_5.7.29-1debian10_amd64.deb && \
    rm -rf /tmp/files && \
    rm -rf /var/lib/apt/lists/*
COPY src/elephant.war /usr/local/tomcat/webapps/ROOT.war
CMD /start.sh

db.sql文件给了账密:admin:admin

直接登录即可

来到/upload

看一下对应的代码

@RequestMapping({"/upload"})
public String upload(HttpServletRequest request, Model model, HttpSession session, @RequestParam(value = "action",required = false) String action) throws URISyntaxException {
    String user = (String)session.getAttribute("user");
    if (!"admin".equals(user)) {
        model.addAttribute("message", "no way");
        return "upload";
    } else if (action == null) {
        model.addAttribute("message", "传个文件吧");
        return "upload";
    } else {
        String json = (new ActionEnter(request, UploadController.class.getResource("/").toURI().getPath() + this.rootPath)).exec();
        String contextPath = request.getContextPath();
        String handlerOut = this.uploadService.uploadHandle(action, json, contextPath);
        model.addAttribute("message", handlerOut);
        return "upload";
    }
}

白名单后缀名限制

public static int getFileType(String ext) {
    if (!"doc".equals(ext) && !"docx".equals(ext) && !"xls".equals(ext) && !"xlsx".equals(ext) && !"ppt".equals(ext) && !"pptx".equals(ext) && !"pdf".equals(ext) && !"xml".equals(ext) && !"json".equals(ext) && !"txt".equals(ext) && !"log".equals(ext) && !"md".equals(ext)) {
        if (!"bmp".equals(ext) && !"jpg".equals(ext) && !"jpeg".equals(ext) && !"gif".equals(ext) && !"png".equals(ext)) {
            if (!"mp3".equals(ext) && !"wav".equals(ext) && !"mid".equals(ext) && !"aif".equals(ext)) {
                if (!"flv".equals(ext) && !"swf".equals(ext) && !"mkv".equals(ext) && !"avi".equals(ext) && !"rm".equals(ext) && !"rmvb".equals(ext) && !"mpeg".equals(ext) && !"mpg".equals(ext) && !"ogg".equals(ext) && !"ogv".equals(ext) && !"mov".equals(ext) && !"wmv".equals(ext) && !"mp4".equals(ext) && !"webm".equals(ext)) {
                    return !"rar".equals(ext) && !"zip".equals(ext) && !"tar".equals(ext) && !"gz".equals(ext) && !"7z".equals(ext) && !"bz2".equals(ext) && !"cab".equals(ext) && !"iso".equals(ext) ? 9 : 8;
                } else {
                    return 4;
                }
            } else {
                return 3;
            }
        } else {
            return 2;
        }
    } else {
        return 1;
    }
}

hackjs(复现)

const express = require('express')
const fs = require('fs')
var bodyParser = require('body-parser');
const app = express()
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(bodyParser.json());

app.post('/plz', (req, res) => {

    venom = req.body.venom

    if (Object.keys(venom).length < 3 && venom.welcome == 159753) {
        try {
            if(venom.hasOwnProperty("text")){
                res.send(venom.text)
            }else{
                res.send("no text detected")
            }
        } catch {
            if (venom.text=="flag") {
                let flag=fs.readFileSync("/flag");
                res.send("Congratulations:"+flag);
            } else {
                res.end("Nothing here!")
            }
        }
    } else {
        res.end("happy game");
    }
})



app.get('/',
function(req, res, next) {
    res.send('<title>oldjs</title><a>Hack me plz</a><br><form action="/plz" method="POST">text:<input type="text" name="venom[text]" value="ezjs"><input type="submit" value="Hack"></form>  ');
});

app.listen(80, () => {
  console.log(`listening at port 80`)
}) 

首先是要进入catch语句,就要让venom.hasOwnProperty("text")报错

方法也很简单,参考:https://www.cnblogs.com/wengxuesong/p/5613189.html

我们使用dict对象的hasOwnProperty方法,但其实它自身并没有这个方法,而是继承自Object.prototype对象。如果dict字典对象有一个同为”hasOwnProperty”名称的属性,那么原型中的hasOwnProperty方法不会被访问到。这里会优先读取自身包含的属性,找不到才会从原型链中查找

所以只需要传入venom[welcome]=159753&venom[hasOwnProperty]=即可进入catch

测试的时候发现传venom[__proto__]进去没被检测成键

然后我不会了。。做的时候一直想用hasOwnProperty键构造原型链污染text

其实依旧是传三个参数进去,但是hasOwnProperty这里可以前面可以带个__proto__,用来绕过键长检测

payload:

venom[__proto__][hasOwnProperty]=&venom[text]=flag&venom[welcome]=159753

Misc

checkin

f12在控制台发现hint:Quick pass cheat: I heard that Venom is ChaMd5’s, here is a mysterious string for you. 88d18c420654d158d22b65626bc7a878

一眼md5,somd5爆破一下得到flag:flag{We1c0m3_VCTF_2024}