By Manny Fernandez

November 5, 2026

APT CVE-2026-109518: How a Download Retry Lets an Attacker Swap a Verified Package for Root

APT CVE-2026-109518: How a Download Retry Lets an Attacker Swap a Verified Package for Root

No fluff. Just the config that works.

Executive Summary

APT CVE-2026-109518 is a verification bypass in APT, the package manager behind apt, apt-get, aptitude and the graphical software tools on Debian, Ubuntu and their derivatives. An attacker who can tamper with plain HTTP traffic between a host and its package mirror can get APT to install a package that never passed signature verification. Package scripts run as root, so the end state is root code execution.

Fixes shipped on November 4, 2026 from the APT developers, Debian (DSA-6607-1, DLA-4880-1) and Canonical (USN-8996-1). Canonical scores it CVSS 4.0 7.7 (High); the APT maintainers call it critical. As of November 5, 2026 there is no reported exploitation, no public proof of concept, and it is not in the CISA KEV catalog.

Objective: find every exposed Debian and Ubuntu host, patch APT without opening the very window you are trying to close, move mirrors to HTTPS, and lock down where your Linux servers are allowed to pull packages from.

Target audience: Linux admins, DevOps and platform teams running CI runners or Docker builds, and network engineers who own the egress path to package mirrors.

Prerequisites and Architecture

Assumed knowledge: basic APT usage, sudo, reading /etc/apt/sources.list, and (for the network section) FortiGate firewall policy CLI.

Lab requirements: any Debian 12/13 or Ubuntu 22.04/24.04/26.04 host. The optional network section uses a FortiGate with the Linux server LAN on 10.0.20.0/24 and the WAN on the 198.18.0.0/15 transit range.

Component Role in this flaw
APT HTTP method Downloads .deb files over plain HTTP, resumes partial downloads, follows redirects
Release / InRelease file Signed index that carries the expected hash of every package
Acquire::Retries How many times APT retries a failed download (default 3 since APT 2.3.2)
dpkg Installs the package as root and runs its preinst and postinst scripts
Mirror, proxy or cache Anything on the HTTP path that could answer APT’s requests

Why plain HTTP was supposed to be fine

APT does not trust the connection, it trusts signatures. The repository signs its Release file, the Release file lists the hash of every package, and APT refuses any download whose hash does not match. That design is why most Debian and Ubuntu mirrors still serve packages over port 80. CVE-2026-109518 does not break the signature or the hash check. Both still run and both still pass. The problem is that the file dpkg installs is no longer the file APT checked.

How the swap works

Figure 1: APT CVE-2026-109518 attack flow: redirect merge, forced retry, swap after verify, dpkg runs payload as root

APT CVE-2026-109518 attack flow: redirect merge, forced retry, swap after verify, dpkg runs payload as root

The Debian and Ubuntu advisories describe three moves, and researcher Max Justicz (who also found the 2019 APT redirect bug, CVE-2019-3462) chained them together:

  1. Merge two downloads. The attacker answers two different package requests with HTTP redirects that land on the same URI. APT treats them as one download and links them on disk with a hard link.
  2. Force a retry. The attacker replies with ordinary temporary server errors. APT, configured to retry, resumes the transfer instead of starting over.
  3. Swap after the check. APT mishandles the hard link during the resume. The file passes its hash check, then the resumed transfer overwrites it with the attacker’s payload. dpkg installs the payload and runs its postinst as root.

This is a classic time-of-check to time-of-use bug. Debian’s update also fixes several unrelated out-of-bounds access issues in APT.

Who is actually exposed

All three conditions must line up:

  • On-path HTTP control. A compromised or flat network, a malicious or compromised mirror, or a proxy or package cache that can rewrite responses.
  • Retries enabled. Any non-zero Acquire::Retries. That is the default on Debian 12 and later and Ubuntu 22.04 LTS and later. Older releases (Debian 11, Ubuntu 20.04 and earlier) default to zero, but CI scripts and Dockerfiles often raise it to ride out flaky mirrors.
  • A package download. Any apt install, apt upgrade or unattended upgrade that fetches a .deb.

Not affected: sources served over HTTPS (against an on-path attacker), FTP sources, and snap packages. HTTPS does not save you from a malicious mirror or a TLS-terminating proxy, so patching APT is still required.

Fixed versions

