1、问题分析
API 成批分配利用可能导致特权升级、数据篡改、绕过安全机制。
对于POST请求,如果请求时body里存在后台不需要的字段,但是响应成功
2、问题重现
正常传参是 {"page":1,"pageSize":10,"userPhone":""}
,现在给一个多几个属性的参数也能响应成功,这是不对的:{"page":1,"pageSize":10,"userPhone":"","isadmin":true,"issso":true,"role":"admin"}
,这说明测试结果存在漏洞,因为测试响应成功(返回 200 OK),这表明多传参了但应用程序/API 访问成功。
测试多传参数属性的请求和响应:
POST /ngbsp-api/user-act/admin/discuss/black/list HTTP/1.1
Host: 10.110.120.101:89
Accept: application/json, text/plain, */*
X-CSRF-TOKEN: a6a5d13a-74e5-4507-bbc9-4fc0ccf94d7a
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36Content-Type: application/json;
Origin: http://10.110.120.101:89
Referer: http://10.110.120.101:89/ngbsp/
Accept-Language: en-US
Connection: keep-alive
Content-Length: 82
Cookie: LCUID=LzWATXrrydZKuIW4n18FFtf1mAoEUwqHy2AO0zGqMk9wz7l3tiOqZXpz/seb3o4IOyVQA4jS93S2k5OpJyRS8W62Y5Rxx+fTURpA+eQzD5g=;NGBSPSID=OGEwMjlmOTAtMDEzZi00NGY2LTk0OGQtOTM3YTg1NjYyYThj; LCSSID=OGEwMjlmOTAtMDEzZi00NGY2LTk0OGQtOTM3YTg1NjYyYThj{
"page": 1,
"pageSize": 10,
"userPhone": "",
"isadmin": true,
"issso": true,
"role": "admin"
}
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 24 Jul 2024 09:15:53 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1 ; mode=block
{
"code": "P00000",
"msg": "",
"data": {
"total": 4,
"records": [
{
"userPhone": "17863200022",
"nickName": "178****0022",
"userName": null,
"joinTime": "2021-06-16 14:14:53"
} ,
{
"userPhone": "13710000001",
"nickName": "137****0001",
"userName": null,
"joinTime": "2021-06-12 14:55:27"
} ,
{
"userPhone": "15564156415",
"nickName": "155****6415",
"userName": null,
"joinTime": "2021-06-10 14:22:19"
} ,
{
"userPhone": "13620000001",
"nickName": "136****0001",
"userName": null,
"joinTime": "2021-06-08 10:23:21"
}
] ,
"current": 1,
"size": 4,
"pages": 1,
"listSize": 0
}
}
2、修复建议
- (1)body使用VO或者DTO接收
- (2)对body进行校验,增加反序列化配置方案
方案一,通过yml配置
spring:
jackson:
deserialization:
fail-on-unknown-properties: true
方案二,自定义配置类
@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
converters.add(0, mappingJackson2HttpMessageConverter());
}
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
- (3)统一异常捕获或者返回处增加非200状态码
@ExceptionHandler(value = HttpMessageNotReadableException.class)
public ResponseEntity<ResponseDTO> methodHttpMessageNotReadableExceptionHandler(
HttpServletRequest request, HttpMessageNotReadableException e) {
logger.error("请求body中存在实体类中不存在的参数");
logger.error("请求地址:{}", request.getServletPath());
ResponseDTO responseDTO = ResponseDTO.error(ErrorCodeEnum.S_REQ_PARAM_ERROR, "");
return new ResponseEntity<>(responseDTO, HttpStatus.INTERNAL_SERVER_ERROR);
}
4、具体操作方法
1、建DTO,前端传什么参数,DTO里的属性就是什么,可以将页面的属性提取出来继承。
public class AdminDiscussBlackDTO {
private String userPhone;
private Integer page;
private Integer limit;
// get、set方法
}
2、接口参数接收修改
之前的出现API成批分配的java代码接口:
之前是用@RequestBody Map param
接收的参数,现在是@RequestBody AdminDiscussBlackDTO discussBlackDTO
接收参数。
@PostMapping("/list")
public ResponseDTO list(@Valid @RequestBody AdminDiscussBlackDTO discussBlackDTO) {
Map<String, Object> param = BeanUtil.beanToMap(discussBlackDTO);
PageBean<DiscussBlackEntity> pageBean = discussBlackService.list(param);
return WebUtils.createSuccessResponse(pageBean);
}
3、在application.yml里配置反序列化
4、异常处理
/**
* 异常处理器
*/
@RestControllerAdvice
public class RRExceptionHandler {
/**
* 捕获反序列化异常HttpMessageNotReadableException,增加500状态码返回
*
* @param request 请求
* @param e 异常对象
* @return 响应
*/
@ExceptionHandler(value = HttpMessageNotReadableException.class)
public ResponseEntity<ResponseDTO> methodHttpMessageNotReadableExceptionHandler(
HttpServletRequest request, HttpMessageNotReadableException e) {
logger.error("请求body中存在实体类中不存在的参数");
logger.error("请求地址:{}", request.getServletPath());
logger.error("用户id:{}", MsySecurityContextHolder.getUser().getUserId());
ResponseDTO responseDTO = WebUtils.createFailureResponse(ErrorCodeEnum.S_REQ_PARAM_ERROR, "参数不合法");
return new ResponseEntity<>(responseDTO, HttpStatus.INTERNAL_SERVER_ERROR);
// 判断是否是整数溢出问题,其他的问题的异常处理
if (e.getRootCause() instanceof InputCoercionException || e.getRootCause() instanceof InvalidFormatException) {
logger.error("类型转换错误");
logger.error("请求地址:{}", request.getServletPath());
ResponseDTO responseDTO = WebUtils.createFailureResponse(ErrorCodeEnum.S_REQ_PARAM_ERROR, "非法的请求参数");
return new ResponseEntity<>(responseDTO, HttpStatus.OK);
}
}
}
5、修改完的测试
1、多传个其他的请求参数,报错。
2、参数名不对,也报错
3、少传个参数没事,比如说userPhone不传,不报错。