自动绑定漏洞

介绍

本文主要通过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注解

@ModelAttribute注解

该注解主要有两个作用:

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

@SessionAttributes注解

在默认情况下,ModelMap 中的属性作用域是 request 级别,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。

Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 @SessionAttributes(“user”) 注解来实现的。SpringMVC 就会自动将 @SessionAttributes 定义的属性注入到 ModelMap 对象,在 setup action 的参数列表时,去 ModelMap 中取到这样的对象,再添加到参数列表。只要不去调用 SessionStatus 的 setComplete() 方法,这个对象就会一直保留在 Session 中,从而实现 Session 信息的共享

举个栗子

justiceleague

@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
在这里插入图片描述

edik

代码如下

@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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值