Use default authentication and separate cloaking/impersonation in DCOM call - security

I'm trying achieve two things with DCOM (Out of process)
Set the process wide authentication using CoInitializeSecurity and its parameter pAuthList.
Using cloaking to change the caller's identity in special situations (COM calls)
My thoughts:
AFAIK the auth info structure contains the default authentication information (like username and password for RPC_C_AUTHN_WINNT) for all new COM calls. So instead of the process token the information in the auth structure should be used by COM.
However, all COM calls/connections are always using the process' identity instead of the applied default one.
Usually, one can use CoSetProxyBlanket to change the auth info for a proxy. This works for me. My question here is whether it must or must not work if I impersonate the token myself and call the COM function. I've read in various MSDN articles that applying EOAC_DYNAMIC_CLOAKING to CoInitializeSecurity should make it working. However, my manually "impersonated COM calls always shows the process identity on the server side.
The client looks like this (Delphi)
var
authList : SOLE_AUTHENTICATION_LIST;
authidentity : SEC_WINNT_AUTH_IDENTITY_W;
authInfo : array[0..1] of SOLE_AUTHENTICATION_INFO;
pcAuthSvc : DWORD;
asAuthSvc : array[0..0] of SOLE_AUTHENTICATION_SERVICE;
Token : TJwSecurityToken;
begin
ZeroMemory( #authidentity, sizeof(authidentity) );
authidentity.User := 'Testbenutzer';
authidentity.UserLength := Length('Testbenutzer');
authidentity.Domain := '';
authidentity.DomainLength := 0;
authidentity.Password := 'test';
authidentity.PasswordLength := 4;
authidentity.Flags := SEC_WINNT_AUTH_IDENTITY_UNICODE;
ZeroMemory( #authInfo, sizeof( authInfo ) );
// NTLM Settings
authInfo[0].dwAuthnSvc := RPC_C_AUTHN_WINNT;
authInfo[0].dwAuthzSvc := RPC_C_AUTHZ_NONE;
authInfo[0].pAuthInfo := #authidentity;
authList.cAuthInfo := 1;
authList.aAuthInfo := #authInfo;
OleCheck(CoInitializeSecurity(
NULL, // Security descriptor
-1, // Count of entries in asAuthSvc
NULL, // asAuthSvc array
NULL, // Reserved for future use
RPC_C_AUTHN_LEVEL_CONNECT, // Authentication level
RPC_C_IMP_LEVEL_IMPERSONATE, // Impersonation level
#authList, // Authentication Information
DWORd(EOAC_DYNAMIC_CLOAKING), // Additional capabilities
NULL // Reserved
));
//create COM object
int := CoSecurityTestObj.Create;
int.TestCall;
The server also has set the flag EOAC_DYNAMIC_CLOAKING. It uses CoImpersonateClient to get the thread token and the username. It also uses CoQueryClientBlanket to get the authInfo (as SEC_WINNT_AUTH_IDENTITY_W structure). However both calls always return the process identity of the client.
Also impersonating manually doesn't work (2.):
Token := TJwSecurityToken.CreateLogonUser(authidentity.User, '', authidentity.Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT);
Token.ImpersonateLoggedOnUser;
int := CoSecurityTestObj.Create;
int.TestCall;
Questions again:
Am I wrong or why is the default auth info structure (WinNT with username and password) not used as default authentication in each COM connection/call ?
Am I wrong or why doesn't manual impersonation work? Be aware that I tested number 2. separately so number 1. cannot interfere.
This is basic work for the JEDI Windows Security Code Library which I extend to support COM security. So your help will go GPL/MPL.
References:
Cloaking:
http://msdn.microsoft.com/en-us/library/ms683778%28VS.85%29.aspx
http://msdn.microsoft.com/en-us/library/cc246058%28PROT.10%29.aspx
http://alt.pluralsight.com/wiki/default.aspx/Keith.GuideBook/WhatIsCoInitializeSecurity.html
CoInitializeSecurity and pAuthInfo
http://www.codeguru.cn/vc&mfc/apracticalguideusingvisualcandatl/93.htm
Getting security blanket (server side)
http://www.codeguru.cn/vc&mfc/apracticalguideusingvisualcandatl/92.htm

Have you tried calling CoInitializeSecurity() with RPC_C_AUTHN_LEVEL_CALL instead of RPC_C_AUTHN_LEVEL_CONNECT?
Usually when I create DCOM clients I create COSERVERINFO and pass to CoCreateInstanceEx() with security credentials, remembering to call CoSetProxyBlanket() on all interfaces.

Related

How to specify custom SSL roots with Go on Linux

Suppose I'm using a third party network library that I don't want to modify. It uses the standard http.Request interface to make some HTTPS requests.
I'm running it on an embedded Linux instance that doesn't have a certificate roots installed, and the only directory I can access is /data. This means you get the error:
Get https://example.com/: x509: failed to load system roots and no roots provided
Is there any way to actually provide roots? As far as I can tell Go looks in these directories for X509 certificate roots (see also):
var certFiles = []string{
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
}
var certDirectories = []string{
"/system/etc/security/cacerts", // Android
}
As I said, I don't have access to those, and the root pool seems to be private so you can't append to it:
var (
once sync.Once
systemRoots *CertPool
)
func systemRootsPool() *CertPool {
once.Do(initSystemRoots)
return systemRoots
}
Is this just impossible with Go?
Something like this seems to work (I actually only need one certificate chain, which you can get from Firefox easily by clicking on the SSL lock icon, More Information, View Certificate, Details, Export, Change type to "X509 certificate with chain (PEM)".
func initCerts() error {
certs := x509.NewCertPool()
pemData, err := ioutil.ReadFile("api.example.crt")
if err != nil {
return err
}
certs.AppendCertsFromPEM(pemData)
newTlsConfig := &tls.Config{}
newTlsConfig.RootCAs = certs
defaultTransport := http.DefaultTransport.(*http.Transport)
defaultTransport.TLSClientConfig = newTlsConfig
return nil
}
I'm not certain, but the docs suggest that you must do that before using any TLS functions, so I put it at the start of my main().

Inno Setup run/execute code as another user

Much as it is possible to use ExecAsOriginalUser to run something using the starting/logged in user's credentials, is there a command or way to run a particular piece of code as a specific user?
To explain further, I am writing the computer name into a log on a file share at the end of installation. This works perfectly as long as not installing using a local Administrator account which does not have a matching local Administrator account on the server. Therefore, what I need to do is have the particular bit of code that writes to the log execute as a specific user account, which I have the credentials for, that I know exists on the server.
The code I am using is as follows:
{ Auto audit the computer name }
procedure AutoAudit();
var
strComputerName: TArrayOfString;
strAutoAuditFile: String;
begin
strAutoAuditFile := '\\' + ClientConnectionPage.Values[0] + '\Audit\Client Audit.txt';
SetArrayLength(strComputerName, 1);
strComputerName[0] :=
ExpandConstant('{computername} ') + GetDateTimeString('dd/mm/yyyy hh:mm', '/', ':');
SaveStringsToFile(strAutoAuditFile, strComputerName, True);
end;
So, what I need is a way of executing the SaveStringsToFile function as another user.
I have thought of a rather messy way to do this, which would involve testing for an available drive letter, mapping a drive using the account credentials, writing to the file and then disconnecting the drive. However, I am hoping there is a more elegant way to do this?
You have to use the LogonUser WinAPI function to run a piece of code as a different account.
It's likely possible to run this from an Inno Setup code, but I haven't found an example.
Alternatively, you can develop a separate tiny application that impersonates the other user and embed the application into your installer.
In C#/VB.NET, you can use the Impersonator class (which uses the LogonUser internally):
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
using (new Impersonator("username", "domain", "password"))
{
string strAutoAuditFile = #"\\" + args[0] + #"\Audit\Client Audit.txt";
string strComputerName =
Environment.MachineName + " " + DateTime.Now.ToString("dd/mm/yyyy hh:mm");
File.WriteAllText(strAutoAuditFile, strComputerName);
}
}
}
Run the application from Inno Setup like:
[Files]
Source: "getcompname.exe"; Flags: dontcopy
[Code]
...
var
ResultCode: Integer;
begin
...
ExtractTemporaryFile('getcompname.exe');
ExecAsOriginalUser(
ExpandConstant('{tmp}') + '\getcompname.exe ',
ClientConnectionPage.Values[0],
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
...
end;
If you are not experienced with a console application development, note that the "tiny" application can be even a batch file with use of the runas command (which does not allow specifying password automatically) or the PsExec (which does).

Getting the current context ID in a Apache-hosted Delphi-created ISAPI dll

I have a thread-safe class that I use to get the correct TADOConnection for any given thread. This works great in my executables. It boils down to the following:
function ConnectionForCurrentThread () : TADOConnection;
var
thread : TThread;
begin
thread := TThread.CurrentThread;
result := adoConnectionFactory.ConnectionForID(thread.Handle);
end;
Where the adoConnectionFactory ends up handling a critical section to either return an existing ADO connection that's stored in a list, or making a new one if one isn't already created for that thread handle. Later, I test them to free them with a
if Windows.GetExitCodeThread(threadID, res) then begin
if res <> Windows.STILL_ACTIVE then begin
TADOConnection(connections.Objects[i]).Free;
connections.Delete(i);
end;
end else begin
TADOConnection(connections.Objects[i]).Free;
connections.Delete(i);
end;
This all seems to be working well. Where it's falling over is when used in a SOAP webservice ISAPI dll, hosted by Apache. The Thread.Handle ends up being the same for two simultaneous calls to the web service, so they attempt to share the same ADO Connection, which will occasionally throw exceptions due to the different threads fiddling with the same connection.
My question is, what can I use instead of the CurrentThread.handle? I'd really like this to be self-contained in this function call, so that if I recognize it's in web service dll mode, I would instead do something like:
function ConnectionForCurrentThread () : TADOConnection;
var
thread : TThread;
ct : TContextThing;
id : integer;
begin
if WebServiceMode then begin
ct := TContextThing.CurrentContext;
id := ct.ContextID;
end else begin
thread := TThread.CurrentThread;
id := thread.Handle;
end;
result := adoConnectionFactory.ConnectionForID(id);
end;
But I'm not too sure what that ContextThing might be. Any suggestions? And then on the flip side, how to see that a given context ID is no longer active so that its ADOConnection can be closed.
This is using Delphi XE and the SOAP server application (So, WebBroker, TWebModule, all that jazz).
Thank you.
For an ISAPI application, the following should work (assuming Apache fills in the ISAPI ECB correctly with a unique connection ID).
uses
Web.WebCntxt,
Web.Win.IsapiHttp;
if WebServiceMode then
id := TIsapiRequest(WebContext.Request).ECB^.ConnID
else
...

CreateEvent from Windows-7 Logon Screen

I'm asking this question because it turns out that there's some difficulty in writing a screensaver app in Delphi that's capable of running from the Logon screen.
See question: Windows 7 logon screensaver in Delphi
I've narrowed down the problem (or at least one problem) to a particular Win API call CreateEvent.
SyncEvent := CreateEvent(nil, True, False, '');
if SyncEvent = 0 then
RaiseLastOSError;
This code only fails if called from the Logon screen. And GetLastError returns that access is denied. So clearly the security restrictions on the Logon screen prevent CreateEvent(nil, True, False, ''); from creating the event as desired.
(I don't really see how an Event could be an exploitable security risk.)
So, the question is: Is it possible to create an Event from the Logon screen? Presumably via either:
Using an appropriate lpEventAttributes
Or calling CreatingEventEx instead.
Although the problem was experienced in Delphi, this is more about the Win API. So feel free to answer in your language of choice.
Try setting the last parameter of CreateEvent()
to nil instead of '' . There is a difference between
a nil pointer and a pointer to a zero-length string. The
documentation does not say anything about a zero-
length string being treated any differently than any
other named string. So maybe there is another zero-length-named event that exists somewhere else on your machine that your app does not have access to, thus the Access Denied error when CreateEvent() tries to access the existing event and fails. If you want to create an unnamed event,
use nil instead.

Delphi: Open a file from another computer

my names's Carlos Im from Brazil.
Im trying to open a file like this:
image1.picture.loadfromfile('\\ntmemo01\c$\ozzy2.bmp');
but it doesnt work. Im receving the exception
class EFOpenError with message "Cannot open file '\ntmemo01\c$\ozzy2.bmp' Access denied."
Thanks,
Carlos
You have to use double backslashes.
image1.picture.loadfromfile('\\ntmemo01\c$\ozzy2.bmp');
If you still get the exception, then the file is inaccessible from your application.
The first thing you should do is making sure, that you can access the file using the Windows Explorer.
Just type it into the Run dialog of the start menu (WinKey+R) and see what happens. If it doesn't work, make it work there first and then go back to your program.
Is the C: drive on ntmemo01 shared? If it's not shared, you can't access it. If it's shared but requires a user name and password to access, you'll have to access it differently. You can map a drive letter to it, providing a user name and password in the process:
const
RemoteName = '\\ntmemo01\C$';
UserName = 'yourusername';
Password = 'yourpassword';
function MapNetworkDrive: Boolean;
var
NetRes: TNetResource;
Res: DWord;
begin
Result := True;
FillChar(NetRes, SizeOf(TNetResource), 0);
NetRes.dwType := RESOURCETYPE_DISK;
NetRes.lpRemoteName := PChar(RemoteName);
NetRes.lpLocalName := 'H:'; // Whatever drive letter you want
Res := WNetAddConnection2(NetRes, PChar(Password), PChar(UserName), 0);
Result := (Res = NO_ERROR);
end;
To unmap afterwards:
function UnMapNetworkDrive: Boolean;
var
Res: DWord;
begin
Res := WNetCancelConnection2(PChar('H:'), 0, True); // same drive letter as above
Result := (Res + NO_ERROR);
end;
As mentioned by DR, the filename requires double backslashes for a UNC path
The access denied message suggests the you do not have permission to access the C$ share on the ntmemo01 computer.
C$ a is hidden administrative share are you sure the current user account has the correct permissions? You make have to the map a drive first, as suggested by Ken White
Administrive shares are disabled by default in Windows Vista and Windows 7, unless you join a domain. You can be enable them manually as follows.
Click on the start button and in the search box type ‘regedit’ and hit enter.
Browse to HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System.
Add a new DWORD called LocalAccountTokenFilterPolicy and give it a value of 1.
Reboot and yer done!
Source: http://www.paulspoerry.com/2007/05/09/how-to-access-administrative-shares-on-vista-c/
when you copy this exact same string in windows explorer, is the file opened?
Otherwise it might be a rights problem, as suggested by the error.

Resources