|
| 1 | +--- |
| 2 | +title: Investigate CVE Findings in Debian Packages |
| 3 | +description: A repeatable, evidence-based playbook for determining whether a CVE in a Debian package is a false positive or requires an accept-risk justification using binary analysis. |
| 4 | +--- |
| 5 | + |
| 6 | +# How to Investigate CVE Findings in Debian Packages (False Positive vs. Accept Risk) |
| 7 | + |
| 8 | +This playbook describes a repeatable, evidence-based process for evaluating CVE findings in Debian packages — specifically for determining whether a vulnerability is a **False Positive** or requires an **Accept Risk** justification. |
| 9 | + |
| 10 | +It applies to any context where Debian packages are present in your workload: container images, VMs, bare-metal servers, or CI pipelines. |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Background |
| 15 | + |
| 16 | +Vulnerability scanners report CVEs based on the presence of a package. However, a package being present does not mean the vulnerable code path is actually reachable. This process uses binary analysis to prove whether the vulnerable function is ever called. |
| 17 | + |
| 18 | +**Two possible outcomes:** |
| 19 | + |
| 20 | +| Outcome | Meaning | |
| 21 | +|---|---| |
| 22 | +| **False Positive** | The vulnerable function is never called in this workload. The code path is structurally unreachable. | |
| 23 | +| **Accept Risk** | The vulnerable function could theoretically be called, but the risk is acceptable (e.g. deprecated feature, DoS-only, Debian classifies as minor). | |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## Required Tools |
| 28 | + |
| 29 | +- `curl` — to download `.deb` packages |
| 30 | +- `ar` + `tar` — to extract `.deb` packages |
| 31 | +- `nm` / `objdump` — to inspect binary symbols (part of `binutils`) |
| 32 | +- `dpkg` — to inspect package metadata |
| 33 | +- `python3` — to parse `config.amd64.json` |
| 34 | + |
| 35 | +### Reproducible Environment with Nix |
| 36 | + |
| 37 | +All required tools are provided as a Nix flake for a fully reproducible analysis environment. This is the recommended way to run the analysis, especially on macOS or in CI. |
| 38 | + |
| 39 | +[Download flake.nix](/labs/binary-analysis/flake.nix) |
| 40 | + |
| 41 | +```nix |
| 42 | +# flake.nix — provides: curl, tar, binutils (nm, objdump, ar), dpkg |
| 43 | +``` |
| 44 | + |
| 45 | +**Usage:** |
| 46 | + |
| 47 | +```bash |
| 48 | +# Enter the dev shell (installs all tools automatically) |
| 49 | +nix develop |
| 50 | + |
| 51 | +# Or run a single command without entering the shell |
| 52 | +nix develop --command nm -D <binary> |
| 53 | +``` |
| 54 | + |
| 55 | +> **Note:** The flake targets `x86_64-linux` for Debian binary analysis. On macOS (Apple Silicon), use a remote builder or a Linux VM/container. The `nm -D` analysis must be run against the amd64 Debian binaries, not your host's native architecture. |
| 56 | +
|
| 57 | +--- |
| 58 | + |
| 59 | +## Step-by-Step Process |
| 60 | + |
| 61 | +### Step 1 — Understand the Dependency Path |
| 62 | + |
| 63 | +Note the full path shown in your scanner, e.g.: |
| 64 | + |
| 65 | +``` |
| 66 | +Your application → debian/wget → debian/libgnutls30t64 → debian/libtasn1-6 |
| 67 | +``` |
| 68 | + |
| 69 | +This tells you: |
| 70 | +- Which package is **vulnerable** (the last node) |
| 71 | +- Which package **pulls it in** (the intermediate node) |
| 72 | +- You need to prove whether the intermediate package actually calls the vulnerable function |
| 73 | + |
| 74 | +--- |
| 75 | + |
| 76 | +### Step 2 — Research the CVE |
| 77 | + |
| 78 | +Before doing any binary analysis, understand what you're looking for. |
| 79 | + |
| 80 | +**Search for:** |
| 81 | +``` |
| 82 | +CVE-XXXX-XXXXX <library-name> vulnerable function details |
| 83 | +CVE-XXXX-XXXXX debian security tracker |
| 84 | +``` |
| 85 | + |
| 86 | +**Key questions to answer:** |
| 87 | +1. **What is the exact vulnerable function?** (e.g. `asn1_expand_octet_string`) |
| 88 | +2. **What triggers the vulnerability?** Does it require a specific call pattern or input? |
| 89 | +3. **How does Debian classify it?** |
| 90 | + - Check: `https://security-tracker.debian.org/tracker/CVE-XXXX-XXXXX` |
| 91 | + - `<no-dsa>` = Minor issue, Debian does not plan a security update → additional argument for Accept Risk |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +### Step 3 — Get the Package URLs |
| 96 | + |
| 97 | +You need the **exact version** of the package that is present in your container image. Debian's [snapshot archive](https://snapshot.debian.org) preserves every historical package version and is the canonical source for downloading them. |
| 98 | + |
| 99 | +#### 3a — Find the exact version |
| 100 | + |
| 101 | +The version is usually reported directly by your scanner. If you need to verify it on the system itself: |
| 102 | + |
| 103 | +```bash |
| 104 | +dpkg -l | grep <package-name> |
| 105 | +# Example output: ii libgnutls30t64 3.8.3-1.1 amd64 GNU TLS library ... |
| 106 | +``` |
| 107 | + |
| 108 | +#### 3b — Look up the package on Debian Snapshot |
| 109 | + |
| 110 | +Go to **https://snapshot.debian.org/binary/\<package-name\>/** and find the exact version. The direct download URL follows this pattern: |
| 111 | + |
| 112 | +``` |
| 113 | +https://snapshot.debian.org/archive/debian/<timestamp>/pool/<section>/<letter>/<package>/<package>_<version>_amd64.deb |
| 114 | +``` |
| 115 | + |
| 116 | +**Example** for `libgnutls30t64` version `3.8.3-1.1`: |
| 117 | +``` |
| 118 | +https://snapshot.debian.org/archive/debian/20240831T204032Z/pool/main/g/gnutls28/libgnutls30t64_3.8.6-2_amd64.deb |
| 119 | +``` |
| 120 | + |
| 121 | +> **Tip:** If you know the package version but not the timestamp, use the binary search page at `https://snapshot.debian.org/binary/<package-name>/` — it lists all available versions with direct download links. |
| 122 | +
|
| 123 | + |
| 124 | +--- |
| 125 | + |
| 126 | +### Step 4 — Download and Extract the .deb Packages (Run in `nix develop`) |
| 127 | + |
| 128 | +```bash |
| 129 | +# Download |
| 130 | +curl -L "<url-from-config>" -o <package>.deb |
| 131 | + |
| 132 | +# Extract |
| 133 | +mkdir <package>-extracted && cd <package>-extracted |
| 134 | +ar x ../<package>.deb |
| 135 | +tar xf data.tar.xz |
| 136 | +cd .. |
| 137 | +``` |
| 138 | + |
| 139 | +Repeat for each relevant package in the dependency chain. |
| 140 | + |
| 141 | +--- |
| 142 | + |
| 143 | +### Step 5 — Binary Analysis |
| 144 | + |
| 145 | +This is the core of the investigation. Use `nm -D` to inspect which external symbols a binary references at runtime. |
| 146 | + |
| 147 | +> **How to read `nm -D` output:** |
| 148 | +> - `U` = Undefined = the binary **calls** this function from an external library |
| 149 | +> - If a function does **not appear** in the output → it is **never called** |
| 150 | +
|
| 151 | +#### 5a — Is the vulnerable library linked at all? |
| 152 | + |
| 153 | +```bash |
| 154 | +nm -D <binary> | grep -i "<library-keyword>" |
| 155 | +# e.g. for libexpat: |
| 156 | +nm -D usr/bin/git | grep -i "xml" |
| 157 | +``` |
| 158 | + |
| 159 | +No output → the binary is not linked against the library → **strong False Positive signal** |
| 160 | + |
| 161 | +#### 5b — Is the specific vulnerable function called? |
| 162 | + |
| 163 | +```bash |
| 164 | +nm -D <library>.so* | grep -i "<vulnerable_function>" |
| 165 | +# e.g.: |
| 166 | +nm -D usr/lib/x86_64-linux-gnu/libgnutls.so.30* | grep -i "expand_octet" |
| 167 | +``` |
| 168 | + |
| 169 | +No output → the function is never called by this library → **False Positive** |
| 170 | + |
| 171 | +#### 5c — For packages with multiple binaries (e.g. git-core) |
| 172 | + |
| 173 | +```bash |
| 174 | +for f in usr/lib/git-core/*; do |
| 175 | + result=$(nm -D "$f" 2>/dev/null | grep -i "<keyword>") |
| 176 | + if [ -n "$result" ]; then |
| 177 | + echo "=== $f ===" |
| 178 | + echo "$result" |
| 179 | + fi |
| 180 | +done |
| 181 | +``` |
| 182 | + |
| 183 | +This reveals exactly which sub-binary uses the vulnerable library. |
| 184 | + |
| 185 | +--- |
| 186 | + |
| 187 | +### Step 6 — Make the Decision |
| 188 | + |
| 189 | +| Situation | Decision | |
| 190 | +|---|---| |
| 191 | +| Vulnerable function does not appear in any binary's symbol table | **False Positive** → "Vulnerable Code Not In Execute Path" | |
| 192 | +| Vulnerable function is only used by a legacy/deprecated sub-binary that is never invoked | **False Positive** → with justification | |
| 193 | +| Vulnerable function is called, but the specific trigger condition is never met | **False Positive** → with detailed justification | |
| 194 | +| Vulnerable function is reachable, risk is low (DoS-only, `<no-dsa>`, mitigated by other controls) | **Accept Risk** | |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +### Step 7 — Write the VEX Comment (English) |
| 199 | + |
| 200 | +A good VEX comment always contains these three parts: |
| 201 | + |
| 202 | +1. **What is the vulnerability** — the vulnerable function and what it does |
| 203 | +2. **Binary analysis result** — what `nm -D` showed (or didn't show) and why that proves the code path is unreachable |
| 204 | +3. **Supporting evidence** (optional but recommended) — Debian's `<no-dsa>` classification, deprecated feature status, etc. |
| 205 | + |
| 206 | +**False Positive template:** |
| 207 | +``` |
| 208 | +CVE-XXXX-XXXXX is assessed as a false positive. |
| 209 | +
|
| 210 | +The vulnerability is a <type> in the <function_name> function of <library>. |
| 211 | +According to the official security advisory, exploitation requires that the |
| 212 | +target program explicitly calls <function_name>. |
| 213 | +
|
| 214 | +Binary analysis of the extracted Debian package (<package_version>.deb) via |
| 215 | +nm -D confirms that <intermediate_library> does not reference or call |
| 216 | +<function_name> at all. Since <top_level_package> accesses <library> |
| 217 | +exclusively through <intermediate_library>, and <intermediate_library> never |
| 218 | +invokes the vulnerable function, the vulnerable code path is never reached. |
| 219 | +
|
| 220 | +[Optional: Additionally, Debian's security team has classified this CVE as a |
| 221 | +minor issue (<no-dsa>) for Trixie and does not plan a dedicated security update.] |
| 222 | +
|
| 223 | +This finding is therefore classified as a false positive. |
| 224 | +``` |
| 225 | + |
| 226 | +**Accept Risk template:** |
| 227 | +``` |
| 228 | +CVE-XXXX-XXXXX is assessed as accepted risk. |
| 229 | +
|
| 230 | +The vulnerability affects <library> version <version>, which is present as a |
| 231 | +[direct/transitive] dependency of <package>. The vulnerable function |
| 232 | +<function_name> is called by <binary>, which implements <feature>. |
| 233 | +
|
| 234 | +Risk is considered acceptable for the following reasons: |
| 235 | +1. <reason 1 — e.g. feature is deprecated/not used in this workload> |
| 236 | +2. <reason 2 — e.g. vulnerability class is DoS only, no RCE> |
| 237 | +3. <reason 3 — e.g. Debian classifies as <no-dsa>, no patch planned> |
| 238 | +
|
| 239 | +We will re-evaluate once Debian ships a patched version. |
| 240 | +``` |
| 241 | + |
| 242 | +--- |
| 243 | + |
| 244 | +## Real Examples |
| 245 | + |
| 246 | +### Example 1 — False Positive: CVE-2025-59375 (libexpat via git) |
| 247 | + |
| 248 | +**Path:** `Your application → debian/git → debian/libexpat1` |
| 249 | + |
| 250 | +**CVE:** Stack overflow in libexpat via recursive XML entity expansion (DoS). Fixed in 2.7.2. |
| 251 | + |
| 252 | +**Analysis:** |
| 253 | +```bash |
| 254 | +nm -D usr/bin/git | grep -i xml |
| 255 | +# → no output (git binary not linked against libexpat) |
| 256 | + |
| 257 | +for f in usr/lib/git-core/*; do |
| 258 | + result=$(nm -D "$f" 2>/dev/null | grep -i xml) |
| 259 | + if [ -n "$result" ]; then echo "=== $f ==="; echo "$result"; fi |
| 260 | +done |
| 261 | +# → only git-http-push uses XML symbols |
| 262 | +``` |
| 263 | + |
| 264 | +**Decision:** False Positive — libexpat is only used by `git-http-push`, a deprecated legacy binary implementing WebDAV-based HTTP push, which is never invoked in this workload. |
| 265 | + |
| 266 | +--- |
| 267 | + |
| 268 | +### Example 2 — False Positive: CVE-2025-13151 (libtasn1 via libgnutls via wget) |
| 269 | + |
| 270 | +**Path:** `Your application → debian/wget → debian/libgnutls30t64 → debian/libtasn1-6` |
| 271 | + |
| 272 | +**CVE:** Stack-based buffer overflow in `asn1_expand_octet_string`. Exploitation requires the target program to **explicitly call** this function. |
| 273 | + |
| 274 | +**Analysis:** |
| 275 | +```bash |
| 276 | +nm -D usr/lib/x86_64-linux-gnu/libgnutls.so.30* | grep -i "expand_octet" |
| 277 | +# → no output |
| 278 | +``` |
| 279 | + |
| 280 | +**Decision:** False Positive — libgnutls never calls `asn1_expand_octet_string`. Since wget accesses libtasn1 exclusively through libgnutls, the vulnerable function is never reached. |
| 281 | + |
| 282 | +--- |
| 283 | + |
| 284 | +## Notes |
| 285 | + |
| 286 | +- `nm -D` only works on **dynamically linked** binaries. Statically linked binaries require `nm` without `-D` |
| 287 | +- The absence of a symbol in `nm -D` output is **definitive proof** that the function is never called at runtime via dynamic linking |
| 288 | +- Debian's `<no-dsa>` classification is supporting evidence, not proof by itself — always combine with binary analysis |
0 commit comments