Kode C modern jarang berdiri sendiri—sebagian besar proyek mengandalkan library pihak ketiga seperti OpenSSL, zlib, libcurl, atau SQLite. Setiap dependency adalah attack surface baru: bila library memiliki CVE, program kita ikut rentan. Studi Sonatype 2023 melaporkan lebih dari 95% kerentanan dalam aplikasi modern berasal dari dependency pihak ketiga, bukan kode internal. Pada chapter ini kita membangun pemindai dependency sederhana yang membandingkan daftar library terpakai dengan database CVE tiruan, lalu memetakan praktik manajemen dependency yang sehat di industri.

Alur Kerja Manajemen Dependency #

Empat langkah utama menjaga dependency tetap aman:

  1. Daftar lengkap: Catat setiap dependency beserta versi di lock file atau manifest.
  2. Pindai CVE: Bandingkan daftar dengan database CVE seperti NVD (National Vulnerability Database).
  3. Pin versi: Kunci versi spesifik untuk reproducibility—hindari * atau latest.
  4. Update berkala: Jadwalkan pembaruan minimal sekali per sprint, bukan saat ada darurat.
c
/* secure_dependency_management.c */
/* Konseptual: pindai daftar dependency terhadap daftar CVE yang diketahui.
   Versi sederhana — produksi memerlukan database CVE asli (NVD). */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    const char *name;
    const char *version;
} Dependency;

typedef struct {
    const char *name;
    const char *affected_below; /* versi di bawah ini rentan */
    const char *cve_id;
} KnownCVE;

/* Daftar dependency yang dipakai proyek (mock). */
static const Dependency deps[] = {
    { "libfoo", "1.2.0" },
    { "libbar", "3.0.1" },
    { "libbaz", "0.9.5" },
    { "libqux", "2.1.0" },
};

/* Daftar CVE yang diketahui (mock). */
static const KnownCVE cve_db[] = {
    { "libfoo", "1.3.0", "CVE-2024-0001" },
    { "libbar", "3.1.0", "CVE-2024-0002" },
    { "libbaz", "1.0.0", "CVE-2023-9999" },
};

/* Bandingkan dua versi sederhana "major.minor.patch". */
static int compare_version(const char *a, const char *b) {
    int a1,a2,a3, b1,b2,b3;
    sscanf(a, "%d.%d.%d", &a1,&a2,&a3);
    sscanf(b, "%d.%d.%d", &b1,&b2,&b3);
    if (a1 != b1) return a1 < b1 ? -1 : 1;
    if (a2 != b2) return a2 < b2 ? -1 : 1;
    if (a3 != b3) return a3 < b3 ? -1 : 1;
    return 0;
}

/* Pindai satu dependency. Kembalikan 1 bila rentan. */
static int scan_dependency(const Dependency *d, const KnownCVE **hit) {
    size_t n_cve = sizeof(cve_db) / sizeof(cve_db[0]);
    for (size_t i = 0; i < n_cve; i++) {
        if (strcmp(d->name, cve_db[i].name) != 0) continue;
        if (compare_version(d->version, cve_db[i].affected_below) < 0) {
            *hit = &cve_db[i];
            return 1;
        }
    }
    return 0;
}

int main(void) {
    size_t n_deps = sizeof(deps) / sizeof(deps[0]);
    int vulnerabilities = 0;

    printf("=== Pemindaian Dependency ===\n");
    printf("Total dependency terdaftar: %zu\n\n", n_deps);

    for (size_t i = 0; i < n_deps; i++) {
        const KnownCVE *hit = NULL;
        int vuln = scan_dependency(&deps[i], &hit);
        if (vuln) {
            printf("[VULN] %-8s %-8s -> %s (perbaiki ke >= %s)\n",
                   deps[i].name, deps[i].version, hit->cve_id, hit->affected_below);
            vulnerabilities++;
        } else {
            printf("[OK]   %-8s %-8s\n", deps[i].name, deps[i].version);
        }
    }

    printf("\nRingkasan: %d dari %zu dependency rentan.\n",
           vulnerabilities, n_deps);

    printf("\n=== Praktik yang Direkomendasikan ===\n");
    printf("- Pin versi eksplisit (mis. libfoo==1.3.0) di lock file.\n");
    printf("- Jalankan pemindaian otomatis pada CI setiap commit.\n");
    printf("- Berlangganan advisory (NVD, GitHub Advisory Database).\n");
    printf("- Update dependency minimal sekali per sprint.\n");
    printf("- Hapus dependency yang tidak dipakai (attack surface).\n");
    return 0;
}

