保护 Delphi 连接字符串免受黑客攻击

Protect Delphi Connection String From Hack

我们有一个 delphi 应用程序使用 tadoconnection 连接 sql 服务器数据库 一个问题是当我们在 运行 exe 文件上打开资源黑客时,它清楚地显示了连接字符串,任何人都可以连接到我们的服务器

下面显示的示例代码描述了问题

您没有使用资源黑客。如果您的字符串存储在资源中,它们将更容易查找和查看。

您正在使用内存扫描器,它会在 运行ning 时查看程序的原始内存。最终,您将不得不在内存中创建连接字符串以传递给数据库引擎。如果黑客可以访问该应用程序及其内存,他们就可以访问其数据。具有这种访问权限的 专用 黑客将能够在使用该字符串时获取该字符串。

但有些事情可以让 临时 黑客变得更难。

一方面,不允许 non-admin 用户访问您的应用。当用户 运行 使用扫描仪应用时,同一用户可能会访问其他应用 运行 中的内存。 运行 您的应用在不同的用户上下文中,或在提升的进程中。用户 运行 的应用程序无法访问其他用户正在 运行 的其他应用程序的内存,除非 he/she 被明确授予他们权限,或者 he/she 是管理员开始和。如果黑客有权访问您的应用程序,那么一切都将失败。

此外,将字符串分解为更小的子字符串,您必须在需要时将其连接起来。动态构建连接字符串。这样,如果黑客尝试查看磁盘上的 EXE 文件本身,子字符串就不会存储在一个地方。

并且无论如何,不要在应用程序代码中存储敏感凭据以开头。将它们存储在外部,并对其进行加密。仅在绝对需要时将它们检索到内存中,并在您不主动使用内存时使用 SecureZeroMemory() when you are done using it. If you need to hold on to the credentials, or the connection string, in memory for more than a few milliseconds, consider using CryptProtectMemory() 加密该内存来安全地销毁该内存的内容。

拼凑连接字符串所花费的工作越多,临时黑客也必须做的工作就越多。但是一个敬业的黑客会等到你完成所有的工作,然后在使用它的那一刻从内存中提取最后一个字符串,所以你必须首先做你能做的来阻止对内存的访问。

  1. 确保您的密码单独存储并尽可能安全地满足您的要求。*
  2. 确保您不会在连接时保留密码。 IE。 Connection.Properties 必须包括 'Persist Security Info=False'
  3. Connection.LoginPrompt设置为True
  4. OnLogin 事件实施处理程序,您可以在其中加载、解密并向 Connection.Open 调用提供密码。
  5. 作为最后的安全步骤,请确保从内存中删除已解密的密码。

*There a plenty of simple encryption libraries available that you might as well encrypt your password. Microsoft's Data Protection API is worth considering. Bear in mind that perfect security is impossible if someone has access to your application. The best you can do is add layers of obfuscation making it more difficult for a hacker to crack your database login credentials.

一些示例代码

procedure TAbc.HandleOnLogin(Sender: TObject; Username, Password: string);
var
  LPassword: string;
begin
  LPassword := GetDecryptedPassword; //Your choice how you do this
  Connection.Open(Username, LPassword);
  //The next line ensures memory is erased before it is deallocated
  //which would otherwise leave the password hanging around.
  SecureZeroMemory(Pointer(LPassword), Length(LPassword) * SizeOf(Char));
end;

除了其他答案之外,您还可以删除读取进程内存的权限,如果您控制用户权限(例如在企业环境中),这将很有用。

在进程开始时调用以下过程可防止 non-admin 用户读取进程内存。 (你需要 JEDI API 单位来弥补 API 次来电)

uses JwaWinNT, JwaWinBase, JwaAclApi, JwaAccCtrl;

//...
{$SCOPEDENUMS ON}
function ProtectProcess(): DWORD;
type
  TSidType = (Everyone, CurrentUser, System, Admin);
var
  // Released on exit
  ProcessToken: THandle;
  TokenInfo: PVOID;

  SidCurUser: PSID;
  SidEveryone: PSID;
  SidSystem: PSID;
  SidAdmins: PSID;

  ACL: PACL;
  SecDesc: PSECURITY_DESCRIPTOR;
  Size: DWORD;
  TokenSize: DWORD;
  BResult: Bool;

  SIDAuthEveryone: SID_IDENTIFIER_AUTHORITY;
  SIDAuthSystem: SID_IDENTIFIER_AUTHORITY;
  SIDAuthAdministrators: SID_IDENTIFIER_AUTHORITY;

  SIDArray: array[TSidType] of PSID;
  I: TSidType;
