在Powershell中使用Chrome Cookies进行Invoke-WebRequest

3

有没有一种方法可以在Powershell中使用已存在的Chrome Web会话中获得的cookie作为SessionVariable?

我知道SessionVariable的字段是:

Headers               : {}
Cookies               : System.Net.CookieContainer
UseDefaultCredentials : False
Credentials           : 
Certificates          : 
UserAgent             : Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) 
                        WindowsPowerShell/5.1.17134.407
Proxy                 : 
MaximumRedirection    : -1

我能否通过一些方式在Web请求初始阶段设置这些值,例如:?如果请求类型可能是多种/不同的,我是否需要为会话指定特定的标头?

$ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
    $Params = @{
        Headers = $headers 
        Cookies = $cookies 
        UseDefaultCredentials = "False" 
        Credentials = $creds
        UserAgent = $ua
        Proxy = ""
        MaximumRedirection = "-1"
    }

如果可以做到这样的话,我也有点困惑该如何输入Cookie。我在使用PowerShell获取Cookie中找到了以下内容:
$webrequest = Invoke-WebRequest -Uri $url -SessionVariable websession 
$cookies = $websession.Cookies.GetCookies($url) 
 
# Here, you can output all of $cookies, or you can go through them one by one. 
 
foreach ($cookie in $cookies) { 
     # You can get cookie specifics, or just use $cookie 
     # This gets each cookie's name and value 
     Write-Host "$($cookie.name) = $($cookie.value)" 
}

我可以将任何网站的Cookie以JSON格式导出,但如果需要的话,我也可以将其简化为只包含“名称”=“值”的格式。
[
  {
    "domain": "",
    "expirationDate": "",
    "hostOnly": "",
    "httpOnly": "",
    "name": "",
    "path": "",
    "sameSite": "",
    "secure": "",
    "session": "",
    "storeId": "",
    "value": "",
    "id": ""
  },
  {
    "domain": "",
    etc...
  }
]

我不知道如何将每个部分指定为$cookies中的一个$cookie,也不知道如何以不同的方式添加它们,因为它不是从URL / WebRequest而是从JSON获取。

感谢任何帮助!


每个浏览器都有自己存储 cookie 的方式。我刚学到,Chrome 将 cookie 存储在您的个人资料中的 SQLite 数据库文件中 (https://dev59.com/F10Z5IYBdhLWcg3w0zJW)。因此,无论如何,您都必须从那里提取 cookie 信息以在脚本中使用。如果可能的话... - Gert Jan Kraaijeveld
1个回答

1
这是一个使用mySQLiteBouncy CastleGitHub Logo Gist)的PowerShell脚本解决方案。
# One time setup
if (-not (Get-Package 'Portable.BouncyCastle' -ErrorAction Ignore)) {
    if (-not (Get-PackageSource -Name NuGet -ErrorAction Ignore)) {
        Register-PackageSource -Name NuGet -Location https://api.nuget.org/v3/index.json -ProviderName NuGet | Set-PackageSource -Trusted
    }
    Install-Package -Name 'Portable.BouncyCastle' -Source NuGet -Scope CurrentUser -SkipDependencies
}
# Download the MySQLite repository (for PowerShell <5 without PowerShellGet)
if (-not(Get-Module -Name MySQLite -ErrorAction Ignore) -and ($PSVersionTable.PSVersion.Major -lt 5)) {
    $RepositoryZipUrl = 'https://api.github.com/repos/jdhitsolutions/MySQLite/zipball/master'
    Invoke-RestMethod -Uri $RepositoryZipUrl -OutFile 'MySQLite.zip'
    # Unblock the zip
    Unblock-File 'MySQLite.zip'
    # Extract the MySQLite folder to a module path (e.g. $env:USERPROFILE\Documents\WindowsPowerShell\Modules\)
    Expand-Archive -Path 'MySQLite.zip' -DestinationPath $($env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { (Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue) -and $(try { $tmp = New-Item -Path $_ -Name ([System.IO.Path]::GetRandomFileName()) -ItemType File -Value (Get-Random) -ErrorAction SilentlyContinue; Remove-Item -Path $tmp; $true | Write-Output } catch { $false | Write-Output } ) } | Select-Object -First 1) -Force -Confirm
}
elseif (-not(Get-Module -Name MySQLite -ErrorAction Ignore) -and ($PSVersionTable.PSVersion.Major -ge 5)) {
    #Simple alternative, if you have PowerShell ≥5, or the PowerShellGet module:
    Install-Module MySQLite -Repository PSGallery -Scope CurrentUser
}

