Tanulj meg C és C++ programozást a teljesen új tesztelési útmutatónk segítségével.
CYBERSECURITY KIEMELT ELEMZÉS

Tanulj meg C és C++ programozást a teljesen új tesztelési útmutatónk segítségével.

FORRÁS

The Trail of Bits Blog

DATE

READ

5 perc olvasás

Új fejezet került be a Tesztképzőkönyvbe, amely biztonsági ellenőrzési listát tartalmaz C és C++ kódhoz. Ez az általános hibák típusait azonosítja és Linux, Windows és seccomp környezetekhez kategorizálja, hangsúlyozva a …

Adjunkítottunk egy új fejezetet a tesztelési kézi útmutatásunkba: egy átfogó biztonsági ellenőrzőlistát a C és C++ kódhoz. Azonosítottunk egy széles körű gyakori hibák, ismert “csapdák” és API-k problémák körének, amely a C és C++ kódalapokat érint, és ezeket csoportosítottuk Linux, Windows és seccomp területen. Míg más kézi útmutatás fejezetei a statikus és dinamikus elemzésra összpontosítanak, ez a fejezet erős alapot biztosít a manuális kódellenőrzéshez. Az LLM-ek rajongói örülhetnek: mi is fejlesztünk egy Claude-képességet, amely ezt az új fejezetet használja. Ez a listát hibák megtalálásához olyan promptokká alakítja, amelyeket egy LLM futtathat egy kódalapon, és platform és veszélymodellt is figyelembe vesz. Kérjük, próbálja ki, amikor kiadjuk. És miután elolvassa a fejezetet, tesztelheti a C/C++ ellenőrzési készségeit két kihívás segítségével, amely a poszt végén található. Ha a 10-edik helyen van, amikor helyes válaszokat küld, Trail of Bits ajándékot kap. Mit tartalmaz a fejezet? A fejezet öt területet fed le: általános hibák, Linux usermode és kernel, Windows usermode és kernel, valamint seccomp/BPF sandboxok. A nyelvi szintű problémák területén, a hibák kategóriájában a fejezet a memória biztonságával, az egész szám típushibákkal, a típusok keveredésével és a fordító által bevezetett hibákkal kezdődik, majd egyre specifikusabb környezeti szempontokra halad. A Linux usermode rész a libc “csapdáit” tárgyalja. Ez a rész a legtöbb POSIX rendszerre is alkalmazható. A problémák kategóriája a jól ismert problémáktól a string-metódokhoz, a “csapdákhoz” a jogosultság-csökkentéshez és az környezeti változókezeléshez. A Linux kernel bonyolult, és egyetlen ellenőrzőlista sem tudja lefedni a teljes működését. Azonban a mi új tesztelési kézi útmutatásunk megadja a kezdeti pontot a driverek és modulok manuális ellenőrzésének elindításához. A Windows részletek a DLL-ek “csapdáit”, a CreateProcess-ben található, nem idézett útvonal-sebezhetőségeket és a path traversal problémákat tárgyal. Ez a legutóbbi hibakategória a WorstFit Unicode hibákat, amelyek lehetővé teszik, hogy a karaktereket a alapvető ANSI-készleten kívül újra értelmeződjön, így teljesen megkerülve a path ellenőrzéseket. A kernel rész a driver-specifikus problémákat, például a eszköz-hozzáférési szabályzatokat, a szolgáltatási elutasításokat a rosszul használt spinlock használatán, a biztonsági problémákat, amelyek a usermode-ból a kernelbe továbbított kezelésekből származnak, valamint a Windows kernel API-inak különböző “csapdáit” tárgyalja. A Linux seccomp és BPF funkciókat gyakran sandbox-okhoz használják. Bár a Landlock és a névterelemek modern eszközök léteznek, mégis a régi funkciók kombinációját használjuk az audit során. Mindig sok problémát találunk. A új tesztelési kézi útmutatás a sandbox elkerüléseit, mint például az io_uring hívásokat, amelyek a BPF-szűrő nem látják, a CLONE_UNTRACED-et, amely lehetővé teszi, hogy a “tracet” hatékonyan deaktiválja a seccomp-szűrőket, és a ptrace-alapú sandboxokban a memória szintű versenyt, fedezik. Ellenőrzési készségek tesztelése Mi két kihívást biztosítottunk, amelyek a listában található valódi hibákat tartalmazzák. Próbálja meg azokat megtalálni, majd küldje el a válaszokat. Ha a 10-edik helyen van, amikor helyes válaszokat küld, Trail of Bits ajándékot kap. A kihívás a 17-én zárul, ezért küldje el a válaszokat azt megelőzően. Elakad? Semmi baj. A válaszokat egy kövendező blogbejegyzésben publikáljuk, így ne felejtsen meg #like-olni és #subscribe-olni, amely azt jelenti, hogy hozzon létre RSS-feed-et. A Linux libc-jének sok “csapdája” Ebben a egyszerű ping programban két libc “csapda” van, amelyek a programot könnyen kihasználhatóvá teszik. Meg tudja találni és elmagyarázni a problémákat? Ha nem, akkor nézze meg a kézi útmutatást. Mindkét hiba a Linux usermode részben található. #include #include #include #include #define ALLOWED_IP 127.3.3.1 int main() { char ip_addr[128]; struct in_addr to_ping_host, trusted_host; // get address if (!fgets(ip_addr, sizeof(ip_addr), stdin)) return 1; ip_addr[strcspn(ip_addr, \n)] = 0; // verify address if (!inet_aton(ip_addr, &to_ping_host)) return 1; char *ip_addr_resolved = inet_ntoa(to_ping_host); // prevent SSRF if ((ntohl(to_ping_host.s_addr) » 24) == 127) return 1; // only allowed if (!inet_aton(ALLOWED_IP, &trusted_host)) return 1; char trusted_resolved = inet_ntoa(trusted_host); if (strcmp(ip_addr_resolved, trusted_resolved) != 0) return 1; // ping char cmd[256]; snprintf(cmd, sizeof(cmd), ping ‘%s’, ip_addr); system(cmd); return 0; } Windows driver registry gotchas Ez a Windows Driver Framework (WDF) driver request handler a product version-eket a registry-ből kérdezi. Itt több hiba van, beleértve a könnyen kihasználható szolgáltatási elutasítást, de egyik a registry-ben található értékek manipulálásával a kernel-ben való kód végrehajtásához vezet. Meg tudja találni a hibát és a kihasználást? NTSTATUS InitServiceCallback( In WDFREQUEST Request ) { NTSTATUS status; PWCHAR regPath = NULL; size_t bufferLength = 0; // fetch the product registry path from the request status = WdfRequestRetrieveInputBuffer(Request, 4, ®Path, &bufferLength); if (!NT_SUCCESS(status)) { TraceEvents( TRACE_LEVEL_ERROR, TRACE_QUEUE, %!FUNC! Failed to retrieve input buffer. Status: %d, (int)status ); return status; } / check that the buffer size is a null-terminated Unicode (UTF-16) string of a sensible size */ if (bufferLength < 4 || bufferLength > 512 || (bufferLength % 2) != 0 || regPath[(bufferLength / 2) - 1] != L’\0’) { TraceEvents( TRACE_LEVEL_ERROR, TRACE_QUEUE, %!FUNC! Buffer length %d was incorrect., (int)bufferLength ); return STATUS_INVALID_PARAMETER; } ProductVersionInfo version = { 0 }; HandlerCallback handlerCallback = NewCallback; int readValue = 0; // read the major version from the registry RTL_QUERY_REGISTRY_TABLE regQueryTable[2]; RtlZeroMemory(regQueryTable, sizeof(RTL_QUERY_REGISTRY_TABLE) * 2); regQueryTable[0].Name = LMajorVersion; regQueryTable[0].EntryContext = &readValue; regQueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT; regQueryTable[0].QueryRoutine = NULL; status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE, regPath, regQueryTable, NULL, NULL ); if (!NT_SUCCESS(status)) { TraceEvents( TRACE_LEVEL_ERROR, TRACE_QUEUE, %!FUNC! Failed to query registry. Status: %d, (int)status ); return status; } TraceEvents( TRACE_LEVEL_INFORMATION, TRACE_QUEUE, %!FUNC! Major version is %d, (int)readValue ); version.Major = readValue; if (version.Major < 3) { // versions prior to 3.0 need an additional check RtlZeroMemory(regQueryTable, sizeof(RTL_QUERY_REGISTRY_TABLE) * 2); regQueryTable[0].Name = LMinorVersion; regQueryTable[0].EntryContext = &readValue; regQueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT; regQueryTable[0].QueryRoutine = NULL; status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE, regPath, regQueryTable, NULL, NULL ); if (!NT_SUCCESS(status)) { TraceEvents( TRACE_LEVEL_ERROR, TRACE_QUEUE, %!FUNC! Failed to query registry. Status: %d, (int)status ); return status; } TraceEvents( TRACE_LEVEL_INFORMATION, TRACE_QUEUE, %!FUNC! Minor version is %d, (int)readValue ); version.Minor = readValue; if (!DoesVersionSupportNewCallback(version)) { handlerCallback = OldCallback; } } SetGlobalHandlerCallback(handlerCallback); }