const
  // Mimic Protected Process
  // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684880%28v=vs.85%29.aspx
  // Protected processes allow PROCESS_TERMINATE, which is
  // probably not appropriate for high integrity software.
  DeniedPermissions =
            {READ_CONTROL |}
            WRITE_DAC or WRITE_OWNER or
            PROCESS_CREATE_PROCESS or PROCESS_CREATE_THREAD or
//            PROCESS_DUP_HANDLE or // this permission is needed for printing
            PROCESS_QUERY_INFORMATION or
            PROCESS_SET_QUOTA or PROCESS_SET_INFORMATION or
            PROCESS_VM_OPERATION or
            PROCESS_VM_READ or PROCESS_VM_WRITE
            // In addition to protected process
//            or PROCESS_SUSPEND_RESUME or PROCESS_TERMINATE
            ;
  // Standard and specific rights not explicitly denied
  AllowedPermissions = ((not DeniedPermissions) and FFF) or PROCESS_TERMINATE or SYNCHRONIZE;
begin
  ACL := nil;
  TokenInfo := nil;
  SecDesc := nil;
  try
    TokenSize := 0;
    ProcessToken := 0;
    // If this fails, you can try to fallback to OpenThreadToken
    if (not OpenProcessToken(GetCurrentProcess(), TOKEN_READ, ProcessToken)) then
    begin
      Result := GetLastError();
      Exit;
    end;

    BResult := GetTokenInformation(ProcessToken, TokenUser, nil, 0, TokenSize);
    Result := GetLastError();
    Assert((not BResult) and (ERROR_INSUFFICIENT_BUFFER = Result));
    if(not ((BResult = FALSE) and (ERROR_INSUFFICIENT_BUFFER = Result))) then
    begin
      // failed;
      Exit;
    end;

    if (TokenSize > 0) then
    begin
      TokenInfo := HeapAlloc(GetProcessHeap(), 0, TokenSize);
      Result := GetLastError();
      Assert(Assigned(TokenInfo));
      if (nil = TokenInfo) then
      begin
        // failed;
        Exit;
      end;
    end;

    BResult := GetTokenInformation(ProcessToken, TokenUser, TokenInfo, TokenSize, TokenSize);
    Result := GetLastError();
    Assert(BResult and Assigned(TokenInfo));
    if not (BResult and Assigned(TokenInfo)) then
    begin
      Exit;
    end;

    SidCurUser := (PTokenUser(TokenInfo)).User.Sid;

    SIDAuthEveryone := SECURITY_WORLD_SID_AUTHORITY;
    BResult := AllocateAndInitializeSid(@SIDAuthEveryone, 1,
        SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, SidEveryone);
    Result := GetLastError();
    Assert(BResult and Assigned(SidEveryone));
    if not (BResult and Assigned(SidEveryone)) then
    begin
      Exit;
    end;

    SIDAuthSystem := SECURITY_NT_AUTHORITY;
    BResult := AllocateAndInitializeSid(@SIDAuthSystem, 1,
        SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, SidSystem);
    Result := GetLastError();
    Assert(BResult and Assigned(SidSystem));
    if not (BResult and Assigned(SidSystem)) then
    begin
      Exit;
    end;

    SIDAuthAdministrators := SECURITY_NT_AUTHORITY;
    BResult := AllocateAndInitializeSid(@SIDAuthAdministrators, 2,
        SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
        0, 0, 0, 0, 0, 0, SidAdmins);
    Result := GetLastError();
    Assert(BResult and Assigned(SidAdmins));
    if not (BResult and Assigned(SidAdmins)) then
    begin
      Exit;
    end;

    SIDArray[TSidType.Everyone] := SidEveryone;   // Deny most rights to everyone
    SIDArray[TSidType.CurrentUser] := SidCurUser; // Allow what was not denied
    SIDArray[TSidType.System] := SidSystem;       // Full control
    SIDArray[TSidType.Admin] := SidAdmins;        // Full control

    // Determine required size of the ACL
    Size := SizeOf(ACL);

    // First the DENY, then the ALLOW
    Size := Size + GetLengthSid(SIDArray[TSidType.Everyone]);
    Size := Size + SizeOf(ACCESS_DENIED_ACE) - SizeOf(DWORD);

    for I := TSidType.CurrentUser to High(SIDArray) do
    begin
      // DWORD is the SidStart field, which is not used for absolute format
      Size := Size + GetLengthSid(SIDArray[I]);
      Size := Size + SizeOf(ACCESS_ALLOWED_ACE) - SizeOf(DWORD);
    end;

    Size := Size + SizeOf(DWORD);

    ACL := PACL(HeapAlloc(GetProcessHeap(), 0, Size));
    Result := GetLastError();
    Assert(Assigned(ACL));
    if not Assigned(ACL) then
    begin
      Exit;
    end;

    BResult := InitializeAcl(ACL, Size, ACL_REVISION);
    Result := GetLastError();
    Assert(BResult);
    if not BResult then
    begin
      Exit;
    end;

    BResult := AddAccessDeniedAce(ACL, ACL_REVISION, DeniedPermissions,
      SIDArray[TSidType.Everyone]);
    Result := GetLastError();
    Assert(BResult);
    if not BResult then
    begin
      Exit;
    end;

    BResult := AddAccessAllowedAce(ACL, ACL_REVISION, AllowedPermissions,
      SIDArray[TSidType.CurrentUser]);
    Result := GetLastError();
    Assert(BResult);
    if not BResult then
    begin
      Exit;
    end;

    // Because of ACE ordering, System will effectively have dwAllowed even
    // though the ACE specifies PROCESS_ALL_ACCESS (unless software uses
    // SeDebugPrivilege or SeTcbName and increases access).
    // As an exercise, check behavior of tools such as Process Explorer under XP,
    // Vista, and above. Vista and above should exhibit slightly different behavior
    // due to Restricted tokens.
    BResult := AddAccessAllowedAce(ACL, ACL_REVISION, PROCESS_ALL_ACCESS,
      SIDArray[TSidType.System]);
    Result := GetLastError();
    Assert(BResult);
    if not BResult then
    begin
      Exit;
    end;

    // Because of ACE ordering, Administrators will effectively have dwAllowed
    // even though the ACE specifies PROCESS_ALL_ACCESS (unless the Administrator
    // invokes 'discretionary security' by taking ownership and increasing access).
    // As an exercise, check behavior of tools such as Process Explorer under XP,
    // Vista, and above. Vista and above should exhibit slightly different behavior
    // due to Restricted tokens.
    BResult := AddAccessAllowedAce(ACL, ACL_REVISION, PROCESS_ALL_ACCESS, SIDArray[TSidType.Admin]);
    Result := GetLastError();
    Assert(BResult);
    if not BResult then
    begin
      SiMain.LogWin32Error('AddAccessAllowedAce failed: ', Result);
      Exit;
    end;

    SecDesc := PSECURITY_DESCRIPTOR(HeapAlloc(GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH));
    Result := GetLastError();
    Assert(Assigned(SecDesc));
    if not Assigned(SecDesc) then
    begin
      Exit;
    end;

    // InitializeSecurityDescriptor initializes a security descriptor in
    // absolute format, rather than self-relative format. See
    // http://msdn.microsoft.com/en-us/library/aa378863(VS.85).aspx
    BResult := InitializeSecurityDescriptor(SecDesc, SECURITY_DESCRIPTOR_REVISION);
    Result := GetLastError();
    Assert(BResult);
    if not BResult then
    begin
      Exit;
    end;

    BResult := SetSecurityDescriptorDacl(SecDesc, TRUE, ACL, FALSE);
    Result := GetLastError();
    Assert(BResult);
    if not BResult then
    begin
      Exit;
    end;

    SetSecurityInfo(
        GetCurrentProcess(),
        SE_KERNEL_OBJECT, // process object
        OWNER_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION,
        SidCurUser, // Owner SID
        nil, // Group SID
        ACL,
        nil // SACL
        );
    Result := GetLastError();
    Assert(ERROR_SUCCESS = Result);
  finally
    if (nil <> SecDesc) then
    begin
      HeapFree(GetProcessHeap(), 0, SecDesc);
    end;
    if (nil <> ACL) then
    begin
      HeapFree(GetProcessHeap(), 0, ACL);
    end;
    if (SidAdmins <> nil) then
    begin
      FreeSid(SidAdmins);
    end;
    if (SidSystem <> nil) then
    begin
      FreeSid(SidSystem);
    end;
    if (SidEveryone <> nil) then
    begin
      FreeSid(SidEveryone);
    end;
    if (nil <> TokenInfo) then
    begin
      HeapFree(GetProcessHeap(), 0, TokenInfo);
    end;
    if (0 <> ProcessToken) then
    begin
      CloseHandle(ProcessToken);
    end;
  end;
end;