系統垂直越權與水平越權漏洞修復記錄

最近系統的安全滲透測試中,檢測出存在垂直越權和水平越權的漏洞。確實項目的權限管理,只是限制到菜單和按鈕粒度,沒有細到業務代碼的每個接口上。在此記錄一下自己的修復思路。

一、什麼是垂直/水平越權?

水平越權

何爲水平越權呢?就是相同權限用戶之間在未經授權的情況下,可以訪問到一方的資源。比如說同是一個網站的普通用戶A和B,A通過越權操作訪問了B的信息。

例如用戶A與用戶B屬於同一角色,擁有同樣菜單權限,但他們擁有私人的客戶數據(A私有數據,B私有數據),假如B用戶獲取到了某條A私人數據信息(例如數據id),通過調用同樣的查詢接口,成功查詢了到該私人數據的詳細信息,那麼這種行爲就叫做水平越權訪問。


垂直越權

垂直越權呢,就是低權限用戶實現了高權限用戶的功能。比如普通用戶通過越權登錄到了管理員頁面,實現管理員才能的操作。

例如管理員A可查看編輯系統所有人員信息,普通用戶B只能查看編輯自己部門的人。但是用戶B通過抓包獲取了其他部門用戶C的id,調用修改接口,成功把18歲的C改成了38歲。那麼這種行爲就叫做垂直越權訪問。

二、如何防範?

由於,我接手這個漏洞時,修復時間很緊迫,所以就用了最簡單粗暴的方式。

思路

利用每個模塊的查詢功能,默認所見即爲你可以操作。在每個接口執行前,默認查詢該登陸用戶的查詢所有結果,再將執行的業務數據id對比查詢結果,如果在其中則正常操作,如果不在其中則拋出越權結果。

實現

1、自定義註解,將註解放在需要鑑權的接口上,並寫上接口所屬模塊,方法

2、利用springboot的AOP面向切面編程,按註解切,在每個接口執行前做查詢結果校驗處理

優化:

1、利用註解上的模塊名與方法名,用策略模式,統一編寫不用模塊,方法的參數處理。

2、查詢性能上的優化

附上自己的統一參數處理代碼編寫:

/**
 * @Author: dyf
 * @Date: 2020/5/8 21:09
 * @Description:
 */
public enum ModuleParam {
    SYS_ORG("sysOrg"),
    RIVER_USER("riverUsers"),
    RIVER("riverInfo"),
    RIVER_STAGE("riverStageInfo"),
    LAKE("lakeInfo"),
    LAKE_SLICES("lakeSlicesInfo"),
    RESERVIOR("reservoirInfo"),
    RESERVIOR_SLICES("reservoirSlicesInfo"),
    BOARD("board"),
    POLICY("policy"),
    ARCHIVES("archives"),
    CAMERA("camera"),
    NOTICE("notice"),
    COMPANY("company");

    String name;
    IGetParam getParam;

    ModuleParam(String name) {
        this.name = name;
    }

    public ModuleParam initMethod(String methodType){
        switch(methodType){
            case "update":
                getParam = new GetUpdateId();break;
            case "query":
                getParam = new GetQueryId();break;
            case "del":
                getParam = new GetDelId();break;

            //按需自定義,初始化...


        }
        return this;
    }

    public String getId(Object object){
        return (String) getParam.getId(name, object);
    }

    public List<String> getIds(Object object){
        return (List<String>)getParam.getId(name, object);
    }


}
public interface IGetParam {
    Object getId(String methodType, Object object);
}

public class GetDelId implements IGetParam {
    @Override
    public Object getId(String moduleName, Object object) {
        switch (moduleName){
            case "sysOrg":
            case "riverUsers": return ((BaseEntity)object).getId();

            case "riverInfo":
            case "reservoirInfo":
            case "lakeInfo": ((DeleteParamDTO)object).getId();

            case "riverStageInfo":
            case "reservoirSlicesInfo":
            case "lakeSlicesInfo": return ((ArrayList<String>)object);

            case "board":
            case "policy":
            case "archives":
            case "camera":
            case "notice": return ((List<String>)object);

            case "company": return (String)object;
        }
        return null;
    }
}

public class GetQueryId implements IGetParam {
    @Override
    public String getId(String moduleName, Object object) {
        switch (moduleName) {
            case "sysOrg":
            case "riverUsers":
            case "riverInfo":
            case "riverStageInfo":
            case "reservoirInfo":
            case "reservoirSlicesInfo":
            case "lakeInfo":
            case "lakeSlicesInfo":
            case "board":
            case "policy":
            case "archives":
            case "company":
            case "camera":
            case "notice":return (String)object;
        }
        return "";
    }
}

public class GetUpdateId implements IGetParam {
    @Override
    public String getId(String moduleName, Object object) {
        switch (moduleName) {
            case "sysOrg":return ((SysOrg) object).getId();
            case "riverUsers":return ((RegisterUser) object).getId();
            case "riverInfo":return ((River) object).getId();
            case "riverStageInfo":return ((RiverStageAddDTO) object).getId();
            case "reservoirInfo":return ((Reservoir) object).getId();
            case "reservoirSlicesInfo":return ((ReservoirSlicesAddDTO) object).getId();
            case "lakeInfo":return ((Lake) object).getId();
            case "lakeSlicesInfo":return ((LakeSlicesAddDTO) object).getId();
            case "board":return ((BulletinBoardAddDTO) object).getId();
            case "policy":return ((PolicyInfoResultDTO) object).getId();
            case "archives":return ((ArchivesInfoResultDTO) object).getId();
            case "company":return ((RCompanyInfo) object).getId();
            case "camera":return ((RCamera) object).getId();
            case "notice":return ((Notice) object).getId();
        }
        return "";
    }
}

總結:

我的解決方法,比較粗暴,雖然是暫時解決了問題,但是每一個操作都需要查詢所有,帶來的性能問題是一個隱患,但考慮到系統本身也只是一個業務系統,沒有多少併發,所以可以用此方式苟延殘喘下去。

附上網上查詢的一些防範越權漏洞的方法:
1、調用功能前驗證用戶是否有權限調用相關功能(也就是我這種方式)
2、執行關鍵操作前必須驗證用戶身份,驗證用戶是否具備操作數據的權限
3、控制參數,加密資源ID,防止攻擊者枚舉ID,敏感數據特殊化處理。但參數加密僅僅只能防止的是遍歷,並不能真正解決越權,還只是緩解的方式;
4、永遠不要相信來自用戶的輸入,對於可控參數進行嚴格的檢查與過濾

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章