终于学到了Java安全!!!!

在学习前,需要先搭建几个Java靶场,方便进行学习

一、靶场

1、JavaSec

https://github.com/bewhale/JavaSec

2、Hello-Java-Sec

https://github.com/j3ers3/Hello-Java-Sec

搭建这两个靶场的时候需要注意数据库的配置!!!

3、inxedu

还有一个代码审计的案例网站inxedu,也需要我们自己搭建。把源码下载下来后,打开IDEA,把文件导入。

其中需要配置几个文件,第一个是

src/main/resources/project.properties

其中需要配置数据库的信息,以及端口信息等

配置好后。就可以配置Tomcat。其中也需要配置他的端口号和url:

在部署这里的请用程序上下文路径需要和project.properties文件中的路径对应。然后就可以启动了

二、SQL注入-JDBC&MyBatis

环境搭建好之后就可以正式的学习了。

1、JDBC

JDBC 是什么?JDBC = Java Database Con来让 Java 程序连接 MySQL、Oracle、SQL Server 等数据库。

JDBC的SQL注入其实和之前在PHP中学到的sql注入没什么区别,我们可以通过前面已经搭建好的两个靶场来看一下。

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

2>、PrepareStatement会对SQL语句进行预编译,但如果直接采取拼接的方式构造SQL,此时进行预编译也无用。

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

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

2、MyBatis

MyBatis 是什么?MyBatis 是一个 Java 持久层框架。它底层还是用 JDBC,但它帮你封装了很多麻烦的操作。

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

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

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

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

同样可以通过这两个靶场来进行学习。

同时可以使用搭建好的inxedu这个实例来学习MyBatis注入。

首先我们先判断,他是否使用了MyBatis

通过外部库,可以看出他是使用了MyBatis的。因为在MyBatis中'#'是安全的'$'是不安全的,其中'(${','${','%${'都是比较可能出现安全问题的地方,所以,可以在文件中搜索可以找到这样的一些代码

这里有一个删除文章的操作,我们可以追踪一下这个操作搜索一下deleteArticleByIds这个:

可以检索到这样一个操作:

然后我们打开这个网站,进入到删除文章这个操作的界面

可以点一下删除文章,然后看一下他的数据包

可以看到,当点击删除后有一个articelId=22的负载然后就可以使用SQLmap工具帮我们进行分析。

三、XXE注入

1、XXE是什么

XXE (XML External Entity Injection), XML外部实体注入

它是 Java Web 安全里很经典的一类漏洞,核心原因是:

后端解析用户提交的 XML 时,允许解析 DOCTYPE 和外部实体,攻击者就可以让服务器去读取本地文件、访问内网地址,甚至造成拒绝服务。

先看 XML 实体是什么,普通 XML:

<user>
    <name>admin</name>
</user>

XML 里可以定义实体:

<?xml version="1.0"?>
<!DOCTYPE user [
    <!ENTITY test "hello">
]>
<user>
    <name>&test;</name>
</user>

解析后:

<name>hello</name>

这里的<!ENTITY test "hello">意思是定义一个实体 test,后面用&test;就会被替换成 hello

2、XXE 的关键:外部实体

危险点在这里:

<!ENTITY xxe SYSTEM "file:///etc/passwd">

SYSTEM 表示这个实体不是普通字符串,而是从外部资源读取。完整结构类似:

