本文主要通过spring介绍自动绑定漏洞,并给出栗子和环境帮助消化。
漏洞war包:https://github.com/3wapp/ZeroNights-HackQuest-2016,后序将会通过该源码进行举例
tomcat下载地址:https://tomcat.apache.org/download-70.cgi
解压tomcat到某个目录,然后修改apache-tomcat-7.0.106\conf目录下的server.xml中的端口,防止端口冲突,如果需要使用idea进行debug,参考我前面的帖子
将github下载出来的东西全部拷贝到webapps目录下
动绑定漏洞的漏洞点在于,攻击者可能将额外的HTTP请求参数绑定到一个对象上,使用这种方法来创建、修改、更新开发人员或者业务本身从未打算设计到的参数,而这些新参数反过来又会影响程序代码中不需要的新变量或对象,进而触发一些业务逻辑漏洞。
举个不严谨、但是好消化的栗子:
客户端发送请求http://aa.com/addUser2?useranme=ligoudan&password=xxx
服务端本来是应该像下面一样接受参数,一个萝卜一个坑:
@RequestMapping("/addUser2")
public String addUser2(HttpServletRequest request) {
String username=request.getParameter("username");
String password=request.getParameter("password");
...
}
结果有个服务端用user类去接受参数,这时候useranme、password的值会自动赋给user对象中的对应属性:
@RequestMapping(value = "/addUser2", method = RequestMethod.POST)
public String resetViewQuestionHandler(@ModelAttribute User user) {
...
}
假设其中User 类中除了useranme、password两个属性之外,还有father这个属性,那么通过发送http://aa.com/addUser2?useranme=ligoudan&password=xxx&father=laowang,ligoudan的father就变成了laowang了。
在spring mvc中,有两个关键注解与该漏洞有关:@ModelAttribute注解和@SessionAttributes注解
该注解主要有两个作用:
1、运用在方法上
会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中
处理器。当@RequestMapping运用于方法上时,可以分为两种情况:a、有返回值;b、没有返回值:
a、没有返回值
@ModelAttribute
public void getUser(@RequestParam(value="userName",required=false) String userName,Model model){
User user = new User(userName,"123456");
model.addAttribute("user", user);
}
@ModelAttribute注解getUser方法,getUser方法接收前台提交的userName数据,在model中放入user属性和数据。
@RequestMapping("/testModelAttribute")
public String testModelAttribute(ModelMap model){
System.out.println("testModelAttribute user:"+model.get("user"));
return "success";
}
此时如果访问/testModelAttribute,会先经过执行getUser函数,创建user对象,即也是model.get(“user”)的值。
b、有返回值
当@ModelAttribute有返回值时,又分为两种情况:
情况b-1:
@ModelAttribute
public User getUser(@RequestParam(value="userName",required=false) String userName){
User user = new User(userName,"123456");
return user;
}
此时,返回的数据会被隐含地放入到model中,在 model 中的 key 为 “返回类型首字母小写”,value 为返回的值。上面相当于model.addAttribute(“user”, user);
情况b-2:
@ModelAttribute("user1")
public User getUser(@RequestParam(value="userName",required=false) String userName,Model model){
User user = new User(userName,"123456");
return user;
}
此时,将key值修改为user1,相当于model.addAttribute(“user1”, user);
2、运用在方法的参数上:
@ModelAttribute("user")
public User getUser(Model model){
User user = new User("Tom","123456");
return user;
}
@RequestMapping({"/home"})
public String homeHandler(@ModelAttribute User user, Model model) {
if (user == null) {
model.addAttribute("error", "You don't have access");
return "error";
} else {
logger.info("Welcome home ! " + user);
return "home";
}
}
如果访问http://aa.com/home?useranme=ligoudan,会将客户端传递过来的参数按名称注入到指定对象中,并将原来User(“Tom”,“123456”)对象,替换成User(“ligoudan”,“123456”),并赋值给homeHandler函数中的user对象,并且会将这个对象自动加入ModelMap中,便于View层使用;
view端通过${user.name}即可访问。注意这时候这个User类一定要有没有参数的构造函数,形如:
public class User {
private String username;
private String password;
private Boolean isSupaAdministrata;
private String email;
private String answer;
public User(String username, String password, Boolean isSupaAdministrata, String email, String answer) {
this.username = username;
this.password = password;
this.isSupaAdministrata = isSupaAdministrata;
this.email = email;
this.answer = answer;
}
public User() {
}
...
}
如果不想被替换呢,可以稍微改动一下homeHandler函数:
@RequestMapping({"/home"})
public String homeHandler(@ModelAttribute("newUser")User user, Model model) {
if (user == null) {
model.addAttribute("error", "You don't have access");
return "error";
} else {
logger.info("Welcome home ! " + user);
return "home";
}
}
这是再访问http://aa.com/home?useranme=ligoudan,model数据中就会有两个值,分别为user=User[userName=Tom,password=123456]和
newUser=User[userName=Jack, password=null]。
在默认情况下,ModelMap 中的属性作用域是 request 级别,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。
Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 @SessionAttributes(“user”) 注解来实现的。SpringMVC 就会自动将 @SessionAttributes 定义的属性注入到 ModelMap 对象,在 setup action 的参数列表时,去 ModelMap 中取到这样的对象,再添加到参数列表。只要不去调用 SessionStatus 的 setComplete() 方法,这个对象就会一直保留在 Session 中,从而实现 Session 信息的共享
@Controller
@SessionAttributes({"user"})
public class ResetPasswordController {
private static final Logger logger = LoggerFactory.getLogger(ResetPasswordController.class);
@Autowired
private UserService userService;
public ResetPasswordController() {
}
@RequestMapping(
value = {"/reset"},
method = {RequestMethod.GET}
)
public String resetViewHandler() {
logger.info("Welcome reset ! ");
return "reset";
}
@RequestMapping(
value = {"/reset"},
method = {RequestMethod.POST}
)
public String resetHandler(@RequestParam String username, Model model) {
logger.info("Checking username " + username);
User user = this.userService.findByName(username);
if (user == null) {
logger.info("there is no user with name " + username);
model.addAttribute("error", "Username is not found");
return "reset";
} else {
model.addAttribute("user", user);
return "redirect:resetQuestion";
}
}
@RequestMapping(
value = {"/resetQuestion"},
method = {RequestMethod.GET}
)
public String resetViewQuestionHandler(@ModelAttribute User user) {
logger.info("Welcome resetQuestion ! " + user);
return "resetQuestion";
}
@RequestMapping(
value = {"/resetQuestion"},
method = {RequestMethod.POST}
)
public String resetQuestionHandler(@RequestParam String answerReset, SessionStatus status, User user, Model model) {
logger.info("Checking resetQuestion ! " + answerReset + " for " + user);
if (!user.getAnswer().equals(answerReset)) {
logger.info("Answer in db " + user.getAnswer() + " Answer " + answerReset);
model.addAttribute("error", "Incorrect answer");
return "resetQuestion";
} else {
status.setComplete();
String newPassword = GeneratePassword.generatePassowrd(10);
user.setPassword(newPassword);
this.userService.updateUser(user);
model.addAttribute("message", "Your new password is " + newPassword);
return "success";
}
}
}
/reset接口就是直接对应的resetHandler()函数。在POST方式的resetHandler()函数中,先判断当前用户名是否存在,若存在则将user添加到model中,再重定向到resetQuestion中作进一步处理;这里从参数获取username并检查有没有这个用户,如果有则把这个user对象放到Model中。因为这个Controller使用了@SessionAttributes(“user”),所以同时也会自动把user对象放到session中。然后跳转到resetQuestion密码找回安全问题校验页面。
/resetQuestion接口就是直接对应的resetQuestionHandler()函数。在GET方式的resetQuestionHandler()函数中,其唯一的user参数使用了·@ModelAttribute·注解修饰,即会将传递过来的user参数按名称注入到指定对象中,而这里实际上是从session中获取user对象;在POST方式的函数中,并没有使用@ModelAttribute注解修饰参数,但是Spring MVC会自动从session中提取user,并且使用相同的逻辑,用http请求参数去自动绑定对应的用户参数,该函数的代码逻辑,先获取user对象的answer属性值来跟我们从外部表单输入的answerReset值进行比较,若相等则往下成功重置用户密码,否则报错;
点击忘记密码,进入/reset接口
点击check进入 /resetQuestion接口
抓包,修改answer参数
看日志,参数已经被修改
然后答案就输入答案了
当然,也可以直接改密码:
密码被改成了5wimming
代码如下
@Controller
@SessionAttributes({"user"})
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
private static String firstSecret = "2TvoixPalca";
@Value("${secret}")
private String secondSecret;
@Value("${show}")
private Boolean showSecret;
@Autowired
public UserService userService;
public HomeController() {
}
@ModelAttribute("secondSecret")
public String getSecretCode() {
logger.debug(this.secondSecret);
return this.secondSecret;
}
@ModelAttribute("showSecret")
public Boolean getShowSectet() {
logger.debug("flag: " + this.showSecret);
return this.showSecret;
}
@RequestMapping(
value = {"/"},
method = {RequestMethod.GET}
)
public String index() {
return "index";
}
@RequestMapping(
value = {"/index"},
method = {RequestMethod.GET}
)
public String index2() {
return "index";
}
@RequestMapping(
value = {"/authentication"},
method = {RequestMethod.POST}
)
public String auth(@RequestParam String name, @RequestParam String pass, RedirectAttributes attributes, Model model) {
User user = this.userService.findByNamePassword(name, pass);
if (user == null) {
logger.debug("there is no user with name " + name);
model.addAttribute("error", "The username or password is incorrect");
return "/index";
} else {
attributes.addFlashAttribute("user", user);
return "redirect:home";
}
}
@RequestMapping(
value = {"/home"},
method = {RequestMethod.GET}
)
public String home(@ModelAttribute User user, Model model) {
if (this.showSecret) {
model.addAttribute("firstSecret", firstSecret);
}
return "home";
}
@RequestMapping({"/logout"})
public String logoutHandler(SessionStatus status) {
status.setComplete();
return "index";
}
}
分析上面代码可以知道,/home接口满足条件
过程类似,不再赘述。
参考:
https://xz.aliyun.com/t/1089
https://blog.csdn.net/abc997995674/article/details/80464023