在PowerShell脚本中安全地使用保存的凭证
4.5(90%) 投票

该领域最常见的任务之一是需要运行PowerShell脚本,该脚本要求以某种形式保存凭据,以便可以将凭据输入脚本中,以便自动执行。这在运行脚本的当前用户上下文(例如在计划任务内)不足以或不适合远程执行。Office 365管理就是一个很好的例子,为了连接和执行cmdlet,通常必须传递凭据对象。

快速和肮脏(和所有太常见)的方法是简单地有一些像下面这样的代码:

这里,我们只需将用户名和密码以明文形式直接写入PowerShell脚本,然后创建一个PowerShell凭据对象,然后可以将其传递到Connect MSolService之类的东西上。

它起作用了,但显然非常不安全特别是因为这些帐户通常是高性能的管理员帐户。

更好的方法是利用PowerShell凭据对象固有的“安全字符串”性质及其表示密码的方式。通过执行以下操作,可以将“安全字符串”导出为可读字符串:

这会给你很长的时间,看起来像这样的复杂字符串(为了简洁而缩短):1000000 d08c9ddf0115d1 ..... d3d491bb6d740864122a041d11

你可以把字符串存储到文本文件中,在需要的时候,重新读取它,然后将其反转为“安全字符串”对象,并通过执行以下操作输入到凭证对象创建中:

这里实际发生的是PowerShell使用本机Windows数据保护API(DAPI)功能将密码从“安全字符串”加密为文本字符串。此字符串可以写入纯文本文件,但是dapi的工作方式是加密只有在原始机器上执行加密的原始用户才能将字符串解密回“安全字符串”,以便重用。

虽然不是100%万无一失的解决方案,这是一种非常有效的保护密码的方法,因为它大大减少了攻击向量。至少只要对最初运行加密的帐户的凭据受到保护,即使恶意管理员可以访问机器(因此可以访问存储在脚本中的明文密码)。他们无法对存储的密码进行反向工程。

从这个角度来看,使用安全“已保存”密码的PowerShell脚本的过程如下:

  1. 运行get credential命令提示管理员提供要保存的凭据
  2. 将属于凭证对象一部分的secure-string对象转换为文本字符串(现在已加密),并将其存储在文件中
  3. 对于需要保存凭据的脚本,在文件中读取,解密字符串并重新创建凭证对象,并将其馈送给相应的Cmdlet。

这种方法有几个关键注意事项:

  • 运行和读取已保存凭据的脚本,必须在相同的机器上和相同的用户上下文中运行。
  • 这意味着您不能将“已保存的凭据”文件复制到其他计算机并重用它。
  • 在脚本在服务帐户下作为计划任务运行的场景中,请注意,为了提示管理员首先提供凭据,服务帐户需要“交互”功能。这意味着服务帐户,至少是暂时的,需要“本地登录”才能提供交互式会话。
  • 为了让达皮工作,GPO设置网络访问:不允许存储网络身份验证的密码和凭据必须设置为残疾人(或未配置)。否则,加密密钥将只在用户会话的生命周期(即用户注销或机器重新启动时,密钥丢失,无法解密安全字符串文本)

现在,假设上面的警告对你没有好处。不管什么原因,您不能让您的服务帐户在本地登录,即使是暂时的。或者,需要这些保存的密码的脚本需要在Bajillion机器上运行,并且您无法登录到每台机器上为该机器创建唯一的安全字符串。还有一种选择,虽然不是很安全,实际上,只有稍微安全一点,然后将其存储为纯文本。

在上面的例子中,当你在表演Convert-FromSecureString没有参数的cmdlet,您有效地告诉PowerShell使用DAPI进行加密。但是,您也可以为它提供一个特定的AES密钥,以用于执行加密。在下面的示例中,我正在生成一个随机的aes密钥以使用:

要重新读取密码,具体做法如下:

在这种情况下,AES密钥是你的秘密,任何知道AES密钥的人都可以解密你的密码,所以简单地将aes密钥嵌入到脚本中并不是一个好主意。我将aes密钥导出为单独的文本文件,然后我建议您使用一些像ntfs-acls这样的安全方法。您的安全屏障是访问“密码文件”。

可以说你可以用一个纯文本密码文件来完成这个任务,但是添加额外的加密/解密层的目的是为了阻止一般的懒惰恶意/好奇/任何管理员(或用户!)很容易看到你的密码。