# Import the MySQLite module
Import-Module MySQLite    #Alternatively, Import-Module \\Path\To\MySQLite
# Import Bouncy Castle Classes
Get-Package 'Portable.BouncyCastle' | ForEach-Object { Add-Type -LiteralPath ($_.Source | Split-Path | Get-ChildItem -Filter 'netstandard*' -Recurse -Directory | Get-ChildItem -Filter *.dll -Recurse -File ).FullName }
# Specify for which domain you want to retrieve cookies
$domain = 'mavaddat.ca'
$cookiesPath = "$env:LOCALAPPDATA\Google\Chrome Beta\User Data\Default\Network\Cookies" # "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default\Network\Cookies" # "$env:APPDATA\Opera Software\Opera Stable\Cookies"

# Investigate the db structure
Get-MySQLiteTable -Path $cookiesPath -Detail

# Based on the schema of table `cookies`, form the query
$query = "SELECT name,encrypted_value,path,host_key FROM `"main`".`"cookies`" WHERE `"host_key`" LIKE '%$domain%' ESCAPE '\' LIMIT 0, 49999;"

# Or, get all cookies for all domains
$query = "SELECT name,encrypted_value,path,host_key FROM `"main`".`"cookies`" LIMIT 0, 49999;"

# Read the cookies from the SQLite
$cookies = Invoke-MySQLiteQuery -Path $cookiesPath -Query $query

# Get Chromium cookie master key
$localStatePath = "$env:LOCALAPPDATA\Google\Chrome Beta\User Data\Local State" # "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Local State" # "$env:APPDATA\Opera Software\Opera Stable\Local State"
$cookiesKeyEncBaseSixtyFour = (Get-Content -Path $localStatePath | ConvertFrom-Json).'os_crypt'.'encrypted_key'
$cookiesKeyEnc = [System.Convert]::FromBase64String($cookiesKeyEncBaseSixtyFour) | Select-Object -Skip ([System.Text.Encoding]::UTF8.GetBytes('DPAPI').Count)
$cookiesKey = [System.Security.Cryptography.ProtectedData]::Unprotect($cookiesKeyEnc, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)

# Create a web session object for the IWR work
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession

# Prep the cipher elements
$cipher = [Org.BouncyCastle.Crypto.Modes.GcmBlockCipher]::new([Org.BouncyCastle.Crypto.Engines.AesEngine]::new())

# Stuff the cookies into the session
foreach ($cookie in $cookies) {
    $path = [string]::IsNullOrEmpty($cookie.path) ? '/' : $cookie.path
    try {
        $cipherStream = [System.IO.MemoryStream]::new($cookie.encrypted_value)
        $cipherReader = [System.IO.BinaryReader]::new($cipherStream)
    
    
        <# 
        # We don't need to keep the non-secret payload; however, this is how to retrieve it (spoiler alert, it's 'v10')
        $nonSecretPayload = $cipherReader.ReadBytes(([System.Text.Encoding]::ASCII.GetBytes('v10').Count)) # 
        
        # if you want to read this, just use 
        [System.Text.Encoding]::Default.GetString($nonSecretPayload) | Out-Host
        #>

        # Alternatively, if you don't care about 'v10', move the stream pointer past it
        $cipherReader.BaseStream.Position = [System.Text.Encoding]::ASCII.GetBytes('v10').Count

        $nonce = $cipherReader.ReadBytes([System.Security.Cryptography.AesGcm]::NonceByteSizes.MinSize)

        $parameters = [Org.BouncyCastle.Crypto.Parameters.AeadParameters]::new( ([Org.BouncyCastle.Crypto.Parameters.KeyParameter]::new($cookiesKey)), ([System.Security.Cryptography.AesGcm]::TagByteSizes.MaxSize * [byte]::MaxValue.GetShortestBitLength()), $nonce)
        $cipher.Init($false, $parameters)
        $cipherText = $cipherReader.ReadBytes($cookie.encrypted_value.Length)
        $plainText = [byte[]]::new($cipher.GetOutputSize($cipherText.Length))
        if (-not [string]::IsNullOrEmpty($plainText)) {
            try {
                $len = $cipher.ProcessBytes($cipherText, 0, $cipherText.Length, $plainText, 0)
                $bytesDeciphered = $cipher.DoFinal($plainText, $len)
                Write-Verbose "Deciphered $bytesDeciphered bytes"
            }
            catch [System.Management.Automation.MethodInvocationException] {
                # if inner exception [Org.BouncyCastle.Crypto.InvalidCipherTextException]
                if ($_.Exception.InnerException -is [Org.BouncyCastle.Crypto.InvalidCipherTextException]) {
                    Write-Error 'Invalid Cipher Text'
                }
                else {
                    Write-Error $_ # Echo the error unless you have a better way to handle
                }
                continue
            }
            finally {
                $cipher.Reset()
            }
            try {
                $session.Cookies.Add([System.Net.Cookie]::new(($cookie.name), [System.Text.Encoding]::Default.GetString($plainText), $path, ($cookie.host_key -replace '^\.')))
            }
            catch [System.Management.Automation.MethodInvocationException] {
                if ($_.Exception.InnerException -is [System.Net.CookieException]) {
                    $session.Cookies.Add([System.Net.Cookie]::new(($cookie.name), [System.Web.HttpUtility]::UrlEncode([System.Text.Encoding]::Default.GetString($plainText)), $path, ($cookie.host_key -replace '^\.')))
                }
                else {
                    Write-Error $_ # Echo the error unless you have a better way to handle
                }
            }
        }
    }
    finally {
        $cipherStream.Dispose()
        $cipherReader.Dispose()
    }
}

