Skip to content

chemrid/nmap-sast-report

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Аналитический отчёт по безопасности — nmap-unprivileged

Проект: chemrid/nmap-unprivileged Анализируемый коммит: 5da159d77 (ветка master) Дата анализа: 2026-03-29 Платформа запуска: Apple M1 / Docker debian:bookworm Инструменты: Cppcheck 2.10 · Flawfinder 2.0.19 · Semgrep OSS 1.156.0 · ShellCheck 0.9.0 GitHub Code Scanning: SARIF загружен, результаты доступны в разделе Security → Code scanning


1. Резюме для руководства

nmap-unprivileged — форк nmap, адаптированный для работы без root/CAP_NET_RAW на изолированных (air-gapped) системах Linux. Команда удалила пути с повышением привилегий (SYN-сканирование, UDP-сканирование, определение ОС) и добавила офлайн-сборку с бандлингом OpenSSL 3.4.1.

Главный вывод: введённые изменения не добавили ни одной уязвимости. Модификации улучшают профиль безопасности проекта относительно upstream nmap.

Все сколько-нибудь значимые находки принадлежат оригинальному коду nmap и существовали до форка. Критических уязвимостей в scope проекта не обнаружено.


2. Скоуп и методология

Слой Файлы Приоритет анализа
Наши изменения nmap.cc, NmapOps.cc, libnetutil/PacketElement.h, libnetutil/netutil.cc, build-offline.sh Все инструменты, ручной ревью
Ядро nmap 24 upstream .cc-файла Cppcheck + Flawfinder
Бандлированные зависимости openssl/ssl/, openssl/crypto/ Cppcheck summary, CVE-мониторинг

Стандарты: OWASP Top 10 · CWE Top 25 · CERT C/C++ Coding Standard

Методика Cppcheck для nmap.cc: три независимых прохода (HAVE_GETADDRINFO=0, UHAVE_GETADDRINFO, HAVE_GETADDRINFO=1 с --max-configs=5) для исключения False Negative за счёт нераскрытых конфигурационных путей. Результаты дедуплицированы.


3. Матрица рисков

ID Файл Строка CWE Severity Инструмент Риск Статус
F-01 libnetutil/netutil.cc 162, 181 CWE-134 Level 4 Flawfinder LOW Upstream · FP в контексте
F-02 traceroute.cc 1385 CWE-457 Error Cppcheck LOW Upstream · реальный дефект
F-03 nmap.cc 1658 CWE-788 Warning Cppcheck INFO Upstream · False Positive
F-04 nmap.cc 2704 CWE-807/20 Level 3 Flawfinder INFO Upstream · не нарушение модели угроз
F-05 osscan.cc, service_scan.cc, tcpip.cc, nmap.cc multiple CWE-682/476 Warning Cppcheck INFO Upstream · redundant-check pattern
F-06 libnetutil/netutil.cc 2169+ CWE-475 Portability Cppcheck INFO Upstream · теоретический UB
F-07 osscan.h 204 CWE-398 Warning Cppcheck INFO Upstream · quality
F-08 build-offline.sh 31 SC2038 ShellCheck INFO Наш файл · приемлемо

4. Детальный разбор находок

F-01 — CWE-134: Format String в функциях вывода ошибок

Файл: libnetutil/netutil.cc:162, 181 Инструмент: Flawfinder Level 4 Код:

void netutil_fatal(const char *str, ...) {
    va_list list;
    va_start(list, str);
    vfprintf(stderr, str, list);   // ← L162: str является format string
    ...
}

int netutil_error(const char *str, ...) {
    va_list list;
    va_start(list, str);
    vfprintf(stderr, str, list);   // ← L181: аналогично
    ...
}

Оценка: Это корректный паттерн для вариативной функции логирования — str является format string по контракту. Уязвимость возникает только если вызывающий код передаёт user-controlled строку без форматных аргументов: netutil_fatal(user_input).

Проверка всех вызывающих мест по коду nmap показывает, что str всегда является строковым литералом (netutil_fatal("Error: %s", ...)) — нет ни одного места, где пользовательский ввод подставляется как первый аргумент напрямую.

Вердикт: False positive в данном контексте. Паттерн не нарушает принципов безопасного использования variadic-функций при соблюдении существующих соглашений кода.

Рекомендация: Upstream — добавить аннотацию __attribute__((format(printf, 1, 2))) к сигнатурам функций. Это превратит потенциальное runtime-нарушение в compile-time ошибку и снимет сигнал с детекторов.


F-02 — CWE-457: Использование неинициализированного члена структуры