用这个,对密码进行加密的aes方法,该过程现在变成:

  1. 运行get credential命令提示管理员提供要保存的凭据
  2. 生成随机AES密钥以转换安全字符串对象。将AES密钥和安全字符串文本存储为单独的文件。使用NTFS权限保护这些文件。
  3. 对于需要保存凭据的脚本,读取这两个文件并重新创建凭据对象,并将其提供给适当的cmdlet。

这种方法的主要区别在于:

  • 密码文件可由任何用户在任何机器上生成一次,随后使用,只要他们能读到文件
  • 这里的安全屏障是密码文件上的NTFS权限。

脚本模板

现在,如果你能做到这一点,我祝贺你,并以此奖励你——我上面所说的那些胡言乱语都被包装成了一个漂亮的,易于使用PowerShell脚本模板。此模板的结构使您可以简单地将核心“worker”代码嵌入到主函数中,模板将处理所有其他内容。它包括以下便利功能:

  • 两种“模式”:一种是收集和准备凭证文件(即以交互方式),另一个用于执行核心工作人员代码(即在计划任务中运行的模式)
  • 为用户提供他们希望使用的加密方法的选项,更安全的DPAPI(默认)方法或更灵活的AES方法。
  • 代码将根据已创建的凭据文件自动确定用于解密的方法
  • 此外,所有这些无聊的错误处理和日志功能都包括在内,没有额外的费用

享受!

通过Github下载链接

类别:
Office 365PowerShell安全
标签:

加入谈话!25条评论

  1. C:\scripts\script-template-withcreds.ps1
    在c:\scripts\script template withcreds.ps1:55 char:1
    +[CmdletBinding()]
    +~~~~~~~~~~~~~~~~~
    意外的属性“CmdletBinding”。
    位于c:\scripts\script template withcreds.ps1:56 char:1
    +参数(((开关)PrepareCredentials美元,[开关]$执行,param1美元,$par…
    +~~~~~
    表达式或语句中的意外令牌“参数”。
    在c:\scripts\script template withcreds.ps1:56 char:74
    +……m(((开关)PrepareCredentials美元,[开关]$执行,param1美元,param2美元)
    +~
    表达式中缺少关闭')'。
    + CategoryInfo: ParserError: (:) [],ParentContainsErrorRecordException
    +完全限定错误ID:未预期的属性

    答复
    • 嗨,希尔顿,

      谢谢提醒!它看起来像一个流氓角色偷偷溜了进来
      我已经修复了gist repo,但是如果你想修改你自己的版本,您会注意到在代码的第46行中,它有参数(([开关]……请删除其中一个'('字符,您就可以走了!

      欢呼,
      戴夫。

      答复
  2. 好文章。我很高兴看到你指出重新启动会导致加密文件不再被读取。很遗憾,我的服务器已经将网络访问策略设置为禁用。如果我找到解决方案,我会发回邮件,如果可能的话,我宁愿坚持这个。

    答复
    • 嗨,克里斯,

      很高兴你发现这篇文章有用。不幸的是,我找不到解决应用了该GPO设置的DPAPI问题的方法。很多头发都掉了,这实际上是我添加“不太安全”的AES密钥文件方法的原因。

      另一种通过同事向我建议的技术是,可以通过使用pki证书的私钥作为该aes密钥的实际来源来进一步改进aes密钥方法。这个过程基本上包括:

      1)从任何PKI证书颁发机构生成/获取证书
      2)使用私钥导入证书(为了安全,不允许将其导出)在计算机证书存储中
      3)向证书授予ACLs以允许您的服务帐户读取该证书的私钥。
      4)更新脚本以查找该特定证书并读取私钥,并将其用作加密密码的AES密钥。
      5)这种方法在Windows中的加密证书存储机制下有效地保护您的密码。

      但从未尝试过简化这种方法以使其切实可行,但欢迎你给我一个机会

      当做,
      戴夫。

      答复
  3. 你好,
    我有个问题。我要创建aes.key和file.txt,但现在我想执行我的脚本ps1。我如何使用这个脚本做到这一点??

    答复
    • 嗨,安德森,

      只需填入我已注释的脚本部分,并使用$credObject作为您的creoudential参数。然后,当您使用-execution参数执行脚本时,它将触发脚本的该部分来读取凭证并相应地执行代码。

      答复
  4. 你好大卫,

    很棒的文章。我已经决定尝试代码的某些部分,在我的测试过程中,我遇到了一个问题,我希望您能帮助解决。
    我已经成功地将安全字符串导出到csv中,并将其导入。
    我可以验证变量是否包含我以前导出的字符串,但当我试图转换到securestring,我得到以下错误:
    Convertto SecureString:无法将参数绑定到参数“String”,因为该参数为空。
    有什么建议吗?

    斯雷克

    答复
    • 嗨,斯瑞克

      您所描述的错误最常见的原因是变量名错误(即错别字,因此指的是不存在的变量,给出空值)或参数定义不正确,自动参数映射不起作用。

      你在尝试哪一套代码?如果你给我看你的样本代码,能从这里算出来吗

      欢呼,
      戴夫。

      答复
  5. 你好大卫,

    顺便来看看这篇很棒的文章!

    欢呼,

    尼克

    答复
  6. 这让我开始思考如何存储安全密码。然而,当我尝试您的示例脚本时,我遇到了这一部分
    $password=$passwordsecureString convertFrom secureString-key$aeskey并获取以下错误
    "ConvertFrom-SecureString:无法将参数绑定到参数' SecureString',因为它是空的。"

    查看示例的所有前面部分,我在任何地方都看不到对$passwordSecureString的引用。我只是想知道我遗漏了什么或者说在这里输入的变量应该是什么?感谢你的帮助。

    答复
    • 嘿,安吉洛!

      很高兴我能帮忙!
      关于你的问题,在我的示例中,这有点草率的代码一致性

      这里有一个更实际的例子,关于你如何使用我的例子代码:

      提示您输入用户名和密码
      $CredObject=获取凭证

      #CredObject现在将密码保存为“SecureString”格式
      passwordSecureString = credObject.password美元

      #定义存储aeskey的位置
      $aeskyfilepath=“c:\temp\aeskey.txt”
      #定义一个位置来存储承载加密密码的文件
      $CredentialFilePath=“c:\temp\credpassword.txt”

      #生成随机AES加密密钥。
      $aeskey=新对象字节[]32
      [security.cryptography.rngCryptoServiceProvider]::create().getBytes($aeskey)

      #将AESKey存储到文件中。应该保护此文件!(例如)文件上的ACL,仅允许选择要读取的人)

      设置内容$aeskyfilepath$aes key将覆盖任何现有的aes密钥文件

      $password=$passwordsecureString convertFrom secureString-键$aeskey

      添加内容$CredentialFilePath$密码

      希望会有帮助!
      另外,不要忘了在文章结尾处查看链接——我有一个PowerShell模板,它将所有这些内容以一种易于使用的格式打包,这样您就不必进行所有额外的编码了!

      答复
  7. 真正伟大的文章. .事实上,我正在寻找一种方法来存储凭证并在我为团队准备的Windows窗体项目中重用…

    我没什么问题…

    1。我了解如何使用创建/存储凭证。\ mycredentials.ps1-对我来说,准备的凭据但重用部分不清楚。对于ExMaple,如果我的表单中有一个按钮,一旦单击该按钮,凭证就应该根据该按钮进行验证?

    2。我想存储多个凭证…是否有一种方法来存储多个凭证而不是覆盖相同的位置文件?

    答复
    • 你好,艾米尔,

      很高兴能帮上忙!回答你的问题:

      1。所以如果你提到我的脚本模板,您将在代码的执行块下看到以下代码位:

      $credFiles =获取内容$credentialFilePath
      用户名= credFiles美元[0]
      如果($ decryptMode eq DPAPI)
      {
      $password=$credfiles[1]转换为SecureString
      }
      ELSEIF($decryptMode-eq“aes”)。
      {
      $password = $credFiles[1] | converto - securestring -Key $AESKey
      }
      其他的
      {
      #占位符,以防有其他解密模式
      }
      $credObject=new object system.management.automation.pscredential-argumentlist$username,$密码

      从这里,我只需读取文件(在确定存在哪些文件以确定我们使用的模式之后),并使用Convertto-SecureString Cmdlet重新生成密码安全字符串以馈送到PSCredential对象的创建中。然后您可以将该凭证对象馈送到任何其他Cmdlet中,如。
      连接msolservice-凭据$CredObject

      2。在我的脚本模板中,我假设一个凭证。但是你可以修改它,这样就不用重写文件了,附加。但我想诀窍是重用,您需要知道存储凭证的位置—因此您需要编写一些额外的逻辑来处理它。

      答复
  8. 这种方法唯一的问题是,您可以通过$securepwd变量显示实际的密码。

    授予,如果具有恶意意图的人能够访问该框并能够在本地执行脚本,则会遇到更大的问题。

    答复
    • 嗨,Seal,

      站住!这种方法当然不是傻瓜式的——它只是提供了一个进入的障碍。您会注意到,我甚至有一点松懈,因为我的调试代码允许将密码写入文件(糟糕的实践!)

      最终,最低权限的主体仍然需要应用-不要授予您的SVC帐户超过必要的权限,不允许不必要的管理员进入保存脚本的计算机,不允许修改脚本等。

      欢呼,
      戴夫。

      答复
  9. 你真是太棒了。你让我很开心。我从4天开始就一直在努力解决这个问题,但是没有成功,但是现在在本文的帮助下,现在修好了。再次感谢你

    答复
  10. 如果我们的帐户启用了mfa怎么办?我们可以使用带安全字符串的app密码吗?

    答复
    • 理论上是的,但一般来说,你可能应该开始不再强调应用程序密码的使用,因为它们的安全性较低。同样,当你使用诸如Azure广告之类的东西来控制mfa时,应用密码也不起作用。

      我的建议是,在需要使用专用服务帐户的情况下,应将其从mfa策略中排除,并具有长安全的密码。您甚至可以考虑使用Azure AD应用其他控件,例如确保仅从受信任的位置使用服务帐户(例如)。

      答复
  11. 你好,
    我正在努力完成这个我想开始工作的剧本。主要问题是自动插入凭证根本不起作用,它甚至不插入用户名,但弹出凭证窗口。第一部分是一个文件副本,工作正常,但当安装开始时,问题就出现了。任何建议都是很好的。谢谢。

    删除项目C:\temp\*-递归-错误操作忽略
    新项目-itemtype目录-路径“c:\temp”-错误操作忽略
    $sourcepath='\\192.168.7.11\下载\etos'
    $destinationPath='c:\temp\etos'
    $files = get - child - item -Path $sourcePath -Recurse
    $fileCount=$files.count
    i=0
    $host.privatedata.progressForegroundcolor=“白色”;
    $host.privatedata.progressbackgroundcolor=“蓝色”;
    foreach($file in$files){
    美元+
    写进度-活动“Kopierar文件管理器……..安装自动启动……”-状态“($I/$fileCount)$file”-完成百分比(($i/$fileCount)*100)

    确定此对象的父容器的绝对路径。这是作为文件和文件夹对象上的不同属性存储的,因此我们使用if块来满足这两种情况
    if($file.psisContainer)$sourcefilecontainer=$file.parent else$sourcefilecontainer=$file.directory

    #计算父文件夹相对于源文件夹的路径
    $relativePath=$sourcefilecontainer.fullname.substring($sourcepath.length)

    #将对象复制到目标文件夹中的相应文件夹
    复制项目$file.fullname($destinationPath+$relativePath)
    }
    $ User = "管理"
    美元PasswordFile = " \ \ 192.168.7.11 \ \ Scripts \ test2.txt下载”
    密钥文件美元= " \ \ 192.168.7.11 \ \ Scripts \ test2.key下载”
    $key=获取内容$keyfile
    $myCredential=new object-typename system.management.automation.psCredential-argumentList$user,(获取内容$passwordfile convertto securestring-key$key)
    启动进程setup.exe-filepath c:\temp\etos-credential$mycredential

    答复
  12. 2018年底还在看这篇文章!谢谢你!

    答复
  13. 还打算提到,您可以将这些脚本添加到PowerShell配置文件中,以进一步节省时间并自动化进程:my profile.ps1包含几个全局变量,比如$usrname,$pwdxt,,securePwd美元,和$CredObject。如果我需要更新密码,我创建了一个保存密码功能,它将提示输入新的凭据,更新我的cred.file,然后退出。重新发射炮弹时,我的新密码获得。从那里,我可以运行其他函数,例如,connect exch使用我保存的凭据启动到prem Exchange服务器上的远程PowerShell会话,而connect-365函数使用我保存的凭据连接到Office 365和Exchange Online。将我的凭证存储到一个文件中,可以使连接到这些服务和应用程序变得更快、更容易!

    答复
  14. 很好的来源满足我的需求。感谢。

    答复

留下一个回复