PAYATHON 2026

IIS 7.5 下 ASP.NET 写入网络 UNC 文件夹时权限被拒

排障赵师傅

结论

ApplicationPoolIdentity 可以访问远程 SMB 共享。IIS 工作进程在本机以 IIS APPPOOL\My.Site.Com 身份运行;访问真正的远程 UNC 资源时,通常使用 Web 服务器的计算机账户 MYDOMAIN\mywebserver$ 进行认证。

目前的测试环境存在一个干扰项:UNC 共享仍位于 mywebserver 本机。通过 UNC 路径访问本机共享属于 SMB 回环访问,其身份解析、NTLM 回环保护和本地虚拟账户处理方式可能不同于访问另一台服务器。因此,这套环境无法可靠验证计算机账户能否正常访问远程共享。

建议先做两项测试:

  1. 在另一台服务器上建立临时共享,直接向 MYDOMAIN\mywebserver$ 授予共享权限和 NTFS 权限,然后测试访问。
  2. 如果计算机账户刚加入 mygroup-www,重启 mywebserver 后再测试。新的组成员关系不一定会立即反映到现有登录会话和票据中。

如果直接授权计算机账户后仍然失败,请到文件服务器的安全日志中确认 SMB 实际收到的账户。Web 服务器上的 Process Monitor 无法单独确定远程认证身份。

Process Monitor 为什么仍显示 Application Pool Identity

同一次访问会涉及两种身份:

  • w3wp.exe 在 Web 服务器本机运行时,进程身份是 IIS APPPOOL\My.Site.Com。
  • 该进程连接远程 SMB 服务器时,网络身份通常是 MYDOMAIN\mywebserver$。

Process Monitor 显示本机操作使用的令牌,无法完整反映远程 SMB 服务器最终认证了哪个账户。要确认远程身份,应查看目标文件服务器的登录和共享访问审计日志。

共享位于同一台服务器时,本机操作和网络访问还可能混在一起。Windows 可能将请求作为本机访问处理,也可能因服务器别名、NTLM 回环保护或凭据冲突而产生不同结果。

建议的排查顺序

1. 确认 UNC 路径写法

标准 UNC 路径以两个反斜杠开头:

\\myotherserver\mydirectories\output

配置文件中的实际值也应采用这种写法。路径放在 C# 字符串中时,可以使用逐字字符串:

string outputDirectory = @"\\myotherserver\mydirectories\output";
string output = Path.Combine(outputDirectory, "test.html");
File.WriteAllLines(output, new[] { "Hello", "World!" });

不要使用映射盘符。IIS 工作进程通常无法看到交互式用户会话创建的网络驱动器映射。

2. 在真正的远程服务器上测试

临时准备一台域成员服务器,并创建测试共享,例如:

\\fileserver\mydirectories\output

为了隔离问题,先绕过域组,直接向计算机账户授权:

MYDOMAIN\mywebserver$

需要同时检查两层权限:

  • 共享权限
  • 文件系统 NTFS 权限

最终有效权限由这两层权限共同决定。还要检查父目录中是否有显式 Deny、权限继承是否关闭,以及账户是否具有目录遍历、读取、创建文件和写入数据的权限。

排查时可以暂时授予 Modify 或 Full Control。测试成功后,再按业务需要收紧到最低权限。

3. 刷新计算机账户的组成员关系

如果通过 mygroup-www 授权,而且 mywebserver$ 刚加入该组,请先确认 Active Directory 已完成复制,再重启 Web 服务器。

只回收 application pool 可能无法刷新相关信息,因为计算机账户的网络凭据和组信息并不完全由 w3wp.exe 的生命周期决定。对于旧系统,重启服务器是最直接、也最容易验证的刷新方式。

测试时可以依次比较:

  1. 直接向 MYDOMAIN\mywebserver$ 授权。
  2. 重启服务器后,通过 MYDOMAIN\mygroup-www 授权。

