掌握PowerShell图形API:易于跟随的见解

Microsoft Graph API 是一项服务,允许您在单个 REST API 终结点下读取、修改和管理几乎 Azure AD 和 Office 365 的所有方面。在本文中,了解如何将您的 API 转换为 PowerShell Graph API。

先决条件

如果您想在本文中跟随我进行操作,请确保首先满足以下条件:

  • 运行 Windows PowerShell 5.1(这是我测试的版本。其他版本可能可以运行,但不能保证)
  • 一个 Azure 租户
  • 使用具有全局管理员权限或订阅上的应用程序注册权限的帐户进行 Azure 身份验证,并使用全局管理员接受您的应用程序注册请求。

为 Microsoft Graph API 创建应用程序标识

要访问 Microsoft Graph API,您首先需要一个身份来获取 OAuth 令牌。这主要是通过在 Azure 门户中创建应用程序标识来完成的。您可以通过 Azure 门户创建应用程序标识。操作步骤如下:

  • 转到Azure 门户,然后转到 Azure Active Directory。
  • 在左侧菜单中单击管理下的应用注册,然后单击新注册按钮。
Authenticating before creating the PowerShell Graph API
  • 输入应用程序的名称,然后单击注册
  • 复制用于后续使用的应用程序标识 GUID。
Registering an application

为 Microsoft Graph API 创建密码

您可以通过两种主要方法对 Graph API 进行身份验证:AppId/Secret 和基于证书的身份验证。在使用 PowerShell 连接到图形 API 时,您需要进行身份验证。

让我们来看看如何使用这两种方法进行身份验证。

AppId/Secret

应用程序 ID/密钥就像常规的用户名/密码一样。应用程序 ID 由 GUID 组成,而密码只是一个随机字符串。

要创建一个密钥,请单击左侧菜单中的证书和密码,然后按新客户端密钥

Creating a client secret

为密钥输入描述,并选择希望何时过期。现在只需请求权限,以便您可以访问所需的数据。

证书

有可能创建自签名证书并将其公钥上传到 Azure。这是首选且更安全的身份验证方式。

您首先需要生成自签名证书。幸运的是,使用 PowerShell 可以轻松完成这一步骤。

# 租户名称(也可以更具体一些)
$TenantName        = "contoso.onmicrosoft.com"

# 导出证书而不包含私钥的位置
$CerOutputPath     = "C:\Temp\PowerShellGraphCert.cer"

# 想要将证书存储在哪个证书存储区
$StoreLocation     = "Cert:\CurrentUser\My"

# 新证书的过期日期
$ExpirationDate    = (Get-Date).AddYears(2)


# 为了可读性而分隔的参数
$CreateCertificateSplat = @{
    FriendlyName      = "AzureApp"
    DnsName           = $TenantName
    CertStoreLocation = $StoreLocation
    NotAfter          = $ExpirationDate
    KeyExportPolicy   = "Exportable"
    KeySpec           = "Signature"
    Provider          = "Microsoft Enhanced RSA and AES Cryptographic Provider"
    HashAlgorithm     = "SHA256"
}

# 创建证书
$Certificate = New-SelfSignedCertificate @CreateCertificateSplat

# 获取证书路径
$CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint

# 导出不包含私钥的证书
Export-Certificate -Cert $CertificatePath -FilePath $CerOutputPath | Out-Null

现在,通过单击左侧菜单中的$CerOutputPath下的证书和密钥,然后按上传证书的方式,上传导出的自签名证书到你的 Azure 应用程序。

Uploading a certificate

为应用程序添加权限

为应用程序赋予适当的权限非常重要 – 不仅对你的应用程序的功能而言,也对安全性而言。关于此以及 Microsoft Graph API 中的(几乎)所有其他信息均可在文档中找到。

一旦我设置好了,我就会收集我的租户中的所有安全事件。为了被允许这样做,我需要至少具备SecurityEvents.Read.All权限。有了这个权限,我就可以收集并对不可能的登录事件、用户通过VPN/TOR连接等采取行动。

要将SecurityEvents.Read.All添加到你的应用程序中 – 点击API权限,然后点击添加权限。这将向你呈现不仅是Graph API,还有大量其他应用程序在Azure中。一旦你知道如何连接到Microsoft Graph API,大多数这些应用程序都很容易连接。

点击Microsoft Graph > 应用程序权限 > 安全事件,然后勾选SecurityEvents.Read.All。之后按下添加权限按钮。

你看到了那个权限的需要管理员同意列被设置为吗?这意味着租户管理员需要在权限添加到应用程序之前批准。

Tenant admin permission approval

如果你是全局管理员,请按下批准管理员同意或者请全局管理员来批准。请求用户权限而不是管理员设置读/写权限是OAuth认证的一个重要部分。但这使我们能够绕过大多数Microsoft Graph中的权限。

你可能已经在Facebook或Google上看到过这种情况:“你允许应用程序X访问你的个人资料吗?”

Granting admin consent

