//Copyright (c) Microsoft Corporation. All rights reserved. /* security.cpp */ #ifdef WHISTLER_BUILD #include "ntverp.h" #else #include #endif //WHISTER_BUILD #include #include #include #include #include #include #include #include using namespace _Utils; using CDebugLevel::TRACE_DEBUGGING; using CDebugLevel::TRACE_HANDLE; using CDebugLevel::TRACE_SOCKET; #pragma warning( disable: 4127 ) #pragma warning( disable: 4706 ) extern HANDLE g_hSyncCloseHandle; bool CIoHandler::StartNTLMAuth() { TimeStamp tsExpiry; SECURITY_STATUS secStatus; m_hContext.dwLower = m_hContext.dwUpper = 0 ; m_hCredential.dwLower = m_hCredential.dwUpper = 0 ; if ( SEC_E_OK != (secStatus = AcquireCredentialsHandle( NULL, // name of principal L"NTLM", // name of package SECPKG_CRED_BOTH, // flags indicating use NULL, //PLUID pvLogonID, // pointer to logon identifier NULL, //PVOID pAuthData, // package-specific data NULL, //PVOID pGetKeyFn, // pointer to GetKey function NULL, //PVOID pvGetKeyArgument, // value to pass to GetKey &m_hCredential, // credential handle &tsExpiry) ) ) // life time of the returned credentials); { return false; } secStatus = QuerySecurityPackageInfo(L"NTLM", &m_pspi); if ( secStatus != SEC_E_OK ) { return false; } return true; } bool CIoHandler::DoNTLMAuth( PUCHAR pBuffer, DWORD dwSize, PUCHAR* pBuf ) { m_SocketControlState = CIoHandler::STATE_NTLMAUTH; SECURITY_STATUS secStatus; SecBufferDesc InBuffDesc; SecBuffer InSecBuff; SecBufferDesc OutBuffDesc; SecBuffer OutSecBuff; ULONG fContextAttr; TimeStamp tsExpiry; __try { OutSecBuff.pvBuffer = NULL; // if we are getting nothing then we fail. if( dwSize == 0 ) { goto error; } // make sure we get only NTLM now - thats the only thing we support if( *pBuffer != AUTH_TYPE_NTLM ) { goto error; } if( dwSize < ( 3 + sizeof( SecBuffer ) )) { goto error; } // get past the auth type and the modifier byte and the Auth scheme. pBuffer += 3; dwSize -= 3; // // Prepare our Input buffer - Note the server is expecting the client's // negotiation packet on the first call // InBuffDesc.ulVersion = SECBUFFER_VERSION; InBuffDesc.cBuffers = 1; InBuffDesc.pBuffers = &InSecBuff; // Copy the 1st two fields of SecBuffer from pBuffer to pInSecBuffer. Use memcpy because // pBuffer is not guaranteed to be an aligned pointer. memcpy((PVOID)&InSecBuff, (PVOID)pBuffer, offsetof(SecBuffer, pvBuffer)); // NO Attack here, Baskar. // if we don't have enough buffer then let this call return if( dwSize < InSecBuff.cbBuffer ) { //m_pReadFromSocketBufferCursor += dwSize; return false; } InSecBuff.pvBuffer = (PVOID)(pBuffer + offsetof(SecBuffer, pvBuffer)); // // Prepare our output buffer. We use a temporary buffer because // the real output buffer will most likely need to be uuencoded // OutBuffDesc.ulVersion = SECBUFFER_VERSION; OutBuffDesc.cBuffers = 1; OutBuffDesc.pBuffers = &OutSecBuff; OutSecBuff.cbBuffer = m_pspi->cbMaxToken; OutSecBuff.BufferType = SECBUFFER_TOKEN; OutSecBuff.pvBuffer = new WCHAR[m_pspi->cbMaxToken]; if( !OutSecBuff.pvBuffer ) { return false; } SfuZeroMemory( OutSecBuff.pvBuffer, m_pspi->cbMaxToken ); secStatus = AcceptSecurityContext( &m_hCredential, // handle to the credentials ((fDoNTLMAuthFirstTime) ? NULL: &m_hContext), // handle of partially formed context &InBuffDesc, // pointer to the input buffers ASC_REQ_REPLAY_DETECT | ASC_REQ_MUTUAL_AUTH | ASC_REQ_DELEGATE, // required context attributes SECURITY_NATIVE_DREP, // data representation on the target &m_hContext, // receives the new context handle &OutBuffDesc, // pointer to the output buffers &fContextAttr, // receives the context attributes &tsExpiry // receives the life span of the security context ); } __except( EXCEPTION_EXECUTE_HANDLER ) { secStatus = SEC_E_LOGON_DENIED; } switch ( secStatus ) { case SEC_E_OK: m_bNTLMAuthenticated = true; // done with the authentication, we need to send an accept to the client. (*pBuf)[0] = TC_IAC; (*pBuf)[1] = TC_SB; (*pBuf)[2] = TO_AUTH; (*pBuf)[3] = AU_REPLY; (*pBuf)[4] = AUTH_TYPE_NTLM; (*pBuf)[5] = AUTH_CLIENT_TO_SERVER | AUTH_HOW_ONE_WAY; (*pBuf)[6] = NTLM_ACCEPT; (*pBuf)[7] = TC_IAC; (*pBuf)[8] = TC_SE; *pBuf += 9; m_SocketControlState = CIoHandler::STATE_CHECK_LICENSE; if( m_bNTLMAuthenticated ) { GetUserName(); //Needed for logging at logging off m_pSession->m_fLogonUserResult = SUCCESS; } break; case SEC_I_COMPLETE_NEEDED: case SEC_I_COMPLETE_AND_CONTINUE: // these two return values should never be returned for NTLM. // so we treat them as if we need a continue. we send the data to the client // and wait for something to come back. case SEC_I_CONTINUE_NEEDED: fDoNTLMAuthFirstTime = false; (*pBuf)[0] = TC_IAC; (*pBuf)[1] = TC_SB; (*pBuf)[2] = TO_AUTH; (*pBuf)[3] = AU_REPLY; (*pBuf)[4] = AUTH_TYPE_NTLM; (*pBuf)[5] = AUTH_CLIENT_TO_SERVER | AUTH_HOW_ONE_WAY; (*pBuf)[6] = NTLM_CHALLENGE; *pBuf += 7; { DWORD dwResultSize = sizeof( OutSecBuff ) - sizeof( LPSTR ); StuffEscapeIACs( *pBuf, ( PUCHAR ) &OutSecBuff, &dwResultSize ); *pBuf += dwResultSize; dwResultSize = OutSecBuff.cbBuffer; StuffEscapeIACs( *pBuf, ( PUCHAR ) OutSecBuff.pvBuffer, &dwResultSize ); *pBuf += dwResultSize; } *(*pBuf) = TC_IAC; *pBuf +=1; *(*pBuf) = TC_SE; *pBuf +=1; break; default: #ifdef LOGGING_ENABLED m_pSession->LogIfOpted( FAIL, LOGON, true ); #endif // AcceptSecurityContext returned a value which we don't like. // We Reject the NTLM authentication and then continue with the clear text user name // and password. (*pBuf)[0] = TC_IAC; (*pBuf)[1] = TC_SB; (*pBuf)[2] = TO_AUTH; (*pBuf)[3] = AU_REPLY; (*pBuf)[4] = AUTH_TYPE_NTLM; (*pBuf)[5] = AUTH_CLIENT_TO_SERVER | AUTH_HOW_ONE_WAY; (*pBuf)[6] = NTLM_REJECT; (*pBuf)[7] = TC_IAC; (*pBuf)[8] = TC_SE; *pBuf += 9; strncpy( (char *)*pBuf, NTLM_LOGON_FAIL,MAX_READ_SOCKET_BUFFER); // No Attack, internal Baskar ? *pBuf += strlen(NTLM_LOGON_FAIL); switch( secStatus ) { case SEC_E_INVALID_TOKEN: case SEC_E_INVALID_HANDLE: case SEC_E_INTERNAL_ERROR: strncpy( (char *)*pBuf, INVALID_TOKEN_OR_HANDLE,MAX_READ_SOCKET_BUFFER); // No Attack, internal Baskar ? *pBuf += strlen(INVALID_TOKEN_OR_HANDLE); break; case SEC_E_LOGON_DENIED: strncpy( (char *)*pBuf, LOGON_DENIED,MAX_READ_SOCKET_BUFFER); // No Attack, internal Baskar ? *pBuf += strlen(LOGON_DENIED); break; case SEC_E_NO_AUTHENTICATING_AUTHORITY: strncpy( (char *)*pBuf, NO_AUTHENTICATING_AUTHORITY,MAX_READ_SOCKET_BUFFER); // No Attack, internal Baskar ? *pBuf += strlen(NO_AUTHENTICATING_AUTHORITY); break; default: strncpy( (char *)*pBuf, NTLM_REJECT_STR,MAX_READ_SOCKET_BUFFER); // No Attack, internal Baskar ? *pBuf += strlen(NTLM_REJECT_STR); break; } strncpy( (char *)*pBuf, USE_PASSWD,MAX_READ_SOCKET_BUFFER); // No Attack, internal Baskar ? *pBuf += strlen(USE_PASSWD); error: char* p = (char*)*pBuf; if( m_pSession->m_dwNTLMSetting == NTLM_ONLY ) { sprintf(p, "%s%s", NTLM_ONLY_STR, TERMINATE); // No Attack, internal Baskar ? *pBuf += strlen(p); m_SocketControlState = CIoHandler::STATE_TERMINATE; m_pSession->CIoHandler::m_fShutDownAfterIO = true; } else { m_SocketControlState = CIoHandler::STATE_BANNER_FOR_AUTH; } } if ( OutSecBuff.pvBuffer != NULL ) delete [] OutSecBuff.pvBuffer; return true; } bool CIoHandler::GetUserName() { bool success = false; int iStatus = 0; if ( m_pSession->CIoHandler::m_bNTLMAuthenticated ) { HANDLE hToken = NULL; HANDLE hTempToken = NULL; if (SEC_E_OK == ImpersonateSecurityContext(&m_pSession->CIoHandler::m_hContext)) { if (OpenThreadToken( GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, FALSE, &hTempToken )) { if (DuplicateTokenEx( hTempToken, MAXIMUM_ALLOWED, //TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY, NULL, SecurityImpersonation, TokenPrimary, &hToken )) { DWORD dwSizeReqd; TOKEN_USER *tokenData; GetTokenInformation( hToken, TokenUser, NULL, 0, &dwSizeReqd ); // This call must fail with insufficient buffer tokenData = (TOKEN_USER*) new BYTE[dwSizeReqd]; //allocate that memory if (NULL != tokenData) { //actually get the user info if (0 != GetTokenInformation( hToken, TokenUser, (LPVOID)tokenData, dwSizeReqd, &dwSizeReqd )) { //convert user SID into a name and domain SID_NAME_USE sidType; DWORD dwStrSize1 = MAX_PATH + 1; DWORD dwStrSize2 = MAX_PATH + 1; WCHAR szUser [ MAX_PATH + 1 ]; WCHAR szDomain [ MAX_PATH + 1 ]; if(LookupAccountSid( NULL, tokenData->User.Sid, szUser, &dwStrSize1, szDomain, &dwStrSize2, &sidType ) ) { dwStrSize2++; //To account for null char at the end dwStrSize1++; //LookupAccountSid seems to return data as per 1252. Convert accordingly _chVERIFY2( iStatus = WideCharToMultiByte( GetConsoleCP(), 0, szUser, -1, m_pSession->m_pszUserName, dwStrSize1, NULL, NULL ) ); _chVERIFY2( iStatus = WideCharToMultiByte( GetConsoleCP(), 0, szDomain, -1, m_pSession->m_pszDomain, dwStrSize2, NULL, NULL ) ); wcscpy(m_pSession->m_szDomain,szDomain); success = true; } else { _TRACE( TRACE_DEBUGGING, "Error: LookupAccountSid()" ); } } else { _TRACE( TRACE_DEBUGGING, "Error: GetTokenInformation()" ); } delete [] tokenData; } m_pSession->m_hToken = hToken; } else { _TRACE( TRACE_DEBUGGING, "Error: DuplicateTokenEx() - 0x%lx", GetLastError()); _chASSERT( 0 ); } TELNET_CLOSE_HANDLE(hTempToken); } else { _TRACE( TRACE_DEBUGGING, "Error: OpenThreadToken() - 0x%lx", GetLastError()); _chASSERT( 0 ); } if(SEC_E_OK != RevertSecurityContext( &m_pSession->CIoHandler::m_hContext )) { _TRACE( TRACE_DEBUGGING, "Error: RevertSecurityContext() - " "0x%lx", GetLastError()); _chASSERT( 0 ); } } else { _TRACE( TRACE_DEBUGGING, "Error: ImpersonateSecurityContext() - " "0x%lx", GetLastError()); _chASSERT( 0 ); } } return success; }