<?xml version="1.0"?>
<!DOCTYPE user [
    <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>
    <name>&xxe;</name>
</user>

如果服务器 XML 解析器没有禁用外部实体,那么它可能会尝试读取服务器本地文件。

在 Windows 上也可能是类似:

<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">

3、Java 为什么容易出现 XXE?

Java 里很多地方会解析 XML,比如:

DocumentBuilderFactory
SAXParserFactory
SAXReader
XMLInputFactory
SAXBuilder
JAXB
dom4j
XStream

如果代码这样写,就可能有风险:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(request.getInputStream());

4、审计的函数

* 1. XMLReader

 * 2. SAXReader

 * 3. DocumentBuilder

 * 4. XMLStreamReader

 * 5. SAXBuilder

 * 6. SAXParser

 * 7. SAXSource

 * 8. TransformerFactory

 * 9. SAXTransformerFactory

 * 10. SchemaFactory

 * 11. Unmarshaller

 * 12. XPathExpression

四、SSTI模版

1、SSTI模版

SSTI(Server Side Template Injection) 服务器模板注入, 服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容。

模板引擎本来是用来把数据渲染成 HTML 的,比如:

<p th:text="${username}"></p>

后端传入:

model.addAttribute("username", "admin");

页面显示:

admin

正常情况下,username 只是数据,不会被当成代码执行。

但是如果开发者把用户可控内容放进了模板表达式、模板片段名、模板名、动态模板内容里,就可能导致 SSTI。

2、Thymeleaf 是什么?

Thymeleaf 是 Java/Spring 里常见的模板引擎。

Spring Boot 项目里经常这样用:

@Controller
public class IndexController {

    @GetMapping("/index")
    public String index(Model model) {
        model.addAttribute("name", "admin");
        return "index";
    }
}

这里:

return "index";

不是返回字符串内容,而是返回一个视图名

Spring + Thymeleaf 会去找:

templates/index.html

Thymeleaf 里有几种常见表达式:

表达式 含义
${...} 变量表达式
*{...} 选择表达式
#{...} 国际化消息
@{...} URL 表达式
~{...} 片段表达式

3、 Thymeleaf SSTI 常见入口一:动态返回视图名

危险代码:

@GetMapping("/page")
public String page(@RequestParam String name) {
    return name;
}

这个问题很大。

因为 return name;@Controller 里不是普通字符串响应,而是:

把 name 当作 Thymeleaf 视图名解析

如果用户访问:

/page?name=index

后端就会找:

templates/index.html

如果用户可以控制这个视图名,就可能造成:

任意模板解析
模板路径控制
片段表达式解析
SSTI

五、SPEL表达式注入

SpEL 表达式注入就是:

用户输入被后端当成 Spring Expression Language 表达式 执行了,导致攻击者可以读取对象属性、调用方法,严重时可能造成代码执行。

SpEL 全称是:Spring Expression Language,它是 Spring 提供的一套表达式语言,正常用途是做动态取值、条件判断、配置解析等。

1、SPEL是什么?

比如 Spring 里可以写:

@Value("#{systemProperties['user.home']}")
private String userHome;

这里的:

#{systemProperties['user.home']}

就是 SpEL 表达式。

它可以做很多事情,比如:

#{1 + 2}
#{'hello'.toUpperCase()}
#{user.name}
#{user.age > 18}

所以 SpEL 本质上是一个表达式执行器

正常 SpEL 使用示例:

ExpressionParser parser = new SpelExpressionParser();

Expression expression = parser.parseExpression("'hello'.toUpperCase()");

Object value = expression.getValue();

System.out.println(value);

输出Hello,这没问题,因为表达式是开发者自己写死的。

2、漏洞怎么产生?

危险点在这里:

parser.parseExpression(userInput).getValue();

如果 userInput 来自用户请求,那就危险了。

例如:

@GetMapping("/calc")
@ResponseBody
public Object calc(@RequestParam String exp) {
    ExpressionParser parser = new SpelExpressionParser();
    return parser.parseExpression(exp).getValue();
}

用户访问:/calc?exp=1+2,后端返回3,表面看是一个“在线计算器”。

但问题是:

后端把用户输入当成 SpEL 表达式执行了。

这就是 SpEL 表达式注入。

3、为什么 SpEL 危险?

因为 SpEL 不只是简单的数学表达式,它可以访问对象、调用方法、访问类型。

例如它支持:

属性访问
方法调用
构造对象
集合操作
类型引用
Bean 引用
条件表达式

所以如果没有限制,攻击者可能利用它做很多危险操作。

T() 是 SpEL 的类型引用语法。例如:

T(java.lang.Math).abs(-1)

意思是调用:

java.lang.Math.abs(-1)

所以危险在于:

一旦攻击者能控制完整 SpEL 表达式,就不只是“算术计算”,而是可能访问 Java 类型和方法。

4、常见危险代码

危险 1:直接解析用户输入

@GetMapping("/spel")
@ResponseBody
public Object spel(@RequestParam String exp) {
    ExpressionParser parser = new SpelExpressionParser();
    return parser.parseExpression(exp).getValue();
}

问题;

parseExpression(exp)

exp 用户可控。这是最典型的 SpEL 注入。

危险 2:把用户输入拼进表达式

String expression = "name == '" + userInput + "'";
Boolean result = parser.parseExpression(expression).getValue(Boolean.class);

如果 userInput 可控,就可能破坏原表达式结构。更危险的是:

String expression = "#user." + fieldName;
Object value = parser.parseExpression(expression).getValue(context);

如果 fieldName 完全由用户控制,也有风险。

六、RCE执行漏洞

这个漏洞在前面学习PHP安全的时候的已经学过。在Java中主要学习其中的不同点就是5大类函数调用。

第一条类:直接创建系统进程:

Runtime.exec()
ProcessBuilder.start()
ProcessImpl.start()

第二类:先执行脚本/表达式,再间接调用系统命令

Groovy
ScriptEngineManager

本质都是:攻击者控制的输入,最后进入了“代码执行点”或者“系统命令执行点”。

1、RuntimeExec

Runtime.getRuntime().exec()。这是 Java 里最经典的命令执行入口。

典型形式:

Runtime.getRuntime().exec("whoami");

或者:

String cmd = request.getParameter("cmd");
Runtime.getRuntime().exec(cmd);

如果 cmd 来自用户输入,就可能形成 RCE。它的危险点是Runtime.exec() 会启动一个系统进程。比如:

Runtime.getRuntime().exec("ipconfig");

但注意一个很重要的点:

Runtime.getRuntime().exec("ls | grep test");

不一定能正常执行管道。因为 Runtime.exec(String) 默认不是直接开 shell,它是把字符串解析成程序和参数。管道、重定向、&&; 这类 shell 语法,通常需要:

/bin/sh -c "ls | grep test"

或者 Windows:

cmd.exe /c "dir"

所以审计时看到这些更危险:

Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", userInput});
Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", userInput});

这类基本就是高危点。

2、ProcessBuilder:

new ProcessBuilder(...).start()。ProcessBuilder 是 Java 推荐的进程启动方式,比Runtime.exec() 更灵活。典型形式:

ProcessBuilder pb = new ProcessBuilder("whoami");
pb.start();

带参数:

ProcessBuilder pb = new ProcessBuilder("ping", "127.0.0.1");
pb.start();

危险写法

String ip = request.getParameter("ip");
ProcessBuilder pb = new ProcessBuilder("ping", ip);
pb.start();

这个不一定就是命令注入,因为参数是分开的,攻击者不能轻易插入 && whoami 这种 shell 语义。

但是如果写成这样就危险了:

String cmd = request.getParameter("cmd");
ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", cmd);
pb.start();
或者
String cmd = request.getParameter("cmd");
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", cmd);
pb.start();

这时候用户输入被 shell 解释,RCE 风险极高。

3、ProcessImpl

ProcessImpl底层进程执行类。ProcessImpl 是 Java 底层真正创建进程的实现类。你平时一般不会直接写:

ProcessImpl.start(...)

因为它不是普通业务代码常用 API。但是:

Runtime.exec()
ProcessBuilder.start()

底层最终都会走到类似 ProcessImpl 的实现。

可以简单理解为:

Runtime.exec()
        ↓
ProcessBuilder.start()
        ↓
ProcessImpl.start()
        ↓
操作系统创建进程

所以在漏洞分析、调用链分析、反序列化 RCE 分析里,经常会看到 ProcessImpl

为什么安全分析里会提它?

因为很多 Java RCE 最终堆栈会出现:

java.lang.ProcessImpl.start
java.lang.ProcessBuilder.start
java.lang.Runtime.exec

你调试漏洞时,如果调用栈里出现 ProcessImpl.start,说明已经进入系统命令执行阶段。

在白盒时重点看:

// 业务代码里重点看:
Runtime.exec()
ProcessBuilder.start()

// 底层调用链里重点看:
ProcessImpl.start()

ProcessImpl 更多是用来确认“确实执行到了系统命令创建流程”。

4、Groovy

Groovy:脚本执行导致 RCE。Groovy 是运行在 JVM 上的动态语言。它可以直接调用 Java 类,所以如果用户输入进入 Groovy 表达式执行,就非常危险。

常见危险点:

GroovyShell shell = new GroovyShell();
shell.evaluate(userInput);

或者:

Eval.me(userInput);

或者:

GroovyClassLoader loader = new GroovyClassLoader();
loader.parseClass(userInput);

那么,Groovy为什么危险?

因为 Groovy 不只是“表达式计算”,它可以执行 Java 代码。

例如它可以调用:

Runtime.getRuntime().exec(...)

所以链路是:

用户输入
   ↓
GroovyShell.evaluate()
   ↓
Groovy 代码执行
   ↓
调用 Java Runtime / ProcessBuilder
   ↓
系统命令执行

典型风险场景

比如系统提供了一个“在线规则配置”“动态表达式计算”“模板渲染”“脚本扩展”功能:

String rule = request.getParameter("rule");
Object result = new GroovyShell().evaluate(rule);

如果没有沙箱和白名单,这种代码非常危险。

白盒审计关键词:

GroovyShell
evaluate
Eval.me
GroovyClassLoader
parseClass
GroovyScriptEngine
Binding

看到 evaluate(userInput) 这种写法,基本要重点标红。

5、ScriptEngineManager
ScriptEngineManager:脚本引擎执行。ScriptEngineManager 是 Java 提供的脚本引擎管理器,可以执行 JavaScript、Groovy、Python、Ruby 等脚本,取决于环境里有什么引擎。

典型写法:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
engine.eval(userInput);

为什么危险?

因为 eval() 会执行传入的脚本内容。如果脚本引擎允许访问 Java 类,就可能从脚本层调用 Java 的危险 API。

链路是:

用户输入
   ↓
ScriptEngine.eval()
   ↓
脚本执行
   ↓
访问 Java 类
   ↓
Runtime.exec / ProcessBuilder
   ↓
系统命令执行

常见危险代码

String exp = request.getParameter("exp");
engine.eval(exp);

这就是典型的表达式注入 / 脚本注入风险。

JavaScript 引擎补充

老版本 Java 常见的是 Nashorn:

getEngineByName("nashorn")
getEngineByName("JavaScript")

审计关键词

ScriptEngineManager
getEngineByName
eval
Nashorn
JavaScript
js
groovy

尤其注意:

engine.eval(userInput);

这类是重点危险点。

6、这 5 类的关系

Groovy / ScriptEngineManager
        ↓
    脚本执行
        ↓
Runtime.exec / ProcessBuilder.start
        ↓
ProcessImpl.start
        ↓
操作系统命令执行

也就是说:

类别 本质 危险等级
Runtime.exec() 直接执行系统命令
ProcessBuilder.start() 直接创建系统进程
ProcessImpl.start() 底层进程创建实现 高,但多见于调用链
GroovyShell.evaluate() 动态执行 Groovy 脚本 极高
ScriptEngineManager.eval() 动态执行脚本 极高

更多推荐