网安学习第26天 Java安全——常见漏洞
终于学到了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() |
动态执行脚本 | 极高 |
更多推荐
所有评论(0)