Файл: traceroute.cc:1385 Инструмент: Cppcheck (единственный error-level в ядре) Код:

TracerouteHop hop;               // POD struct, rtt не инициализирован

if (p->addr.ss_family == 0) {
    hop.timedout = true;
    // hop.rtt НЕ присваивается
} else {
    hop.timedout = false;
    hop.rtt = p->rtt;            // rtt присваивается только в этой ветке
}
hop.name = p->hostname;
hop.addr = p->addr;
hop.ttl  = p->ttl;
(*it)->target->traceroute_hops.push_front(hop);   // L1385: hop.rtt = garbage если timedout

Оценка: Реальный дефект — при timedout=true в список попадает hop с неинициализированным float rtt (значение из stack). Это не приводит к прямой уязвимости: traceroute — диагностический режим, данные не влияют на логику сканирования и не экспортируются как бинарные данные.

Однако утечка stack-значений в XML/grepable-вывод (-oX) теоретически возможна, если потребитель рендерит rtt для timed-out хопов без проверки флага timedout. Проверка output.cc показывает, что вывод RTT обусловлен !hop.timedout, поэтому в практике garbage-значение не попадает в отчёт.

Вердикт: Дефект существует, но не эксплуатабелен в данной конфигурации. Upstream-код.

Рекомендация: Инициализировать hop.rtt = 0.0f; при timedout=true — однострочный патч в upstream. Можно вынести в отдельный тикет.


F-03 — CWE-788: Out-of-Bounds в обработке MAC-адреса

Файл: nmap.cc:1658 Инструмент: Cppcheck Warning Код:

u8 mac_data[6];
int pos = 0;
// ...
if (pos >= 6)
    fatal("Bogus --spoof-mac ...");   // exit() — никогда не возвращает
mac_data[pos] = (u8) strtol(tmphex, NULL, 16);   // L1658: pos < 6 гарантирован
pos++;

Оценка: False positive. Cppcheck интерпретирует if (pos >= 6) fatal(...) как неполную защиту, не зная что fatal() вызывает exit() и никогда не возвращает управление. При pos >= 6 запись в массив недостижима — инвариант гарантирован guard'ом.

Вердикт: False positive. Реального OOB нет.

Рекомендация upstream: Добавить [[noreturn]] или __attribute__((noreturn)) к fatal() — это снимет ложные срабатывания во всём codebase.


F-04 — CWE-807/CWE-20: Использование переменной окружения NMAPDIR

Файл: nmap.cc:2704 Инструмент: Flawfinder Level 3 Код:

if (!foundsomething && (dirptr = getenv("NMAPDIR"))) {
    res = Snprintf(filename_returned, bufferlen, "%s/%s", dirptr, file);
    if (res > 0 && res < bufferlen) {
        foundsomething = file_is_readable(filename_returned);
    }
}

Оценка: В suid/setuid-бинарном файле управление NMAPDIR позволило бы атаковать повышение привилегий через path traversal. Однако нmap-unprivileged специфически спроектирован для работы без привилегий — бинарный файл запускается от имени того же пользователя, который контролирует своё окружение. Path traversal в этом контексте ограничен правами запускающего пользователя, то есть не является нарушением модели угроз.

Буфер ограничен через Snprintf(... bufferlen ...) с проверкой res < bufferlen — нет возможности переполнения.

Вердикт: Не нарушение модели угроз для unprivileged-бинарника. Upstream-код.


F-05 — CWE-682/CWE-476: Паттерн redundant-check с NULL-арифметикой

Файлы: osscan.cc, service_scan.cc, tcpip.cc, nmap.cc Количество: 15 (CWE-682) + 12 (CWE-476) = 27 warning Паттерн:

if (!p)
    // какое-то действие
// ...позже...
result = end - p;   // Cppcheck: либо !p redundant, либо pointer arithmetic with NULL

Оценка: Это системный паттерн в nmap codebase — код проверяет указатель, а затем использует его в арифметике, и Cppcheck не может статически определить, возможен ли путь без проверки. В большинстве случаев это защитное программирование (defensive coding), где проверка на NULL исторически добавлялась для надёжности, а арифметика гарантированно выполняется только в ненулевом пути.

Несколько мест заслуживают ручного ревью при следующем аудите (особенно tcpip.cc:1146-1223 где одна проверка !packet закрывает два последовательных использования).

Вердикт: Upstream шум. Не демонстрирует активную эксплуатируемость.


F-06 — CWE-475: NULL в variadic-функциях (STRAPP)

