精通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 Portal 中創建應用程式身份來完成的。您可以通過 Azure Portal 創建應用程式身份。操作如下:

  • 前往 Azure Portal,然後轉到 Azure Active Directory。
  • 在左側菜單的 管理 下,點擊 應用程式註冊,然後點擊 新註冊 按鈕。
Authenticating before creating the PowerShell Graph API
  • 輸入應用程式的名稱,然後點擊 註冊
  • 複製應用程式 ID GUID,以便稍後使用。
Registering an application

為 Microsoft Graph API 創建密鑰

您可以使用两种主要方法对Graph API进行身份验证:AppId/Secret和基于证书的身份验证。连接到PowerShell的Graph 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)


#為了可讀性而使用的Splat
$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

現在,通過點擊左側菜單中的證書和密鑰,然後按下上傳證書,將自簽名證書上傳到您的Azure應用程序中,上傳到$CerOutputPath

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

現在您已經準備好了,讓我們登錄並獲取一些數據!

獲取存取權杖(應用程式識別碼和密鑰)

為此,我們需要向 Microsoft Graph OAuth 端點發送請求以獲取存取權杖。在該請求的主體中,我們需要提供:

  • client_id – 您的應用程式識別碼 – 網址編碼
  • client_secret – 您的應用程式密鑰 – 網址編碼
  • scope – 指定您要存取的網址的網址編碼
  • 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 以進行網址編碼
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/Secret 流程略有不同。若要使用憑證獲取存取權杖,您需要:

  1. 創建一個Java Web Token(JWT)標頭。
  2. 創建一個JWT有效負載。
  3. 使用之前創建的自簽名憑證對JWT標頭和有效負載進行簽名。這將創建一個自製的訪問權杖,用於請求Microsoft Graph訪問權杖。
  4. 創建一個包含以下內容的請求主體:
  • client_id=<應用程序ID>
  • client_assertion=<JWT>
  • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • scope=<URL編碼的範圍>
  • grant_type=client_credentials
  1. 使用主體進行POST請求到oauth端點,其標頭中包含Authorization=<JWT>

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的編碼
    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參數創建哈希
$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,您可以調用該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/