freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

越权漏洞之测试与修复
FreeBuf_300783 2020-06-09 09:00:47 966593

这几年,开发人员的安全意识与安全开发水平都有了很大程度的提高。像文件上传、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,从而防止重放和遍历订单号这类攻击(篡改订单号)。

*本文作者:freebuf01,转载请注明来自FreeBuf.COM

# 漏洞 # 越权 # 未授权访问
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 FreeBuf_300783 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
吃灰收藏夹
FreeBuf_300783 LV.3
这家伙太懒了,还未填写个人描述!
  • 6 文章数
  • 8 关注者
渗透痕迹分析随笔
2020-02-27
注册模块上线前安全测试checklist
2020-02-20
个人PWN入坑常见方法总结
2019-04-16
文章目录