现在你已经准备好了 – 让我们登录并获取一些数据!

获取访问令牌(应用程序 ID 和密钥)

为此,我们需要向 Microsoft Graph OAuth 终点发送一个请求来获取访问令牌。在该请求的主体中,我们需要提供:

  • client_id – 您的应用程序 ID – URL 编码
  • client_secret – 您的应用程序密钥 – URL 编码
  • scope – 一个 URL 编码的 URL,指定您要访问的内容
  • grant_type – 您正在使用的认证方法

终点的 URL 是 https://login.microsoftonline.com/<tenantname>/oauth2/v2.0/token。您可以使用以下代码片段通过 PowerShell 和 Graph API 请求访问令牌。

# 定义 AppId、密钥和范围,您的租户名称和终点 URL
$AppId = '2d10909e-0396-49f2-ba2f-854b77c1e45b'
$AppSecret = 'abcdefghijklmnopqrstuv12345'
$Scope = "https://graph.microsoft.com/.default"
$TenantName = "contoso.onmicrosoft.com"

$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# 添加 System.Web 以进行 URL 编码
Add-Type -AssemblyName System.Web

# 创建主体
$Body = @{
    client_id = $AppId
	client_secret = $AppSecret
	scope = $Scope
	grant_type = 'client_credentials'
}

# 用 Invoke-Restmethod 的参数展开以使代码更清晰
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    # 通过将 bodylist 用 '&' 连接创建字符串
    Body = $Body
    Uri = $Url
}

# 请求令牌!
$Request = Invoke-RestMethod @PostSplat

获取访问令牌(使用证书)

使用证书进行对 Microsoft Graph API 的身份验证与正常的 AppId/密钥流程有些不同。要使用证书获取访问令牌,您必须:

  1. 创建一个Java Web Token(JWT)头部。
  2. 创建一个JWT负载。
  3. 用先前创建的自签名证书对JWT头部和负载进行签名。这将创建一个自制的访问令牌,用于请求Microsoft Graph访问令牌。
  4. 创建一个包含的请求主体:
  • client_id=<application id>
  • client_assertion=<the JWT>
  • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • scope=<URLEncoded scope>
  • grant_type=client_credentials
  1. Authorization=<JWT>在其头部进行POST请求到OAuth端点。

如何做这件事在Microsoft的文档中并不明显,但这是用于实现的PowerShell脚本:

$TenantName = "<your tenant name>.onmicrosoft.com"
$AppId = "<your application id"
$Certificate = Get-Item Cert:\CurrentUser\My\<self signed and uploaded cert thumbprint>
$Scope = "https://graph.microsoft.com/.default"

# 创建证书的base64哈希
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())

# 创建JWT过期时间戳
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)

# 创建JWT有效性开始时间戳
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)

# 创建JWT头部
$JWTHeader = @{
    alg = "RS256"
    typ = "JWT"
    # 使用CertificateBase64Hash并替换/剥离以匹配base64的Web编码
    x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}

# 创建JWT负载
$JWTPayLoad = @{
    # 哪个端点被允许使用此JWT
    aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"

    # 过期时间戳
    exp = $JWTExpiration

    # 发行者 = 您的应用程序
    iss = $AppId

    # JWT ID:随机GUID
    jti = [guid]::NewGuid()

    # 不得在此之前使用
    nbf = $NotBefore

    # JWT主题
    sub = $AppId
}

# 将头部和负载转换为base64
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)

$JWTPayLoadToByte =  [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)

# 使用“。”连接头部和负载以创建有效(未签名的)JWT
$JWT = $EncodedHeader + "." + $EncodedPayload

# 获取您的证书的私钥对象
$PrivateKey = $Certificate.PrivateKey

# 定义RSA签名和哈希算法
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

# 创建JWT的签名
$Signature = [Convert]::ToBase64String(
    $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='

# 使用“。”将签名连接到JWT
$JWT = $JWT + "." + $Signature

# 使用正文参数创建哈希
$Body = @{
    client_id = $AppId
    client_assertion = $JWT
    client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    scope = $Scope
    grant_type = "client_credentials"

}

$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# 使用自动生成的JWT作为授权
$Header = @{
    Authorization = "Bearer $JWT"
}

# 为Invoke-Restmethod展开参数,以获得更清晰的代码
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    Body = $Body
    Uri = $Url
    Headers = $Header
}

$Request = Invoke-RestMethod @PostSplat

了解请求访问令牌输出

一旦您通过应用程序ID/密钥或证书获得访问令牌,应该看到一个具有四个属性的对象。

  • token_type – 令牌类型
  • expires_in – 访问令牌有效的秒数
  • ext_expires_in – 类似于expires_in,但用于在令牌服务中断的情况下保持弹性
  • access_token – 我们的目标

接下来,您将使用token_typeaccess_token创建一个标题,并开始使用PowerShell向Microsoft Graph API发出请求。

向Microsoft Powershell Graph API发出请求

现在开始向API发出一些请求。