如果直接授权成功,而域组授权失败,问题通常出在组成员关系刷新、AD 复制或令牌上,与 IIS 的文件写入代码关系不大。

4. 检查应用是否发生模拟

ASP.NET 开启 impersonation 后,文件访问可能使用请求用户、匿名认证账户或显式配置的账户,而非 application pool identity。

检查站点及其父级配置中是否存在以下内容:

<system.web>
  <identity impersonate="true" />
</system.web>

还要检查 IIS Authentication 设置,包括:

  • Anonymous Authentication 使用 Application pool identity 还是指定账户。
  • Windows Authentication 是否启用。
  • web.config、父站点配置或服务器级配置是否存在覆盖。
  • 代码是否调用 WindowsIdentity.Impersonate。
  • 第三方组件是否通过其他账户执行文件操作。

可以临时记录请求线程的 Windows 身份:

using System.Security.Principal;
using System.Threading;
using System.Web;

private static string GetIdentityDiagnostics()
{
    WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent();
    WindowsIdentity impersonatedIdentity = WindowsIdentity.GetCurrent(true);

    return string.Format(
        "WindowsIdentity={0}; Impersonating={1}; ThreadPrincipal={2}; HttpUser={3}",
        windowsIdentity == null ? "<null>" : windowsIdentity.Name,
        impersonatedIdentity == null ? "<no>" : impersonatedIdentity.Name,
        Thread.CurrentPrincipal == null ||
        Thread.CurrentPrincipal.Identity == null
            ? "<null>"
            : Thread.CurrentPrincipal.Identity.Name,
        HttpContext.Current == null ||
        HttpContext.Current.User == null ||
        HttpContext.Current.User.Identity == null
            ? "<null>"
            : HttpContext.Current.User.Identity.Name);
}

这段代码只用于诊断。完整的身份信息应写入受保护的服务器日志,不要长期返回给客户端。

题目中曾出现”另一个网站的域账户”,这通常意味着实际执行路径中存在模拟、共享连接复用或保存的凭据,也可能是观察了错误的 w3wp.exe。应通过进程 ID 确认 application pool 对应的工作进程,不能只看进程名。

可以使用 IIS 命令确认工作进程归属:

%windir%\system32\inetsrv\appcmd list wp

输出会列出工作进程 PID 及其 application pool。将 PID 与 Process Monitor 中的事件对应起来即可。

5. 在文件服务器上确认实际认证账户

在目标文件服务器上启用相应的登录、文件共享和对象访问审计,然后查看访问失败时记录的账户。常见的相关事件有:

  • 4624:登录成功
  • 4625:登录失败
  • 5145:检查网络共享对象访问权限
  • 4663:文件系统对象访问

能否看到这些事件取决于服务器的审计策略。检查时重点关注:

  • Account Name
  • Logon Type
  • Authentication Package
  • Share Name
  • Relative Target Name
  • Accesses
  • Status 或 Failure Reason

正常情况下,远程文件服务器应看到:

MYDOMAIN\mywebserver$

如果日志中出现其他域用户,请继续检查模拟、IIS Authentication 配置和已有 SMB 凭据。如果出现匿名账户,则要检查域信任、计算机账户安全通道、NTLM 策略以及目标服务器的认证设置。

6. 检查计算机账户和域安全通道

可以在 mywebserver 上检查与域的安全通道:

nltest /sc_verify:MYDOMAIN

如果检查失败,应由域管理员处理计算机账户、DNS、时间同步或安全通道问题。只有在确认安全通道已经损坏并获得运维审批后,才应考虑将服务器移出域再重新加入。

授权时还要确认选中的是计算机对象,并包含结尾的 $:

MYDOMAIN\mywebserver$

显示名称有时不会直接展示 $,但 ACL 最终解析出的 SID 必须对应正确的计算机账户。

7. 检查服务器别名、DNS 和 SMB 认证

