catalog
0. 引言 1. windows系统账户弱密码检测 2. windows弱密码检测遇到的问题 3. linux系统账户弱密码检测
0. 引言
windows、linux密码暴力破解、身份认证、密码策略加固的相关知识,请参阅另外两篇文章
http://www.cnblogs.com/LittleHann/p/3662161.html http://www.cnblogs.com/LittleHann/p/4515498.html
今天我们来讨论一下如何在客户端通过系统API实现弱密码的检测
总体来说,我们希望在系统层实现一套账户安全(弱密码)检测,需要注意以下几点
1. 采用模拟网络连接的方式,进行系统登录尝试在大多数情况是不可行的,操作系统身份认证中会进行账户防破解防御,例如windows上的账户安全策略(当尝试次数超过一个阈值,就会自动锁定帐号一定时间) 2. 采用操作系统"账户安全管理"框架,通过API方式进行弱密码的检测,会是一个更好的方向,windows、linux都提供了统一的身份认证可扩展框架 3. 虽然3389 RDP是常见的遭受弱密码暴力破解的途径,且账户是否可以登录3389远程桌面还受到账户所属组的影响(注册表中的一个键值标识),但是要明白,windows中账户管理是一个核心机构,IPC、匿名IPC、NTM,底层都采用了windows账户认证框架,我们对windows系统管理员弱密码的检测,重点应该放在对windows账户认证框架的检测上,即直接检测administrator是否存在弱密码
0x1: Linux和Windows用户管理中的对应技术
Linux 与 Windows 用户管理中的对应技术 | ||
Linux | Windows | |
登录程序 * | login | Winlogon |
认证入口 | PAM (Pluggable Authentication Modules) | GINA (Graphical Identification and Authentication) |
默认认证模块 | pam_unix.so | msgina.dll |
用户系统入口 # | NSS (Name Service Switch) | LSASS (Local Security Authority Subsystem Service) |
默认本地用户系统 | files (/etc/passwd, /etc/group, /etc/shadow) | SAM (Security Account Manager) |
常用目录用户系统 | LDAP (Lightweight Directory Access Protocol) | AD (Active Directory) |
* 仅以 Linux 本地 shell 登录和 Windows 本地图形界面登录为例 | ||
# NSS 与 LSASS 功能并不相同,只在用户认证的流程中的位置相似 |
Windows和Linux一样,开放了用户身份认证与用户管理系统的接口,允许第三方如同开发Linux PAM/NSS那样,为Windows开发新的认证入口(GINA)与认证包(Windows Authentication Packages)
完整的GINA模块需要实现数十个接口,而系统中一次只能配置一个有效的GINA,不像PAM那样可以通过配置文件灵活地组合使用只关注特定功能(认证、账户、密码或会话)的小模块。因此连微软自己的文档也告诫开发者,在编写新的GINA模块之前看看能不能用Hook之类偏Hack的技术实现自己所需的功能
正是由于GINA开发的繁琐性,才蕴生了开源的pGINA框架。pGINA本身是一个GINA模块,它提供了一致的图形界面,并将GINA的数十个接口二次抽象为几个简单易懂的接口
1. 密码认证(本文重点关注的内容) 2. 密码修改 3. 会话Hook ..
pGINA允许二次开发人员利用这些接口开发插件,实现自定义的认证交互程序。pGINA只适用于以密码作为认证凭证,在这一前提下,其功能可以涵盖GINA接口的主要功能点(认证管理、密码管理、屏幕锁定等),而且图形界面交互的开发也由必须变为可选。因此,使用pGINA将在很大程度上简化自定义用户认证机制的开发。对于最终用户,只需要安装pGINA并配置指定的插件,即可使用新的用户名/密码源取代Windows默认的SAM用户名/密码认证
Relevant Link:
http://blog.linjian.org/tech/windows/kernel/gina-pgina
1. windows系统账户弱密码检测
需要明白的是,windows 3389 RDP采用的windows系统的身份认证机制,也就是账户管理中的账户,我们对系统身份认证进行检测,就等同于在进行3389 RDP弱密码的检测
0x1: 方案思路
1. 通过将密码字典静态编译到程序中,或者单独保存在服务端的一个文件中,需要使用时下载(容易受到客户端网络状况的影响) 2. 调用系统API NetUserEnum获取当前系统的所有账户 /* 注意: 不要对枚举出的帐号做任何过滤,出于以下几点考虑 1) 虽然administors组是windows系统的管理员组,但是不排除用户自定义了一些组,并赋予了和administrators等同的权限 2) windows上复用了统一身份认证的模块很多,除了3389之外,还有很多攻击向量可以被黑客利用进行密码破解,例如 2.1) IPC 2.2) NTM 3) 为了达到"底层中枢防御"的效果,直接对枚举出的所有账户都进行一次弱密码检测 */ 3. 调用LogonUser function对获得的帐号进行登录尝试 4. 将检测结果以JSON形式保存
0x2: LogonUser API
The LogonUser function attempts to log a user on to the local computer. The local computer is the computer from which LogonUser was called. You cannot use LogonUser to log on to a remote computer. You specify the user with a user name and domain and authenticate the user with a plaintext password. If the function succeeds, you receive a handle to a token that represents the logged-on user. You can then use this token handle to impersonate the specified user or, in most cases, to create a process that runs in the context of the specified user.
BOOL LogonUser( /* A pointer to a null-terminated string that specifies the name of the user. This is the name of the user account to log on to. 1. If you use the user principal name (UPN) format, [email protected], the lpszDomain parameter must be NULL. */ _In_ LPTSTR lpszUsername, /* A pointer to a null-terminated string that specifies the name of the domain or server whose account database contains the lpszUsername account. 1. If this parameter is NULL, the user name must be specified in UPN format. 2. If this parameter is ".", the function validates the account by using only the local account database. */ _In_opt_ LPTSTR lpszDomain, /* A pointer to a null-terminated string that specifies the plaintext password for the user account specified by lpszUsername. When you have finished using the password, clear the password from memory by calling the SecureZeroMemory function. */ _In_opt_ LPTSTR lpszPassword, /* The type of logon operation to perform 1. LOGON32_LOGON_BATCH: This logon type is intended for batch servers, where processes may be executing on behalf of a user without their direct intervention 2. LOGON32_LOGON_INTERACTIVE: This logon type is intended for users who will be interactively using the computer, such as a user being logged on by a terminal server, remote shell, or similar process. 3. LOGON32_LOGON_NETWORK: This logon type is intended for high performance servers to authenticate plaintext passwords. 4. LOGON32_LOGON_NETWORK_CLEARTEXT: This logon type preserves the name and password in the authentication package, which allows the server to make connections to other network servers while impersonating the client. 5. LOGON32_LOGON_NEW_CREDENTIALS: This logon type allows the caller to clone its current token and specify new credentials for outbound connections. 6. LOGON32_LOGON_SERVICE: Indicates a service-type logon. The account provided must have the service privilege enabled. 7. LOGON32_LOGON_UNLOCK: Windows Server 2003 and Windows XP: This logon type is for GINA DLLs that log on users who will be interactively using the computer. */ _In_ DWORD dwLogonType, /* Specifies the logon provider 1. LOGON32_PROVIDER_DEFAULT: Use the standard logon provider for the system. The default security provider is negotiate, unless you pass NULL for the domain name and the user name is not in UPN format. In this case, the default provider is NTLM. 2. LOGON32_PROVIDER_WINNT50: Use the negotiate logon provider. 3. LOGON32_PROVIDER_WINNT40: Use the NTLM logon provider */ _In_ DWORD dwLogonProvider, /* A pointer to a handle variable that receives a handle to a token that represents the specified user. */ _Out_ PHANDLE phToken );
Return value
1. If the function succeeds, the function returns nonzero. 2. If the function fails, it returns zero. To get extended error information, call GetLastError.
0x3: NetUserEnum
NET_API_STATUS NetUserEnum( /* A pointer to a constant string that specifies the DNS or NetBIOS name of the remote server on which the function is to execute. 1. If this parameter is NULL, the local computer is used. */ _In_ LPCWSTR servername, /* Specifies the information level of the data 1. 0: Return user account names. The bufptr parameter points to an array of USER_INFO_0 structures. 2. 1: Return detailed information about user accounts. The bufptr parameter points to an array of USER_INFO_1 structures. 3. 2: Return detailed information about user accounts, including authorization levels and logon information. The bufptr parameter points to an array of USER_INFO_2 structures. 4. 3: Return detailed information about user accounts, including authorization levels, logon information, RIDs for the user and the primary group, and profile information. The bufptr parameter points to an array of USER_INFO_3 structures. 5. 10: Return user and account names and comments. The bufptr parameter points to an array of USER_INFO_10 structures. 6. 11: Return detailed information about user accounts. The bufptr parameter points to an array of USER_INFO_11 structures. 7. 20: Return the user‘s name and identifier and various account attributes. The bufptr parameter points to an array of USER_INFO_20 structures. Note that on Windows XP and later, it is recommended that you use USER_INFO_23 instead. */ _In_ DWORD level, /* A value that specifies the user account types to be included in the enumeration. A value of zero indicates that all normal user, trust data, and machine account data should be included. 1. FILTER_TEMP_DUPLICATE_ACCOUNT: Enumerates account data for users whose primary account is in another domain 2. FILTER_NORMAL_ACCOUNT: Enumerates normal user account data. This account type is associated with a typical user. 3. FILTER_INTERDOMAIN_TRUST_ACCOUNT: Enumerates interdomain trust account data. This account type is associated with a trust account for a domain that trusts other domains. 4. FILTER_WORKSTATION_TRUST_ACCOUNT: Enumerates workstation or member server trust account data. 5. FILTER_SERVER_TRUST_ACCOUNT: Enumerates member server machine account data. */ _In_ DWORD filter, /* A pointer to the buffer that receives the data. The format of this data depends on the value of the level parameter. */ _Out_ LPBYTE *bufptr, /* The preferred maximum length, in bytes, of the returned data. If you specify MAX_PREFERRED_LENGTH, the NetUserEnum function allocates the amount of memory required for the data. If you specify another value in this parameter, it can restrict the number of bytes that the function returns */ _In_ DWORD prefmaxlen, /* A pointer to a value that receives the count of elements actually enumerated. */ _Out_ LPDWORD entriesread, /* A pointer to a value that receives the total number of entries that could have been enumerated from the current resume position */ _Out_ LPDWORD totalentries, /* A pointer to a value that contains a resume handle which is used to continue an existing user search. The handle should be zero on the first call and left unchanged for subsequent calls. If this parameter is NULL, then no resume handle is stored. */ _Inout_ LPDWORD resume_handle );
Return value
1. If the function succeeds, the return value is NERR_Success. 2. If the function fails, the return value can be one of the following error codes. 1) ERROR_ACCESS_DENIED: The user does not have access to the requested information. 2) ERROR_INVALID_LEVEL: The system call level is not correct. This error is returned if the level parameter is set to a value not supported. 3) NERR_BufTooSmall: The buffer is too small to contain an entry. No information has been written to the buffer. 4) NERR_InvalidComputer: The computer name is invalid. 5) ERROR_MORE_DATA: More entries are available. Specify a large enough buffer to receive all entries.
0x4: LookupAccountName
BOOL WINAPI LookupAccountName( /* A pointer to a null-terminated character string that specifies the name of the system. 1. This string can be the name of a remote computer. 2. If this string is NULL, the account name translation begins on the local system. 3. If the name cannot be resolved on the local system, this function will try to resolve the name using domain controllers trusted by the local system. */ _In_opt_ LPCTSTR lpSystemName, /* A pointer to a null-terminated string that specifies the account name. 1. Use a fully qualified string in the domain_name\user_name format to ensure that LookupAccountName finds the account in the desired domain. */ _In_ LPCTSTR lpAccountName, /* A pointer to a buffer that receives the SID structure that corresponds to the account name pointed to by the lpAccountName parameter. 1. If this parameter is NULL, cbSid must be zero. */ _Out_opt_ PSID Sid, /* A pointer to a variable. On input, this value specifies the size, in bytes, of the Sid buffer. 1. If the function fails because the buffer is too small or if cbSid is zero, this variable receives the required buffer size. */ _Inout_ LPDWORD cbSid, /* A pointer to a buffer that receives the name of the domain where the account name is found. 1. For computers that are not joined to a domain, this buffer receives the computer name. 2. If this parameter is NULL, the function returns the required buffer size. */ _Out_opt_ LPTSTR ReferencedDomainName, /* A pointer to a variable. On input, this value specifies the size, in TCHARs, of the ReferencedDomainName buffer. 1. If the function fails because the buffer is too small, this variable receives the required buffer size, including the terminating null character. 2. If the ReferencedDomainName parameter is NULL, this parameter must be zero. */ _Inout_ LPDWORD cchReferencedDomainName, /* A pointer to a SID_NAME_USE enumerated type that indicates the type of the account when the function returns. */ _Out_ PSID_NAME_USE peUse );
Return value
1. If the function succeeds, the function returns nonzero. 2. If the function fails, it returns zero. For extended error information, call GetLastError.
0x5: SID Components
A SID value includes components that provide information about the SID structure and components that uniquely identify a trustee. A SID consists of the following components:
1. The revision level of the SID structure 2. A 48-bit identifier authority value that identifies the authority that issued the SID 3. A variable number of subauthority or relative identifier (RID) values that uniquely identify the trustee relative to the authority that issued the SID
成功获取到SID之后
但是发现使用USER SID进行继续获取当前账户属主很麻烦,重新整理思路,使用NetLocalGroupGetMembers function获取指定组(administrators)的成员
0x6: 代码示例
// AliHealthExamination.cpp : 定义控制台应用程序的入口点。 // #include <iostream> #include <string> #include <windows.h> #include <assert.h> #include <lm.h> #include <mq.h> #include "Dictionaries/windows_system.h" #include "Cverify.h" #include "JsonEasy.h" using namespace std; #pragma comment(lib,"lib_json") #pragma comment(lib, "version.lib") #pragma comment(lib, "netapi32.lib") #pragma warning(disable:C4996) //moduleid enum MODULEID { rdpBackdoor = 1, windowsSyspwdcheck = 2 }; char* TCHAR2char(TCHAR* tchStr) { int iLen = 2 * wcslen(tchStr);//CString,TCHAR汉字算一个字符,因此不用普通计算长度 char * chRtn = new char[iLen+1]; wcstombs(chRtn, tchStr, iLen+1);//转换成功返回为非负值 return chRtn; } HRESULT FileBinaryCheck(TCHAR exeName[], JsonEasy & JsonResult) { int Bresult_sethc = -1, Bresult_osk = -1, Bresult_magnify = -1; int temResult_magnify, temResult_screenmagnifier; DWORD dwSize; DWORD dwRtn; //file ersion info TCHAR* szVersion; CString csVersion; //get the size of version info dwSize = GetFileVersionInfoSize(exeName, NULL); if (dwSize == 0) { //在windows server 2003、2008、windows7上屏幕放大键的程序名是不同的,当传入错误的程序名时,这里会打开失败而自动退出 return E_FAIL; } char *pBuf; pBuf = new char[dwSize + 1]; if(pBuf == NULL) { return E_FAIL; } memset(pBuf, 0, dwSize + 1); //get file version information dwRtn = GetFileVersionInfo(exeName ,NULL, dwSize, pBuf); if(dwRtn == 0) { return E_FAIL; } LPVOID lpBuffer = NULL; UINT uLen = 0; //pBuf: Pointer to a buffer that receives the file-version information. //Retrieves specified version information from the specified version-information resource. dwRtn = VerQueryValue(pBuf, TEXT("\\StringFileInfo\\080404b0\\OriginalFilename"), //0804: 中文、04b0: 1252ANSI //可以测试的属性 /* CompanyName FileDescription FileVersion InternalName LegalCopyright OriginalFilename ProductName ProductVersion Comments LegalTrademarks PrivateBuild SpecialBuild */ &lpBuffer, &uLen); if(dwRtn == 0) { return E_FAIL; } //get output //这里获得的文件名可能magnify.exe、screenmagnifier.exe szVersion = (TCHAR*)lpBuffer; //check result(对magnify单独做特殊处理) csVersion = (CString)szVersion; csVersion.MakeLower(); CString ssExeName( exeName ); if(ssExeName.Find(_T("sethc.exe")) != -1 ) { JsonResult.execinfoItemSethc["binarycoderesult"] = TCHAR2char(szVersion); Bresult_sethc = csVersion.Find(_T("sethc.exe")); } else if(ssExeName.Find(_T("osk.exe")) != -1 ) { JsonResult.execinfoItemOsk["binarycoderesult"] = TCHAR2char(szVersion); Bresult_osk = csVersion.Find(_T("osk.exe")); } //else if(ssExeName.CompareNoCase(_T("magnifier.exe")) == 0 || ssExeName.CompareNoCase(_T("magnify.exe")) == 0 ) else if(ssExeName.Find(_T("magnify.exe")) != -1 ) { JsonResult.execinfoItemMagnify["binarycoderesult"] = TCHAR2char(szVersion); temResult_magnify = csVersion.Find(_T("magnify.exe")); temResult_screenmagnifier = csVersion.Find(_T("screenmagnifier.exe")); Bresult_magnify = ( (temResult_magnify != -1) || (temResult_screenmagnifier != -1) ) ? 1 : -1; } if( (Bresult_sethc != -1) || (Bresult_osk != -1) || (Bresult_magnify != -1) ) { return S_OK; } else { return E_FAIL; } } HRESULT SignatureCheck(TCHAR exePath[], JsonEasy & JsonResult) { //sigName: signature name CStdString sigName; CString csExePath; //new CFileDigitalSignVerify CFileDigitalSignVerify fileVerifySign = CFileDigitalSignVerify(); //verify the sethc fileSign VERIFY_RESULT signResult = fileVerifySign.VerifyFileBySmartMode(exePath, sigName); //如果用户对sethc.exe、osk.exe、magnify.exe进行了加固,则直接判定为正常 //get the sign info json csExePath = (CString)exePath; if(csExePath.Find(_T("sethc.exe")) != -1) { JsonResult.execinfoItemSethc["exename"] = TCHAR2char(exePath); JsonResult.execinfoItemSethc["signcheckresult"] = TCHAR2char((TCHAR*)sigName.GetData()); JsonResult.execinfoItemSethc["trustcheckresult"] = signResult == 0 ? "VrUnknown" : signResult == 1 ? "VrNoSignature" : signResult == 2 ? "VrTrusted" : signResult == 3 ? "VrExpired" : signResult == 4 ? "VrRevoked" : signResult == 5 ? "VrDistrust" : signResult == 6 ? "VrSecuritySettings" : ""; } else if(csExePath.Find(_T("osk.exe")) != -1) { JsonResult.execinfoItemOsk["exename"] = TCHAR2char(exePath); JsonResult.execinfoItemOsk["signcheckresult"] = TCHAR2char((TCHAR*)sigName.GetData()); JsonResult.execinfoItemOsk["trustcheckresult"] = signResult == 0 ? "VrUnknown" : signResult == 1 ? "VrNoSignature" : signResult == 2 ? "VrTrusted" : signResult == 3 ? "VrExpired" : signResult == 4 ? "VrRevoked" : signResult == 5 ? "VrDistrust" : signResult == 6 ? "VrSecuritySettings" : ""; } else if(csExePath.Find(_T("magnify.exe")) != -1) { JsonResult.execinfoItemMagnify["exename"] = TCHAR2char(exePath); JsonResult.execinfoItemMagnify["signcheckresult"] = TCHAR2char((TCHAR*)sigName.GetData()); JsonResult.execinfoItemMagnify["trustcheckresult"] = signResult == 0 ? "VrUnknown" : signResult == 1 ? "VrNoSignature" : signResult == 2 ? "VrTrusted" : signResult == 3 ? "VrExpired" : signResult == 4 ? "VrRevoked" : signResult == 5 ? "VrDistrust" : signResult == 6 ? "VrSecuritySettings" : ""; } //Microsoft versign CString csSigName = CString((TCHAR*)sigName.GetData()); int vResult = csSigName.Find(L"O=Microsoft Corporation"); if(vResult != -1) { if(signResult == 2) { //VrTrusted return S_OK; } else { //NOT VrTrusted return E_FAIL; } } else { return E_FAIL; } } /* BOOL GetTextualSid(PSID pSid, LPTSTR TextualSid, LPDWORD lpdwBufferLen) { if( !IsValidSid(pSid) ) return FALSE; //returns a pointer to the SID_IDENTIFIER_AUTHORITY structure PSID_IDENTIFIER_AUTHORITY psia = GetSidIdentifierAuthority(pSid); //returns a pointer to the member in a security identifier (SID) structure that contains the subauthority count. DWORD dwSubAuthorities = *GetSidSubAuthorityCount(pSid); DWORD dwSidSize = (15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR); if( *lpdwBufferLen < dwSidSize ) { *lpdwBufferLen = dwSidSize; SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } DWORD dwSidRev = SID_REVISION; dwSidSize = wsprintf(TextualSid, TEXT("S-%lu-"), dwSidRev); if( (psia->Value[0] != 0) || (psia->Value[1] != 0) ) { dwSidSize += wsprintf(TextualSid + lstrlen(TextualSid), TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"), (USHORT) psia->Value[0], (USHORT) psia->Value[1], (USHORT) psia->Value[2], (USHORT) psia->Value[3], (USHORT) psia->Value[4], (USHORT) psia->Value[5]); } else { dwSidSize += wsprintf(TextualSid + lstrlen(TextualSid), TEXT("%lu"), (ULONG) (psia->Value[5] ) + (ULONG) (psia->Value[4] << 8) + (ULONG) (psia->Value[3] << 16) + (ULONG) (psia->Value[2] << 24) ); } for (DWORD dwCounter = 0; dwCounter < dwSubAuthorities; ++dwCounter) { dwSidSize += wsprintf(TextualSid + dwSidSize, TEXT("-%lu"), *GetSidSubAuthority(pSid, dwCounter)); } return TRUE; } void UserNameToSid(LPTSTR UserName) { DWORD dwSidSize = 0; TCHAR szDomain[128] = {0}; DWORD dwDomainSize = 128; SID_NAME_USE SidType; LookupAccountName( NULL, UserName, NULL, &dwSidSize, szDomain, &dwDomainSize, &SidType ); if( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { PSID pSid = (PSID) LocalAlloc(LMEM_FIXED, dwSidSize); if( pSid ) { ZeroMemory(szDomain, sizeof(szDomain)); dwDomainSize = 128; if( LookupAccountName(NULL, UserName, pSid, &dwSidSize, szDomain, &dwDomainSize, &SidType) ) { dwSidSize = 0; GetTextualSid(pSid, NULL, &dwSidSize); if( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { LPTSTR lpSidStr = (LPTSTR) LocalAlloc(LMEM_FIXED, dwSidSize); if( lpSidStr ) { GetTextualSid(pSid, lpSidStr, &dwSidSize); // use pSid, lpSidStr, szDomain, and SidType as //std::wcout << "pSid: " << pSid << " lpSidStr: " << lpSidStr << " szDomain: " << szDomain << " SidType: " << SidType << std::endl; std::wcout << " lpSidStr: " << lpSidStr << " szDomain: " << szDomain << " SidType: " << SidType << std::endl; //wprintf(L"pSid: %s\n", pSid); LocalFree(lpSidStr); } } } LocalFree(pSid); } } } */ int main(int argc, char* argv[]) { //parameter check std::string action; if (argc <= 1) { std::cout << "usage: " << argv[0] << " --suspicion-account" << std::endl; return 0; } else { action = argv[1]; } //main func if (action == "--suspicion-account") { } else if (action == "--port-scan") { } else if (action == "--system-vulscan") { } else if (action == "--windowsSyspwdcheck") { //json class JsonEasy JsonResult = JsonEasy(); JsonResult.jRoot["isvul"] = 0; //NetUserEnum related int LoginResult; HRESULT LookupSidResult; HANDLE hUser; int Pwdlength; NET_API_STATUS nStatus; LPUSER_INFO_3 pBuf = NULL; LPUSER_INFO_3 pTmpBuf; DWORD dwLevel = 3; //LPUSER_INFO_3 DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH; DWORD dwEntriesRead = 0; DWORD dwTotalEntries = 0; DWORD dwResumeHandle = 0; int i,j; DWORD dwTotalCount = 0; //LookupAccountName related //PSID ppSid = NULL; //Userlength = sizeof(windowsSysUser) / sizeof(windowsSysUser[0]); Pwdlength = sizeof(windowsSysPassword) / sizeof(windowsSysPassword[0]); //get system account list do // begin do { nStatus = NetUserEnum( NULL, dwLevel, FILTER_NORMAL_ACCOUNT, // global users (LPBYTE*)&pBuf, dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries, &dwResumeHandle); //If the call succeeds, if ((nStatus == NERR_Success) || (nStatus == ERROR_MORE_DATA)) { if ((pTmpBuf = pBuf) != NULL) { //Loop through the entries. for (i = 0; i < dwEntriesRead; i++) { assert(pTmpBuf != NULL); if (pTmpBuf == NULL) { break; } //std::wcout << "username: " << pTmpBuf->usri3_name << std::endl; //check the password for( j = 0; j < Pwdlength; j++ ) { //check authentication LoginResult = LogonUser( pTmpBuf->usri3_name, NULL, windowsSysPassword[j], LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hUser ); if(LoginResult != 0) { JsonResult.jRoot["isvul"] = 1; //fill username/password JsonResult.execinfoItemSyspwd["password"] = TCHAR2char(windowsSysPassword[j]); JsonResult.execinfoItemSyspwd["username"] = TCHAR2char(pTmpBuf->usri3_name); JsonResult.execinfoArrayObj.append(JsonResult.execinfoItemSyspwd); break; } } //end pTmpBuf++; dwTotalCount++; } } } // Otherwise, print the system error. else { std::cout << "A system error has occurred: " << nStatus << std::endl; } // Free the allocated buffer. if (pBuf != NULL) { NetApiBufferFree(pBuf); pBuf = NULL; } } // Continue to call NetUserEnum while // there are more entries. while (nStatus == ERROR_MORE_DATA); // end do //wrap the json JsonResult.jRoot["execinfo"] = JsonResult.execinfoArrayObj; JsonResult.jRoot["moduleid"] = rdpBackdoor; JsonResult.jRoot["execresult"] = 1; //output finnal result std::string out = JsonResult.jRoot.toStyledString(); std::cout << out << std::endl; return 0; } else if(action == "--rdpBackdoor") { JsonEasy JsonResult = JsonEasy(); //check sign TCHAR sethcPath[MAX_PATH] = _T(""); TCHAR oskPath[MAX_PATH] = _T(""); TCHAR magnifyPath[MAX_PATH] = _T(""); //get system32 path TCHAR lpszFileFullPath[MAX_PATH]; GetSystemDirectory(lpszFileFullPath, sizeof(lpszFileFullPath)); lstrcat(lpszFileFullPath, _T("\\")); //lpszFileFullPath = %SystemRoot%\system32\sethc.exe lstrcat(sethcPath, lpszFileFullPath); lstrcat(sethcPath, _T("sethc.exe")); //lpszFileFullPath = %SystemRoot%\system32\osk.exe lstrcat(oskPath, lpszFileFullPath); lstrcat(oskPath, _T("osk.exe")); //lpszFileFullPath = %SystemRoot%\system32\magnify.exe lstrcat(magnifyPath, lpszFileFullPath); lstrcat(magnifyPath, _T("magnify.exe")); { //which hava read permission, go on //SignatureCheck result: S_OK or E_FAIL HRESULT SresultSethc = SignatureCheck(sethcPath, JsonResult); HRESULT SresultOsk = SignatureCheck(oskPath, JsonResult); HRESULT SresultMagnify = SignatureCheck(magnifyPath, JsonResult); //may be angly if(SresultSethc == S_OK) { if(SresultOsk == S_OK) { if(SresultMagnify == S_OK) { //if SignatureCheck is ok, then we go on //certificate check is ok, go on binary characteristics //if the program contains correct binary program name,then ok HRESULT BresultSethc = FileBinaryCheck(sethcPath, JsonResult); HRESULT BresultOsk = FileBinaryCheck(oskPath, JsonResult); HRESULT BresultMagnify = FileBinaryCheck(magnifyPath, JsonResult); if(BresultSethc == S_OK && BresultOsk == S_OK && BresultMagnify == S_OK ) { JsonResult.jRoot["isvul"] = 0; } else { JsonResult.jRoot["isvul"] = 1; } } else { //std::wcout << "JsonResult.execinfoItemMagnify: " << JsonResult.execinfoItemMagnify["signcheckresult"].empty() << std::endl; //magnify.exe //check target is if accessable if ( JsonResult.execinfoItemMagnify["signcheckresult"].empty() == 0 ) { JsonResult.jRoot["isvul"] = 0; JsonResult.execinfoItemMagnify["exename"] = TCHAR2char(magnifyPath); JsonResult.execinfoItemMagnify["signcheckresult"] = "CN=Microsoft Windows Component Publisher, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; JsonResult.execinfoItemMagnify["trustcheckresult"] = "VrTrusted"; JsonResult.execinfoItemMagnify["binarycoderesult"] = "Target Program Has Been Reinforcemented"; } else { JsonResult.jRoot["isvul"] = 1; } } } else { //osk.exe //check target is if accessable if ( JsonResult.execinfoItemOsk["signcheckresult"].empty() == 0 ) { JsonResult.jRoot["isvul"] = 0; JsonResult.execinfoItemOsk["exename"] = TCHAR2char(oskPath); JsonResult.execinfoItemOsk["signcheckresult"] = "CN=Microsoft Windows Component Publisher, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; JsonResult.execinfoItemOsk["trustcheckresult"] = "VrTrusted"; JsonResult.execinfoItemOsk["binarycoderesult"] = "Target Program Has Been Reinforcemented"; } else { JsonResult.jRoot["isvul"] = 1; } } } else { //sethc.exe //check target is if accessable if ( JsonResult.execinfoItemSethc["signcheckresult"].empty() == 0 ) { //no read permission JsonResult.jRoot["isvul"] = 0; JsonResult.execinfoItemSethc["exename"] = TCHAR2char(sethcPath); JsonResult.execinfoItemSethc["signcheckresult"] = "CN=Microsoft Windows Component Publisher, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; JsonResult.execinfoItemSethc["trustcheckresult"] = "VrTrusted"; JsonResult.execinfoItemSethc["binarycoderesult"] = "Target Program Has Been Reinforcemented"; } else { JsonResult.jRoot["isvul"] = 1; } } } //wrapper protocol JsonResult.jRoot["moduleid"] = rdpBackdoor; JsonResult.jRoot["execresult"] = 1; JsonResult.execinfoArrayObj.append(JsonResult.execinfoItemSethc); JsonResult.execinfoArrayObj.append(JsonResult.execinfoItemOsk); JsonResult.execinfoArrayObj.append(JsonResult.execinfoItemMagnify); JsonResult.jRoot["execinfo"] = JsonResult.execinfoArrayObj; //output finnal result std::string out = JsonResult.jRoot.toStyledString(); std::cout << out << std::endl; } return 0; }
1. windows server 2003: 检测程序均工作正常 2. windows server 2008: 检测程序均工作正常 3. windows server 2012: 检测程序均工作正常 4. 32bit操作系统下测试通过
Relevant Link:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms717794(v=vs.85).aspx https://msdn.microsoft.com/en-us/library/windows/desktop/bb648647(v=vs.85).aspx https://msdn.microsoft.com/en-us/library/windows/desktop/aa375177(v=vs.85).aspx https://msdn.microsoft.com/zh-cn/aa302353.aspx http://pinvoke.net/default.aspx/advapi32.LogonUser http://forums.codeguru.com/showthread.php?241037-Logonuser https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa378184(v=vs.85).aspx https://github.com/pgina/pgina/wiki https://github.com/pgina/pgina/wiki/Install http://blog.chinaunix.net/uid-24709751-id-4066775.html https://msdn.microsoft.com/en-us/library/windows/desktop/aa370652(v=vs.85).aspx https://msdn.microsoft.com/en-us/library/windows/desktop/aa371338(v=vs.85).aspx http://www.delphigroups.info/3/4/61239.html https://msdn.microsoft.com/ZH-CN/library/windows/desktop/aa370601(v=vs.85).aspx
2. 弱密码检测遇到的问题
0x1: 和系统帐号登录有关的开关、以及安全审核事件日志
1. 审核登录事件: 产生"登录/注销"事件 //基于日志的入侵检测系统普遍依赖这个事件日志 2. 审核账户登录事件: 产生"账户登录"事件
需要明白的是,在开启这两个配置选项的情况下(成功、失败),普通3389UI密码暴力破解、调用LogonUser()密码枚举、IPC密码枚举都会同时产生这2个事件
1. 安全性日志: 记录了诸如有效和无效的登录尝试等事件,以及与资源使用相关的事件,例如创建、打开或删除文件或其他对象,系统管理员可以指定在安全性日志中记录什么事件 2. 默认设置下,安全性日志是关闭的,管理员可以使用组策略来启动安全性日志,或者在注册表中设置审核策略,以便当安全性日志满后使系统停止响应 3. 成功审核: 成功的审核安全访问尝试,主要是指安全性日志,这里记录着 1) 用户登录/注销 2) 对象访问 3) 特权使用 4) 账户管理 5) 策略更改 6) 详细跟踪 7) 目录服务访问 8) 账户登录等事件(例如所有的成功登录系统都会被记录为"成功审核"事件) 3.失败审核: 失败的审核安全登录尝试,例如用户试图访问网络驱动器失败,则该尝试会被作为失败审核 4. 用户需要以管理员或Administrators组成员的身份登录系统才能拥有足够的权限清除或改写事件日志。或者,你也可以进入\WINDOWS\ SYSTEM32\config\文件夹 1) 其中以*.evt作为扩展名的文件就是所谓的日志文件 2) AppEvent.evt即"应用程序"日志 3) SysEvent.evt即"系统"日志 4) SecEvent.evt即"安全性"日志,直接在这里删除相应的文件就可以了 //如果使用的是NTFS格式的系统,在删除日志文件之前必须首先关闭事件检查器服务才行
对于基于日志的入侵检测程序来说,它们关注的往往是
#define EVENT_ID_AN_ACCOUNT_SUCCESSFUL_LOGGED_ON 4624 #define EVENT_ID_AN_ACCOUNT_FAILED_TO_LOG_ON 4625 #define EVENT_ID_SUCCESSFUL_LOGON 528 #define EVENT_ID_LOGON_FAIL 529 #define EVENT_ID_IPC_LOGON_SUCCESS 540
同时,基于日志的入侵检测系统在解析系统安全审核事件的时候,会判断事件中是否包含"源网络地址"、"源网络端口"这2个字段是否存在,如果不存在则忽略该消息,而API触发的安全事件是不带IP/PORT信息的,因此不会被入侵检测系统捕获造成误报
0x2: 解决思路:事务
1. 关闭windows安全事件审核 2. 进行弱密码检测 3. 打开windows安全事件审核 //在设计的时候需要考虑到如果运行中出现致命错误,程序没有完整运行的情况,即事件完整性的问题,如果安全事件审核被关闭后没有正确打开,则主机上基于日志的入侵检测系统就会失效
secedit /export /cfg gp.inf /log 1.log
通过对比配置文件可知
1. 审核账户登录事件 1.1 成功、失败 [Event Audit] AuditAccountLogon = 3 1.2 成功 [Event Audit] AuditAccountLogon = 1
我们的目标是在运行期间将AuditAccountLogon设置为1,这样暴力破解程序就不会产生失败事件,等检测程序结束之后,再修改为3
0 = No auditing 1 = Success 2 = Failure 3 = Success, Failure
code example
//switch the security event audit off //1. backup system( "secedit /export /cfg gp_bak.inf" ); //2. switch off system( "echo [Event Audit] > gp.inf" ); system( "echo AuditAccountLogon = 0 >> gp.inf" ); system( "echo [Version] >> gp.inf" ); system( "echo signature=\"$CHICAGO$\" >> gp.inf" ); system( "echo Revision=1 >> gp.inf" ); system( "secedit /configure /db gp.sdb /cfg gp.inf /quiet" ); system( "if exist gp.sdb del gp.sdb" ); //switch the security event audit back to original state system( "secedit /configure /db gp.sdb /cfg gp_bak.inf /quiet" ); system( "if exist gp.sdb del gp.sdb" );
0x3: windows事件审核事件编号
1. 帐号登录事件(事件编号与描述) 672: 身份验证服务(AS)票证得到成功发行与验证 673: 票证授权服务(TGS)票证得到授权TGS是一份由Kerberos 5.0版票证授权服务(TGS)发行、且允许用户针对域中特定服务进行身份验证的票证 674: 安全主体重建AS票证或TGS票证 675: 预身份验证失败这种事件将在用户输入错误密码时由密钥分发中心(KDC)生成 676: 身份验证票证请求失败这种事件在Windows XP Professional操作系统或Windows Server产品家族成员中将不会产生 677: TGS票证无法得到授权这种事件在Windows XP Professional操作系统或Windows Server产品家族成员中将不会产生 678: 指定帐号成功映射到一个域帐号 681: 登录失败域帐号尝试进行登录这种事件在Windows XP Professional操作系统或Windows Server产品家族成员中将不会产生 682: 用户重新连接到一个已经断开连接的终端服务器会话上 683: 用户在没有注销的情况下与终端服务器会话断开连接 2. 帐号管理事件 624: 一个用户帐号被创建 627: 一个用户密码被修改 628: 一个用户密码被设置 630: 一个用户密码被删除 631: 一个全局组被创建 632: 一个成员被添加到特定全局组中 633: 一个成员从特定全局组中被删除 634: 一个全局组被删除 635: 一个新的本地组被创建 636: 一个成员被添加到本地组中 637: 一个成员从本地组中被删除 638: 一个本地组被删除 639: 一个本地组帐号被修改 641: 一个全局组帐号被修改 642: 一个用户帐号被修改 643: 一个域策略被修改 644: 一个用户帐号被自动锁定 645: 一个计算机帐号被创建 646: 一个计算机帐号被修改 647: 一个计算机帐号被删除 648: 一个禁用安全特性的本地安全组被创建说明:正式名称中的SECURITY_DISABLED意味着这个组无法用于在访问检查中授予权限 649: 一个禁用安全特性的本地安全组被修改 650: 一个成员被添加到一个禁用安全特性的本地安全组中 651: 一个成员从一个禁用安全特性的本地安全组中被删除 652: 一个禁用安全特性的本地组被删除 653: 一个禁用安全特性的全局组被创建 654: 一个禁用安全特性的全局组被修改 655: 一个成员被添加到一个禁用安全特性的全局组中 656: 一个成员从一个禁用安全特性的全局组中被删除 657: 一个禁用安全特性的全局组被删除 658: 一个启用安全特性的通用组被创建 659: 一个启用安全特性的通用组被修改 660: 一个成员被添加到一个启用安全特性的通用组中 661: 一个成员从一个启用安全特性的通用组中被删除 662: 一个启用安全特性的通用组被删除 663: 一个禁用安全特性的通用组被创建 664: 一个禁用安全特性的通用组被修改 665: 一个成员被添加到一个禁用安全特性的通用组中 666: 一个成员从一个禁用安全特性的通用组中被删除 667: 一个禁用安全特性的通用组被删除 668: 一个组类型被修改 684: 管理组成员的安全描述符被设置说明: 在域控制器上,一个后台线程每60秒将对管理组中的所有成员(如域管理员、企业管理员和架构管理员)进行一次搜索并对其应用一个经过修复的安全描述符这种事件将被记录下来 685: 一个帐号名称被修改 3. 审核登录事件 528: 用户成功登录到计算机上 529: 登录失败:试图使用未知用户名或带有错误密码的已知用户名进行登录 530: 登录失败:试图在允许时间范围以外进行登录 531: 登录失败:试图通过禁用帐号进行登录 532: 登录失败:试图通过过期帐号进行登录 533: 登录失败:试图通过不允许在特定计算机上进行登录的用户帐号进行登录 534: 登录失败:用户试图通过不允许使用的密码类型进行登录 535: 登录失败:针对指定帐号的密码已经过期 536: 登录失败:网络登录服务未被激活 537: 登录失败:由于其它原因导致登录失败说明:在某些情况下,登录失败原因可能无法确定 538: 针对某一用户的注销操作完成 539: 登录失败:登录帐号在登录时刻已被锁定 540: 用户成功登录到网络 541: 本地计算机与所列对等客户身份标识之间的主模式Internet密钥交换(IKE)身份验证操作已经完成(建立一条安全关联),或者快速模式已经建立一条数据通道 542: 数据通道被中断 543: 主模式被中断说明:这种事件可能在安全关联时间限制到期(缺省值为8小时)、策略修改或对等客户中断时发生 544: 由于对等客户未能提供合法证书或签署未通过验证导致主模式身份验证失败 545: 由于Kerberos失败或密码不合法导致主模式身份验证失败 546: 由于对等客户发送非法了非法提议,IKE 安全关联建立没有成功收到一个包含非法数据的数据包 547: IKE握手过程中发生错误 548: 登录失败:来自信任域的安全标识符(SID)与客户端的帐号域SID不匹配 549: 登录失败:在跨域身份验证过程中,所有同非信任名称空间相对应的SID均已被过滤掉 550: 能够指示可能发生拒绝服务(DoS)攻击的通知消息 551: 用户发起注销操作 552: 用户在已经通过其他身份登录的情况下使用明确凭据成功登录到计算机上 682: 用户重新连接到一个已经断开连接的终端服务器会话上 683: 用户在没有注销的情况下与终端服务器会话断开连接说明:这种事件将在用户通过网络与终端服务器会话建立连接时产生它将出现在终端服务器上 680: 账户登录审核失败(0xC000006A) 4. 对象访问事件 560: 访问由一个已经存在的对象提供授权 562: 一个对象访问句柄被关闭 563: 试图打开并删除一个对象说明:当您在Createfile()函数中指定FILE_DELETE_ON_CLOSE标志时,这种事件将被文件系统所使用 564: 一个保护对象被删除 565: 访问由一种已经存在的对象类型提供授权 567: 一种与句柄相关联的权限被使用说明:一个授予特定权限(读取、写入等)的句柄被创建当使用这个句柄时,至多针对所用到的每种权限产生一次审核 568: 试图针对正在进行审核的文件创建硬连接 569: 身份验证管理器中的资源管理器试图创建客户端上下文 570: 客户端试图访问一个对象说明:针对对象的每次操作尝试都将产生一个事件 571: 客户端上下文被身份验证管理器应用程序删除 572: 管理员管理器初始化应用程序 772: 证书管理器拒绝了挂起的证书申请 773: 证书服务收到重新提交的证书申请 774: 证书服务吊销了证书 775: 证书服务收到发行证书吊销列表(CRL) 的请求 776: 证书服务发行了证书吊销列表(CRL) 777: 更改了证书申请扩展 778: 更改了多个证书申请属性 779: 证书服务收到关机请求 780: 已开始证书服务备份 781: 已完成证书服务备份 782: 已开始证书服务还原 783: 已完成证书服务还原 784: 证书服务已经开始 785: 证书服务已经停止 786: 证书服务更改的安全权限 787: 证书服务检索了存档密钥 788: 证书服务将证书导入数据库中 789: 证书服务更改的审核筛选 790: 证书服务收到证书申请 791: 证书服务批准了证书申请并颁发了证书 792: 证书服务拒绝证书申请 793: 证书服务将证书申请状态设为挂起 794: 证书服务更改的证书管理器设置 795: 证书服务更改的配置项 796: 证书服务更改属性 797: 证书服务存档了密钥 798: 证书服务导入和存档了密钥 799: 证书服务将证书发行机构(CA)证书发行到Active Directory 800: 从证书数据库删除一行或多行 801: 角色分隔被启用 5. 审核策略更改事件 608: 用户权限已被分配 609: 用户权限已被删除 610: 与另一个域的信任关系已被创建 611: 与另一个域的信任关系已被删除 612: 审核策略已被更改 613: Internet协议安全性(IPSec)策略代理已经启动 614: IPSec策略代理已被禁用 615: IPSec策略代理已被更改 616: IPSec策略代理遇到一个潜在的严重问题 617: Kerberos 5.0版策略已被更改 618: 经过加密的数据恢复策略已更改 620: 与另一个域的信任关系已被修改 621: 系统访问权限已被授予帐号 622: 系统访问权限已从帐号中删除 623: 审核策略以对等用户为单位进行设置 625: 审核策略以对等用户为单位进行刷新 768: 检测到一个森林中的名称空间元素与另一个森林中的名称空间元素发生冲突说明:当一个森林中的名称空间元素与另一个森林中的名称空间元素发生重叠时,它将无法明确解析属于这两个名称空间元素的名称这种重叠现象也称作冲突并非针对每种记录类型的参数均合法举例来说,诸如DNS名称、NetBIOS名称和SID之类的字段对于"TopLevelName"类型的记录便是非法的 769: 添加了受信任的森林信息说明:这种事件消息将在更新受信任的森林信息以及添加一条或多条记录时生成针对每条添加、删除或修改的记录都将生成一条事件消息如果在针对森林信任信息的单一更新操作中添加、删除或修改多条记录,生成的所有事件消息都将被分配一个相同且唯一标识符(称作操作编号)这种方式使您能够判断出多条事件消息是由一次操作生成的并非针对每种记录类型的参数均合法举例来说,诸如DNS名称、NetBIOS名称和SID之类的字段对于"TopLevelName"类型的记录便是非法的 770: 删除了受信任的森林信息说明:查看编号为769的事件描述 771: 修改了受信任的森林信息说明:查看编号为769的事件描述 805: 事件日志服务读取针对会话的安权限使用事件 6. 权限使用事件 576: 特定权限已被添加到用户访问令牌中说明:这种事件将在用户登录时产生 577: 用户试图执行受到权限保护的系统服务操作 578: 在已经处于打开状态的受保护对象句柄上使用权限 7. 详细跟踪事件 592: 已经创建新的过程 593: 已经退出某过程 594: 对象的句柄被重复 595: 已经取得对象的间接访问权 596: 数据保护主密钥备份说明:主密钥将供CryptProtectData和CryptUnprotectData例程以及加密文件系统(EFS)所使用这种主密钥将在每次创建新增主密钥时予以备份(缺省设置为90天)密钥备份操作通常由域控制器执行 597: 数据保护主密钥已由恢复服务器恢复完毕 598: 审核数据已得到保护 599: 审核数据保护已取消 600: 分派给进程一个主令牌 601: 用户尝试安装服务 602: 一个计划作业已被创建 8. 面向审核系统事件的系统事件消息 512: 正在启动Windows 513: Windows正在关机 514: 本地安全机制机构已加载身份验证数据包 515: 受信任的登录过程已经在本地安全机制机构注册 516: 用来列队审核消息的内部资源已经用完,从而导致部分审核数据丢失 517: 审核日志已经清除 518: 安全帐户管理器已经加载通知数据包 519: 一个过程正在试图通过无效本地过程调用(LPC)端口来模拟客户端并针对客户端地址空间执行回复、读取或写入操作 520: 系统时间已更改说明:这种审核操作通常成对出现
Relevant Link:
https://msdn.microsoft.com/zh-cn/library/ms731669(v=vs.110).aspx http://blog.csdn.net/dba_huangzj/article/details/7566851 https://technet.microsoft.com/en-us/library/cc758201(v=ws.10).aspx https://social.msdn.microsoft.com/Forums/vstudio/en-US/ee08e919-5bc6-4330-a4d4-eae64064d781/programmatically-accessing-local-security-settings?forum=vcgeneral http://www.xuebuyuan.com/1349007.html http://www.codeproject.com/Articles/61658/Query-the-New-Windows-Audit-Policies-Programmatica https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa363654(v=vs.85).aspx https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa363654(v=vs.85).aspx http://immortalluo.blog.163.com/blog/static/12864691620115734641150/
3. linux系统账户弱密码检测
undone
Copyright (c) 2015 LittleHann All rights reserved