Delphi programmers develop COM + applications very quickly, mainly because it encapsulates the windows of COM +
S underlying functions, developers can avoid complicated COM + underlying technical details through simple class inheritance, so that developers can
Focus on the functions of the application. Delphi adopts many trade-offs when encapsulating COM + applications, and retains Universality
At the same time, it also avoids some underlying features of COM +, which are difficult to implement but not widely used. Among these avoidance features
Security is a concern of Delphi com developers. Since Delphi 5, many people have faced such problems.
Question: COM applications are developed and run normally on the local machine, but once distributed for remote access, no
Method is running normally. I felt dizzy when I saw the "Access Denied" error message for a while. In fact, seriously pursue
The reason is that you do not know much about Windows security technology. Over the years, I have never found W
Indows Security comparison system documents and books until the Chinese version of Keith brownr <Windows security programming>
. Based on this book, I have tried some of the following experiments and learned why I always make up for the reasons for rejection. Lower
The above discussion is just a few small and difficult methods I have summarized when solving the security access problem of my existing code. Suggestion
If you want to learn about Windows security, go to <Windows security programming> and you will find that Windows security is not
Mysterious. This article describes how to activate COM + objects as a user on a remote workstation and use this user identity.
Access interface. 1. Delphi remote activation of COM + objects by default
In delph, Remote COM + object activation is generally implemented through tdispatchconnection and its subclass. In actual code
Tdcomconnection or tsocketconnectoion are used. The tdcomconnection component finally calls Co
Createinstanceex creates a COM + object. Cocreateinstanceex (const CLSID: tclsid; unkouter:
Iunknown; dwclsctx: longint; serverinfo: pcoserverinfo; dwcount: longint; rgmqres
Ults: pmultiqiarray): hresult. Tdcomconnection is pcose when cocreateinstanceex is called
Pauthinfo in the rverinfo parameter passes a null value, so what tdcomconnection uses when creating a COM object is
The User Token Of the attacker on the local computer. If the Login User auser on computer A uses the tdcomconnection class for connection
For the COM + object on the remote computer B, computer B uses the user name/password of auser to establish a logon session on computer B.
And finally create the COM + object. However, local users on a Windows Workstation can only log on locally, but cannot log on to other
Therefore, auser on computer A cannot establish a logon session on workstation B. Of course, C cannot be created.
Om + object. In this case, remote workstation B tries to use the Guest account to establish a session and use this account to activate the COM + object. In this
If the Guest account on workstation B is not enabled or the Guest account is not authorized to activate the COM + object, you will see
The dizzy prompt "Access Denied ". You are not using the most "popular" DCOM configuration method on the Internet.
I realized it. That method allows everyone to access, activate COM objects, and set "Default Authentication level"
. This method can enable your com application to "use", but it can be accessed by "anyone. And this
You will not be able to use the role-based security access control function of COM +. 2. How can I activate this function without using a guest account?
The problem is: how to use a user on a remote workstation to activate a Remote COM object. Actually, it is very difficult to solve this problem.
Simple: you only need to specify the user name and password on the remote workstation when you call cocreateinstanceex.
The account name/password is verified by a remote computer and the user is granted the permission to "remotely activate" COM + object.
The Remote Workstation uses this user identity to activate the COM + object. Take a look at the Code:
VaR
MTS: imtsxjpimsdb;
Ov: variant;
I: integer;
Cai: _ coauthinfo;
CID: _ coauthidentity;
CSI: coserverinfo;
Mqi: multi_qi;
Iid_unk: tguid;
IDSP: idispatch;
Wuser, wdomain, wpsw: widestring;
Begin
Wuser: = eduser. Text; // User Name
Wdomain: = edsvr. Text; // remote computer name
Wpsw: = edpsw. Text; // Password
CID. User: = punshort (@ wuser [1]);
CID. userlength: = length (wuser );
CID. Domain: = punshort (@ wdomain [1]);
CID. domainlength: = length (wdomain );
CID. Password: = punshort (@ wpsw [1]);
CID. passwordlength: = length (wpsw );
CID. Flags: = 2;
// The above filled _ coauthidentity Structure
Cai. dwauthnsvc: = 10; // Default Authentication Service of winnt
Cai. dwauthzsvc: = 0;
Cai. pwszserverprincname: = wdomain;
Cai. dwauthnlevel: = 0;
Cai. dwimpersonationlevel: = 3; // The value must be set to analog.
Cai. pauthidentitydata: [email protected];
Cai. dwcapabilities: = $0800; // The above filled _ coauthinfo Structure
Fillchar (CSI, sizeof (CSI), 0 );
CSI. dwreserved1: = 0;
CSI. pwszname: = pwidechar (wdomain );
CSI. pauthinfo: [email protected];
// Fill in the coserverinfo structure above
Iid_unk: = iunknown;
Mqi. IID: [email protected] _ unk; mqi. ITF: = nil; mqi. HR: = 0;
Screen. cursor: = crhourglass;
Olecheck (cocreateinstanceex (class_mtsxjpimsdb, nil, clsctx_remote_server, @ CSI, 1, @ mqi ));
In this Code, except for actually calling cocreateinstanceex, the previous Code sets parameters. For the meanings of these parameters, refer to msdn, except the user name and host name.
Except the password, there is only one important part to note: Cai. dwimpersonationlevel must be set to allow simulation (Value
3). Otherwise, the remote computer will not be able to recommend network sessions based on the provided user/password.
3. Can I activate it as a remote user without modifying the existing code? Of course. I have extended the tdcomconnection class and added the username and
Password, and modify its default doconnect method so that it uses the specified user name and
Password filling parameters. The Code is as follows:
Unit secdcomconnection;
Interface
Uses
Windows, sysutils, classes, ActiveX, DB, dbclient, mconnect, comobj, and Midas;
Type
{Typedef struct _ sec_winnt_auth_identity
Unsigned short _ rpc_far * user;
Unsigned long userlength;
Unsigned short _ rpc_far * domain;
Unsigned long domainlength;
Unsigned short _ rpc_far * password;
Unsigned long passwordlength;
Unsigned long flags;
Sec_winnt_auth_identity, * psec_winnt_auth_identity;
}
{Typedef struct _ coauthidentity
Ushort * user;
Ulong userlength;
Ushort * domain;
Ulong domainlength;
Ushort * password;
Ulong passwordlength;
Ulong flags;
Coauthidentity ;}
{# Define rpc_c_authn_none 0
# Define rpc_c_authn_dce_private 1
# Define rpc_c_authn_dce_public 2
# Define rpc_c_authn_dec_public 4
# Define rpc_c_authn_gss_negotiate 9
# Define rpc_c_authn_winnt 10
# Define rpc_c_authn_gss_schannel 14
# Define rpc_c_authn_gss_kerberos 16
# Define rpc_c_authn_msn 17
# Define rpc_c_authn_dpa 18
# Define rpc_c_authn_mq 100
# Define rpc_c_authn_default 0 xffffffffl
}
{# Define rpc_c_authz_none 0
# Define rpc_c_authz_name 1
# Define rpc_c_authz_dce 2
# Define rpc_c_authz_default 0 xffffffff}
{
# Define rpc_c_authn_level_default 0
# Define rpc_c_authn_level_none 1
# Define rpc_c_authn_level_connect 2
# Define rpc_c_authn_level_call 3
# Define rpc_c_authn_level_pkt 4
# Define rpc_c_authn_level_pkt_integrity 5
# Define rpc_c_authn_level_pkt_privacy 6}
{Sec_winnt_auth_identity_unicode = 2}
Punshort = ^ word;
Pcoauthidentity = ^ _ coauthidentity;
_ Coauthidentity = record
User: punshort;
Userlength: ulong;
Domain: punshort;
Domainlength: ulong;
Password: punshort;
Passwordlength: ulong;
Flags: ulong;
End;
_ Coauthinfo = record
Dwauthnsvc: DWORD;
Dwauthzsvc: DWORD;
Pwszserverprincname: widestring;
Dwauthnlevel: DWORD;
Dwimpersonationlevel: DWORD;
Pauthidentitydata: pcoauthidentity;
Dwcapabilities: DWORD;
End;
Tsecdcomconnection = Class (tdcomconnection)
Private
Fcai: _ coauthinfo;
Fcid: _ coauthidentity;
Fsvinfo: coserverinfo;
Fuser: widestring;
Fpassword: widestring;
Procedure setpassword (const value: widestring );
Procedure setuser (const value: widestring );
Procedure setsvinfo (const value: coserverinfo );
Protected
Procedure doconnect; override;
Public
Property svinfo: coserverinfo read fsvinfo write setsvinfo;
Constructor create (aowner: tcomponent); override;
Procedure mysetblanket (ITF: iunknown; const vcai: _ coauthinfo );
Function getserver: iappserver; override;
Published
Property User: widestring read Fuser write setuser;
Property password: widestring read fpassword write setpassword;
End;
Procedure register;
Implementation
Constructor tsecdcomconnection. Create (aowner: tcomponent );
Begin
Inherited create (aowner );
Fillmemory (@ fcai, sizeof (fcai), 0 );
Fillmemory (@ fcid, sizeof (fcid), 0 );
Fillmemory (@ fsvinfo, sizeof (fsvinfo), 0 );
With fcai do begin
Dwauthnsvc: = 10; // rpc_c_authn_winnt
Dwauthzsvc: = 0; // rpc_c_authz_none
Dwauthnlevel: = 0; // rpc_c_authn_level_default
Dwimpersonationlevel: = 3;
Pauthidentitydata: [email protected];
Dwcapabilities: =$ 0800;
End;
End;
Procedure tsecdcomconnection. doconnect;
VaR
Tmpcmpname: widestring;
Iid_iunknown: tguid;
IIU: idispatch;
Mqi: multi_qi;
Qr: hresult;
Begin
If (objectbroker) <> nil then
Begin
Repeat
If computername = ''then
Computername: = objectbroker. getcomputerforguid (getserverclsid );
Try
Setappserver (createremotecomobject (computername, getserverclsid) as idispatch );
Objectbroker. setconnectstatus (computername, true );
Except
Objectbroker. setconnectstatus (computername, false );
Computername: = '';
End;
Until connected;
End
Else if (computername <> '') then
Begin
With fcid do begin
User: = punshort (@ Fuser [1]);
Userlength: = length (Fuser );
Tmpcmpname: = computername;
Domain: = punshort (@ tmpcmpname [1]);
Domainlength: = length (tmpcmpname );
Password: = punshort (@ fpassword [1]);
Passwordlength: = length (fpassword );
Flags: = 2; // Unicode
End;
Fsvinfo. pwszname: = pwidechar (tmpcmpname );
Fsvinfo. pauthinfo: [email protected];
Iid_iunknown: = iunknown;
Mqi. IID: [email protected] _ iunknown; mqi. ITF: = nil; mqi. HR: = 0;
Olecheck (cocreateinstanceex (getserverclsid, nil, clsctx_remote_server, @ fsvinfo, 1, @ mqi ));
Olecheck (mqi. hr );
Mysetblanket (mqi. ITF, fcai );
Qr: = mqi. ITF. QueryInterface (idispatch, IIU );
Olecheck (QR );
Mysetblanket (iunknown (IIU), fcai );
Setappserver (IIU );
End
Else
Inherited doconnect;
End;
Function tsecdcomconnection. getserver: iappserver;
VaR
Qiresult: hresult;
Begin
Connected: = true;
Qiresult: = idispatch (appserver). QueryInterface (iappserver, result );
If qiresult <> s_ OK then
Begin
Result: = tdispatchappserver. Create (iappserverdisp (idispatch (appserver )));
End;
Mysetblanket (iunknown (result), fcai );
End;
Procedure tsecdcomconnection. mysetblanket (ITF: iunknown;
Const vcai: _ coauthinfo );
Begin
With vcai do
Cosetproxyblanket (ITF, dwauthnsvc, dwauthzsvc, pwidechar (pauthidentitydata ^. domain ),
Dwauthnlevel, dwimpersonationlevel, pauthidentitydata, dwcapabilities );
End;
Procedure tsecdcomconnection. setpassword (const value: widestring );
Begin
Fpassword: = value;
End;
Procedure tsecdcomconnection. setsvinfo (const value: coserverinfo );
Begin
Fsvinfo: = value;
End;
Procedure tsecdcomconnection. setuser (const value: widestring );
Begin
Fuser: = value;
End;
Procedure register;
Begin
Registercomponents ('datasnap ', [tsecdcomconnection]);
End;
End.
The Code has some C-style annotations Because delphi did not predefine these variables and data structures for us. For example
How to use it? Install this component in the IDE and put it in the remote data module of your existing code.
Set the dataset control pointing to tdocmconnection to this new tsecdcomconnection control. Then you can
Set the strictest security options on the remote computer. But remember to set the appropriate permissions for the users you want to use:
Grant remote activation and remote access permissions. 4. access issues have not yet been discussed. Activate and access
Question is not the same thing. A user may have the activation permission but no access permission, or may have only the access permission but no access permission.
Activate permissions. As mentioned above, cocreateinstacnceex can activate an object with another identity and obtain one of the iunknown pointers.
Local references. If you directly use this pointer to obtain the iappserver interface and call the method, you may
See "Access Denied. This is the local reference of the iunknown pointer that exists in the client process and is no longer unique.
Before special settings, the pointer inherits the local token of the client process, that is, when this pointer is used to obtain the remote iappserv
The QueryInterface is called with the current logon token of the client.
The user name and password cached in the token will be re-logged on for verification. Of course, this will be rejected again, and then the remote computer will
Attempt to log on with the Guest account and obtain the COM object interface. If no guest access permission is available, the client access is lost.
Failed. Windows returns the "Access Denied" message. So how can I make QueryInterface call use remote user identity?
This requires calling cosetproxyblanket to forcibly set the local interface reference to use the token of a remote user. Above
In the code, I wrapped the API with mysetbalancer to call QueryInterface using the user identity during activation.
Then, call mysetblanket again on the obtained iappserver interface to ensure that this interface is also used remotely.
User identity.
Mysetblanket (mqi. ITF, fcai );
Qr: = mqi. ITF. QueryInterface (idispatch, IIU );
Olecheck (QR );
Mysetblanket (iunknown (IIU), fcai); to ensure direct reference to the tclientd of dataprovider
Ataset can also work according to the preceding requirements. The getserver method is overloaded in the extended tsecdcomconnection control.
In this way, tsecdcomconnection can completely replace tdcomconnection to implement convenient COM + application programming. Due to time
In a rush, many terms are not explained at the time of writing this article, so it may not be easy to understand, but for de
Lphi fans provides a simple method to implement secure access to com. I will post this article
A friend who needs to directly copy the code is used in his own application. After tsecdcomconnection is used, the server's COM +
The object can be forced to open the access check and open the component-level access check. When the access check is enabled
The User Name allowed to access the COM + object on the server can be added to the role for correct access. (The above code is in Delphi7/wi
NXP SP2 passes debugging. For Windows 98 and Windows NT4.0 and below, cocreateinsta
Nceex cannot directly generate the security context of the COM + object, so the code is unavailable)
Access COM + applications as a user on a remote computer