Output yang Dihasilkan:

text
=== Pemindaian Dependency ===
Total dependency terdaftar: 4

[VULN] libfoo   1.2.0    -> CVE-2024-0001 (perbaiki ke >= 1.3.0)
[VULN] libbar   3.0.1    -> CVE-2024-0002 (perbaiki ke >= 3.1.0)
[VULN] libbaz   0.9.5    -> CVE-2023-9999 (perbaiki ke >= 1.0.0)
[OK]   libqux   2.1.0

Ringkasan: 3 dari 4 dependency rentan.

=== Praktik yang Direkomendasikan ===
- Pin versi eksplisit (mis. libfoo==1.3.0) di lock file.
- Jalankan pemindaian otomatis pada CI setiap commit.
- Berlangganan advisory (NVD, GitHub Advisory Database).
- Update dependency minimal sekali per sprint.
- Hapus dependency yang tidak dipakai (attack surface).

Mari kita telaah baris-baris kode di atas:

  1. Baris 13-22 (struct Dependency dan KnownCVE): Dua tipe data sederhana. Dependency mencatat nama dan versi yang dipakai proyek. KnownCVE mencatat nama library, versi minimum yang diperbaiki, dan ID CVE. Struktur ini menyederhanakan database CVE nyata yang berisi ribuan entri dengan field tambahan (severity CVSS, vektor serangan, platform terdampak).
  2. Baris 37-46 (compare_version): Pembanding versi semantik (semver). Memecah major.minor.patch jadi tiga integer dan bandingkan dari kiri ke kanan. Implementasi nyata harus menangani pre-release tag (1.2.0-rc1), build metadata, dan format non-semver (1.2, 2024.01).
  3. Baris 49-59 (scan_dependency): Untuk setiap dependency, loop mencari CVE yang nama library-nya cocok. Bila versi dependency lebih rendah dari affected_below, dependency tersebut rentan. Logika ini menangani kasus “rentan di bawah versi X” yang umum di CVE. Beberapa CVE juga memiliki affected range kompleks (rentan pada [1.0, 1.5] tetapi tidak pada 1.6+).
  4. Baris 62-82 (main): Loop memindai semua dependency, mencetak status. Tiga dari empat dependency terdeteksi rentan—kasus yang realistis pada banyak proyek yang jarang mengupdate.
  5. Output (ringkasan): Angka akhir 3 dari 4 memberi gambaran cepat untuk dashboard CI. Pada pipeline produksi, angka ini menjadi quality gate—build ditolak bila ada kerentanan kritis (CVSS >= 9).

Pembanding Versi Semver #

Pembanding versi adalah inti pemindai dependency. Berikut implementasi sederhana yang memecah major.minor.patch dan membandingkan dari kiri ke kanan:

c
/* extra_secure_dependency_management.c — bandingkan versi semver. */
#include <stdio.h>
#include <string.h>

static int compare_version(const char *a, const char *b) {
    int a1, a2, a3, b1, b2, b3;
    if (sscanf(a, "%d.%d.%d", &a1, &a2, &a3) != 3) return -2;
    if (sscanf(b, "%d.%d.%d", &b1, &b2, &b3) != 3) return -2;
    if (a1 != b1) return a1 < b1 ? -1 : 1;
    if (a2 != b2) return a2 < b2 ? -1 : 1;
    if (a3 != b3) return a3 < b3 ? -1 : 1;
    return 0;
}

