PAM Python Bindings: Double-Free-Fehler

Wie das IT-Sicherheitsunternehmen LSE vor kurzem meldete, findet sich eine kritische Sicherheitslücke in den PAM Python Bindings (PyPam). Laut Advisory ist ein entfernter Angreifer dadurch in der Lage, Befehle mit höheren Rechten auszuführen.

PyPam kommt häufig zum Einsatz, da es im Vergleich zum C-API viel einfacher zu verwenden ist. Die nun entdeckte Schwachstelle entsteht durch einen Programmierfehler in der Funktion “PyPAM_conv()” in der Quelltextdatei “PAMmodule.c”:

static int PyPAM_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{ ... *resp = (struct pam_response *) malloc(PyList_Size(respList) * sizeof(struct pam_response)); spr = *resp; for (i = 0; i < PyList_Size(respList); i++, spr++) { respTuple = PyList_GetItem(respList, i); resp_retcode = 0; if (!PyArg_ParseTuple(respTuple, "si", &resp_text, &resp_retcode)) { free(*resp); Py_DECREF(respList); return PAM_CONV_ERR; } spr->resp = strdup(resp_text); spr->resp_retcode = resp_retcode; } Py_DECREF(respList); return PAM_SUCCESS;
}

Diese Funktion ist für das Verarbeiten von Konversations-Hooks (Callbacks) zuständig. Ein Programmierer kann eine eigene Python-Konversations-Routine schreiben, über die der Anwender mit dem PAM-Interface interagiert. Diese Funktion wird an “pam_start()” übergeben. Dabei wird unter anderem auch Speicherplatz für den Zeiger “*resp” alloziert. Hier kommt “PyArg_ParseTuple()” zum Einsatz, um die Struktur “pam_response” zu füllen. Falls dabei etwas schiefgeht, wird der allozierte “*resp”-Speicher via “free()” freigegeben und der Fehler-Code “PAM_CONV_ERR” zurückgeliefert, der anzeigt, dass etwas während der Interaktion schiefging.

Das Problem besteht nun darin, dass die anderen Teile der Bibliothek Libpam von der Freigabe des “*resp”-Speichers nichts mitbekommen. So gibt “v_prompt.c” diesen Speicher nochmals frei, was zu einem typischen Double-Free-Fehler führt.

Es bleibt die Frage, ob man diesen Fehler absichtlich hervorrufen kann. Wie sich herausstellte, genügt es beispielsweise, Null-Zeichen für das Passwort zu verwenden, um “(!PyArg_ParseTuple())” zu erfüllen. Das folgende einfache Python-Beispiel demonstriert das Problem:

#!/usr/bin/env python
##
## python-pam 0.4.2 double free PoC
##
## 2012 Leading Security Experts GmbH
## Markus Vervier
##
# -*- coding: utf-8 -*-
def verify_password(user, password): import PAM def pam_conv(auth, query_list, userData): resp = [] resp.append( (password, 0)) return resp res = -3 service = 'passwd' auth = PAM.pam() auth.start(service) auth.set_item(PAM.PAM_USER, user) auth.set_item(PAM.PAM_CONV, pam_conv) try: auth.authenticate() auth.acct_mgmt() except PAM.error, resp: print 'Go away! (%s)' % resp res = -1 except: print 'Internal error' res = -2 else: print 'Good to go!' res = 0 return res
print verify_password("root", "a\x00secret")

Hier wird die Funktion “pam_conv()” als Konversations-Callback-Funktion via “auth.set_item(PAM.PAM_CONV, pam_conv)” gesetzt. Der anschließende Try-Block führt eine Authentifikation via PAM mit diesem Hook durch. Dabei werden in das Passwort gezielt Nullzeichen eingestreut, was zur doppelten Speicher-Freigabe von “*resp” führt. Eine solche Attacke kann je nach Applikation sowohl ein entfernter als auch ein lokaler Angreifer durchführen.

Diese Schwachstelle lässt sich sehr einfach beheben: Es genügt, “*resp” nach der Freigabe auf NULL zu setzen. Dies löst das Problem, denn der Code in “v_prompt.c” gibt “*resp “nur dann frei, wenn dieser Zeiger ungleich NULL ist.

Dieser Fehler wurde bereits September 2008 in Rpath Linux korrigiert.

Betroffen sind die folgenden Systeme und Versionen:

  • PyPAM <= 0.4.2
  • Red Hat PyPAM <= 0.5.0-12
  • Debian python-pam <= 0.4.2-12.2
  • Ubuntu python-pam <= 0.4.2-12.2
  • Suse python-pam <= 0.5.0-79.1.2
  • Gentoo pypam <= 0.5.0
Nach oben