# Remove sensitive objects
$cipherReader = $null
$cipherStream = $null
$cookiesKey = $null
$cookiesKeyEnc = $null
$cookiesKeyEncBaseSixtyFour = $null
$nonce = $null
$cipher = $null
$cipherText = $null
$plainText = $null

Remove-Variable cipher, cipherReader, cipherStream, cookiesKey, cookiesKeyEnc, cookiesKeyEncBaseSixtyFour, nonce, cipherText, plainText

# Do IWR Work
Invoke-WebRequest -Uri $domain -WebSession $session

我如何实现了上述内容

有两个需要查询的Chromium文件:

  1. User Data local state
  2. SQLite中的Cookies

User Data 是一个以JSON格式保存了Cookies加密密钥的文件。对于我来说(使用Chrome Beta 107.0.5304.18在Windows 11上),它位于 %LOCALAPPDATA%\Google\Chrome Beta\User Data\Local State

基于Chromium的浏览器 使用存储在二进制SQLite数据库文件中的Cookies。请参考Superuser上关于如何找到Chrome的这个数据库的问题,"有没有一种方法可以实时查看Cookies及其值?"

对我来说,这个文件被命名为Cookies,并且位于%LOCALAPPDATA%\Google\Chrome Beta\User Data\Default\Network\Cookies。使用MySQLite,我可以看到数据库有两个表,其模式如下:

Cookies

名称 类型 模式
creation_utc 整数 "creation_utc" 非空整数
host_key 文本 "host_key" 非空文本
name 文本 "name" 非空文本
value 文本 "value" 非空文本
path 文本 "path" 非空文本
expires_utc 整数 "expires_utc" 非空整数
is_secure 整数 "is_secure" 非空整数
is_httponly 整数 "is_httponly" 非空整数
last_access_utc 整数 "last_access_utc" 非空整数
has_expires 整数 "has_expires" 非空整数 默认为1
is_persistent 整数 "is_persistent" 非空整数 默认为1
priority 整数 "priority" 非空整数 默认为1
encrypted_value BLOB "encrypted_value" BLOB 默认为空字符串
samesite 整数 "samesite" 非空整数 默认为-1
source_scheme 整数 "source_scheme" 非空整数 默认为0
source_port 整数 "source_port" 非空整数 默认为-1
is_same_party 整数 "is_same_party" 非空整数 默认为0

Meta

名称 类型 模式
LONGVARCHAR "key" LONGVARCHAR NOT NULL UNIQUE
LONGVARCHAR "value" LONGVARCHAR

查看 stackoverflow 问题 "如何在 C# (.NET Core) 中读取 Brave 浏览器 cookie 数据库的加密值?" 此外,用户 @michael-fromberger 在这里详细介绍了基于 Chromium 的 cookie 结构:Google Chrome 加密的 Cookies — 这些详细说明了上述代码中许多看似无关紧要的字符串 (DPAPIv10) 的来源。我还按照 PowerShell cookie 方法使用了 this gist 作为模板,作者是 @lawrencegripper


你似乎已经获取了加密密钥,但是你似乎没有在任何地方使用它 $cookiesKey - Chris
@Chris 是的,你说得对。我的解决方案只是半成品。我已经用完整的解决方案加以补救。我将把它复制到 gist 并添加测试。 - Mavaddat Javid
@Chris,你试过了吗? - Mavaddat Javid
谢谢询问。目前它被搁置了,但我肯定会在某个时候回来处理它。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接