Release Fixed APT version Advisory
Debian 12 bookworm 2.6.2 DLA-4880-1
Debian 13 trixie 3.0.4 DSA-6607-1
Debian unstable sid 3.3.6 Debian tracker
Ubuntu 26.10 3.3.3ubuntu0.1 USN-8996-1
Ubuntu 26.04 LTS 3.2.1ubuntu0.1 USN-8996-1
Ubuntu 24.04 LTS 2.8.4ubuntu0.1 USN-8996-1
Ubuntu 22.04 LTS 2.4.15ubuntu0.1 USN-8996-1
Ubuntu 20.04 LTS 2.0.12+esm1 (Ubuntu Pro) USN-8996-1
Ubuntu 18.04 LTS 1.6.19+esm1 (Ubuntu Pro) USN-8996-1
Ubuntu 16.04 / 14.04 LTS 1.2.36+esm1 / 1.0.1ubuntu2.24+esm4 (Pro Legacy) USN-8996-1

Debian testing (forky) was still listed as vulnerable at 3.3.5 on the Debian tracker when this was written and picks up the fix as 3.3.6 migrates from unstable. Debian 11 bullseye is past the end of its LTS support; it is only exposed if retries were raised, and it is overdue for an upgrade anyway.

Step-by-Step: Fixing APT CVE-2026-109518

Step 1: Find out if a host is exposed

Goal: know the installed APT version, the retry setting, and whether any source still uses plain HTTP.

Action: run these three checks on each host (or push them through your config management).

# 1. Installed APT build (compare against the fixed versions table)
apt list --installed '?source-package(^apt$)'

# 2. Effective retry setting (no output means the built-in default applies)
apt-config dump | grep -i 'Acquire::Retries'

# 3. Any sources still on plain HTTP
grep -rnE 'http://' /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null

Verification: a host is exposed when the APT version is below the fixed build, retries are non-zero (or unset on Debian 12+ / Ubuntu 22.04+), and at least one source is http://.

Step 2: Patch APT with retries turned off

Goal: install the fixed APT without giving an attacker the retry they need during the upgrade itself.

Action: the command that installs the fixed APT runs on the vulnerable APT. Canonical recommends turning retries off for that one run, which blocks both an on-path attacker and a malicious mirror.

# Preferred: full upgrade with retries disabled for this run only
sudo apt update && sudo apt upgrade -o Acquire::Retries=0

# If you cannot upgrade everything, target APT alone
sudo apt update && sudo apt install -o Acquire::Retries=0 --only-upgrade '?source-package(^apt$)'

On Debian you can also use sudo apt-get install -o Acquire::Retries=0 --only-upgrade apt. If a flaky mirror makes the run fail, rerun it; do not raise retries.

Ubuntu’s unattended-upgrades will pick up the fix within 24 hours on most installs, but that run uses the vulnerable APT and your normal retry setting. On any host whose network path you do not fully trust, patch by hand with retries off.

Verification: rerun the version check from Step 1 and confirm the fix is listed in the package changelog.

apt list --installed '?source-package(^apt$)'
zgrep -c 'CVE-2026-109518' /usr/share/doc/apt/changelog.Debian.gz

A count of 1 or more means the installed build carries the fix.

Step 3: Move your sources to HTTPS

Goal: remove the on-path attacker’s foothold for this bug and the next one like it.

Action: on Ubuntu, Canonical’s one-liner rewrites archive, security and ports URLs to HTTPS in both the classic .list and the deb822 .sources formats. It only touches official Ubuntu hosts, so check any third-party repos by hand.

sudo sed -Ei 's!http://((([a-z]+\.)?archive|security|ports)\.ubuntu\.com)!https://\1!' \
  /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources
sudo apt update

On Debian, point sources at https://deb.debian.org/debian and https://deb.debian.org/debian-security. A deb822 example for trixie:

sudo tee /etc/apt/sources.list.d/debian.sources > /dev/null <<'EOF'
Types: deb
URIs: https://deb.debian.org/debian
Suites: trixie trixie-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Types: deb
URIs: https://deb.debian.org/debian-security
Suites: trixie-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
sudo apt update

Verification: apt update completes with no errors and every Get: line shows https://. Some community and country mirrors do not offer HTTPS; if apt update fails, switch that host to a mirror that does.

Step 4: Fix CI runners and Docker images

Goal: stop ephemeral builders from booting the old APT every time.

