diff --git a/python/ql/src/experimental/Security/CWE-285/PamAuthorization.ql b/python/ql/src/experimental/Security/CWE-285/PamAuthorization.ql index e67745cceac..3f11728de4e 100644 --- a/python/ql/src/experimental/Security/CWE-285/PamAuthorization.ql +++ b/python/ql/src/experimental/Security/CWE-285/PamAuthorization.ql @@ -13,46 +13,24 @@ import semmle.python.ApiGraphs import experimental.semmle.python.Concepts import semmle.python.dataflow.new.TaintTracking -private class LibPam extends API::Node { - LibPam() { - exists( - API::Node cdll, API::Node find_library, API::Node libpam, API::CallNode cdll_call, - API::CallNode find_lib_call, StrConst str - | - API::moduleImport("ctypes").getMember("CDLL") = cdll and - find_library = API::moduleImport("ctypes.util").getMember("find_library") and - cdll_call = cdll.getACall() and - find_lib_call = find_library.getACall() and - DataFlow::localFlow(DataFlow::exprNode(str), find_lib_call.getArg(0)) and - str.getText() = "pam" and - cdll_call.getArg(0) = find_lib_call and - libpam = cdll_call.getReturn() - | - libpam = this - ) - } - - override string toString() { result = "libpam" } -} - -class PamAuthCall extends API::Node { - PamAuthCall() { exists(LibPam pam | pam.getMember("pam_authenticate") = this) } - - override string toString() { result = "pam_authenticate" } -} - -class PamActMgt extends API::Node { - PamActMgt() { exists(LibPam pam | pam.getMember("pam_acct_mgmt") = this) } - - override string toString() { result = "pam_acct_mgmt" } -} - -from PamAuthCall p, API::CallNode u, Expr handle -where - u = p.getACall() and - handle = u.asExpr().(Call).getArg(0) and - not exists(PamActMgt pam | - DataFlow::localFlow(DataFlow::exprNode(handle), - DataFlow::exprNode(pam.getACall().asExpr().(Call).getArg(0))) +API::Node libPam() { + exists(API::CallNode findLibCall, API::CallNode cdllCall, StrConst str | + findLibCall = API::moduleImport("ctypes.util").getMember("find_library").getACall() and + cdllCall = API::moduleImport("ctypes").getMember("CDLL").getACall() and + DataFlow::localFlow(DataFlow::exprNode(str), findLibCall.getArg(0)) and + str.getText() = "pam" and + cdllCall.getArg(0) = findLibCall + | + result = cdllCall.getReturn() ) -select u, "This PAM authentication call may be lead to an authorization bypass." +} + +from API::CallNode authenticateCall, DataFlow::Node handle +where + authenticateCall = libPam().getMember("pam_authenticate").getACall() and + handle = authenticateCall.getArg(0) and + not exists(API::CallNode acctMgmtCall | + acctMgmtCall = libPam().getMember("pam_acct_mgmt").getACall() and + DataFlow::localFlow(handle, acctMgmtCall.getArg(0)) + ) +select authenticateCall, "This PAM authentication call may be lead to an authorization bypass." diff --git a/python/ql/src/experimental/Security/CWE-285/PamAuthorizationBad.py b/python/ql/src/experimental/Security/CWE-285/PamAuthorizationBad.py index 3b06156f551..257f9b99729 100644 --- a/python/ql/src/experimental/Security/CWE-285/PamAuthorizationBad.py +++ b/python/ql/src/experimental/Security/CWE-285/PamAuthorizationBad.py @@ -1,11 +1,9 @@ def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True): libpam = CDLL(find_library("pam")) pam_authenticate = libpam.pam_authenticate - pam_acct_mgmt = libpam.pam_acct_mgmt pam_authenticate.restype = c_int pam_authenticate.argtypes = [PamHandle, c_int] - pam_acct_mgmt.restype = c_int - pam_acct_mgmt.argtypes = [PamHandle, c_int] + handle = PamHandle() conv = PamConv(my_conv, 0) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-285/PamAuthorization.expected b/python/ql/test/experimental/query-tests/Security/CWE-285/PamAuthorization.expected index 52c4c8ac669..cde7271874a 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-285/PamAuthorization.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-285/PamAuthorization.expected @@ -1 +1 @@ -| bad.py:92:18:92:44 | ControlFlowNode for pam_authenticate() | This PAM authentication call may be lead to an authorization bypass. | +| pam_test.py:44:18:44:44 | ControlFlowNode for pam_authenticate() | This PAM authentication call may be lead to an authorization bypass. | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-285/bad.py b/python/ql/test/experimental/query-tests/Security/CWE-285/bad.py deleted file mode 100644 index 84527d6f6fb..00000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-285/bad.py +++ /dev/null @@ -1,95 +0,0 @@ -from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, byref, sizeof -from ctypes import c_void_p, c_size_t, c_char_p, c_char, c_int -from ctypes import memmove -from ctypes.util import find_library - -class PamHandle(Structure): - _fields_ = [ ("handle", c_void_p) ] - - def __init__(self): - Structure.__init__(self) - self.handle = 0 - -class PamMessage(Structure): - """wrapper class for pam_message structure""" - _fields_ = [ ("msg_style", c_int), ("msg", c_char_p) ] - - def __repr__(self): - return "" % (self.msg_style, self.msg) - -class PamResponse(Structure): - """wrapper class for pam_response structure""" - _fields_ = [ ("resp", c_char_p), ("resp_retcode", c_int) ] - - def __repr__(self): - return "" % (self.resp_retcode, self.resp) - -conv_func = CFUNCTYPE(c_int, c_int, POINTER(POINTER(PamMessage)), POINTER(POINTER(PamResponse)), c_void_p) - -class PamConv(Structure): - """wrapper class for pam_conv structure""" - _fields_ = [ ("conv", conv_func), ("appdata_ptr", c_void_p) ] - -# Various constants -PAM_PROMPT_ECHO_OFF = 1 -PAM_PROMPT_ECHO_ON = 2 -PAM_ERROR_MSG = 3 -PAM_TEXT_INFO = 4 -PAM_REINITIALIZE_CRED = 8 - -libc = CDLL(find_library("c")) -libpam = CDLL(find_library("pam")) - -calloc = libc.calloc -calloc.restype = c_void_p -calloc.argtypes = [c_size_t, c_size_t] - -# bug #6 (@NIPE-SYSTEMS), some libpam versions don't include this function -if hasattr(libpam, 'pam_end'): - pam_end = libpam.pam_end - pam_end.restype = c_int - pam_end.argtypes = [PamHandle, c_int] - -pam_start = libpam.pam_start -pam_start.restype = c_int -pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)] - -pam_setcred = libpam.pam_setcred -pam_setcred.restype = c_int -pam_setcred.argtypes = [PamHandle, c_int] - -pam_strerror = libpam.pam_strerror -pam_strerror.restype = c_char_p -pam_strerror.argtypes = [PamHandle, c_int] - -pam_authenticate = libpam.pam_authenticate -pam_authenticate.restype = c_int -pam_authenticate.argtypes = [PamHandle, c_int] - -pam_acct_mgmt = libpam.pam_acct_mgmt -pam_acct_mgmt.restype = c_int -pam_acct_mgmt.argtypes = [PamHandle, c_int] - -class pam(): - code = 0 - reason = None - - def __init__(self): - pass - - def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True): - @conv_func - def my_conv(n_messages, messages, p_response, app_data): - return 0 - - - cpassword = c_char_p(password) - - handle = PamHandle() - conv = PamConv(my_conv, 0) - retval = pam_start(service, username, byref(conv), byref(handle)) - - retval = pam_authenticate(handle, 0) - auth_success = retval == 0 - - return auth_success \ No newline at end of file diff --git a/python/ql/test/experimental/query-tests/Security/CWE-285/good.py b/python/ql/test/experimental/query-tests/Security/CWE-285/good.py deleted file mode 100644 index e9996c770ed..00000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-285/good.py +++ /dev/null @@ -1,97 +0,0 @@ -from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, byref, sizeof -from ctypes import c_void_p, c_size_t, c_char_p, c_char, c_int -from ctypes import memmove -from ctypes.util import find_library - -class PamHandle(Structure): - _fields_ = [ ("handle", c_void_p) ] - - def __init__(self): - Structure.__init__(self) - self.handle = 0 - -class PamMessage(Structure): - """wrapper class for pam_message structure""" - _fields_ = [ ("msg_style", c_int), ("msg", c_char_p) ] - - def __repr__(self): - return "" % (self.msg_style, self.msg) - -class PamResponse(Structure): - """wrapper class for pam_response structure""" - _fields_ = [ ("resp", c_char_p), ("resp_retcode", c_int) ] - - def __repr__(self): - return "" % (self.resp_retcode, self.resp) - -conv_func = CFUNCTYPE(c_int, c_int, POINTER(POINTER(PamMessage)), POINTER(POINTER(PamResponse)), c_void_p) - -class PamConv(Structure): - """wrapper class for pam_conv structure""" - _fields_ = [ ("conv", conv_func), ("appdata_ptr", c_void_p) ] - -# Various constants -PAM_PROMPT_ECHO_OFF = 1 -PAM_PROMPT_ECHO_ON = 2 -PAM_ERROR_MSG = 3 -PAM_TEXT_INFO = 4 -PAM_REINITIALIZE_CRED = 8 - -libc = CDLL(find_library("c")) -libpam = CDLL(find_library("pam")) - -calloc = libc.calloc -calloc.restype = c_void_p -calloc.argtypes = [c_size_t, c_size_t] - -# bug #6 (@NIPE-SYSTEMS), some libpam versions don't include this function -if hasattr(libpam, 'pam_end'): - pam_end = libpam.pam_end - pam_end.restype = c_int - pam_end.argtypes = [PamHandle, c_int] - -pam_start = libpam.pam_start -pam_start.restype = c_int -pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)] - -pam_setcred = libpam.pam_setcred -pam_setcred.restype = c_int -pam_setcred.argtypes = [PamHandle, c_int] - -pam_strerror = libpam.pam_strerror -pam_strerror.restype = c_char_p -pam_strerror.argtypes = [PamHandle, c_int] - -pam_authenticate = libpam.pam_authenticate -pam_authenticate.restype = c_int -pam_authenticate.argtypes = [PamHandle, c_int] - -pam_acct_mgmt = libpam.pam_acct_mgmt -pam_acct_mgmt.restype = c_int -pam_acct_mgmt.argtypes = [PamHandle, c_int] - -class pam(): - code = 0 - reason = None - - def __init__(self): - pass - - def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True): - @conv_func - def my_conv(n_messages, messages, p_response, app_data): - return 0 - - - cpassword = c_char_p(password) - - handle = PamHandle() - conv = PamConv(my_conv, 0) - retval = pam_start(service, username, byref(conv), byref(handle)) - - retval = pam_authenticate(handle, 0) - if retval == 0: - retval = pam_acct_mgmt(handle, 0) - auth_success = retval == 0 - - return auth_success \ No newline at end of file diff --git a/python/ql/test/experimental/query-tests/Security/CWE-285/pam_test.py b/python/ql/test/experimental/query-tests/Security/CWE-285/pam_test.py new file mode 100644 index 00000000000..60408ade722 --- /dev/null +++ b/python/ql/test/experimental/query-tests/Security/CWE-285/pam_test.py @@ -0,0 +1,59 @@ +from ctypes import CDLL, POINTER, Structure, byref +from ctypes import c_char_p, c_int +from ctypes.util import find_library + + +class PamHandle(Structure): + pass + + +class PamMessage(Structure): + pass + + +class PamResponse(Structure): + pass + + +class PamConv(Structure): + pass + + +libpam = CDLL(find_library("pam")) + +pam_start = libpam.pam_start +pam_start.restype = c_int +pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)] + +pam_authenticate = libpam.pam_authenticate +pam_authenticate.restype = c_int +pam_authenticate.argtypes = [PamHandle, c_int] + +pam_acct_mgmt = libpam.pam_acct_mgmt +pam_acct_mgmt.restype = c_int +pam_acct_mgmt.argtypes = [PamHandle, c_int] + + +class pam(): + + def authenticate_bad(self, username, service='login'): + handle = PamHandle() + conv = PamConv(None, 0) + retval = pam_start(service, username, byref(conv), byref(handle)) + + retval = pam_authenticate(handle, 0) + auth_success = retval == 0 + + return auth_success + + def authenticate_good(self, username, service='login'): + handle = PamHandle() + conv = PamConv(None, 0) + retval = pam_start(service, username, byref(conv), byref(handle)) + + retval = pam_authenticate(handle, 0) + if retval == 0: + retval = pam_acct_mgmt(handle, 0) + auth_success = retval == 0 + + return auth_success