int main(void) {
    struct { const char *a; const char *b; } cases[] = {
        { "1.2.0", "1.3.0" },
        { "2.0.0", "1.9.9" },
        { "1.0.0", "1.0.0" },
        { "1.10.0", "1.9.0" },
    };
    const size_t n = sizeof(cases) / sizeof(cases[0]);
    for (size_t i = 0; i < n; i++) {
        int r = compare_version(cases[i].a, cases[i].b);
        const char *op = (r < 0) ? "<" : (r > 0) ? ">" : "==";
        printf("%s %s %s\n", cases[i].a, op, cases[i].b);
    }
    return 0;
}

Output yang Dihasilkan:

text
1.2.0 < 1.3.0
2.0.0 > 1.9.9
1.0.0 == 1.0.0
1.10.0 > 1.9.0

Catatan: kasus keempat (1.10.0 > 1.9.0) memperlihatkan keunggulan pembanding numerik dibanding string. Bila kita memakai strcmp("1.10.0", "1.9.0"), hasil akan keliru 1.10.0 < 1.9.0 karena '1' < '9' secara leksikal. Pembanding numerik per komponen menghindari jebakan ini.

Database CVE Resmi #

Tiga sumber utama yang digunakan industri:

  • NVD (National Vulnerability Database): https://nvd.nist.gov —database resmi pemerintah AS, gratis, dapat diunduh sebagai JSON feed.
  • GitHub Advisory Database: https://github.com/advisories —berbasis kontribusi komunitas dengan integrasi langsung ke GitHub Dependabot.
  • OSV (Open Source Vulnerabilities): https://osv.dev —agregasi banyak sumber dengan API JSON.

Untuk C/C++ spesifik, ada sumber tambahan:

  • Conan Center: Package manager C++ dengan metadata CVE.
  • vcpkg registries: Microsoft C++ package manager dengan policy keamanan.
  • Snyk Open Source: Layanan komersial dengan database luas lintas bahasa.

Alat Produksi #

Beberapa alat otomatis untuk pemindaian dependency C/C++:

  • trivy: Pemindai vulnerability yang mendukung berbagai ekosistem. Dapat memindai conan.lock, vcpkg.json, dan binary yang dikompilasi.
  • syft + grype: Syft menghasilkan Software Bill of Materials (SBOM) dari binary; grype mencocokkan SBOM dengan database CVE.
  • nmap/nikto: Alat pengujian jaringan untuk mendeteksi layanan yang terekspos dan versinya—berguna bila dependency C membentuk server.
  • bandit (konseptual): Meskipun bandit untuk Python, paradigma static analysis linting serupa untuk C adalah cppcheck dengan plugin keamanan.
  • Clang Static Analyzer: Bagian dari LLVM yang dapat mendeteksi pola rentan di kode dan header library.

Software Bill of Materials (SBOM) #

Standar modern (NTIA, executive order AS 14028) mewajibkan setiap binary rilis dilengkapi SBOM—daftar semua komponen dan versinya. Format standar:

  • SPDX: ISO/IEC 5962:2021. Berbasis teks/key-value.
  • CycloneDX: OWASP, JSON/XML. Lebih ringan, populer untuk CI/CD.

Pipeline produksi menghasilkan SBOM otomatis dari build:

bash
# Contoh dengan syft
syft dir:./build -o cyclonedx-json > sbom.json
# Pindai SBOM dengan grype
grype sbom:./sbom.json --fail-on high

--fail-on high menyebabkan pipeline gagal bila ada CVE dengan severity high atau critical. Gate ini mencegah rilis binary dengan kerentanan serius.

Pinning dan Reproducibility #

Pinning versi eksplisit memberikan dua manfaat: keamanan dan reproduksibilitas build. Tanpa pinning, build hari ini dan besok bisa berbeda—sulit untuk debugging dan audit.

cmake
# CMakeLists.txt — contoh pinning
find_package(OpenSSL 3.0.12 REQUIRED EXACT)
find_package(zlib 1.3.0 REQUIRED EXACT)

EXACT menjamin versi yang dipakai persis—tidak ada upgrade diam-diam.