当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 利用您的 API 到 PowerShell Graph API

作者:精品下载站 日期:2024-12-14 20:50:09 浏览:14 分类:玩电脑

利用您的 API 到 PowerShell Graph 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。
  • 单击左侧菜单管理下的应用注册,然后单击新注册按钮。
  • 输入您的应用程序的名称,然后单击“注册”。
  • 复制应用程序 ID 指南以供以后使用。

为 Microsoft Graph API 创建机密

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

让我们介绍一下如何使用这两种方法进行身份验证。

应用程序 ID/秘密

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

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

输入密钥的描述并选择您希望其过期的时间。现在只需请求许可即可访问所需的数据。

证书

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

您首先需要生成一个自签名证书。幸运的是,这可以通过 PowerShell 轻松完成。

# Your tenant name (can something more descriptive as well)
$TenantName        = "contoso.onmicrosoft.com"

# Where to export the certificate without the private key
$CerOutputPath     = "C:\Temp\PowerShellGraphCert.cer"

# What cert store you want it to be in
$StoreLocation     = "Cert:\CurrentUser\My"

# Expiration date of the new certificate
$ExpirationDate    = (Get-Date).AddYears(2)


# Splat for readability
$CreateCertificateSplat = @{
    FriendlyName      = "AzureApp"
    DnsName           = $TenantName
    CertStoreLocation = $StoreLocation
    NotAfter          = $ExpirationDate
    KeyExportPolicy   = "Exportable"
    KeySpec           = "Signature"
    Provider          = "Microsoft Enhanced RSA and AES Cryptographic Provider"
    HashAlgorithm     = "SHA256"
}

# Create certificate
$Certificate = New-SelfSignedCertificate @CreateCertificateSplat

# Get certificate path
$CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint

# Export certificate without private key
Export-Certificate -Cert $CertificatePath -FilePath $CerOutputPath | Out-Null

现在,通过单击左侧菜单中的“证书和机密”并按“上传证书”,将导出到 $CerOutputPath 的自签名证书上传到 Azure 应用程序。

向应用程序添加权限

为应用程序提供适当的权限非常重要 - 不仅对于应用程序的功能而言,而且对于安全性而言。有关这方面的知识以及 Microsoft Graph API 中的(几乎)其他所有内容都可以在文档中找到。

一旦完成此设置,我将收集租户的所有安全事件。为了被允许这样做,我需要 SecurityEvents.Read.All 作为最低权限。有了这个,我可以收集并针对 Impossible Travel 事件、通过 VPN/TOR 连接的用户等采取行动。

要将SecurityEvents.Read.All添加到您的应用程序中 - 单击API Permissions,然后添加权限。这不仅会向您展示 Graph API,还会向您展示 Azure 中的大量其他应用程序。一旦您知道如何连接到 Microsoft Graph API,大多数应用程序就可以轻松连接。

单击Microsoft Graph > 应用程序权限 > 安全事件并检查SecurityEvents.Read.All。之后按添加权限按钮。

您是否看到该权限的需要管理员同意列已设置为?这意味着在将权限添加到应用程序之前需要租户管理员批准。

如果您是全局管理员,请按授予管理员同意或请求全局管理员批准。向用户请求许可而不是仅由管理员设置读/写权限是 OAuth 身份验证的重要组成部分。但这使我们能够绕过 Microsoft Graph 中的大多数权限。

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

现在一切都准备好了 - 让我们登录并获取一些数据!

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

为此,我们需要发布请求以从 Microsoft Graph OAuth 端点获取访问令牌。在该请求的正文中,我们需要提供:

  • client_id - 您的应用程序 ID - url 编码
  • client_secret - 您的应用程序秘密 - url 编码
  • scope - 一个 url 编码的 url,指定您要访问的内容
  • grant_type - 您使用的身份验证方法

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

# Define AppId, secret and scope, your tenant name and endpoint 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"

# Add System.Web for urlencode
Add-Type -AssemblyName System.Web

# Create body
$Body = @{
    client_id = $AppId
	client_secret = $AppSecret
	scope = $Scope
	grant_type = 'client_credentials'
}

# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    # Create string by joining bodylist with '&'
    Body = $Body
    Uri = $Url
}

# Request the token!
$Request = Invoke-RestMethod @PostSplat

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

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

  1. 创建 Java Web 令牌 (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. 使用 header 中的 Authorization= 向 oauth 端点发出带有正文的 post 请求。

    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"
    
    # Create base64 hash of certificate
    $CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())
    
    # Create JWT timestamp for expiration
    $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)
    
    # Create JWT validity start timestamp
    $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
    $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)
    
    # Create JWT header
    $JWTHeader = @{
        alg = "RS256"
        typ = "JWT"
        # Use the CertificateBase64Hash and replace/strip to match web encoding of base64
        x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
    }
    
    # Create JWT payload
    $JWTPayLoad = @{
        # What endpoint is allowed to use this JWT
        aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"
    
        # Expiration timestamp
        exp = $JWTExpiration
    
        # Issuer = your application
        iss = $AppId
    
        # JWT ID: random guid
        jti = [guid]::NewGuid()
    
        # Not to be used before
        nbf = $NotBefore
    
        # JWT Subject
        sub = $AppId
    }
    
    # Convert header and payload to 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)
    
    # Join header and Payload with "." to create a valid (unsigned) JWT
    $JWT = $EncodedHeader + "." + $EncodedPayload
    
    # Get the private key object of your certificate
    $PrivateKey = $Certificate.PrivateKey
    
    # Define RSA signature and hashing algorithm
    $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
    $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
    
    # Create a signature of the JWT
    $Signature = [Convert]::ToBase64String(
        $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
    ) -replace '\+','-' -replace '/','_' -replace '='
    
    # Join the signature to the JWT with "."
    $JWT = $JWT + "." + $Signature
    
    # Create a hash with body parameters
    $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"
    
    # Use the self-generated JWT as Authorization
    $Header = @{
        Authorization = "Bearer $JWT"
    }
    
    # Splat the parameters for Invoke-Restmethod for cleaner code
    $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 Graph API 发出请求

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

    按照我们的示例,您首先需要用于列出安全警报的 URL。请记住使用 Microsoft Graph API 文档来查看需要什么。

    在这种情况下,您需要一个带有 Authorization=Bearer 的标头和一个针对 Graph API Alerts 端点的 GET 请求。以下是如何使用 PowerShell 执行此操作。

    # Create header
    $Header = @{
        Authorization = "$($Request.token_type) $($Request.access_token)"
    }
    
    $Uri = "https://graph.microsoft.com/v1.0/security/alerts"
    
    # Fetch all 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 "
                           }
                       ],
        "vulnerabilityStates":  [
    
                                ]
    }

    了解和管理 API 输出分页

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

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

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

    $Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns"
    
    # Fetch all security alerts
    $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 中的来宾访问的更多信息,我在我的博客上写了一篇深入的文章,我鼓励您查看它。

    您需要 登录账户 后才能发表评论

    取消回复欢迎 发表评论:

    关灯