相关文章推荐
高大的春卷  ·  DetailsList 控件引用 - ...·  2 年前    · 
神勇威武的山羊  ·  mdiapipe pose姿态 ...·  3 年前    · 

0x01 Spring MVC自动绑定漏洞

自动绑定漏洞

自动绑定功能在很多框架中都有实现,主要功能是允许软件框架自动将HTTP请求中的参数绑定到程序变量或对象中以便于开发者访问。

而自动绑定漏洞的漏洞点在于,攻击者可能将额外的HTTP请求参数绑定到一个对象上,使用这种方法来创建、修改、更新开发人员或者业务本身从未打算设计到的参数,而这些新参数反过来又会影响程序代码中不需要的新变量或对象,进而触发一些业务逻辑漏洞。

一般而言,自动绑定漏洞的发现是通过白盒审计的形式才能找到的。

Spring MVC中两个关键注解

在Spring MVC框架中与自动绑定漏洞相关的注解有如下两个。

@ModelAttribute注解

通过@ModelAttribute注解可实现以下两个功能:

1、绑定请求参数到实体对象(表单的命令对象)

@ModelAttribute注解运用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用。

如下代码:

1
2
3
4
5
6
7
8
9
@RequestMapping("/register")
public String register(@ModelAttribute("user") UserForm user) {
if ("zhangsan".equals(uname) && "123456".equals(upass)) {
logger.info("成功");
return "login";
} else {
logger.info("失败");
return "register";
}

在上述代码中 @ModelAttribute("user")UserForm user 语句的功能有两个:

  • 将请求参数的输入封装到user对象中。
  • 创建UserForm实例。
  • 以“user”为键值存储在Model对象中,和 model.addAttribute("user",user) 语句的功能一样。如果没有指定键值,即 @ModelAttribute UserForm user ,那么在创建UserForm实例时以“userForm”为键值存储在Model对象中,和 model.addAtttribute("userForm", user) 语句的功能一样。

    2、注解一个非请求处理方法

    被@ModelAttribute注解的方法将在每次调用该控制器类的请求处理方法前被调用。这种特性可以用来控制登录权限,当然控制登录权限的方法有很多,例如拦截器、过滤器等。

    使用该特性控制登录权限,创建BaseController,代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package controller;
    import javax.servlet.http.HttpSession;
    import org.springframework.web.bind.annotation.ModelAttribute;
    public class BaseController {
    @ModelAttribute
    public void isLogin(HttpSession session) throws Exception {
    if (session.getAttribute("user") == null) {
    throw new Exception("没有权限");
    }
    }
    }

    创建ModelAttributeController,代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    @RequestMapping("/admin")
    public class ModelAttributeController {
    @RequestMapping("/add")
    public String add() {
    return "addSuccess";
    }
    @RequestMapping("/update")
    public String update() {
    return "updateSuccess";
    }
    @RequestMapping("/delete")
    public String delete() {
    return "deleteSuccess";
    }
    }

    在上述ModelAttributeController类中的add、update、delete请求处理方法执行时,首先执行父类BaseController中的isLogin()方法判断登录权限,可以通过地址 http://localhost:8080/springMVCDemo02/admin/add 测试登录权限。

    @SessionAttributes注解

    默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中。

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

    0x02 案例

    这里参考ZeroNigths HackQuest 2016的两道和自动绑定漏洞相关的Web题,源码下载地址: https://github.com/3wapp/ZeroNights-HackQuest-2016

    Justice League

    将war包放置于Tomcat中跑起来后,看到Justice League页面存在4个功能点:关于、注册、登录、找回密码。

    代码审计

    下面先进行代码审计,直接看到注册功能的Controller,ResetPasswordController.java:

    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
    49
    50
    51
    52
    53
    54
    55
    56
    57
    @Controller
    @SessionAttributes("user")
    public class ResetPasswordController {

    private static final Logger logger = LoggerFactory.getLogger(ResetPasswordController.class);

    @Autowired
    private UserService userService;

    @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 = userService.findByName(username);

    if (user == null) {
    logger.info("there is no user with name " + username);
    model.addAttribute("error", "Username is not found");
    return "reset";
    }
    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";
    }

    status.setComplete();
    String newPassword = GeneratePassword.generatePassowrd(10);
    user.setPassword(newPassword);
    userService.updateUser(user);

    model.addAttribute("message", "Your new password is " + newPassword);
    return "success";

    }
    }

    简单理下:

  • ResetPasswordController类是被 @SessionAttributes("user") 注解修饰的,即会自动把user对象放到session中。
  • 这里/reset接口就是直接对应的resetHandler()函数。在POST方式的resetHandler()函数中,先判断当前用户名是否存在,若存在则将user添加到model中,再重定向到resetQuestion中作进一步处理; 可以看到,两个resetHandler()函数都没有直接从参数或者从session中获取user对象,因此resetHandler()函数不存在自动绑定漏洞 。
  • 这里/resetQuestion接口就是直接对应的resetQuestionHandler()函数。在GET方式的resetQuestionHandler()函数中,其唯一的user参数使用了·@ModelAttribute·注解修饰,即会将传递过来的user参数按名称注入到指定对象中,而这里实际上是从session中获取user对象;在POST方式的函数中,并没有使用 @ModelAttribute 注解修饰参数,但是Spring MVC会自动从session中提取user,并且使用相同的逻辑,用http请求参数去自动绑定对应的用户参数,该函数的代码逻辑,先获取user对象的answer属性值来跟我们从外部表单输入的answerReset值进行比较,若相等则往下成功重置用户密码,否则报错; 也就是说,这两个resetQuestionHandler()函数都用到了session中的user对象,都存在自动绑定漏洞 。
  • 由前面分析可知,resetQuestionHandler()函数就是自动绑定漏洞的逻辑漏洞代码所在,我们只需要对这个接口的以GET或POST方式传递User类对象的参数即可修改自动绑定的user对象的属性值,实现自动绑定漏洞的利用。

    攻击利用

    我们先点击忘记密码,在这里输入admin用户名检测是否存在该用户:

    若存在则直接跳转到/resetQuestion界面,这里是个密保找回的表单:

    现在已知是存在admin用户的,下面就对admin用户进行自动绑定漏洞的攻击利用。

    攻击GET方式的/resetQuestion接口

    直接往/resetQuestion接口发送包含user对象属性的参数即可直接篡改user对象的属性值:

    GET /justiceleague/resetQuestion?answer=mi1k7ea

    在Tomcat后台是可以看到日志记录了将admin用户的answer属性篡改了:

    当然,user对象的其他参数也是可以直接通过变量绑定漏洞直接修改的,比如直接修改admin用户的密码:

    攻击POST方式的/resetQuestion接口

    提交POST表单的页面就是前面/resetQuestion的界面,我们知道该表单包含两个参数即question和answerReset。

    我们这里在原报文的基础上,直接添加user对象的属性为参数来篡改user属性值:

    可以看到,虽然我们不知道真正的密保答案即admin用户的answer属性值,因此输入错误的answerReset参数值后就会显示回答错误,但是我们却成功篡改了admin的password,其他属性也是一样的操作即可。

    除了这种利用方式外,POST方式的resetQuestionHandler()函数中还存在逻辑漏洞,即找回密码过程中进行密保问题回答的处理过程存在自动绑定漏洞绕过的风险。现在,我们只需要在原本POST报文的基础上加上answer参数、使其值直接和answerReset参数的一致,由于自动绑定漏洞的存在,将导致后台程序在比较user对象的answer属性值和我们表单提交的answerReset参数值是否相等时直接绕过了,从而执行了后面的代码实现密码重置:

    此时到Tomcat后台查看,admin用户的answer被篡改为test,password被成功重置了和页面返回的一样:

    Edik

    将war包放置于Tomcat中跑起来后,看到Edik主要有主页、注册、登录等几个页面。

    代码审计

    打开源码Controller部分,发现只有HomeController和RegistrationController这两个Controller文件。而其中使用 @ModelAttribute 或 @SessionAttributes 注解的只有HomeController这个文件,也就是说,有且仅有HomeController中会存在自动绑定漏洞。

    我们看到HomeController的源码,这里只看有相关注解的部分即可:

    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
    @Controller
    @SessionAttributes("user")
    public class HomeController {

    ...

    @ModelAttribute("secondSecret")
    public String getSecretCode() {
    logger.debug(secondSecret);
    return secondSecret;
    }

    @ModelAttribute("showSecret")
    public Boolean getShowSectet() {
    logger.debug("flag: " + showSecret);
    return showSecret;
    }

    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home(@ModelAttribute User user, Model model) {
    if (showSecret){
    model.addAttribute("firstSecret", firstSecret);
    }

    return "home";
    }

    ...
    }

    分析可知:

  • HomeController使用 @SessionAttributes 注解修饰user对象,说明user对象会保存到session中;
  • 使用 @ModelAttribute 注解的地方总共有3处,前两处是修饰方法,使方法在每次调用该控制器类的请求处理方法前被调用,主要用于日志记录;第三处是修饰的/home接口对应的home()函数的user参数,该接口是GET方式访问的;
  • 由此可知,HomeController的home()函数处是存在自动绑定漏洞的,因为通过注解和自动绑定机制我们可以直接通过参数的形式给home()函数的user对象传递恶意的属性值,而最后返回”home”会跳转到home界面进行展示。

    攻击利用

    这个站点本身没有更多的业务功能能让我们进行更深的利用。

    常规利用

    先注册个用户,然后登录,看到是跳转到了/home路径,这里页面展示了当前用户名和体重信息:

    这里直接对/home接口以GET方式传递user对象的属性值,比如直接篡改用户名:

    User类对象的另外两个属性password和weight也是同样能被修改的。

    自动绑定漏洞+XSS组合拳

    这里很鸡肋:

    自动绑定漏洞+CSRF组合拳

    此外,自动绑定漏洞能和CSRF组合利用。

    比如将修改password的自动绑定漏洞的链接和CSRF组合,通过诱使受害者访问即可成功修改受害者的密码,恶意csrf.html如下:

    1
    2
    3
    4
    5
    <a href='http://192.168.10.1:8080/edik/home?password=666'>Click Me</a>
    <script>
    var m = document.getElementsByTagName('a')[0];
    m.click();
    </script>

    0x03 防御方法

    Spring MVC中可以使用@InitBinder注解,通过WebDataBinder的方法setAllowedFields、setDisallowedFields设置允许或不允许绑定的参数。

    0x04 参考

    浅析自动绑定漏洞

    Spring MVC Autobinding漏洞实例初窥

    【技术分享】自动绑定漏洞和Spring MVC

    Spring MVC 自动绑定漏洞

    Spring MVC中@ModelAttribute注解的使用