Файл: libnetutil/netutil.cc:2169, 2191, 2195, 2277, 2305, 2313, 2320 Инструмент: Cppcheck Portability

static inline char* STRAPP(const char *fmt, ...) {
    if(!fmt) { bp = 0; return(buf); }  // ← при NULL fmt variadic args игнорируются
    va_list ap;
    va_start(ap, fmt);
    bp += Vsnprintf(buf+bp, left, fmt, ap);
    va_end(ap);
    ...
}
STRAPP(NULL, NULL);    // reset buffer — NULL никогда не читается через va_arg
STRAPP(" EOL", NULL);  // форматная строка без %s/%d — NULL игнорируется Vsnprintf

Оценка: По стандарту C передача NULL как целочисленного 0 в variadic-функцию технически является UB, если функция ожидает указатель. На практике это безопасно на всех поддерживаемых платформах, и сама функция не читает variadic args при fmt==NULL. Комментарий // TODO: Needs refactoring в upstream подтверждает осведомлённость разработчиков.

Вердикт: Теоретический UB, не эксплуатируем. Upstream portability noise.


F-07 — CWE-398: Shallow copy в FingerTest (osscan.h)

Файл: osscan.h:202,204 Инструмент: Cppcheck Warning

struct FingerTest {
    // ...
    std::vector<struct AVal> *results;  // указатель на динамически выделенную память
    // Нет operator=, copy constructor копирует указатель (shallow copy)
};

Оценка: При копировании FingerTest оба объекта указывают на одну область памяти. При уничтожении одного объекта второй получает dangling pointer. OS detection (-O) отключён в нmap-unprivileged на уровне runtime-проверки, поэтому этот код недостижим в production-конфигурации.

Вердикт: Upstream дефект, недостижим в нашей конфигурации (OS detection отключён).


F-08 — ShellCheck SC2038: find | xargs без -print0

Файл: build-offline.sh:31 Инструмент: ShellCheck

find . -type f \( ... \) | xargs sed -i 's/\r$//'

Оценка: Если путь к файлу содержит пробелы или спецсимволы, xargs разобьёт его на несколько аргументов, передав sed несуществующие имена. В контексте build-скрипта на контролируемых исходниках nmap это не приводит к уязвимости — в худшем случае CRLF-fix пропустит часть файлов, что вызовет ошибку сборки, а не эксплуатируемое состояние.

Для продакшн-скриптов рекомендуется find ... -print0 | xargs -0.

Вердикт: Принято. Может быть исправлено одной строкой при следующем изменении build-offline.sh.


5. Оценка наших изменений

Ключевой вопрос безопасности: не ввели ли модификации новых уязвимостей и корректно ли удалены пути повышения привилегий?

5.1 Удаление raw-socket путей (nmap.cc, NmapOps.cc)

Cppcheck на двух проходах (HAVE_GETADDRINFO=0 и UHAVE_GETADDRINFO) и на полном проходе (HAVE_GETADDRINFO=1, --max-configs=5) показал идентичные результаты — изменения в диапазоне строк нашей правки не дали ни одной новой находки.

Дополнительное подтверждение: NmapOps.cc:347 и NmapOps.cc:361 помечены Cppcheck как CWE-561 unusedFunction — это означает, что функции RawScan() и ValidateOptions() действительно недостижимы в нашей конфигурации. Это именно то поведение, которого мы добивались.

5.2 Добавление #include <cstring> (libnetutil/PacketElement.h)

Механическое изменение для совместимости с GCC 9+. Не влияет на runtime-семантику.

5.3 build-offline.sh

Flawfinder на этом файле: 0 Level 2+ hits. ShellCheck: 2 предупреждения (F-08 выше). Скрипт запускается только в build-окружении, не в production.

5.4 Semgrep с загруженным рулсетом p/c

Запуск с сетевым доступом (268 файлов, рулсет p/c): 0 findings. Это наиболее значимый результат по нашим изменениям — паттернный анализ Semgrep не выявил ни одного известного уязвимого паттерна.


6. Состояние OpenSSL 3.4.1

  • CVE-статус: Ветка 3.4.x не имела критических CVE на момент включения (февраль 2025). Мониторинг: https://www.openssl.org/news/vulnerabilities.html
  • Linkage: статическая сборка — nm на итоговом бинарнике не показывает символов OpenSSL. Уязвимости OpenSSL не наследуются через shared library.
  • Cppcheck: 126 находок на openssl/ssl/ и openssl/crypto/ — ожидаемые false positive в 300k-строчном codebase, у которого есть собственный внутренний пайплайн статического анализа.

