Kiểm kê và so khớp
1. Mô hình inventory
Một inventory là ảnh chụp các gói đã cài của một máy tại một thời điểm, cộng thông tin hệ điều hành và kernel. Từ giai đoạn P2 và P6 inventory có thêm dịch vụ, phần mềm ngoài gói, thống kê tệp và tệp đáng chú ý (mục 8). Scanner tạo, secmatch lưu bản mới nhất theo (company_id, server_id) (ADR 0008).
| Trường | Ví dụ | Ghi chú |
|---|---|---|
os.id, os.version_id, os.codename, os.id_like | ubuntu, 22.04, jammy, debian | Từ /etc/os-release |
os.variant | ubuntu-pro, rhel-eus | Chỉ khi xác định được từ tệp cấu hình kho (Q15) |
kernel.running | 6.8.0-142-generic | uname (syscall, không gọi lệnh) |
kernel.installed[] | Danh sách gói kernel đã cài | Để phát hiện "cần khởi động lại" |
packages[].manager | dpkg, rpm, apk | |
packages[].name, arch | libssl3, amd64 | Tên gói nhị phân |
packages[].version | 3.0.2-0ubuntu1.15 | Chuỗi đầy đủ theo hệ (dpkg gồm epoch nếu có) |
packages[].epoch, release | rpm: 1, 11.el9_4 | rpm tách EVR |
packages[].source_name, source_version | openssl, 3.0.2-0ubuntu1.15 | dpkg Source: (có thể kèm phiên bản trong ngoặc), rpm header SOURCERPM |
packages[].vendor | Red Hat, Inc. | rpm header VENDOR, dùng để nhận gói bên thứ ba |
packages[].module | nodejs:18:8090020240123:a75119d5 | rpm header MODULARITYLABEL (RHEL 8 module stream) |
packages[].status | installed | dpkg: chỉ lấy install ok installed; bỏ config-files, half-installed |
Không thu: danh sách tệp của gói, script cài, nội dung tệp cấu hình, người cài, thời điểm cài theo người dùng.
Kích thước: một máy chủ thường có vài trăm đến vài nghìn gói (đo thực tế ở spike S5). Mỗi gói sau mã hóa protobuf khoảng 60 đến 120 byte, nén tốt vì chuỗi lặp. Đích SNFR-11: dưới 150 KB nén cho 3.000 gói.
2. Đọc DB gói trên máy
| Hệ | Nguồn | Cách đọc | Rủi ro |
|---|---|---|---|
| dpkg | /var/lib/dpkg/status | Parser định dạng RFC 822 đơn giản, đọc tuần tự, đọc được không cần root | Tệp lớn vài MB, đọc một lần |
| rpm 4.11 đến 4.14 (RHEL 7, 8) | /var/lib/rpm/Packages (Berkeley DB) | Thư viện Go thuần đọc BDB hash (ứng viên go-rpmdb) | Đọc khi dnf đang ghi: mở chỉ đọc, thử lại, đánh dấu error nếu không nhất quán |
| rpm 4.16 trở lên (RHEL 9) | /var/lib/rpm/rpmdb.sqlite | Cần SQLite thuần Go (ảnh hưởng kích thước binary, S1) | Như trên |
| rpm NDB (SUSE) | /var/lib/rpm/Packages.db | Thư viện như trên nếu hỗ trợ | Best-effort |
| apk | /lib/apk/db/installed | Parser văn bản (khóa một ký tự P:, V:, A:, o:) | Thấp |
Nguyên tắc: đọc tệp trực tiếp, không gọi lệnh. Nếu spike S1 cho thấy không đọc được một định dạng bằng Go thuần, phương án dự phòng duy nhất được phép là gọi binary hệ thống bằng đường dẫn tuyệt đối với đối số cố định trong mã (ví dụ /usr/bin/rpm -qa --qf <định dạng cố định>), không bao giờ nhận đối số từ cấu hình từ xa. Cần ghi lại bằng ADR nếu dùng.
Phát hiện thay đổi rẻ (SFR-07): lưu mtime, kích thước, inode của tệp DB gói ở lần quét trước trong /var/lib/accesshub-scanner/state.json; timer mỗi giờ chỉ stat các tệp này.
3. So sánh phiên bản
Lõi độ chính xác, tự viết và kiểm thử bằng bảng lấy từ công cụ gốc (dpkg --compare-versions, rpmdev-vercmp, apk version -t) chạy trong CI trên container distro (ADR 0007). Không dùng so sánh semver.
3.1 dpkg (Debian, Ubuntu)
- Dạng
[epoch:]upstream_version[-debian_revision]. Epoch vắng là 0. - So epoch theo số, rồi upstream, rồi revision bằng thuật toán
verrevcmp: tách luân phiên phần không số và phần số; phần không số so từng ký tự, chữ cái xếp trước ký tự không phải chữ cái,~xếp trước mọi thứ kể cả chuỗi rỗng; phần số so theo giá trị. - Ví dụ cần có trong bộ kiểm thử:
1.0~rc1 < 1.0,1:0.9 > 2.0,3.0.2-0ubuntu1.15 > 3.0.2-0ubuntu1.9, hậu tố+esm1,+deb12u1.
3.2 rpm (họ RHEL, SUSE)
- So EVR: epoch (vắng là 0), rồi version, rồi release bằng
rpmvercmp: tách thành đoạn chữ hoặc số, bỏ ký tự phân cách; đoạn số mới hơn đoạn chữ; số so theo giá trị (bỏ số 0 đầu), chữ so theo thứ tự byte;~xếp trước (tiền phát hành);^xếp sau bản gốc nhưng trước bản kế tiếp (rpm 4.15 trở lên). - Phạm vi theo luồng: advisory Red Hat gắn với sản phẩm (BaseOS, AppStream, CodeReady, EUS). Gói có
MODULARITYLABELchỉ được so với advisory của đúng module stream đó, nếu không sẽ báo sai giữa các dòng phiên bản. releasechứa dấu hiệu bản phát hành (el9_4), giúp phát hiện gói lẫn từ phiên bản distro khác.
3.3 apk (Alpine)
- Dạng
số[.số]*[chữ][_hậu tố[số]]*[-rN]. Thứ tự hậu tố:_alpha < _beta < _pre < _rc <(không hậu tố)< _cvs < _svn < _git < _hg < _p. Revision-rNso cuối cùng.
4. Thuật toán so khớp (secmatch)
Với mỗi gói đã cài của máy thuộc hệ E (ví dụ ubuntu:22.04):
- Chọn khóa tra: Debian và Ubuntu dùng gói nguồn (
source_name), họ RHEL và Alpine dùng tên gói nhị phân (cộng module stream nếu có). - Lấy các mục
affectedcủaEvới khóa đó. - Với mỗi mục:
- Có phiên bản sửa và
phiên bản đã cài < phiên bản sửa: finding trạng tháiopen,fix_available = true,fixed_version. - Distro báo chưa sửa (
needed,deferred, Debianopen): findingopen,fix_available = false, nhãnno_fix(Q8). - Distro báo không sửa (
ignored,will not fix, Debianno-dsacó chú thích không sửa): finding nhãnwont_fix, mức ưu tiên hạ theo06. not-affected,DNE, phiên bản đã cài lớn hơn hoặc bằng bản sửa: không có finding.
- Có phiên bản sửa và
- Gộp: một finding cho mỗi
(máy, mã lỗ hổng chuẩn, khóa gói), liệt kê mọi gói nhị phân bị ảnh hưởng. Một lần nâng gói nguồn đóng một finding. - Mã lỗ hổng chuẩn: CVE nếu có trong
aliases, nếu không giữ mã advisory (USN, DSA, RHSA, GHSA). Advisory chứa nhiều CVE sinh nhiều finding, mỗi finding nhắc mã advisory để người dùng biết một bản cập nhật sửa nhiều CVE. - Khóa ổn định:
finding_key = SHA-256(company_id | server_id | vuln_id | ecosystem | package_key). Dùng làm khóa idempotent ở secmatch và Access Hub. - So với tập finding đang mở của máy: sinh
finding.opened,finding.resolved(biến mất khi quét lại, ghiresolved_reason = package_updatedhoặcpackage_removed),finding.rescored.
So khớp lại khi DB đổi (5.2 của 02): chạy đúng thuật toán trên mọi inventory đã lưu, theo lô từng công ty, có giới hạn đồng thời, chỉ phát delta. resolved_reason = advisory_changed khi distro đổi dữ liệu (ví dụ sửa lại thành not-affected).
4.1 Bản phát hành hết hỗ trợ
Chính sách của chủ dự án (2026-10-02): lỗ hổng vẫn là lỗ hổng. Bản phát hành hết hỗ trợ (SUPPORT_EOL trong secdb, có eol_date) vẫn được so khớp như bản đang hỗ trợ, máy có trạng thái release_eol nhưng giữ mọi finding:
- Dữ liệu: Debian 10, 11 không còn trong tracker của Debian;
secdb-buildđọc Freexian ELTS tracker (cùng định dạng,--debian-elts-file,--no-elts) chỉ cho các bản hết hỗ trợ. Ubuntu LTS hết chuẩn chuyển sang ESM nên vẫn có dữ liệu. - Distro sẽ không sửa: finding chưa sửa (
no_fix,wont_fix,undetermined) mangno_distro_fix = true. Bản sửa mới hơn mọi phiên bản có trong kho công khai của Debian cho bản phát hành đó chỉ có qua ELTS trả phí: kênhelts, cũngno_distro_fix = true. Bản sửa đã nằm trong kho công khai (DSA, DLA trước khi hết LTS) giữfixed, kênhstandard: cài được ngay. - Khuyến nghị nâng cấp: thêm một finding
kind = release_eol,vuln_id = AHS-EOL-<DISTRO>-<VERSION>,fix_status = upgrade_release, mứchigh,eol_date,upgrade_to(các bản đang hỗ trợ của cùng distro trong secdb). Finding này đóng vớirelease_upgradedkhi máy chuyển bản phát hành. - Finding không bị đóng
out_of_scopekhi bản phát hành chuyển sang hết hỗ trợ.
4.2 Nhiều nguồn
Từ SDB-5 (ADR 0007 đã sửa): secdb mang bản ghi theo từng nguồn; secmatch chọn bản ghi quyết định theo chính sách ưu tiên của distro, dùng ngay bản ghi mới của nguồn nhanh hơn với cờ unconfirmed, và gắn vào finding decided_by, confirmed_by, disputed_by, sources_agree. Thuật toán ở mục 4 không đổi: chỉ bước 2 chọn bản ghi theo chính sách thay vì đọc một bản ghi đã gộp. Bản P1 là trường hợp một nguồn mỗi distro.
5. Kernel
- Finding kernel tính trên gói của kernel đang chạy (Ubuntu:
linux-image-$(uname -r), gói nguồn nhưlinux,linux-hwe-6.8,linux-aws; RHEL:kernel-<uname -r>). - Có gói kernel mới hơn đã cài và bản đó sửa CVE: finding giữ
openvới nhãnreboot_required, hướng dẫn khắc phục là khởi động lại, đồng thời kiểm traAHS-UPD-003không đạt. - Vá nóng (Canonical Livepatch, kpatch) có thể đã sửa CVE mà phiên bản gói không đổi: nguồn dương tính giả đã biết. P1 chỉ ghi nhận
livepatch_active(đọc/sys/kernel/livepatch/) để hiển thị cảnh báo; ánh xạ bản vá nóng sang CVE là câu hỏi mở (Q15).
6. Dương tính giả: nguồn và cách xử lý
| Nguồn | Xử lý |
|---|---|
| So theo NVD CPE cho gói OS (không biết backport) | Cấm theo ADR 0004. NVD chỉ làm giàu |
| Gói từ kho bên thứ ba (PPA, kho hãng như Docker, NGINX, PostgreSQL PGDG) cùng tên gói distro | rpm: so VENDOR với danh sách nhà phát hành distro, khác thì gắn third_party và không so với dữ liệu distro. dpkg: tệp status không mang nguồn kho; giai đoạn P1 gắn origin_unknown khi phiên bản không mang dấu hiệu distro (ví dụ thiếu ubuntu, deb12u), giai đoạn P3 xem xét đọc /var/lib/apt/lists/*_Packages để xác định kho (tốn IO, cần đo) |
Gói ở trạng thái config-files hoặc đã gỡ | Không đưa vào inventory |
Distro derivative (ID_LIKE=ubuntu như Linux Mint) | unsupported_os, không so khớp, hiển thị rõ. Có hỗ trợ hay không là quyết định sản phẩm |
| Kernel cài nhưng không chạy | Chỉ kernel đang chạy sinh finding |
| Vá nóng kernel | Mục 5 |
| Lỗ hổng chỉ khai thác được khi bật tính năng không dùng | Không tự suy luận. Người dùng tạo ngoại lệ false_positive hoặc compensating_control (06 mục 6) |
| Dữ liệu distro sai | Ngoại lệ ở Access Hub; nền tảng nhận thống kê ẩn danh số ngoại lệ dương tính giả theo (vuln_id, ecosystem, package_key) khi ít nhất k công ty cùng đánh dấu, để báo ngược nhà phát hành distro (không kèm tên công ty) |
Ngoại lệ không bao giờ xóa finding: finding vẫn tồn tại với trạng thái accepted hoặc false_positive, có hạn, tự mở lại khi hết hạn (06).
7. Mở rộng sau
7.1 Hệ sinh thái ngôn ngữ (giai đoạn P6)
Ở vị trí hệ thống (mục 8.2) mặc định; thêm tệp khóa phụ thuộc (package-lock.json, poetry.lock, composer.lock, go.sum) ở thư mục do quản trị viên chọn, có trần số tệp, vì tốn IO và dễ lộ đường dẫn dự án. Dữ liệu từ OSV và GitHub advisories, khóa gói dạng purl, so sánh phiên bản semver, PEP 440, Maven (SMT-7). Quyết định phạm vi ở Q19.
7.2 Container và image (giai đoạn P9)
Hai hướng (Q19): (1) scanner trên máy đọc image của container đang chạy qua socket của Docker hoặc containerd, việc này tương đương quyền root và phải xem lại ADR 0001; (2) quét image trong registry từ trung tâm, không chạm máy. Cùng thuật toán so khớp, hệ alpine:3.20, debian:12 lấy từ os-release trong image.
7.3 Windows (giai đoạn P8)
Inventory từ registry (khóa Uninstall), bản build hệ điều hành và KB đã cài. Microsoft công bố theo sản phẩm và KB, không theo gói, nên cần mô hình so khớp riêng (bản build tích lũy). Q18.
8. Kiểm kê mở rộng: dịch vụ, phần mềm, tệp
Mục tiêu: trả lời "trên máy có gì" ngoài danh sách gói, để tìm phần mềm chạy ngoài trình quản lý gói (thường là nơi lỗ hổng bị bỏ quên) và biết dịch vụ nào đang bật. Chỉ siêu dữ liệu, cùng nguyên tắc đọc tệp trực tiếp, không gọi lệnh.
8.1 Dịch vụ (giai đoạn P6, SFR-50)
| Trường | Nguồn |
|---|---|
name | Tệp unit trong /etc/systemd/system, /run/systemd/system, /usr/lib/systemd/system, /lib/systemd/system |
unit_file_state | enabled (có liên kết trong *.wants/, *.requires/), disabled, static, masked (liên kết tới /dev/null) |
active_state | active, inactive, failed, unknown: qua phương thức chỉ đọc ListUnits của systemd trên D-Bus hệ thống nếu S10 và Q44 đồng ý, nếu không thì unknown |
package | Gói sở hữu tệp unit (so danh sách tệp của gói, cùng chi phí với AHS-FS-005) |
Không gửi nội dung tệp unit, Environment=, ExecStart= (có thể chứa bí mật; bí mật trong tệp unit do detector AHK-* tìm trên máy).
8.2 Phần mềm ngoài gói (giai đoạn P6, SFR-51, SFR-52)
| Loại | Nguồn | Trường |
|---|---|---|
snap | /snap/*/current/meta/snap.yaml | tên, phiên bản |
flatpak | /var/lib/flatpak/app/*/current/active/metadata | tên, phiên bản |
binary | ELF trong /usr/local/bin, /usr/local/sbin, /opt/** không thuộc gói nào | tên, đường dẫn, kích thước, SHA-256 |
go | Build info nhúng trong binary Go (module, phiên bản, phụ thuộc) | purl pkg:golang/... |
pypi | dist-info/METADATA dưới /usr/lib/python3*/, /usr/local/lib/python3*/ không thuộc gói | purl pkg:pypi/... |
npm | package.json trong node_modules toàn cục (/usr/lib/node_modules, /usr/local/lib/node_modules) | purl pkg:npm/... |
maven | META-INF/MANIFEST.MF, META-INF/maven/**/pom.properties trong .jar dưới /opt, /usr/share/java không thuộc gói (đọc zip có giới hạn như ADR 0009 mục 3) | purl pkg:maven/... |
Trần: 20.000 mục mỗi báo cáo (07 mục 3.4), ngân sách nằm trong lượt siêu dữ liệu (SNFR-01 đến SNFR-05), thư mục lớn có điểm dừng.
8.3 Tệp (giai đoạn P2, P6, SFR-54)
Chỉ mục cục bộ của lượt nội dung (ADR 0009 mục 5) giữ danh sách đầy đủ tệp trong phạm vi, không rời máy. Báo cáo gửi thống kê theo gốc và tệp đáng chú ý (14 mục 6). Kiểm tra quyền tệp (AHS-FS-*) vẫn dùng lượt lstat của lượt siêu dữ liệu (05 mục 5).