You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
727 lines
21 KiB
727 lines
21 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright(C) 2001 - 2002 Microsoft Corporation
|
|
//
|
|
// File: auditing.cxx
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "..\pch\headers.hxx"
|
|
#include "authzi.h"
|
|
#include "msaudite.h"
|
|
#include "security.hxx"
|
|
#include "auditing.hxx"
|
|
|
|
AUTHZ_RESOURCE_MANAGER_HANDLE ghRM = 0;
|
|
AUTHZ_AUDIT_EVENT_TYPE_HANDLE ghAuditEventType = 0;
|
|
|
|
HRESULT
|
|
AuditATJob(const AT_INFO &AtInfo, LPCWSTR pwszFileName)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Impersonate the caller
|
|
//
|
|
DWORD RpcStatus = RpcImpersonateClient(NULL);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(RpcStatus);
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Open the thread token
|
|
//
|
|
HANDLE hThreadToken = NULL;
|
|
BOOL bTokenOpened = OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_QUERY, // Desired access.
|
|
TRUE, // Open as self.
|
|
&hThreadToken);
|
|
//
|
|
// End impersonation.
|
|
//
|
|
if ((RpcStatus = RpcRevertToSelf()) != RPC_S_OK)
|
|
{
|
|
ERR_OUT("RpcRevertToSelf", RpcStatus);
|
|
schAssert(!"RpcRevertToSelf failed");
|
|
hr = _HRESULT_FROM_WIN32(RpcStatus);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (bTokenOpened)
|
|
{
|
|
|
|
DWORD cbAccountSid = MAX_SID_SIZE;
|
|
BYTE pbTaskSid[MAX_SID_SIZE];
|
|
|
|
hr = GetNSAccountSid(pbTaskSid, cbAccountSid);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = AuditJob(hThreadToken, pbTaskSid, pwszFileName);
|
|
}
|
|
|
|
CloseHandle(hThreadToken);
|
|
}
|
|
else
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT
|
|
AuditJob(
|
|
HANDLE hThreadToken,
|
|
PSID pTaskSid,
|
|
LPCWSTR pwszFileName
|
|
)
|
|
{
|
|
//
|
|
// Caller has already been impersonated and thread token opened prior to calling of this function
|
|
//
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Get the user SID
|
|
//
|
|
BYTE rgbTokenInformation[USER_TOKEN_STACK_BUFFER_SIZE];
|
|
TOKEN_USER* pTokenUser = (TOKEN_USER*) rgbTokenInformation;
|
|
DWORD cbReturnLength;
|
|
|
|
if (GetTokenInformation(hThreadToken,
|
|
TokenUser,
|
|
pTokenUser,
|
|
USER_TOKEN_STACK_BUFFER_SIZE,
|
|
&cbReturnLength))
|
|
{
|
|
//
|
|
// Get the Authentication ID
|
|
//
|
|
BYTE rgbTokenStatistics[sizeof(TOKEN_STATISTICS)];
|
|
TOKEN_STATISTICS* pTokenStatistics = (TOKEN_STATISTICS*) rgbTokenStatistics;
|
|
if (GetTokenInformation(hThreadToken,
|
|
TokenStatistics,
|
|
pTokenStatistics,
|
|
sizeof(TOKEN_STATISTICS),
|
|
&cbReturnLength))
|
|
{
|
|
DWORD dwRet = GenerateJobCreatedAudit(pTokenUser->User.Sid,
|
|
pTaskSid,
|
|
&(pTokenStatistics->AuthenticationId),
|
|
pwszFileName);
|
|
hr = _HRESULT_FROM_WIN32(dwRet);
|
|
}
|
|
else
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
HRESULT
|
|
GetJobAuditInfo(
|
|
LPCWSTR pwszFileName,
|
|
DWORD* pdwFlags,
|
|
LPWSTR* ppwszCommandLine,
|
|
LPWSTR* ppwszTriggers,
|
|
FILETIME* pftNextRun
|
|
)
|
|
{
|
|
if (!pwszFileName || pwszFileName[0] == L'\0' || !pftNextRun || !ppwszTriggers)
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// Init
|
|
//
|
|
*pdwFlags = 0;
|
|
*ppwszCommandLine = NULL;
|
|
*ppwszTriggers = NULL;
|
|
|
|
//
|
|
// Instantiate the job to get its run properties.
|
|
//
|
|
CJob* pJob = CJob::Create();
|
|
if (!pJob)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
HRESULT hr = pJob->LoadP(pwszFileName, 0, FALSE, TRUE);
|
|
if (FAILED(hr))
|
|
{
|
|
schDebugOut((DEB_ERROR, "GetTriggerAuditInfo: pJob->LoadP failed with error 0x%x\n", hr));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Get the flags
|
|
//
|
|
pJob->GetAllFlags(pdwFlags); // this call returns void
|
|
|
|
//
|
|
// Get the application
|
|
//
|
|
WCHAR* pwszApplicationName = NULL;
|
|
|
|
hr = pJob->GetApplicationName(&pwszApplicationName);
|
|
if (FAILED(hr))
|
|
{
|
|
schDebugOut((DEB_ERROR, "GetTriggerAuditInfo: pJob->GetApplicationName failed with error 0x%x\n", hr));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Get the parameters
|
|
//
|
|
WCHAR* pwszParameters = NULL;
|
|
|
|
hr = pJob->GetParameters(&pwszParameters);
|
|
if (FAILED(hr))
|
|
{
|
|
schDebugOut((DEB_ERROR, "GetTriggerAuditInfo: pJob->GetParameters failed with error 0x%x\n", hr));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Produce command line by concatenating application name and parameters
|
|
//
|
|
size_t cchBuff = lstrlenW(pwszApplicationName) + 1 + lstrlenW(pwszParameters) + 1;
|
|
WCHAR* pwszCommandLine = new WCHAR[cchBuff];
|
|
if (!pwszCommandLine)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
StringCchCopy(pwszCommandLine, cchBuff, pwszApplicationName);
|
|
StringCchCat(pwszCommandLine, cchBuff, L" ");
|
|
StringCchCat(pwszCommandLine, cchBuff, pwszParameters);
|
|
|
|
//
|
|
// Return pointer to the command line string.
|
|
// This string will need to be deleted by the caller.
|
|
//
|
|
*ppwszCommandLine = pwszCommandLine;
|
|
|
|
//
|
|
// Zero out next run time return value in case of any failures.
|
|
// Get the next run time, convert it to a local file time then to UTC.
|
|
//
|
|
//pftNextRun->dwLowDateTime = 0;
|
|
//pftNextRun->dwHighDateTime = 0;
|
|
//
|
|
// due to temporary issues, use a different value than 0
|
|
// 1/1/1700 00:00:00,000
|
|
pftNextRun->dwLowDateTime = 0xAEC64000;
|
|
pftNextRun->dwHighDateTime = 0x006EFDDD;
|
|
|
|
SYSTEMTIME stNextRun;
|
|
hr = pJob->GetNextRunTime(&stNextRun);
|
|
if (S_OK == hr)
|
|
{
|
|
FILETIME ftNextRunLocal;
|
|
if (SystemTimeToFileTime(&stNextRun, &ftNextRunLocal))
|
|
{
|
|
LocalFileTimeToFileTime(&ftNextRunLocal, pftNextRun);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now get the triggers.
|
|
//
|
|
// Build up a string consisting of formatted string
|
|
// representations of all the triggers for the task.
|
|
//
|
|
|
|
WORD cTriggers;
|
|
hr = pJob->GetTriggerCount(&cTriggers);
|
|
if (FAILED(hr))
|
|
{
|
|
schDebugOut((DEB_ERROR, "GetTriggerAuditInfo: pJob->GetTriggerCount failed with error 0x%x\n", hr));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Allocate initial buffer for concatenation of all trigger strings
|
|
//
|
|
const DWORD INITIAL_BUFFER_SIZE = 256; // start with enough space for 256 chars including null
|
|
const DWORD BUFFER_SIZE_INCREMENT = 256; // if necessary, grow buffer by this amount
|
|
DWORD dwSize = INITIAL_BUFFER_SIZE;
|
|
WCHAR* pwszTriggers = new WCHAR[dwSize]; // this will need to be deleted by the caller
|
|
|
|
if (!pwszTriggers)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
pwszTriggers[0] = L'\0';
|
|
|
|
DWORD dwSizeRequired;
|
|
WCHAR* pwszTemp;
|
|
WCHAR* pwszTriggerString;
|
|
|
|
for (short nTrigger = 0; nTrigger < cTriggers && SUCCEEDED(hr); nTrigger++)
|
|
{
|
|
hr = pJob->GetTriggerString(nTrigger, &pwszTriggerString);
|
|
if (FAILED(hr))
|
|
{
|
|
schDebugOut((DEB_ERROR, "GetTriggerAuditInfo: pJob->GetTriggerString failed with error 0x%x\n", hr));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If not big enough, create larger buffer, copy, and delete old
|
|
//
|
|
dwSizeRequired = lstrlenW(pwszTriggers)
|
|
+ 2 // two spaces
|
|
+ lstrlenW(pwszTriggerString)
|
|
+ 2; // period plus null terminator
|
|
|
|
if (dwSizeRequired > dwSize)
|
|
{
|
|
dwSize += BUFFER_SIZE_INCREMENT;
|
|
if (dwSizeRequired > dwSize) // if still not big enough (very unlikely),
|
|
dwSize = dwSizeRequired; // make it big enough
|
|
|
|
pwszTemp = new WCHAR[dwSize];
|
|
if (!pwszTemp)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
StringCchCopy(pwszTemp, dwSize, pwszTriggers);
|
|
delete [] pwszTriggers;
|
|
pwszTriggers = pwszTemp;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (lstrlenW(pwszTriggers) > 0)
|
|
StringCchCat(pwszTriggers, dwSize, L" ");
|
|
|
|
StringCchCat(pwszTriggers, dwSize, pwszTriggerString);
|
|
StringCchCat(pwszTriggers, dwSize, L".");
|
|
}
|
|
|
|
CoTaskMemFree(pwszTriggerString);
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// If all was a success, return the pointer to the string of triggers.
|
|
// This string will need to be deleted by the caller.
|
|
//
|
|
*ppwszTriggers = pwszTriggers;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If something went wrong, let's go ahead and clean up now.
|
|
//
|
|
if (pwszTriggers)
|
|
delete [] pwszTriggers;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CoTaskMemFree(pwszParameters);
|
|
}
|
|
|
|
CoTaskMemFree(pwszApplicationName);
|
|
}
|
|
}
|
|
|
|
if (pJob)
|
|
{
|
|
pJob->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* FUNCTION: StartupAuditing
|
|
*
|
|
* PURPOSE: Creates the resource manager and the audit event type handles.
|
|
*
|
|
* PARAMETERS:
|
|
*
|
|
* RETURNS: win32 error code
|
|
*
|
|
* History:
|
|
* 12-05-2001 maxa Created
|
|
*
|
|
\***************************************************************************/
|
|
|
|
DWORD
|
|
StartupAuditing(
|
|
)
|
|
{
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
BOOL fResult = FALSE;
|
|
BOOL fWasEnabled = TRUE;
|
|
|
|
if (ghRM && ghAuditEventType)
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwError = EnableNamedPrivilege(
|
|
L"SeAuditPrivilege",
|
|
TRUE,
|
|
&fWasEnabled);
|
|
|
|
if (dwError != ERROR_SUCCESS)
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
fResult = AuthzInitializeResourceManager(
|
|
0, // no special flags
|
|
NULL, // PFN_AUTHZ_DYNAMIC_ACCESS_CHECK
|
|
NULL, // PFN_AUTHZ_COMPUTE_DYNAMIC_GROUPS
|
|
NULL, // PFN_AUTHZ_FREE_DYNAMIC_GROUPS
|
|
L"Scheduler", // RM name
|
|
&ghRM
|
|
);
|
|
|
|
if (!fResult)
|
|
{
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
fResult = AuthziInitializeAuditEventType(
|
|
0,
|
|
SE_CATEGID_DETAILED_TRACKING,
|
|
SE_AUDITID_JOB_CREATED,
|
|
7,
|
|
&ghAuditEventType
|
|
);
|
|
|
|
if (!fResult)
|
|
{
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (!fWasEnabled)
|
|
{
|
|
EnableNamedPrivilege(
|
|
L"SeAuditPrivilege",
|
|
FALSE,
|
|
&fWasEnabled);
|
|
}
|
|
|
|
if (dwError != ERROR_SUCCESS)
|
|
{
|
|
ShutdownAuditing();
|
|
}
|
|
|
|
return dwError;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* FUNCTION: ShutdownAuditing
|
|
*
|
|
* PURPOSE: Deletes the resource manager and the audit event type handles.
|
|
*
|
|
* PARAMETERS:
|
|
*
|
|
* RETURNS:
|
|
*
|
|
* History:
|
|
* 12-05-2001 maxa Created
|
|
*
|
|
\***************************************************************************/
|
|
|
|
VOID
|
|
ShutdownAuditing(
|
|
)
|
|
{
|
|
if (ghAuditEventType)
|
|
{
|
|
AuthziFreeAuditEventType(ghAuditEventType);
|
|
ghAuditEventType = 0;
|
|
}
|
|
|
|
if (ghRM)
|
|
{
|
|
AuthzFreeResourceManager(ghRM);
|
|
ghRM = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* FUNCTION: GenerateJobCreatedAudit
|
|
*
|
|
* PURPOSE: Generates an audit-event indicating that a job has
|
|
* been created.
|
|
*
|
|
* PARAMETERS: pUserSid:
|
|
* SID of the account that created the job.
|
|
* pTaskSid:
|
|
* SID of the account the job is to run as.
|
|
* pLogonId:
|
|
* LogonId of the account that created the job.
|
|
* pszFileName:
|
|
* File name of the newly created job in the Tasks folder.
|
|
*
|
|
* RETURNS: win32 error code
|
|
*
|
|
* History:
|
|
* 10-01-2001 maxa Created
|
|
* 11-07-2001 shbrown Updated for use with tasks rather than AT jobs
|
|
*
|
|
\***************************************************************************/
|
|
|
|
DWORD
|
|
GenerateJobCreatedAudit(
|
|
IN PSID pUserSid,
|
|
IN PSID pTaskSid,
|
|
IN PLUID pLogonId,
|
|
IN PCWSTR pwszFileName
|
|
)
|
|
{
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
BOOL fResult = FALSE;
|
|
AUTHZ_AUDIT_EVENT_HANDLE hAuditEvent = NULL;
|
|
AUDIT_PARAMS AuditParams = {0};
|
|
AUDIT_PARAM ParamArray[10] = {APT_None};
|
|
PSID pDummySid = NULL;
|
|
|
|
ASSERT(pUserSid);
|
|
ASSERT(pTaskSid);
|
|
ASSERT(pLogonId);
|
|
ASSERT(pwszFileName && pwszFileName[0]);
|
|
ASSERT(ghRM);
|
|
ASSERT(ghAuditEventType);
|
|
|
|
|
|
//
|
|
// Get the job audit info
|
|
//
|
|
DWORD dwFlags;
|
|
LPWSTR pwszCommandLine = NULL; // this will need to be deleted after use
|
|
LPWSTR pwszTriggers = NULL; // this will need to be deleted after use
|
|
FILETIME ftNextRun;
|
|
HRESULT hr = GetJobAuditInfo(pwszFileName, &dwFlags, &pwszCommandLine, &pwszTriggers, &ftNextRun);
|
|
if (FAILED(hr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
AuditParams.Parameters = ParamArray;
|
|
|
|
fResult = AuthziInitializeAuditParams(
|
|
APF_AuditSuccess,
|
|
&AuditParams,
|
|
&pDummySid,
|
|
L"Security",
|
|
7,
|
|
APT_String | AP_Filespec, pwszFileName,
|
|
APT_String, pwszCommandLine,
|
|
APT_String, pwszTriggers,
|
|
APT_Time, ftNextRun,
|
|
APT_Ulong | AP_FormatHex, dwFlags,
|
|
APT_Sid, pTaskSid,
|
|
APT_LogonId, *pLogonId
|
|
);
|
|
|
|
if (!fResult)
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// this is ugly, but currently there is no other way
|
|
// do we still need this?
|
|
//
|
|
|
|
ParamArray[0].Data0 = (ULONG_PTR)pUserSid;
|
|
|
|
fResult = AuthziInitializeAuditEvent(
|
|
0, // flags
|
|
ghRM, // resource manager
|
|
ghAuditEventType,
|
|
&AuditParams,
|
|
NULL, // hAuditQueue
|
|
INFINITE, // time out
|
|
L"", L"", L"", L"", // obj access strings
|
|
&hAuditEvent
|
|
);
|
|
|
|
if (!fResult)
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
fResult = AuthziLogAuditEvent(
|
|
0, // flags
|
|
hAuditEvent,
|
|
NULL // reserved
|
|
);
|
|
|
|
if (!fResult)
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (pwszCommandLine)
|
|
{
|
|
delete [] pwszCommandLine;
|
|
}
|
|
|
|
if (pwszTriggers)
|
|
{
|
|
delete [] pwszTriggers;
|
|
}
|
|
|
|
if (hAuditEvent)
|
|
{
|
|
AuthzFreeAuditEvent(hAuditEvent);
|
|
}
|
|
|
|
if (pDummySid)
|
|
{
|
|
LocalFree(pDummySid);
|
|
}
|
|
|
|
return dwError;
|
|
|
|
|
|
Error:
|
|
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* FUNCTION: EnableNamedPrivilege
|
|
*
|
|
* PURPOSE: Enable or disable a privilege by its name.
|
|
*
|
|
* PARAMETERS: pszPrivName:
|
|
* Name of privilege to enable.
|
|
* fEnable:
|
|
* Enable/Disable flag.
|
|
* pfWasEnabled:
|
|
* Optional pointer to flag to receive the previous state.
|
|
*
|
|
* RETURNS: win32 error code
|
|
*
|
|
* History:
|
|
* 12-05-2001 maxa Created
|
|
*
|
|
\***************************************************************************/
|
|
|
|
DWORD
|
|
EnableNamedPrivilege(
|
|
IN PCWSTR pszPrivName,
|
|
IN BOOL fEnable,
|
|
OUT PBOOL pfWasEnabled OPTIONAL
|
|
)
|
|
{
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
BOOL fResult;
|
|
HANDLE hToken = 0;
|
|
DWORD dwSize = 0;
|
|
TOKEN_PRIVILEGES newPriv;
|
|
TOKEN_PRIVILEGES oldPriv;
|
|
|
|
fResult = OpenProcessToken(
|
|
GetCurrentProcess(),
|
|
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
|
|
&hToken);
|
|
|
|
if (!fResult)
|
|
{
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
fResult = LookupPrivilegeValue(
|
|
0,
|
|
pszPrivName,
|
|
&newPriv.Privileges[0].Luid);
|
|
|
|
if (!fResult)
|
|
{
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
newPriv.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
|
|
newPriv.PrivilegeCount = 1;
|
|
|
|
fResult = AdjustTokenPrivileges(
|
|
hToken,
|
|
FALSE,
|
|
&newPriv,
|
|
sizeof oldPriv,
|
|
&oldPriv,
|
|
&dwSize);
|
|
|
|
if (!fResult)
|
|
{
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (pfWasEnabled)
|
|
{
|
|
if (oldPriv.PrivilegeCount == 0)
|
|
{
|
|
*pfWasEnabled = fEnable;
|
|
}
|
|
else
|
|
{
|
|
*pfWasEnabled =
|
|
(oldPriv.Privileges[0].Attributes & SE_PRIVILEGE_ENABLED) ?
|
|
TRUE : FALSE;
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (hToken)
|
|
{
|
|
CloseHandle(hToken);
|
|
}
|
|
|
|
return dwError;
|
|
}
|