Windows上使用本地用户帐户的基于PHP表单的身份验证

时间:2020-03-05 18:52:43  来源:igfitidea点击:

我正在运行PHP,Apache和Windows。我没有域设置,所以我希望我网站的基于表单的身份验证使用Windows内置的本地用户帐户数据库(我认为它称为SAM)。

我知道,如果设置了Active Directory,则可以使用PHP LDAP模块在脚本中进行连接和身份验证,但是如果没有AD,就不会有LDAP。独立机器的等效功能是什么?

解决方案

回答

好问题!

我已经考虑了一下……我想不出一个好的解决方案。我能想到的是一个可能可行的可怕骇客技巧。在看到将近一天没有人发布有关该问题的答案之后,我觉得很糟糕,但是可以解决问题。

系统运行时,SAM文件超出限制。有一些DLL注入技巧可以使我们工作,但最终我们只会得到密码哈希,并且我们必须对用户提供的密码进行哈希处理才能与之匹配。

我们真正想要的是一种尝试根据SAM文件对用户进行身份验证的东西。我认为我们可以通过执行以下操作来做到这一点。

  • 在服务器上创建一个文件共享,并对其进行分配,以便仅授予我们希望能够以其身份登录的帐户的访问权限。
  • 在PHP中,使用system命令调用一个wsh脚本:使用网站用户提供的用户名和密码安装共享。记录驱动器是否工作,然后卸载驱动器。
  • 以某种方式收集结果。结果可以在脚本的stdout上返回给php,或者希望使用脚本的返回码返回。

我知道它不漂亮,但是应该可以。

我觉得很脏:|

编辑:调用外部wsh脚本的原因是PHP不允许我们使用UNC路径(据我所记得)。

回答

我也没有找到简单的解决方案。有使用CreateObject和WinNT ADSI提供程序的示例。但最终他们都碰上了
Active Directory服务接口WinNT提供程序的用户身份验证问题。我不确定100%,但是我想WSH /网络连接方法也有同样的问题。
根据如何在Microsoft操作系统上验证用户凭据,应使用LogonUser或者SSPI。
它也说

LogonUser Win32 API does not require TCB privilege in Microsoft Windows Server 2003, however, for downlevel compatibility, this is still the best approach.

On Windows XP, it is no longer required that a process have the SE_TCB_NAME privilege in order to call LogonUser. Therefore, the simplest method to validate a user's credentials on Windows XP, is to call the LogonUser API.

因此,如果我确定不需要Win9x / 2000支持,我将编写一个扩展程序,将LogonUser暴露给php。
我们可能还对NT帐户的用户身份验证感兴趣。它使用w32api扩展名,并且需要一个支持dll ...我宁愿写那个小的LogonUser-extension ;-)
如果这不可行,我可能会研究IIS的fastcgi模块及其稳定性,然后让IIS处理身份验证。

编辑:
我还尝试利用System.Security.Principal.WindowsIdentity和php的com / .net扩展名。但是dotnet构造函数似乎不允许将参数传递给对象构造函数,而我的"实验"从GetType()获取程序集(以及由此产生的CreateInstance())失败,并出现"未知的zval"错误。

回答

构建PHP扩展需要大量的硬盘空间投资。由于我已经安装了GCC(MinGW)环境,因此决定采用从PHP脚本启动进程的性能。源代码如下。

// Usage: logonuser.exe /user username /password password [/domain domain]
// Exit code is 0 on logon success and 1 on failure.

#include <windows.h>

int main(int argc, char *argv[]) {
    HANDLE r = 0;
    char *user = 0;
    char *password = 0;
    char *domain = 0;
    int i;

    for(i = 1; i < argc; i++) {
        if(!strcmp(argv[i], "/user")) {
            if(i + 1 < argc) {
                user = argv[i + 1];
                i++;
            }
        } else if(!strcmp(argv[i], "/domain")) {
            if(i + 1 < argc) {
                domain = argv[i + 1];
                i++;
            }
        } else if(!strcmp(argv[i], "/password")) {
            if(i + 1 < argc) {
                password = argv[i + 1];
                i++;
            }
        }
    }

    if(user && password) {
        LogonUser(user, domain, password, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &r);
    }
    return r ? 0 : 1;
}

接下来是展示其用法的PHP源代码。

if($_SERVER['REQUEST_METHOD'] == 'POST') {
    if(isset($_REQUEST['user'], $_REQUEST['password'], $_REQUEST['domain'])) {
        $failure = 1;
        $user = $_REQUEST['user'];
        $password = $_REQUEST['password'];
        $domain = $_REQUEST['domain'];

        if($user && $password) {
            $cmd = "logonuser.exe /user " . escapeshellarg($user) . " /password " . escapeshellarg($password);
            if($domain) $cmd .= " /domain " . escapeshellarg($domain);
            system($cmd, $failure);
        }

        if($failure) {
            echo("Incorrect credentials.");
        } else {
            echo("Correct credentials!");
        }
    }
}
?>
<form action="<?php echo(htmlentities($_SERVER['PHP_SELF'])); ?>" method="post">
    Username: <input type="text" name="user" value="<?php echo(htmlentities($user)); ?>" /><br />
    Password: <input type="password" name="password" value="" /><br />
    Domain: <input type="text" name="domain" value="<?php echo(htmlentities($domain)); ?>" /><br />
    <input type="submit" value="logon" />
</form>