4

我目前正在将几个 Windows 桌面应用程序移植到一个网站。

当前设置包括几个 SQL Server 后端数据库,仅配置了 Windows 身份验证 (SSPI),并且每个用户/组/角色都对特定对象具有特定权限。这很方便,因为应用层不必实现任何访问​​控制。

我想保持与 Web 服务器相同的方式,即 Windows 机器上的 Apache。但是每个与数据库的连接都是使用 Apache 的帐户进行的。这是可以理解和预期的,事实上 Apache 被故意授予访问公共数据的权限,以便能够提供公共内容。

但是,如果域用户登录(登录过程已经实现),我希望处理请求的 Apache 进程模拟该用户,从而在整个请求期间充当他们。

起初,我尝试了 php 的fastcgi.impersonate技巧,使用 IIS 作为 Web 服务器。但我最终放弃了,主要是因为(1)无论如何我们都必须移植到 Apache 以及(2)它是特定于 php 的,结果我们应该将整个 Web 服务器作为目标......

所以我将搜索重定向到 Apache 模块。几个月的研究除了mod_auth_sspi之类的东西之外没有任何成果,这显然不是我想要的(身份验证和模拟是两个不同的东西)。

最后我决定制作自己的模块。我能找到的大多数“101”示例都是用 C 编写的,但我设法在Lazarus/FPC中找到了 2-3 个示例,这是我已经使用了很长一段时间的示例,但从未用于此类任务。

我知道我必须构建一个 .dll 项目,我(或多或少)知道要使用哪些单元,并且我知道我的工具箱LogonUser()ImpersonateLoggedOnUser()应该包含哪些功能。

有没有人做过类似的事情?谁能指出我正确的方向?

一个例子将不胜感激,即使它是一个简单的概念证明。这个问题远非要求一个最终的、确定的解决方案。

4

1 回答 1

3

我最终想出了以下几点:

library mod_winimpersonate;

{$mode objfpc}{$H+}

uses SysUtils, Windows, httpd, apr, Classes;

function DefaultHandler(r: Prequest_rec): Integer; cdecl;
  Var
    cookies:TStringList;
    logindata,username,password:String;
    p:Integer;
  begin
  RevertToSelf;
  cookies:=TStringList.Create;
  cookies.Delimiter:=';';
  cookies.DelimitedText:=apr_table_get(r^.headers_in,'COOKIE');
  logindata:=URLDecode(cookies.Values['WinImpersonate']);
  If Length(logindata)>0 then
    Begin
    p:=Pos(':',logindata);
    username:=LeftStr(logindata,p-1);
    password:=RightStr(logindata,Length(logindata)-p);
    ChangeLoggedInUser(username,password,'');
    End;
  Result:=DECLINED;
  end;

procedure RegisterHooks(p: Papr_pool_t); cdecl;
  begin
  ap_hook_handler(@DefaultHandler, nil, nil, APR_HOOK_REALLY_FIRST);
  end;

var
  TheModule: module;

exports TheModule name 'winimpersonate_module';

begin
FillChar(TheModule, sizeof(TheModule), 0);
STANDARD20_MODULE_STUFF(TheModule);
with TheModule do
  begin
  name := 'mod_winimpersonate.dll';
  register_hooks := @RegisterHooks;
  end;
end.

这绝不是最终的解决方案,但它是一个开始。逻辑如下:

  • 恢复到 Apache 帐户。这是必须的,以防我们使用以前冒充其他人的回收 Apache 线程。

  • 从名为“WinImpersonate”的 cookie 中检索用户的凭据,格式为username:password. 这需要更多的工作(可能加密凭据,或者将它们存储在服务器上的安全(?)位置或更安全的地方)

  • 在单位的帮助下冒充用户windows

  • Return DECLINED,因此 Apache 知道我们没有处理请求,它应该继续向模块询问正确的处理程序。

有许多问题需要解决,以实现良好的安全级别。其中包括凭据保护和浏览器缓存。但正如我所说,这是一个开始,也是一个概念证明。

您会注意到上面的清单中缺少两个实用函数:

URLDecode解码一个 url 编码的字符串:

// Convert URLEncoded string to utf8 string
function URLDecode(const s: String): String;
  var
    sAnsi: String;
    sUtf8: String;
    sWide: WideString;
    i, len: Cardinal;
    ESC: string[2];
    CharCode: integer;
    c: char;
  begin
  sAnsi := PChar(s);
  SetLength(sUtf8, Length(sAnsi));
  i := 1;
  len := 1;
  while (i <= Cardinal(Length(sAnsi))) do 
    begin
    if (sAnsi[i] <> '%') then 
      begin
      if (sAnsi[i] = '+') then c := ' '  else c := sAnsi[i];
      sUtf8[len] := c;
      Inc(len);
      end 
    else 
      begin
      Inc(i);
      ESC := Copy(sAnsi, i, 2);
      Inc(i, 1);
      try
        CharCode := StrToInt('$' + ESC);
        c := Char(CharCode);
        sUtf8[len] := c;
        Inc(len);
      except 
        end;
      end;
    Inc(i);
    end;
  Dec(len);
  SetLength(sUtf8, len);
  sWide := UTF8Decode(sUtf8);
  len := Length(sWide);
  Result := sWide;
  end;

ChangeLoggedInUser尝试使用提供的凭据登录用户,并在成功后尝试冒充他:

Function ChangeLoggedInUser(username, password, domain: string):Boolean;
  var
    creds: Cardinal;
  begin
  Result:=False;
  try
    if LogonUser(PChar(username)
        ,PChar(domain)
        ,PChar(password)
        ,LOGON32_LOGON_NETWORK_CLEARTEXT
        ,LOGON32_PROVIDER_DEFAULT
        ,creds
        ) then
      begin
      ImpersonateLoggedOnUser(creds);
      Result:=True;
      end;
  finally
    //wipe the memory for security
    FillChar(username,SizeOf(username),#0);
    FillChar(password,SizeOf(username),#0);
    FillChar(domain,SizeOf(username),#0);
    end;  //try-finally
  end;

希望有人觉得这很有用。非常欢迎评论。

于 2013-10-30T10:17:12.193 回答