最近系統的安全滲透測試中,檢測出存在垂直越權和水平越權的漏洞。確實項目的權限管理,只是限制到菜單和按鈕粒度,沒有細到業務代碼的每個接口上。在此記錄一下自己的修復思路。
一、什麼是垂直/水平越權?
水平越權
何爲水平越權呢?就是相同權限用戶之間在未經授權的情況下,可以訪問到一方的資源。比如說同是一個網站的普通用戶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、永遠不要相信來自用戶的輸入,對於可控參數進行嚴格的檢查與過濾