善恶娱乐网 – 越权漏洞之测试与修复

林哲

发表文章数:1021

专业SEO优化

  • 正规SEO优化手法
  • 承诺流量+权重提升
  • 强大的团队解决问题
  • 全心全意的服务
  • 立即咨询

    热门标签

    , ,
    首页 » WEB安全 » 善恶娱乐网 – 越权漏洞之测试与修复

    文章目录

    • 一、越权漏洞定义及分类
    • 二、测试方法
      • 2.1、未授权访问
      • 2.2、水平越权
      • 2.3、垂直越权
    • 三、修复方案
    • 四、最后

    这几年,开发人员的安全意识与安全开发水平都有了很大程度的提高。像文件上传、SQL注入漏洞,在网络安全比较受重视的企业,差不多都已经杜绝了,即便有出现,也是凤毛菱角了。目前,部分企业网络安全目前面临的另一大挑战就是越权漏洞,特别是大型系统,功能模块接口特别多,如果未对权限进行全局校验,则难免会有所遗漏,最终出现越权漏洞。

    一、越权漏洞定义及分类

    越权,从字面意思不难理解,主要有以下几种可能:

    1、未授权访问:本来没有账号(即没有某个功能权限),但是通过越权操作,获取了某个功能权限;

    2、水平越权:本来有个账号(即只能操作自己的数据,比如增删改查),但是通过越权操作,能操作其他同等权限账号的数据。

    3、垂直越权:本来有个账号只有小权限,但是通过越权操作,获取了大权限。

    二、测试方法

    2.1、未授权访问

    最简单直接的测试方法是:不登录用户账号,直接访问要测试的功能模块(正常情况是需要登录才能访问),如果能正常访问,则说明存在漏洞。

    修改返回包的测试方法:日常测试时,有些开发人员在前端校验返回包中某个参数的值,在返回包中有类似status=false的值,可将改为status=true(也有碰到过code=-1改为code=0或者1的情况);遇到过最奇葩的一次是:不登录账号直接访问那个系统的后台首页index.html时,返回包主体中通过js脚本,通过script和location跳转到登录页面login.html,通过burpsuite拦截响应表,再将那段用于页面跳转的js脚本删除,就能进入后台。(虽然这种修改返回包的方法最后也没有登录账号,但是进入后台后,能访问一些内部公共的敏感功能模块和信息)。

    部分系统,用户信息并不是从sessionid中获取登录账号;而是在cookie中添加一个类似userid的键值对,再从中识别用户。从而可以尝试通过cookie伪造的方法进行测试。

    2.2、水平越权

    水平越权常见于业务系统中,对用户信息或者订单信息进行增删改查操作时,由于用户编号或者订单编号有规律可循(有序递增,订单编号常发现以日期开头后面再接几位有序增长的数字,类似20200520xxxx1,20200520xxxx2),测试人员通过burpsuite的 intruder对目标参数进行遍历测试即可

    善恶娱乐网 - 越权漏洞之测试与修复
     

    2.3、垂直越权

    日常安全测试时,大多发现是未授权访问和水平越权。只碰到过一次将cookie中的isadmin=0改为isadmin=1,获取到管理员权限。苦于黔驴技穷,有幸某天灵光一现,想到测试任意文件上传和下载漏洞时,都能借助../和./进行绕过,垂直越权时,何不也尝试借助../和./进行绕过呢,最终果然发现新大陆。最后经过反复测试发现以下几种垂直越权测试方法。

    http://www.xxxx.com/admin//admin.do

    http://www.xxxx.com/admin/admin.do%23

    http://www.xxxx.com/js/../admin/admin.do

    http://www.xxxx.com/admin/./admin.do

    善恶娱乐网 - 越权漏洞之测试与修复
    善恶娱乐网 - 越权漏洞之测试与修复
     

    三、修复方案

    建议做一个过滤器,对权限进行全局校验(每次调用某个接口时,可先对权限进行校验)。大体流程是:第一步清洗URL地址,并提取Api接口名称;第二步从session中提取当前登录用户的userid;第三步提取当前用户的角色id;第四步判断当前用户对应的角色是否有权限访问当前Api接口(检查垂直越权);最后判断当前登录用户是否对目标对象有操作权限(检查水平越权)。

    首先是获取URL地址中的Api接口名称,这里对URL地址进行了一次URL解码,还是存在绕过的风险,因为当用户%2523时,就可能绕过。

    public static String GetApiName(String Url) throws Exception {
        String DecodeUrl =  URLDecoder.decode(Url,"UTF-8");URL url = new URL(DecodeUrl);String ApiUrl = url.getPath();    if(ApiUrl !=null){
            String[] ApiPath = ApiUrl.split("/");String ApiName = ApiPath[ApiPath.length-1];        return ApiName;}
        return null;}

    获取userid时,建议从session中获取,而不是在cookie中再新建一个userid字段,用于标识用户身份。

    HttpSession session = ServletActionContext.getRequest().getSession();String userId = session.getAttribute("userId");*/

    从session中提取userid后,就要查询与之对应的角色id

    //UserId为用户id,RoleId为角色ID,通过UserId获取roleidpublic static int GetRoleId(int UserId) {
        Connection conn = Connect();PreparedStatement st = null;ResultSet rs = null;    int RoleId = 0;    try {
            // 查询接口访问的角色id,roleid为权限表里的角色ID字段,apiname为权限表里的API接口名称IDString sql = "select roleid from usertable where userid = ?";st = conn.prepareStatement(sql);// 这里使用PreparedStatementst.setInt(1, UserId);rs = st.executeQuery();        if(rs.next()){
                RoleId = rs.getInt("roleid");            return RoleId;}
        } catch (Exception e) {
            e.printStackTrace();        throw new RuntimeException("查询失败!");}
        return RoleId;}

    校验垂直越权时,判断当前用户是否对指定的接口有访问权限,U_RoleId为用户名对应的角色id,A_RoleId为Api接口对应的角色id,Api_Name为用户尝试访问的API接口名称(这里在系统架构评审,安全设计阶段,就要检查数据库的权限表设置时,Api接口是否有指定对应的角色id)

    public static boolean CheckUpPrivilege(int UserId, String Api_Name) {
        Connection conn = Connect();PreparedStatement st = null;ResultSet rs = null;    int U_RoleId = GetRoleId(UserId);    int A_RoleId = 0;    try {
            String sql = "select roleid from user_role where apiname = ?";st = conn.prepareStatement(sql);// 这里使用PreparedStatementst.setString(1, Api_Name);// 执行sql命令roleid和Role_Id,判断用户是否有权限访问对应的接口地址rs = st.executeQuery();        if(rs.next()) {
                A_RoleId = rs.getInt("roleid");// 通过比较,当用户角色id大于等于接口指定的角色id是,可以访问,部分特定接口只有指定的角色才能访问,可直接限定if (U_RoleId >= A_RoleId) {
                    return true;} else {
                    return false;}
            }
        } catch (Exception e) {
            e.printStackTrace();            throw new RuntimeException("查询失败!");}
        return false;}

    最后再判断一下是否是水平越权,S_UserId为当前登录用户的userid,P_UserId为目标对象对应的userid,比如对订单信息进行操作时,可以先通过订单号提取与之对应的userid,再进行判断(当然,订单表,在系统架构评审,安全设计阶段,就要检查订单号是否有指定对应的用户id)。

    public static boolean CheckLevelPrivilege(int S_UserId, int P_UserId) {
        if(S_UserId == P_UserId){
            return true;}
        else{
            return false;}
    }

    四、最后

    类似订单号这种参数,生成时要无规律可循,可以通过hash算法进行加密;或者请求的数据包额外附带一个参数,比如token,从而防止重放和遍历订单号这类攻击(篡改订单号)。

    *本文作者:ALA林哲01,转载请注明来自ALA林哲

    分享到:
    赞(0)

    评论 抢沙发

    7 + 9 =


    Vieu4.5主题
    专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
    切换注册

    登录

    忘记密码 ?

    切换登录

    注册