7. Приоритеты действий

Приоритет 1 — Не требует изменений (принято, обосновано)

Действие Основание
F-03 (CWE-788), F-04 (CWE-807) — закрыть как accepted False positive / вне модели угроз
F-06 (CWE-475) — закрыть как accepted Теоретический UB, безопасен на практике
F-07 (CWE-398, shallow copy) — закрыть как N/A OS detection недостижим в нашей конфигурации

Приоритет 2 — Рекомендация в upstream nmap (не блокирует деплой)

# Находка Рекомендуемое действие Усилие
1 F-02: traceroute.cc:1385 — uninit hop.rtt Добавить hop.rtt = 0.0f; в ветку timedout=true 1 строка
2 F-01: netutil_fatal/netutil_error — CWE-134 Добавить __attribute__((format(printf,1,2))) 2 строки
3 F-03: fatal() без [[noreturn]] Добавить аннотацию — снимет весь кластер CWE-788 FP 1 строка

Приоритет 3 — Технический долг в нашем коде (следующий спринт)

# Находка Рекомендуемое действие
1 F-08: build-offline.sh:31 SC2038 find ... -print0 | xargs -0 sed -i ...
2 SC2034: OPENSSL_LIBDIR unused Удалить переменную или использовать в configure-аргументах

Приоритет 4 — Процессные улучшения (после деплоя)

# Рекомендация
1 CVE-мониторинг OpenSSL: настроить подписку на openssl-announce@openssl.org или Dependabot alerts для версии 3.4.x
2 CI/CD интеграция: добавить в pipeline запуск shellcheck build-offline.sh и cppcheck на changed files при каждом PR (SARIF уже настроен в GitHub Code Scanning)
3 Semgrep в CI: при наличии сетевого доступа в pipeline добавить semgrep scan --config p/c — 0 findings подтверждает чистую базу
4 Повторный аудит при изменении модели угроз: если в будущем бинарник получит setuid или CAP_NET_ADMIN, F-04 (NMAPDIR) потребует ревью

8. Итоговое заключение

Проект nmap-unprivileged прошёл полный цикл SAST-анализа тремя независимыми инструментами на трёх конфигурационных путях Cppcheck. Совокупные результаты:

По нашим изменениям:

  • 0 уязвимостей введено
  • Модификации корректно удаляют пути повышения привилегий (подтверждено CWE-561 unusedFunction на отключённых функциях)
  • 0 findings от Semgrep p/c (268 файлов)
  • 2 ShellCheck-предупреждения в build-скрипте — приемлемо, не влияют на безопасность

По upstream nmap:

  • 1 реальный дефект (traceroute.cc:1385, CWE-457) — не эксплуатируем в нашем deployment
  • Остальные warning-level находки — false positive, redundant-check паттерн или upstream quality-debt без security-impacta

Вывод: Проект готов к деплою на целевых платформах (RHEL / Astra Linux / Debian) в режиме без привилегий. Риск-аппетит принят.


Приложение A — Инструменты и конфигурация

Инструмент Версия Конфигурация
Cppcheck 2.10 --enable=all --max-configs=3 -j4 --std=c++14
Cppcheck (nmap.cc) 2.10 3 прохода: HAVE_GETADDRINFO=0, UHAVE_GETADDRINFO, HAVE_GETADDRINFO=1 --max-configs=5
Flawfinder 2.0.19 --minlevel=1, CSV+HTML output
Semgrep 1.156.0 p/c, p/bash с сетевым доступом
ShellCheck 0.9.0 -S warning

Приложение B — Файлы результатов

results/
├── cppcheck-nmap-cc-combined.txt         # nmap.cc: дедуплицированные passes 1+2
├── cppcheck-nmap-cc-getaddrinfo1.txt     # nmap.cc: HAVE_GETADDRINFO=1 --max-configs=5
├── cppcheck-nmap.txt                     # 24 файла ядра nmap
├── cppcheck-ours.txt                     # Наши изменения
├── cppcheck-openssl.txt                  # OpenSSL 3.4.1 (reference)
├── cppcheck.sarif                        # SARIF 2.1.0 → GitHub Code Scanning
├── flawfinder-ours.csv                   # Наши файлы (machine-readable)
├── flawfinder-ours.html                  # Наши файлы (human-readable)
├── semgrep-c-network.json                # p/c с сетью: 0 findings
├── semgrep-sh-network.json               # p/bash с сетью: 0 findings
└── shellcheck-build.txt                  # build-offline.sh

About

SAST Security Analysis Report — nmap-unprivileged

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors