Skip to main content
RunBook Academy

← All labs in Linux

Lab · intermediate · ~30 min

Lab: Audit how ping is privileged, and replace setuid with capabilities

B · Nested virtualisationC · Simulation

Objectives

  • Record how ping is privileged on this host before changing anything
  • Distinguish the three ICMP privilege mechanisms: setuid, file capability, ping_group_range
  • Grant the minimum capability to a lab binary and prove it is sufficient
  • Audit setuid and capability binaries across the filesystem
  • Restore the vendor state exactly, without adding a setuid bit

Prerequisites

This lab teaches the setuid-to-capability conversion without damaging the host you run it on. You will first measure how ping is privileged today, then perform the conversion on a copy of the binary, then restore the vendor state exactly.

Objective

By the end of this lab, you can:

  • Record the exact privilege state of a binary before you touch it.
  • Name the three mechanisms that let an unprivileged user send ICMP.
  • Grant the single capability a program needs, and justify refusing the rest.
  • Inventory every setuid and every capability-bearing binary on a host.
  • Restore a binary to its packaged state and prove it with the package manager.

Architecture

One Linux host. No network topology is required; every step is local.

Requirements

  • A Linux host you can take a root shell on, and which you are allowed to modify. Do not run this on a production host.
  • libcap2-bin (Debian/Ubuntu) or libcap (RHEL/SUSE) for getcap and setcap.
  • strace for Task 2. If it is unavailable, read Task 2 and skip the command.
  • A filesystem mounted without nosuid and without nodev, and one that supports extended attributes. setcap stores capabilities in the security.capability xattr; it fails on tmpfs-with-nosuid, NFS, and most overlay upper layers.

Scenario

You have been asked to remove a setuid-root binary from a fleet image and replace it with a capability. Before you write the change, you need to prove three things: what the binary’s privilege actually is today, what the minimum capability is, and that you can put the host back exactly as you found it.

Tasks

Task 1: Record the before-state

Everything you do later is judged against this output. Save it.

mkdir -p ~/cap-lab
{
  echo "=== ls -l ==="
  ls -l "$(command -v ping)"
  echo "=== getcap ==="
  getcap "$(command -v ping)"
  echo "=== ping_group_range ==="
  sysctl net.ipv4.ping_group_range
  echo "=== package ==="
  (dpkg -S "$(command -v ping)" 2>/dev/null || rpm -qf "$(command -v ping)" 2>/dev/null)
} | tee ~/cap-lab/before.txt
Read-only / Safethe real modern state
$ ls -l /usr/bin/ping; getcap /usr/bin/ping; sysctl net.ipv4.ping_group_range
-rwxr-xr-x 1 root root 159552 Oct 31  2025 /usr/bin/ping
/usr/bin/ping cap_net_raw=ep
net.ipv4.ping_group_range = 0	2147483647

Illustrative output

Note what is not here. ping6 is a symlink to ping on Debian and Ubuntu, so chmod and setcap against it act on ping itself - a classic way to change a file you did not think you were touching. traceroute6 is usually not installed at all. Confirm with ls -l before you plan any change against a name.

Task 2: Prove which mechanism is actually in use

A capability that is present is not the same as a capability that is used. Watch the socket call:

strace -f -e trace=socket ping -c 1 127.0.0.1 2>&1 | grep socket

Read the result like this:

  • socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) - the unprivileged ICMP socket. ping_group_range permitted it. No file privilege was used, and the capability could be removed without breaking anything for this user.
  • socket(AF_INET, SOCK_RAW, IPPROTO_ICMP) - a raw socket. That requires CAP_NET_RAW, so the file capability (or setuid) did the work.

Modern iputils tries SOCK_DGRAM first and falls back to SOCK_RAW. This is why “ping works” is not evidence about which privilege mechanism is in play.

Task 3: Build a lab binary with no privilege at all

Do not experiment on /usr/bin/ping. Copy it. cp does not carry the security.capability xattr unless you ask it to, so the copy starts with nothing:

sudo install -m 0755 -o root -g root \
    "$(command -v ping)" /usr/local/bin/ping-lab

getcap /usr/local/bin/ping-lab   # expect: no output
ls -l /usr/local/bin/ping-lab    # expect: -rwxr-xr-x, no 's'

Now force the raw-socket path so the missing capability is visible. -M do sets the “do not fragment” bit, which iputils can only do on a raw socket:

sudo -u nobody /usr/local/bin/ping-lab -c 1 -M do 127.0.0.1

Expect a failure such as ping: socket: Operation not permitted or Lacking privilege for raw socket. If it succeeds, your kernel served the request from the datagram path; set net.ipv4.ping_group_range to 1 0 on a lab host only to close that path, and repeat.

Task 4: Grant the minimum capability

ping needs one capability: CAP_NET_RAW, to open a raw socket. That is all.

sudo setcap cap_net_raw+ep /usr/local/bin/ping-lab

getcap /usr/local/bin/ping-lab
# /usr/local/bin/ping-lab cap_net_raw=ep

sudo -u nobody /usr/local/bin/ping-lab -c 1 -M do 127.0.0.1
# now succeeds

The +ep suffix matters. e (effective) means the kernel raises the capability in the effective set at exec, so the program does not need to be capability-aware. p (permitted) means the capability is available to the process at all. cap_net_raw+p alone would require the binary to call capset() itself.

Task 5: Understand the contrast with setuid, without creating one

You will not create a setuid-root binary in this lab. Creating one is the risk the whole exercise exists to remove. Reason about it instead:

setuid rootcap_net_raw+ep
Effective UID during execution0unchanged
Can read /etc/shadowyesno
Can write anywhere on the filesystemyesno
Can open a raw socketyesyes
Blast radius of a memory-safety bugfull rootone capability

Both let ping do its job. Only one of them also lets a compromised ping add a root SSH key.

Task 6: Inventory the host

# Every setuid binary on the root filesystem
find / -xdev -type f -perm -u+s 2>/dev/null

# Every capability-bearing file - one process, not one per file
getcap -r / 2>/dev/null

Use getcap -r /, not find / -exec getcap {} \;. The find form forks a getcap process for every file on the filesystem - hundreds of thousands of execs, minutes of wall time, and noticeable load. getcap -r walks the tree once inside a single process.

Compare the output with ~/cap-lab/before.txt. The only new line should be /usr/local/bin/ping-lab.

Task 7: Document the change

CAPABILITY CONVERSION - LAB RECORD
==================================
Host: LAB-HOST
Date: 2026-08-11

Before (recorded, not assumed):
- /usr/bin/ping: mode 0755, no setuid, cap_net_raw=ep
- net.ipv4.ping_group_range: 0 2147483647
- Package: iputils-ping

Change (on the lab copy only):
- /usr/local/bin/ping-lab: mode 0755, cap_net_raw=ep
- cap_net_admin considered and REJECTED: not required; grants
  interface, routing and firewall control.

Verification:
- ping-lab -M do works for an unprivileged user
- No new setuid binary on the host
- getcap -r / differs from the before-state by one line

Restore:
- /usr/local/bin/ping-lab removed
- /usr/bin/ping matches the packaged state (dpkg --verify clean)

Validation

  1. getcap /usr/local/bin/ping-lab prints cap_net_raw=ep and nothing else.
  2. sudo -u nobody /usr/local/bin/ping-lab -c 1 -M do 127.0.0.1 succeeds.
  3. find / -xdev -type f -perm -u+s lists no ping variant - not before the lab, and not after it.
  4. getcap -r / 2>/dev/null differs from the Task 1 record by exactly one line.
  5. The ls -l and getcap output for /usr/bin/ping is byte-identical to ~/cap-lab/before.txt.

Expected Outcome

A host that is in exactly its pre-lab security state, plus one extra file (/usr/local/bin/ping-lab) that demonstrates the minimum-capability pattern, plus a written before-state you could hand to a change reviewer.

Troubleshooting

SymptomCauseFix
setcap: Operation not supportedFilesystem has no xattr support, or is mounted nosuid/nodevPut the lab binary on a normal ext4/xfs mount; check findmnt -no OPTIONS /usr/local
ping-lab works even with no capabilityThe kernel served SOCK_DGRAM via ping_group_rangeUse -M do to force the raw path, as in Task 3
getcap prints nothing for a file you just setYou ran setcap on a symlink, or a later cp/install replaced the file and dropped the xattrls -l the path; re-run setcap on the real file
sudo -u nobody fails with a shell or $HOME errornobody has /usr/sbin/nologinUse an ordinary unprivileged account instead
getcap -r / is slow or noisyIt is crossing into /proc, /sys or network mountsKeep the 2>/dev/null, or narrow the walk to getcap -r /usr /bin /sbin

Cleanup

Restore the state you recorded in Task 1. Restoring means returning to the measured before-state - not to a remembered one.

# Remove the lab binary
sudo rm -f /usr/local/bin/ping-lab

# Confirm the vendor binary is untouched
ls -l /usr/bin/ping     # expect -rwxr-xr-x, no 's'
getcap /usr/bin/ping    # expect /usr/bin/ping cap_net_raw=ep

# Compare against the record you took in Task 1
grep -E 'ping$|cap_net_raw' ~/cap-lab/before.txt

# Only if the capability was lost at some point, restore it explicitly
sudo setcap cap_net_raw=ep /usr/bin/ping
sudo chmod 0755 /usr/bin/ping

# Authoritative check against the package
sudo dpkg --verify iputils-ping    # Debian/Ubuntu: no output = clean
# rpm -V iputils                   # RHEL/Fedora

What You Learned

  • Record before you change. The distribution’s actual state is data, not folklore. Task 1 is the deliverable that makes the cleanup possible.
  • Three mechanisms grant ICMP: setuid, cap_net_raw+ep, and net.ipv4.ping_group_range. “ping works” tells you nothing about which one is active - strace does.
  • Grant one capability, then try to remove it. ping needs CAP_NET_RAW and nothing more; CAP_NET_ADMIN would have been a silent privilege escalation shipped as hardening.
  • Practise on a copy. /usr/local/bin/ping-lab gives you the full lesson with none of the blast radius.
  • Audit cheaply. getcap -r / is one process; the find -exec form is one process per file.

Deliverables

  • · A recorded before-state for /usr/bin/ping
  • · A lab binary that works for unprivileged users with cap_net_raw only
  • · A host-wide setuid and capability inventory
  • · Documentation of the change and the restore

Verification status

Last reviewed
2026-08-11
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.