2025-04-27 07:49:33 -04:00

588 lines
19 KiB
C++

// **************************************************************************
//
// Copyright (c) 2001 Microsoft Corporation, All Rights Reserved
//
// File: Security.cpp
//
// Description:
// WMI Client Security Sample. This sample shows how various how to
// handle various DCOM security issues. In particular, it shows how to call
// CoSetProxyBlanket in order to deal with common situations. The sample also
// shows how to access the security descriptors that wmi uses to control
// namespace access.
//
// **************************************************************************
#include <objbase.h>
#include <windows.h>
#include <stdio.h>
#include <wbemidl.h>
#include <comdef.h>
WCHAR wPath[MAX_PATH];
WCHAR wFullUserName[MAX_PATH];
WCHAR wPassWord[MAX_PATH];
bool bPassWordSet = false;
bool bUserNameSet = false;
void __stdcall _com_issue_error(HRESULT){}
//***************************************************************************
//
// DisplayOptions
//
// Purpose: Displays the command line options.
//
//***************************************************************************
void DisplayOptions()
{
printf("\nThis application demonstrates various DCOM security issues. It \n"
"will connect up to the namespace and enumerate the top level classes\n"
"and will also dump the security descriptor used to control access to the namespace.\n\n"
"usage: Security [-P:<Password>] [-U:<UserName>] [-L] [-A:<name>] <ServerNamespace>\n"
" -U:<UserName> Optional User Name\n"
" -P:<Password> Optional Login password\n"
"Example; c:>Security -U:domain\\username -P:xyz -L \\\\server\\root\\default\n"
);
return;
}
//***************************************************************************
//
// ProcessArguments
//
// Purpose: Processes command line arguments. Returns FALSE if there is a
// problem, or if user just wants help.
//
//***************************************************************************
BOOL ProcessArguments(int iArgCnt, WCHAR ** argv)
{
wPath[0] = 0;
wFullUserName[0] = 0;
wPassWord[0] = 0;
if(iArgCnt < 2)
return FALSE;
for(int iCnt = 1; iCnt < iArgCnt-1 ; iCnt++)
{
WCHAR * pArg = argv[iCnt];
if(pArg[0] != '-')
return FALSE;
switch (pArg[1])
{
case L'u':
case L'U':
wcscpy(wFullUserName, pArg+3);
bUserNameSet = true;
break;
case L'p':
case L'P':
wcscpy(wPassWord, pArg+3);
bPassWordSet = true;
break;
default:
return FALSE;
}
}
// Get the path, it is the last argument
if(argv[iArgCnt-1][0] == L'-') // probably -help or -?. Not valid path!
return FALSE;
wcscpy(wPath, argv[iArgCnt-1]);
return TRUE;
}
//***************************************************************************
//
// SetProxySecurity
//
// Purpose: Calls CoSetProxyBlanket in order to control the security on a
// particular interface proxy.
//
//***************************************************************************
HRESULT SetProxySecurity(IUnknown * pProxy)
{
HRESULT hr;
DWORD dwAuthnLevel;
RPC_AUTH_IDENTITY_HANDLE * pAuthInfo = NULL;
// There are various reasons to set security. An application that can
// call CoInitializeSecurity and doesnt use an alternative user\password,
// need not bother with call CoSetProxyBlanket. There are at least
// three cases that do need it though.
//
// 1) Dlls cannot call CoInitializeSecurity and will need to call it just
// to raise the impersonation level. This does NOT require that the
// RPC_AUTH_IDENTITY_HANDLE to be set nor does this require setting
// it on the IUnknown pointer.
// 2) Any time that an alternative user\password are set as is the case
// in this simple sample. Note that it is necessary in that case to
// also set the information on the IUnknown.
// 3) If the caller has a thread token from a remote call to itself and
// it wants to use that identity. That would be the case of an RPC/COM
// server which is handling a call from one of its clients and it wants
// to use the client's identity in calls to WMI. In this case, then
// the RPC_AUTH_IDENTITY_HANDLE does not need to be set, but the
// dwCapabilities arguement should be set for cloaking. This is also
// required for the IUnknown pointer.
if(bPassWordSet == false && bUserNameSet == false)
{
// In this case, nothing needs to be done. But for the sake of
// example the code will set the impersonation level.
// For backwards compatibility, retrieve the previous authentication level, so
// we can echo-back the value when we set the blanket
hr = CoQueryProxyBlanket(
pProxy, //Location for the proxy to query
NULL, //Location for the current authentication service
NULL, //Location for the current authorization service
NULL, //Location for the current principal name
&dwAuthnLevel, //Location for the current authentication level
NULL, //Location for the current impersonation level
NULL, //Location for the value passed to IClientSecurity::SetBlanket
NULL //Location for flags indicating further capabilities of the proxy
);
if(FAILED(hr))
return hr;
hr = CoSetProxyBlanket(pProxy, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL, dwAuthnLevel,
RPC_C_IMP_LEVEL_IMPERSONATE, COLE_DEFAULT_AUTHINFO, EOAC_DEFAULT);
return hr;
}
else
{
// The user\password was set, it is necessary to call CoSetProxyBlanket
COAUTHIDENTITY authident;
pAuthInfo = NULL;
// For backwards compatibility, retrieve the previous authentication level, so
// we can echo-back the value when we set the blanket
hr = CoQueryProxyBlanket(
pProxy, //Location for the proxy to query
NULL, //Location for the current authentication service
NULL, //Location for the current authorization service
NULL, //Location for the current principal name
&dwAuthnLevel, //Location for the current authentication level
NULL, //Location for the current impersonation level
NULL, //Location for the value passed to IClientSecurity::SetBlanket
NULL //Location for flags indicating further capabilities of the proxy
);
memset((void *)&authident,0,sizeof(COAUTHIDENTITY));
// note that this assumes NT. Win9X would fill in the field with
// ascii and then use SEC_WINNT_AUTH_IDENTITY_ANSI flag instead
WCHAR wUserName[MAX_PATH];
WCHAR wDomainName[MAX_PATH];
wUserName[0] = 0;
wDomainName[0] = 0;
bool bDomainNameSet = false;
if(bUserNameSet)
{
// Note that the user name may be in the form domain\user. If so, split it up
for(WCHAR * pTemp = wFullUserName; *pTemp; pTemp++)
{
if(*pTemp == L'\\')
{
*pTemp = 0;
wcscpy(wDomainName, wFullUserName);
wcscpy(wUserName, pTemp +1);
*pTemp = L'\\'; // put it back
bDomainNameSet = true;
break;
}
}
authident.UserLength = wcslen(wUserName);
authident.User = (LPWSTR)wUserName;
}
if(bDomainNameSet)
{
authident.DomainLength = wcslen(wDomainName);
authident.Domain = (LPWSTR)wDomainName;
}
if(bPassWordSet)
{
authident.PasswordLength = wcslen(wPassWord);
authident.Password = (LPWSTR)wPassWord;
}
authident.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
hr = CoSetProxyBlanket(pProxy, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL, dwAuthnLevel,
RPC_C_IMP_LEVEL_IMPERSONATE, &authident, EOAC_DEFAULT);
if(FAILED(hr))
return hr;
// There is a remote IUnknown interface that lurks behind IUnknown.
// If that is not set, then the Release call can return access denied.
IUnknown * pUnk = NULL;
hr = pProxy->QueryInterface(IID_IUnknown, (void **)&pUnk);
if(FAILED(hr))
return hr;
hr = CoSetProxyBlanket(pUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL, dwAuthnLevel,
RPC_C_IMP_LEVEL_IMPERSONATE, &authident, EOAC_DEFAULT);
pUnk->Release();
return hr;
}
}
//***************************************************************************
//
// ListClasses
//
// Purpose: Lists the classes.
//
//***************************************************************************
void ListClasses(IWbemServices *pNamespace)
{
HRESULT hr;
IEnumWbemClassObject *pEnum = NULL;
hr = pNamespace->CreateClassEnum(NULL, 0, NULL, &pEnum);
if(SUCCEEDED(hr))
{
// Note that even though security was set on the namespace pointer, it must
// also be set on this pointer since COM will revert back to the default
// settings for any new pointers!
hr = SetProxySecurity(pEnum);
if(SUCCEEDED(hr))
{
IWbemClassObject* Array[1];
Array[0] = NULL;
ULONG uRet = 0;
while (SUCCEEDED(hr = pEnum->Next(10000, 1, Array, &uRet)) && Array[0])
{
// Note that IWbemClassObjects are inproc and thus have no proxy.
// Therefore, they never need CoSetProxyBlanket.
BSTR strText;
IWbemClassObject* pObj = Array[0];
hr = pObj->GetObjectText(0, &strText);
if(SUCCEEDED(hr) && strText)
{
printf("\nGot class %S", strText);
SysFreeString(strText);
}
pObj->Release();
Array[0] = NULL;
}
}
else
printf("\nFAILED TO SET SECURITY FOR ENUMERATOR!!!!! hr = 0x%x", hr);
pEnum->Release();
}
}
//***************************************************************************
//
// DumpAce
//
// Purpose: Dumps information in an ACE. Note that this is simple cook book
// code. There are many available wrappers to do this sort of thing.
//
//***************************************************************************
void DumpAce(ACCESS_ALLOWED_ACE * pAce)
{
printf("\n Ace, type=0x%x, flags=0x%x, mask=0x%x",
pAce->Header.AceType, pAce->Header.AceFlags, pAce->Mask);
PSID pSid = 0;
SID_NAME_USE use;
WCHAR wcUser[100], wcDomain[100];
DWORD dwNameSize = 100, dwDomainSize = 100;
pSid = &pAce->SidStart;
if(LookupAccountSidW(
NULL, // name of local or remote computer
pSid, // security identifier
wcUser, // account name buffer
&dwNameSize, // size of account name buffer
wcDomain, // domain name
&dwDomainSize, // size of domain name buffer
&use))
printf(" User name = %S, Domain name = %S", wcUser, wcDomain);
}
//***************************************************************************
//
// DumpSD
//
// Purpose: Dumps information in a security descriptor. Note that this is
// simple cook book code. There are many available wrappers to do this sort
// of thing.
//
//***************************************************************************
void DumpSD(PSECURITY_DESCRIPTOR pSD)
{
DWORD dwSize = GetSecurityDescriptorLength(pSD);
printf("\nSecurity Descriptor is of size %d", dwSize);
BOOL DaclPresent, DaclDefaulted;
PACL pDacl;
if(GetSecurityDescriptorDacl(pSD, &DaclPresent,
&pDacl, &DaclDefaulted) && DaclPresent)
{
// Dump the aces
ACL_SIZE_INFORMATION inf;
DWORD dwNumAces;
if(GetAclInformation(
pDacl,
&inf,
sizeof(ACL_SIZE_INFORMATION),
AclSizeInformation
))
{
dwNumAces = inf.AceCount;
printf("\nThe DACL has %d ACEs", dwNumAces);
for(DWORD dwCnt = 0; dwCnt < dwNumAces; dwCnt++)
{
ACCESS_ALLOWED_ACE * pAce;
if(GetAce(pDacl, dwCnt, (LPVOID *)&pAce))
DumpAce(pAce);
}
}
}
}
//***************************************************************************
//
// StoreSD
//
// Purpose: Writes back the ACL which controls access to the namespace.
//
//***************************************************************************
bool StoreSD(IWbemServices * pSession, PSECURITY_DESCRIPTOR pSD)
{
bool bRet = false;
HRESULT hr;
// Get the class object
IWbemClassObject * pClass = NULL;
_bstr_t InstPath(L"__systemsecurity=@");
_bstr_t ClassPath(L"__systemsecurity");
hr = pSession->GetObject(ClassPath, 0, NULL, &pClass, NULL);
if(FAILED(hr))
return false;
// Get the input parameter class
_bstr_t MethName(L"SetSD");
IWbemClassObject * pInClassSig = NULL;
hr = pClass->GetMethod(MethName,0, &pInClassSig, NULL);
pClass->Release();
if(FAILED(hr))
return false;
// spawn an instance of the input parameter class
IWbemClassObject * pInArg = NULL;
pInClassSig->SpawnInstance(0, &pInArg);
pInClassSig->Release();
if(FAILED(hr))
return false;
// move the SD into a variant.
SAFEARRAY FAR* psa;
SAFEARRAYBOUND rgsabound[1]; rgsabound[0].lLbound = 0;
long lSize = GetSecurityDescriptorLength(pSD);
rgsabound[0].cElements = lSize;
psa = SafeArrayCreate( VT_UI1, 1 , rgsabound );
if(psa == NULL)
{
pInArg->Release();
return false;
}
char * pData = NULL;
hr = SafeArrayAccessData(psa, (void HUGEP* FAR*)&pData);
if(FAILED(hr))
{
pInArg->Release();
return false;
}
memcpy(pData, pSD, lSize);
SafeArrayUnaccessData(psa);
_variant_t var;
var.vt = VT_I4|VT_ARRAY;
var.parray = psa;
// put the property
hr = pInArg->Put(L"SD" , 0, &var, 0);
if(FAILED(hr))
{
pInArg->Release();
return false;
}
// Execute the method
IWbemClassObject * pOutParams = NULL;
hr = pSession->ExecMethod(InstPath,
MethName,
0,
NULL, pInArg,
NULL, NULL);
if(FAILED(hr))
printf("\nPut failed, returned 0x%x",hr);
return bRet;
}
//***************************************************************************
//
// ReadACL
//
// Purpose: Reads the ACL which controls access to the namespace.
//
//***************************************************************************
bool ReadACL(IWbemServices *pNamespace)
{
bool bRet = false;
_bstr_t InstPath(L"__systemsecurity=@");
_bstr_t MethName(L"GetSD");
IWbemClassObject * pOutParams = NULL;
// The security descriptor is returned via the GetSD method
HRESULT hr = pNamespace->ExecMethod(InstPath,
MethName,
0,
NULL, NULL,
&pOutParams, NULL);
if(SUCCEEDED(hr))
{
// The output parameters has a property which has the descriptor
_bstr_t prop(L"sd");
_variant_t var;
hr = pOutParams->Get(prop, 0, &var, NULL, NULL);
if(SUCCEEDED(hr))
{
if(var.vt != (VT_ARRAY | VT_UI1))
return false;
SAFEARRAY * psa = var.parray;
PSECURITY_DESCRIPTOR pSD;
hr = SafeArrayAccessData(psa, (void HUGEP* FAR*)&pSD);
if(hr != 0)
return false;
// Dump out some information
DumpSD(pSD);
// Given that the security desciptor is now present, the code could use standard
// nt functions to add or remove ace's. There are also various libraries available
// that can make the job a bit easier. This sample does not change the security
// descriptor, but does write it back unchanged as an example.
StoreSD(pNamespace, pSD);
SafeArrayUnaccessData(psa);
bRet = true;
}
}
return bRet;
}
//***************************************************************************
//
// main
//
// Purpose: Main entry point.
//
//***************************************************************************
BOOL g_bInProc = FALSE;
int wmain(int iArgCnt, WCHAR ** argv)
{
if(!ProcessArguments(iArgCnt, argv))
{
DisplayOptions();
return 1;
}
IWbemLocator *pLocator = NULL;
IWbemServices *pNamespace = 0;
IWbemClassObject * pClass = NULL;
// Initialize COM.
HRESULT hr = CoInitialize(0);
// This has two valuable purposes. First, it sets the default impersonation level
// to "Impersonate" which is what WMI providers will generally require. Second,
// it sets the required authentication level of to None. That means that WINMGMT
// servers running on NT4 will be able to call back.
//
// DLLs cannot call this function. To bump up the impersonation level, they must
// call CoSetProxyBlanket which is illustrated later on. To get around the NT 4 call
// back problem, DLLs should avoid asyncronous methods, or they can use
// Unsecapp.exe which is discussed in the documentation.
hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL, EOAC_NONE, 0);
hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *) &pLocator);
hr = pLocator->ConnectServer(wPath,
(bUserNameSet) ? wFullUserName : NULL,
(bPassWordSet) ? wPassWord : NULL,
NULL, 0, NULL, NULL, &pNamespace);
printf("\n\nConnectServer returned 0x%x:", hr);
if(FAILED(hr))
{
printf("\nConnectServer failed, hr = 0x%x", hr);
}
else
{
printf("\nConnection succeeded");
hr = SetProxySecurity(pNamespace);
if(SUCCEEDED(hr))
{
ListClasses(pNamespace);
ReadACL(pNamespace);
}
pNamespace->Release();
}
CoUninitialize();
printf("Terminating normally\n");
return 0;
}