因为项目里用通过WMI查询remote machine的information,但默认WMI必须要Admin的权限,所以找了non-Admin的solution:
Q 8. How do I set WMI namespace security?
手动测试很快就过了,只要相应地把WMI Control的priviledge和DCOM的priviledge设置正确即可。
但下一个问题就是怎么把这个setting deploy到remote machine上,显然得派上GPO了,最方便的当然是vbs咯,即不像PowerShell那样要runtime,又不想C++那样难以操作,msdn就有:Setting Namespace Security Descriptor。用脚本搞定似乎很容易了,但是,且慢:
Gets the security descriptor that controls access to the WMI namespace associated with the instance of __SystemSecurity. The security descriptor is returned as an instance of__SecurityDescriptor.
Windows Server 2003, Windows XP, Windows 2000, and Windows NT 4.0: This method is not available.
Writes an updated version of the security descriptor that controls access to the printer. The security descriptor is represented by an instance of __SecurityDescriptor.
Windows Server 2003, Windows XP, Windows 2000, and Windows NT 4.0: This method is not available.
晕菜了,这两个强大的api竟然在xp/2000/2003上都不能用,那怎么能适应不同客户端呢~~~
现在唯一的解决方法就只能是用C/C++来写native application了,C#由于需要.net framework也被抛弃(有现成的呢)~~~
很不幸的是,唯一的能找到的实现只有C#的,决定自己写一个,本以为1-2个小时的事情,结果竟然折腾了2个晚上,网上相关的资料太少了,M$的参考资料里很多的WMI的例子也都是用vbs或C#写成的,而用C/C++操作WMI namespace security的就灰常难找咯,一个简单的如何获得WMI里的__SystemSecurity就折腾了半天
自己折腾,最终终于搞定,几点体会:
1. Windows下用C/C++对COM/DCOM编程实在是痛苦,而牵涉到IDispatch,dual interface等,实在想彻底抛弃C/C++,投入script的怀抱
2. 可惜的script对Windows Security的支持比较有限
3. Windows下的error handling也是梦魇,满眼的HRESULT/FAILED/…眼花缭乱
唉,看来Linux玩久了再回头看Windows很多很多的不习惯啊~~~
里面牵涉到的技术点包括:
1. 如何获得WMI namespace的__SystemSecurity class(GetObject)
2. 如何invoke __SystemSecurity的方法(GetMethod/ExecMethod)
3. 如何对security descriptor操作,包括如何把security descriptor和SDDL(Security Descriptor Definition Language)相互转换,以及如何对SDDI作修改
具体技术细节不想细细展开了,直接贴代码,万一有人用到可以copy-paste;-)
// WmiSecurity.cpp : Defines the entry point for the console application. // #define _WIN32_DCOM #include <iostream> using namespace std; #include <comdef.h> #include <Wbemidl.h> #pragma comment(lib, "wbemuuid.lib") #include "stdafx.h" #ifdef WIN32 #include "XGetopt.h" #endif #include <atlbase.h> #include <Sddl.h> int parseSecurityFlag(const LPTSTR sec_flag) { if (_tcsicmp(sec_flag, _T("read")) == 0) return WBEM_ENABLE; if (_tcsicmp(sec_flag, _T("remoteaccess")) == 0) return WBEM_ENABLE | WBEM_REMOTE_ACCESS; if (_tcsicmp(sec_flag, _T("providerwrite")) == 0) return WBEM_ENABLE | WBEM_WRITE_PROVIDER; if (_tcsicmp(sec_flag, _T("partialwrite")) == 0) return WBEM_ENABLE | WBEM_PARTIAL_WRITE_REP; if (_tcsicmp(sec_flag, _T("fullwrite")) == 0) return WBEM_ENABLE | WBEM_FULL_WRITE_REP; if (_tcsicmp(sec_flag, _T("full")) == 0) return WBEM_REMOTE_ACCESS | WBEM_METHOD_EXECUTE | WBEM_FULL_WRITE_REP | WBEM_ENABLE | READ_CONTROL | WRITE_DAC; return 0; } void usage(const LPTSTR cmd) { _tprintf(_T("Usage: %s -n namespace -u user_account -s security_flag [-r]\n"), cmd); _tprintf(_T("Where -n: Namespace to target\n")); _tprintf(_T("Where -u: User account to grant\n")); _tprintf(_T("Where -s: WMI security flag to grant\n")); _tprintf(_T("Where -r: grant all subsequent WMI namespaces as well as present one")); exit(-1); } #ifdef UNICODE #define tstring wstring #else #define tstring string #endif class AceString { public: AceString() : recurse_(false), accessAllowed_(true) {} void addRight(tstring s) { if (_tcsstr(rights_.c_str(), s.c_str()) == NULL) rights_.append(s); } void reset() { rights_ = _T(""); aceString_ = _T(""); recurse_ = false; accessAllowed_ = true; } tstring getAceString() { return aceString_; } void setRecursive(bool b) { recurse_ = b; } void setAccessAllowed(bool b) { accessAllowed_ = b; } void createFinalAceString(LPTSTR sid) { if (accessAllowed_) aceString_.append(_T("A;")); // sddl access allowed else aceString_.append(_T("D;")); // sddl access denied if (recurse_) aceString_.append(_T("CI;")); // recurse through subcontainers else aceString_.append(_T(";")); // initial container only // Now add the rights... if (rights_.size() == 0) throw "AceString.CreateFinalSidString: empty rights string"; else { aceString_.append(rights_); aceString_.append(_T(";")); } // We don't do anything for Object Guid or Inherit Object Guid // in this version... aceString_.append(_T(";;")); if (sid == NULL || _tcslen(sid) == 0) throw "AceString.CreateFinalSidString: no Trustee specified"; else aceString_.append(sid); } void createAceStringFromWmiRight(int sec_flag) { switch (sec_flag) { case WBEM_REMOTE_ACCESS: addRight(_T("WP")); break; case WBEM_METHOD_EXECUTE: addRight(_T("DC")); break; case WBEM_FULL_WRITE_REP: addRight(_T("LC")); addRight(_T("SW")); addRight(_T("RP")); break; case WBEM_PARTIAL_WRITE_REP: addRight(_T("SW")); break; case WBEM_WRITE_PROVIDER: addRight(_T("BP")); break; case WBEM_ENABLE: addRight(_T("CC")); break; case READ_CONTROL: addRight(_T("RC")); break; case WRITE_DAC: addRight(_T("WD")); break; default: throw "AceString.CreateAceStringFromRight: Invalid Wmi right specified"; } } private: tstring rights_; tstring aceString_; bool recurse_; bool accessAllowed_; }; class AceStringManager : public AceString { private: LPTSTR sid_; public: AceStringManager(LPTSTR sid, bool accessAllowed, bool recursive) { AceString::AceString(); setAccessAllowed(accessAllowed); setRecursive(recursive); sid_ = sid; } tstring returnAceString(int sec_flag) { if ((WBEM_ENABLE & sec_flag) > 0) createAceStringFromWmiRight(WBEM_ENABLE); if ((WBEM_METHOD_EXECUTE & sec_flag) > 0) createAceStringFromWmiRight(WBEM_METHOD_EXECUTE); if ((WBEM_FULL_WRITE_REP & sec_flag) > 0) createAceStringFromWmiRight(WBEM_FULL_WRITE_REP); if ((WBEM_PARTIAL_WRITE_REP & sec_flag) > 0) createAceStringFromWmiRight(WBEM_PARTIAL_WRITE_REP); if ((WBEM_WRITE_PROVIDER & sec_flag) > 0) createAceStringFromWmiRight(WBEM_WRITE_PROVIDER); if ((WBEM_REMOTE_ACCESS & sec_flag) > 0) createAceStringFromWmiRight(WBEM_REMOTE_ACCESS); if ((READ_CONTROL & sec_flag) > 0) createAceStringFromWmiRight(READ_CONTROL); if ((WRITE_DAC & sec_flag) > 0) createAceStringFromWmiRight(WRITE_DAC); createFinalAceString(sid_); return getAceString(); } }; BOOL getSidStringFromName(const LPTSTR account, LPTSTR* pszSid) { BYTE sidBuffer[4096] = {0}; PSID psid = (PSID)&sidBuffer; DWORD sidBufferSize = sizeof(sidBuffer) / sizeof(sidBuffer[0]); DWORD domainSize = 255; LPTSTR domain = new TCHAR[domainSize]; SID_NAME_USE snu; BOOL rtn = LookupAccountName(NULL, account, psid, &sidBufferSize, domain, &domainSize, &snu); delete[] domain; if (!rtn) { return FALSE; } rtn = ConvertSidToStringSid(psid, pszSid); if (!rtn) { return FALSE; } return TRUE; } int _tmain(int argc, LPTSTR argv[]) { USES_CONVERSION; int oc; LPTSTR name_space = NULL; LPTSTR account = NULL; int sec_flag = 0; bool recursive = false; while ((oc = getopt(argc, argv, _T("n:u:s::r"))) != -1) { switch (oc) { case 'n': name_space = optarg; break; case 'u': account = optarg; break; case 's': sec_flag = parseSecurityFlag(optarg); break; case 'r': recursive = true; break; } } if (name_space == NULL || account == NULL || sec_flag == 0) { usage(argv[0]); } HRESULT hres; IWbemLocator *pLoc = NULL; IWbemServices *pSvc = NULL; BSTR ClassPath = SysAllocString(L"__SystemSecurity"); BSTR methodGetSD = SysAllocString(L"GetSD"); BSTR methodSetSD = SysAllocString(L"SetSD"); IWbemClassObject* pClass = NULL; IWbemClassObject * pGetSD_InClass = NULL; IWbemClassObject * pGetSD_OutClass = NULL; IWbemClassObject * pGetSD_OutInst = NULL; IWbemClassObject * pSetSD_InClass = NULL; IWbemClassObject * pSetSD_OutClass = NULL; IWbemClassObject * pSetSD_InInst = NULL; IWbemClassObject * pSetSD_OutInst = NULL; VARIANT varRes; // Step 1: -------------------------------------------------- // Initialize COM. ------------------------------------------ hres = CoInitializeEx(0, COINIT_MULTITHREADED); if (FAILED(hres)) { cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl; return 1; // Program has failed. } // Step 2: -------------------------------------------------- // Set general COM security levels -------------------------- // Note: If you are using Windows 2000, you need to specify - // the default authentication credentials for a user by using // a SOLE_AUTHENTICATION_LIST structure in the pAuthList ---- // parameter of CoInitializeSecurity ------------------------ hres = CoInitializeSecurity( NULL, -1, // COM authentication NULL, // Authentication services NULL, // Reserved RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation NULL, // Authentication info EOAC_NONE, // Additional capabilities NULL // Reserved ); if (FAILED(hres)) { cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl; CoUninitialize(); return 1; } LPTSTR pszSid = NULL; BOOL rtn = getSidStringFromName(account, &pszSid); if (!rtn) { cout << "Failed to get SID for account." << endl; CoUninitialize(); return 1; } _tprintf(_T("Account: %s\nSID: %s\n\n"), account, pszSid); AceStringManager aceStringManager(pszSid, true, recursive); tstring sddl = aceStringManager.returnAceString(sec_flag); LocalFree(pszSid); pszSid = NULL; // Step 3: --------------------------------------------------- // Obtain the initial locator to WMI ------------------------- hres = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc); if (FAILED(hres)) { cout << "Failed to create IWbemLocator object." << " Err code = 0x" << hex << hres << endl; goto _clean; } // Step 4: ----------------------------------------------------- // Connect to WMI through the IWbemLocator::ConnectServer method // Connect to the root\cimv2 namespace with // the current user and obtain pointer pSvc // to make IWbemServices calls. hres = pLoc->ConnectServer( _bstr_t(T2W(name_space)), // Object path of WMI namespace NULL, // User name. NULL = current user NULL, // User password. NULL = current 0, // Locale. NULL indicates current NULL, // Security flags. 0, // Authority (e.g. Kerberos) 0, // Context object &pSvc // pointer to IWbemServices proxy ); if (FAILED(hres)) { cout << "Could not connect. Error code = 0x" << hex << hres << endl; goto _clean; } cout << "Connected to WMI namespace: " << T2A(name_space) << endl; // Get system class of __SystemSecurity hres = pSvc->GetObject(ClassPath, 0, NULL, &pClass, NULL); if (FAILED(hres)) { cout << "Failed to get __SystemSecurity. Error code = 0x" << hex << hres << endl; goto _clean; } // Get method GetSD hres = pClass->GetMethod(methodGetSD, 0, &pGetSD_InClass, &pGetSD_OutClass); if (FAILED(hres)) { cout << "Failed to GetMethod of GetSD. Error code = 0x" << hex << hres << endl; goto _clean; } // Execute method GetSD, to get original SD hres = pSvc->ExecMethod(ClassPath, methodGetSD, 0, NULL, pGetSD_InClass, &pGetSD_OutInst, NULL); if (FAILED(hres)) { cout << "Failed to ExecMethod of GetSD. Error code = 0x" << hex << hres << endl; goto _clean; } hres = pGetSD_OutInst->Get(L"SD", 0, &varRes, NULL, 0); if (FAILED(hres)) { cout << "Failed to get SD property of GetSD result. Error Code = 0x" << hex << hres << endl; goto _clean; } SAFEARRAY* pArray = varRes.parray; BYTE* pszOriginalSD = NULL; hres = SafeArrayAccessData(pArray, (void**)&pszOriginalSD); if (FAILED(hres)) { cout << "Failed to lock SafeArray of returned SD. Error code = 0x" << hex << hres << endl; goto _clean; } LPTSTR pszSDDL = NULL; ULONG sizeSDDL = 0; rtn = ConvertSecurityDescriptorToStringSecurityDescriptor(pszOriginalSD, 1, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, &pszSDDL, &sizeSDDL); if (!rtn) { cout << "Failed to ConvertSecurityDescriptorToStringSecurityDescriptor. Error code = 0x" << hex << GetLastError() << endl; SafeArrayUnaccessData(pArray); goto _clean; } _tprintf(_T("\nOriginal SD: \n%s\n"), pszSDDL); hres = SafeArrayUnaccessData(pArray); if (FAILED(hres)) { cout << "Failed to unlock SafeArray of returned SD. Error code = 0x" << hex << hres << endl; LocalFree(pszSDDL); pszSDDL = NULL; goto _clean; } // change pszSDDL (for new SDDL) int len = _tcslen(pszSDDL) + sddl.size() + 100; LPTSTR newSDDL = new TCHAR[len]; wsprintf(newSDDL, _T("%s(%s)"), pszSDDL, sddl.c_str()); LocalFree(pszSDDL); pszSDDL = NULL; _tprintf(_T("\nNew SD: \n%s\n\n"), newSDDL); PSECURITY_DESCRIPTOR pSystemSD = NULL; ULONG iSystemSDSize = 0; rtn = ConvertStringSecurityDescriptorToSecurityDescriptor(newSDDL, 1, &pSystemSD, &iSystemSDSize); delete[] newSDDL; newSDDL = NULL; if (!rtn) { cout << "Failed to ConvertStringSecurityDescriptorToSecurityDescriptor. Error code = 0x" << hex << GetLastError() << endl; goto _clean; } BYTE * securityDescriptor = NULL; SAFEARRAY* pNewArray = SafeArrayCreateVector(VT_UI1, 0, iSystemSDSize); hres = SafeArrayAccessData(pNewArray, (void**)&securityDescriptor); if (FAILED(hres)) { cout << "Failed to lock SD for SetSD. Error code = 0x" << hex << hres << endl; LocalFree(pSystemSD); pSystemSD = NULL; goto _clean; } memcpy(securityDescriptor, pSystemSD, iSystemSDSize); LocalFree(pSystemSD); pSystemSD = NULL; hres = SafeArrayUnaccessData(pNewArray); if (FAILED(hres)) { cout << "Failed to unlock SD for SetSD. Error code = 0x" << hex << hres << endl; goto _clean; } VariantClear(&varRes); varRes.vt = VT_ARRAY | VT_BOOL; varRes.parray = pNewArray; hres = pClass->GetMethod(methodSetSD, 0, &pSetSD_InClass, &pSetSD_OutClass); if (FAILED(hres)) { cout << "Failed to GetMethod of SetSD. Error code = 0x" << hex << hres << endl; goto _clean; } hres = pSetSD_InClass->SpawnInstance(0, &pSetSD_InInst); if (FAILED(hres)) { cout << "Failed to SpawnInstance for SetSD. Error code = 0x" << hex << hres << endl; goto _clean; } hres = pSetSD_InInst->Put(L"SD", 0, &varRes, 0); if (FAILED(hres)) { cout << "Failed to put property of SD for SetSD. Error code = 0x" << hex << hres << endl; goto _clean; } VariantClear(&varRes); hres = pSvc->ExecMethod(ClassPath, methodSetSD, 0, NULL, pSetSD_InInst, &pSetSD_OutInst, NULL); if (FAILED(hres)) { cout << "Failed to SetSD. Error code = 0x" << hex << hres << endl; goto _clean; } hres = pSetSD_OutInst->Get(L"ReturnValue", 0, &varRes, NULL, 0); if (FAILED(hres)) { cout << "Failed to get result of SetSD. Error code = 0x" << hex << hres << endl; goto _clean; } if (varRes.intVal != 0) cout << "SetSD failed. Error cdoe = 0x" << hex << varRes.intVal << endl; else cout << "SetSD successfully." << endl; // Cleanup // ======== _clean: VariantClear(&varRes); SysFreeString(ClassPath); SysFreeString(methodGetSD); SysFreeString(methodSetSD); if (pClass) pClass->Release(); if (pGetSD_InClass) pGetSD_InClass->Release(); if (pGetSD_OutClass) pGetSD_OutClass->Release(); if (pGetSD_OutInst) pGetSD_OutInst->Release(); if (pSetSD_InClass) pSetSD_InClass->Release(); if (pSetSD_OutClass) pSetSD_OutClass->Release(); if (pSetSD_InInst) pSetSD_InInst->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); return 0; // Program successfully completed. }
没有评论:
发表评论