Action: patching a running runner does nothing if the next one boots from the old image. Rebuild runner images and container bases, and until the base images carry the fixed APT, disable retries before the first apt-get.

# Dockerfile: before any other apt-get line
RUN echo 'Acquire::Retries "0";' > /etc/apt/apt.conf.d/99-no-retries \
 && apt-get update \
 && apt-get install -y --only-upgrade apt
# GitHub Actions: before any step that installs packages
- name: Disable apt retries (CVE-2026-109518)
  run: echo 'Acquire::Retries "0";' | sudo tee /etc/apt/apt.conf.d/99-no-retries

Rebuild with docker build --pull --no-cache so you do not reuse a cached layer that still holds the old APT, then check the APT version inside the finished image. The trade-off: with retries off, a flaky mirror fails the job instead of retrying. That is a fair price until your images are rebuilt.

Verification: docker run --rm <image> apt list --installed '?source-package(^apt$)' shows the fixed build.

Step 5: Lock down the network path with FortiGate

Goal: make sure Linux servers can only reach approved mirrors, and only over HTTPS once Step 3 is done. This is defense in depth, not a replacement for the patch.

Action: create FQDN objects for the mirrors you use, group them, allow HTTPS only, and log anything that still tries port 80.

config firewall address
    edit "net-linux-servers"
        set subnet 10.0.20.0 255.255.255.0
    next
    edit "fqdn-ubuntu-archive"
        set type fqdn
        set fqdn "archive.ubuntu.com"
    next
    edit "fqdn-ubuntu-security"
        set type fqdn
        set fqdn "security.ubuntu.com"
    next
    edit "fqdn-debian-deb"
        set type fqdn
        set fqdn "deb.debian.org"
    next
end
config firewall addrgrp
    edit "grp-apt-mirrors"
        set member "fqdn-ubuntu-archive" "fqdn-ubuntu-security" "fqdn-debian-deb"
    next
end
config firewall policy
    edit 0
        set name "linux-apt-https-only"
        set srcintf "port2"
        set dstintf "wan1"
        set srcaddr "net-linux-servers"
        set dstaddr "grp-apt-mirrors"
        set action accept
        set schedule "always"
        set service "HTTPS"
        set nat enable
        set logtraffic all
    next
    edit 0
        set name "linux-apt-http-block"
        set srcintf "port2"
        set dstintf "wan1"
        set srcaddr "net-linux-servers"
        set dstaddr "grp-apt-mirrors"
        set action deny
        set schedule "always"
        set service "HTTP"
        set logtraffic all
    next
end

Two things to watch. First, if you run deep SSL inspection on this policy, the FortiGate becomes a TLS-terminating proxy on the path, which is exactly the case HTTPS cannot protect against; patch APT regardless, and consider exempting the mirror group from deep inspection. Second, an internal apt-cacher or Artifactory proxy moves the problem rather than removing it: patch every client behind it, and use HTTPS on both the client-to-cache and cache-to-upstream legs.

GUI Verification: Policy and Objects, Firewall Policy, confirm both policies sit above any broader Linux egress rule. Then Log and Report, Forward Traffic, filter on source 10.0.20.0/24 and destination port 80 to find hosts that still use HTTP sources.

Verification and Validation

Run this on each host after patching. All four lines should come back clean.

apt list --installed '?source-package(^apt$)'
zgrep -c 'CVE-2026-109518' /usr/share/doc/apt/changelog.Debian.gz
grep -rnE 'http://' /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null | wc -l
sudo apt update 2>&1 | grep -c '^Err'

Expected success output on an Ubuntu 24.04 host:

apt/noble-updates,noble-security,now 2.8.4ubuntu0.1 amd64 [installed]
libapt-pkg6.0t64/noble-updates,noble-security,now 2.8.4ubuntu0.1 amd64 [installed]
1
0
0

That reads as: fixed build installed for every binary from the apt source package, the changelog carries the CVE fix, zero HTTP sources left, and apt update finished with no errors over HTTPS.

Troubleshooting and Gotchas

Gotcha 1: apt update fails right after switching to HTTPS

Symptom: Err: lines or certificate errors on one source after Step 3.

Diagnose:

sudo apt update 2>&1 | grep -E '^(Err|W:|E:)'

Fix: the mirror does not support HTTPS. Point that source at archive.ubuntu.com, security.ubuntu.com or deb.debian.org, or a regional mirror that offers TLS. On minimal images missing CA certificates, run apt install ca-certificates (with retries off) before switching.