以我们的示例为例,您首先需要列出安全警报的URL。记得使用Microsoft Graph API文档查看所需内容。

在这种情况下,您需要一个包含Authorization=Bearer <access_token>的标头,并向Graph API Alerts端点发出GET请求。以下是如何使用PowerShell执行此操作。

# 创建标头
$Header = @{
    Authorization = "$($Request.token_type) $($Request.access_token)"
}

$Uri = "https://graph.microsoft.com/v1.0/security/alerts"

# 获取所有安全警报
$SecurityAlertsRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$SecurityAlerts = $SecurityAlertsRequest.Value

现在,如果您在$SecurityAlerts变量中有任何安全警报,它应该看起来像这样:

$SecurityAlerts | select eventDateTime,Title

eventDateTime                title
-------------                -----
2019-08-05T17:59:47.6271981Z Atypical travel
2019-08-05T08:23:01.7325708Z Anonymous IP address
2019-08-05T08:23:55.5000456Z Anonymous IP address
2019-08-04T22:06:51.063797Z  Anonymous IP address
2019-08-04T21:56:10.981437Z  Anonymous IP address
2019-08-08T09:30:00Z         Creation of forwarding/redirect rule
2019-07-19T13:30:00Z         eDiscovery search started or exported
2019-07-19T08:00:00Z         eDiscovery search started or exported

检查单个安全警报的JSON格式如下:

"id":  "censored",
    "azureTenantId":  "censored",
    "azureSubscriptionId":  "censored",
    "riskScore":  null,
    "tags":  [

             ],
    "activityGroupName":  null,
    "assignedTo":  null,
    "category":  "AnonymousLogin",
    "closedDateTime":  null,
    "comments":  [

                 ],
    "confidence":  null,
    "createdDateTime":  "2019-08-08T09:46:59.65722253Z",
    "description":  "Sign-in from an anonymous IP address (e.g. Tor browser, anonymizer VPNs)",
    "detectionIds":  [

                     ],
    "eventDateTime":  "2019-08-08T09:46:59.65722253Z",
    "feedback":  null,
    "lastModifiedDateTime":  "2019-08-08T09:54:30.7256251Z",
    "recommendedActions":  [

                           ],
    "severity":  "medium",
    "sourceMaterials":  [

                        ],
    "status":  "newAlert",
    "title":  "Anonymous IP address",
    "vendorInformation":  {
                              "provider":  "IPC",
                              "providerVersion":  null,
                              "subProvider":  null,
                              "vendor":  "Microsoft"
                          },
    "cloudAppStates":  [

                       ],
    "fileStates":  [

                   ],
    "hostStates":  [

                   ],
    "historyStates":  [

                      ],
    "malwareStates":  [

                      ],
    "networkConnections":  [

                           ],
    "processes":  [

                  ],
    "registryKeyStates":  [

                          ],
    "triggers":  [

                 ],
    "userStates":  [
                       {
                           "aadUserId":  "censored",
                           "accountName":  "john.doe",
                           "domainName":  "contoso.com",
                           "emailRole":  "unknown",
                           "isVpn":  null,
                           "logonDateTime":  "2019-08-08T09:45:59.6174156Z",
                           "logonId":  null,
                           "logonIp":  "censored",
                           "logonLocation":  "Denver, Colorado, US",
                           "logonType":  null,
                           "onPremisesSecurityIdentifier":  null,
                           "riskScore":  null,
                           "userAccountType":  null,
                           "userPrincipalName":  "[email protected]"
                       }
                   ],
    "vulnerabilityStates":  [

                            ]
}

理解和管理API输出分页

Microsoft Graph API对每个函数返回的项目数量有限制。这个限制是针对每个函数的,但假设是1000个项目。这意味着您在请求中最多只能获取1000个项目。

当达到此限制时,它将使用分页来传递其余的项目。它通过将@odata.nextLink属性添加到您请求的答案中来实现这一点。@odata.nextLink包含一个URL,您可以调用它来获取您请求的下一页。

您可以通过检查此属性并使用循环来读取所有项目:

$Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns"

# 获取所有安全警报
$AuditLogRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$AuditLogs = @()
$AuditLogs+=$AuditLogRequest.value

while($AuditLogRequest.'@odata.nextLink' -ne $null) {
    $AuditLogRequest += Invoke-RestMethod -Uri $AuditLogRequest.'@odata.nextLink' -Headers $Header -Method Get -ContentType "application/json"
}

结论

学会如何对Graph API进行身份验证后,从中收集数据就相当容易了。这是一个强大的服务,但使用的次数远低于预期。

幸运的是,已经有许多模块可以利用Microsoft Graph API,但为了满足您自己的需求,您可能需要为其创建自己的模块。使用本文中学到的技能,您应该能够很好地前进。

有关在Office 365中控制访客访问权限的更多信息,我在我的博客上写了一篇深入的文章,我鼓励您查看一下

Source:
https://adamtheautomator.com/powershell-graph-api/