如果 myotherserver 实际是 DNS 别名、CNAME,或者该名称指向 mywebserver,认证可能回退到 NTLM,也可能触发本机回环保护。

可以使用以下命令核对名称解析:

nslookup myotherserver
ping myotherserver

如果使用 Kerberos,还应由域管理员检查 cifs/myotherserver 对应的 SPN 是否注册在正确的服务器账户上,以及是否存在重复 SPN。

不要通过关闭 NTLM 回环保护或全局降低 SMB 安全策略来绕过测试问题。这样会掩盖实际配置错误,并扩大服务器的攻击面。

用于诊断的写入示例

下面的代码会检查目录是否存在,尝试写入文件,并记录当前 Windows 身份:

using System;
using System.IO;
using System.Security.Principal;
using System.Web.Services;
using System.Web.Script.Services;

[WebMethod]
[ScriptMethod(UseHttpGet = false, ResponseFormat = ResponseFormat.Json)]
public string ExportReport(int reportId)
{
    string directory = ConfigHelper.OutputPath;
    string output = Path.Combine(directory, "test.html");

    try
    {
        WindowsIdentity identity = WindowsIdentity.GetCurrent();

        Logger.InfoFormat(
            "Writing file. Identity={0}, Directory={1}, File={2}, DirectoryExists={3}",
            identity == null ? "<null>" : identity.Name,
            directory,
            output,
            Directory.Exists(directory));

        File.WriteAllLines(output, new[] { "Hello", "World!" });

        return ConfigHelper.VirtualPath + "test.html";
    }
    catch (UnauthorizedAccessException ex)
    {
        Logger.ErrorException(
            "Access denied while writing to " + output,
            ex);
        throw;
    }
    catch (IOException ex)
    {
        Logger.ErrorException(
            "I/O error while writing to " + output,
            ex);
        throw;
    }
}

日志中的 WindowsIdentity 仍然是应用执行身份,并不等同于远程文件服务器最终认证的 SMB 身份。记录它的主要用途是发现 ASP.NET impersonation 或错误的 application pool。

是否应该改用域账户

IIS 支持将 application pool identity 改为专用域账户,因此不能简单地将这种配置视为违反最佳实践。具体选择取决于安全策略和运维要求。

可选方案有两种:

  • 保留 ApplicationPoolIdentity,并向 MYDOMAIN\mywebserver$ 授予远程共享权限。这种配置较简单,但同一台 Web 服务器上的相关应用可能共享该计算机账户的网络权限。
  • 使用专用域服务账户作为 application pool identity。这样可以按应用隔离远程资源权限,但需要管理密码、账户策略和服务登录风险。

如果同一台服务器上运行多套应用,而且各应用需要不同的网络权限,专用域服务账户通常更便于隔离权限。组织如果要求使用计算机账户,则应保留 ApplicationPoolIdentity,并将权限准确授予服务器的计算机账户。

最可能的原因排序

根据现有现象,建议按以下顺序检查:

  1. 共享实际位于 Web 服务器本机,测试受 SMB 回环访问和服务器别名影响。
  2. mywebserver$ 加入域组后,服务器尚未重启,原有计算机登录会话没有获得新的组成员关系。
  3. 只配置了共享权限或 NTFS 权限中的一层,或者子目录中存在显式 Deny。
  4. ASP.NET、IIS Authentication 或应用代码启用了 impersonation。
  5. Process Monitor 观察了错误的 w3wp.exe,或者把本机进程身份当成了远程 SMB 身份。
  6. DNS 别名、SPN、NTLM 策略或计算机账户安全通道存在问题。

建议在真正的第二台服务器上建立共享,直接向 MYDOMAIN\mywebserver$ 授权,重启 Web 服务器,再从文件服务器的安全日志中确认实际认证账户。这项测试可以同时排除本机 UNC 回环、组令牌未刷新和进程观察错误这三类干扰。

备注:内容仅供参考。