SpringMVC学习 && V&N2020公开赛 EasySpringMVC wp(反序列化)

SpringMVC学习

SpringMVC是基于Spring的一个MVC框架,是基于Servlet的一个MVC框架,主要解决WEB开发的问题 https://www.jianshu.com/p/42620a0a2c33

在JavaEE开发中,开发架构几乎全是B/S。在B/S架构中,系统标准的三层架构包括:表现层、业务层、持久层

  • 表现层:web层,接收http请求并响应;表现层的设计一般使用MVC模型

  • 业务层:service层,负责业务逻辑处理;

  • 持久层:dao层,负责和数据库交互

使用IDEA搭建一个简单的springmvc demo:https://www.cnblogs.com/wormday/p/8435617.html

流程大致为:

1.需要自己创建一个包

2.创建的Controller中,类上的注解@RequestMapping("hi")指定URL路径前的一部分,方法上的注解@RequestMapping("say")指定URL路径最后一部分;可以只把注解写方法上

3.IDEA会在web.xml中默认配置一个名字叫做dispatcher的Servlet,可以自行配置Url规则

4.在dispatcher-servlet.xml中配置component-scan,它会告诉Servlet去哪找到相应的Controller。通过base-package指定存放Controller的包

5.创建jsp文件,可以直接用return "/WEB/INF/jsp/say.jsp"绝对路径的方式来找View,也可在dispatcher-servlet.xml中配置,只返回View名字即可返回相应的视图

6.通过Model类向View传值

最终部分文件内容:

HiController.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package springmvc.helloworld;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/hi")
public class HiController {
@RequestMapping("/say")
public String say(Model model){
model.addAttribute("name", "gtfly");
model.addAttribute("url","http://www.gtfly.top");
return "say";
}
}

web.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

dispatcher-servlet.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 视图的路径 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 视图名称后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
<context:component-scan base-package="springmvc.helloworld"/>
</beans>

say.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%--
Created by IntelliJ IDEA.
User: gtfly
Date: 2020/9/24
Time: 7:19 下午
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
hello world,${name}
blog: ${url}
</body>
</html>

V&N2020公开赛 EasySpringMVC

题目给出源码,查看PictureController.class反编译后的源码,定义了两个路由,首先来看showpic.form

接收file参数(直接在Controller的方法中添加参数,就可以直接得到解析),如果为空就赋值为showpic.jsp,否则获取传入的文件名后缀;如果文件后缀不是jsp,获取当前session中admin的值,java的&&运算优先级大于||,不可能同时满足后缀为jpggif,主要看isadmin是否为true,如果为true则会调用定义的私有方法show

其中httpServletRequest.getServletContext().getResoutceAsStream()会返回相应文件流,之后用getOutputStream发送响应消息体;即show方法的功能为:读取文件内容并输出

如果后缀为jsp,使用StringBuilder类创建字符串,其append方法起拼接作用,lastIndexOf方法返回最后一次出现字符的下标,写个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("hello");
sb.append(" world");
sb.append(" gggtfly");
System.out.println(sb.toString());
int pos = sb.lastIndexOf("g");
System.out.println(pos);
}
}
/*
hello world gggtfly
14
*/

另一个路由uploadpic.form,如果属于webmanager,则可以进行文件上传

tools/ClientInfo.class用于存储和获取用户信息:

filters/ClentInfoFilter.class的主要方法:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Cookie[] cookies = ((HttpServletRequest)request).getCookies();
boolean exist = false;
Cookie cookie = null;
if (cookies != null) {
Cookie[] var7 = cookies;
int var8 = cookies.length;

for(int var9 = 0; var9 < var8; ++var9) {
Cookie c = var7[var9];
if (c.getName().equals("cinfo")) {
exist = true;
cookie = c;
break;
}
}
}

byte[] bytes;
if (exist) {
String b64 = cookie.getValue();
Decoder decoder = Base64.getDecoder();
bytes = decoder.decode(b64);
ClientInfo cinfo = null;
if (!b64.equals("") && bytes != null) {
try {
cinfo = (ClientInfo)Tools.parse(bytes);
} catch (Exception var14) {
var14.printStackTrace();
}
} else {
cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());
Encoder encoder = Base64.getEncoder();

try {
bytes = Tools.create(cinfo);
} catch (Exception var15) {
var15.printStackTrace();
}

cookie.setValue(encoder.encodeToString(bytes));
}

((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo);
} else {
Encoder encoder = Base64.getEncoder();

try {
ClientInfo cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());
bytes = Tools.create(cinfo);
cookie = new Cookie("cinfo", encoder.encodeToString(bytes));
cookie.setMaxAge(86400);
((HttpServletResponse)response).addCookie(cookie);
((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo);
} catch (Exception var13) {
var13.printStackTrace();
}
}

chain.doFilter(request, response);
}

它主要作用是判断Cookie name中是否有cinfo,有的话base64解码其值,否则进行初始化:

1
cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());

如果能正常进行base64解码且不为空,则调用Tools类的parse方法:

1
cinfo = (ClientInfo)Tools.parse(bytes);

Tools类主要是用来进行序列化和反序列化的:

那么可以直接伪造cookie:

注意序列化的时候ClientInfo和Tools类的package一定要和题目的一样!

替换cinfo的cookie,刷新,成为admin:

接着可以进行文件读取,读到根目录存在readflag文件,那么要进行命令执行才能获得flag,文件上传权限不足,也没法利用

Tools类存在命令执行点:

使用ProcessBuilder进行命令执行demo:

构造Tools类,创建setTestCall方法用于将testCall变量赋值

替换cookie后刷新即可反弹shell拿到flag