Gotcha 2: the version looks old but the host is patched

Symptom: apt --version shows a number that does not match the table.

Diagnose:

apt-cache policy apt
zgrep -m3 'CVE-2026-109518' /usr/share/doc/apt/changelog.Debian.gz

Fix: trust the package version from apt-cache policy and the changelog, not the banner. Ubuntu Pro builds for 20.04 and older carry +esm suffixes and only appear once the host is attached to Ubuntu Pro (pro status).

Gotcha 3: you patched the hosts, but new builds are still vulnerable

Symptom: fresh CI jobs or containers still report the old APT.

Diagnose:

docker run --rm <image> apt list --installed '?source-package(^apt$)'
grep -rn 'Acquire::Retries' Dockerfile .github/ 2>/dev/null

Fix: rebuild with --pull --no-cache, update runner base images, and remove any hard-coded Acquire::Retries values above zero until the bases ship the fixed APT. Look for postinst scripts that call curl, wget, bash or base64 in recent install logs (/var/log/apt/term.log) on build hosts that sat on untrusted networks.

Sources

Recent posts

  • If you've spent any time configuring user authentication on... Full Story

  • DNS is one of those technologies that quietly underpins... Full Story

  • BGP issues on FortiGate firewalls usually trace back to... Full Story

  • Every time your laptop talks to your router, a... Full Story

  • If you've spent any time configuring NAT on a... Full Story

  • If you have spent any time configuring firewall policies... Full Story

  • High availability on FortiGate is one of those features... Full Story

  • If you've configured SD-WAN on a FortiGate, you've almost... Full Story

  • FortiLink is the management protocol that turns a FortiSwitch... Full Story

  • FortiSwitches are pretty rock solid from Mean Time Between... Full Story

  • This is a quicky tip.  Have you ever gone... Full Story

  • DNS is one of those quiet pieces of internet... Full Story

  • This article is an updated version of the previous... Full Story

  • You will add ns2 as a secondary (slave) BIND9... Full Story

  • In the process of deploying my lab, I needed... Full Story

  • RFC 8805, used to be known as Self-Correcting IP... Full Story

  • Years back, I wrote an article about certificate pinning. ... Full Story

  • FortiGates have the ability to send alerts to Microsoft... Full Story

  • In this post, I am going to walk through... Full Story

  • Troubleshooting VoIP on a FortiGate can feel like trying... Full Story

  • Prior to FortiOS 7.0, there were three commands to... Full Story

  • In this post, I am going to go over... Full Story

  • What we are going to do:  We are going... Full Story

  • Choosing between FGCP (FortiGate Clustering Protocol) and FGSP (FortiGate... Full Story

  • Creating a VLAN on macOS (The "Pro" Move) A... Full Story

  • This blog post explores the logic behind how macOS... Full Story

  • Pretty Fly for a Wi-Fi Tell My Wi-Fi Love... Full Story

  • Part of my daily gig is creating BoMs (Bill-of-Materials)... Full Story

  • ICMP introduces several security risks, but careful filtering, rate... Full Story

  • The command diag debug application dhcps -1 enables full... Full Story

  • In the world of FortiOS, execute tac report is... Full Story

  • LLDP; What is it The Link Layer Discovery Protocol... Full Story

  • What it actually does When you run diagnose fdsm... Full Story

  • Monkey Bites are bite-sized, high-impact security insights designed for... Full Story

  • I have run macOS in macOS with Parallels but... Full Story

  • Don't be confused with my other FortiNAC posts where... Full Story

  • This is the third session in a multi-part article... Full Story

  • Today I was configuring key-based authentication on a FortiGate... Full Story

  • Netcat, often called the "Swiss Army knife" of networking,... Full Story

  • At its core, IEEE 802.1X is a network layer... Full Story

  • In case you did not see the previous FortiNAC... Full Story

  • This is our 5th session where we are going... Full Story

  • Now that we have Wireshark installed and somewhat configured,... Full Story

  • The Philosophy of Packet Analysis Troubleshooting isn't about looking... Full Story

  • APT CVE-2026-109518: How a Download Retry Lets an Attacker... Full Story

  • Managing Windows Firewall from the Command Line: netsh, PowerShell... Full Story

  • Chrome CVE-2026-106378: Patching the macOS Sandbox Escape No fluff.... Full Story