CrowdStrike / psfalcon

PowerShell for CrowdStrike's OAuth2 APIs
The Unlicense
350 stars 66 forks source link

[ QUESTION ] Bulk Removing USB Exception #362

Closed STonEDSTonE closed 10 months ago

STonEDSTonE commented 10 months ago

Description of your question

I am trying to remove USB Exceptions using PSFalcon module for powershell and am running into some issues. I am able to remove one exception at a time, but this is very slow for >300 rules, so I want to be able to bulk remove exceptions. I already managed to bulk add exceptions via this code and wanted to do this for bulk removal too, but i fail miserably.

To Reproduce

This works, removing one exception at a time:

            foreach ($a in $remove) { 
                 $Settings = @{ delete_exceptions = @('id', $a.ExceptionID) }
                 $Policy = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Settings $Settings
             }

This works, adding multiple exceptions at a time:

    # Maximum number of exceptions to add per request
    $Max = 50
    if ($ExceptPrinter) {
        Write-Host "$(Get-Date -Format "dd/MM/yyyy HH:mm"): Adding Printer Exceptions ..."
        for ($i = 0; $i -lt ($ExceptPrinter | Measure-Object).Count; $i += $Max) {
            # Aggregate exceptions in groups of $Max
            [Object[]]$IdGroup = $ExceptPrinter[$i..($i + ($Max - 1))]
            [Object[]]$Exception = @($IdGroup).foreach{ 
                # Check if CominedID is complete or exception will be product wide
                if (($_.CombinedID.ToCharArray() -eq '_').count -eq 2) {
                    @{ 
                        vendor_name  = $_.Vendor;
                        product_name = $_.Product;
                        combined_id  = $_.CombinedID;
                        action       = $_.Action; 
                        description  = $_.Description
                    }
                }
                else {
                    Write-Host "$($_.CombinedID): Exception will be set product wide..."
                    @{ 
                        vendor_name        = $_.Vendor;
                        product_name       = $_.Product;
                        vendor_id_decimal  = $_.VendorID;
                        product_id_decimal = $_.ProductID;
                        action             = $_.Action; 
                        description        = $_.Description
                    }
                }
            }
            $Settings = [PSCustomObject]@{ classes = @(@{ id = 'PRINTER'; exceptions = $Exception }) }
            $Policy = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings
        }
    }

This does not work, removing multiple exceptions at a time:

        for ($i = 0; $i -lt ($remove | Measure-Object).Count; $i += $Max) {
            # Aggregate exceptions in groups of $Max
            [Array]$IdGroup = $remove[$i..($i + ($Max - 1))].ExceptionID
            $Exceptions = @($IdGroup).foreach{ @{ delete_exceptions = @('id', $_.ExceptionID) } }
            $Settings = [PSCustomObject] @{ delete_exceptions = @('id', $IdGroup) }
            $Policy = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings
            $completedCount.Value = ($completedCount.Value + $IdGroup.Count)          
        }

I tried adding the IDs together as an object of strings which returns the error in the screenshot:

2023-11-02 18_20_50-Clipboard

The error seems strange, since the argument that cannot be validated seems to be the $Policy.ID, not $Settings, which surely is correct, since the command in the non-bulk version directly below works just fine, and the commands are identical (besides $Settings). So there also could be a bug in here, not just my bad scripting skills.

I also tried to join the object as comma separated string like that:

        for ($i = 0; $i -lt ($remove | Measure-Object).Count; $i += $Max) {
            # Calculate the percentage completed and show progress bar
            [Int]$PercentComplete = ((($completedCount).Value / ($remove).Count) * 100)
            [String]$CurrentOperation = "Exception: $(($completedCount).Value) / $($remove.Count)"
            Write-Progress -Activity "Remove Exceptions" -CurrentOperation $CurrentOperation -PercentComplete $percentComplete -Status $CurrentOperation
            # Aggregate exceptions in groups of $Max
            [Array]$IdGroup = $remove[$i..($i + ($Max - 1))]
            [String]$Exceptions = @($IdGroup).foreach{ $_.ExceptionID } -join ", "
            $Settings = [PSCustomObject] @{ delete_exceptions = @('id', $Exceptions) }
            $Policy = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings
            $completedCount.Value = ($completedCount.Value + $IdGroup.Count)          
        }

2023-11-06 14_56_16-Clipboard This does not return an error, but also it does not remove any exceptions so yeah ... am a bit lost here.

Expected behavior Bulk removal should work like bulk adding.

Environment (please complete the following information):

Additional context Nothing I would be aware of.

bk-cs commented 10 months ago

In the first example, I think you had a problem here:

[Array]$IdGroup = $remove[$i..($i + ($Max - 1))].ExceptionID
$Exceptions = @($IdGroup).foreach{ @{ delete_exceptions = @('id', $_.ExceptionID) } }

Because $IdGroup only consists of .ExceptionID, there's nothing under $_.ExceptionID. You corrected it in your second example, but it should have looked like this:

[Array]$IdGroup = $remove[$i..($i + ($Max - 1))].ExceptionID
$Exceptions = @($IdGroup).foreach{ @{ delete_exceptions = @('id', $_) } }

The error seems strange, since the argument that cannot be validated seems to be the $Policy.ID...

  1. Where is $Policy originally defined? Could you be getting to your edit step without a valid id under $Policy?
  2. Can you enable $VerbosePreference='Continue' and provide some examples of the json bodies being submitted to the API?
STonEDSTonE commented 10 months ago

Thanks for your quick and helpful answer!

Regarding $Policy, thats set at the start:

    [Array]$Policies = Get-FalconDeviceControlPolicy -Detailed
    foreach ($a in $Policies) {
        if ($a.name -match $policyname) {
            $Policy = $a
        }
    }
    if (!$Policy) {
        Write-Host "!!! Policy not found, no action taken !!!"
        $MissingParams = $true
    }

But I reassigned it during edits to reflect the changes:

$Policy = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings

I think that caused the 2nd Error because the first Code 500 error may mangle up the return to $Policy, and is therefore not accessible for the other commands afterwards. After changing it to

$Policytmp = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings

the error is not present anymore.

Furthermore you are absolutely right, I made a mistake with $_.ExceptionID. After changing it to

[Array]$IdGroup = $remove[$i..($i + ($Max - 1))].ExceptionID
            $Settings = @($IdGroup).foreach{ @{ delete_exceptions = @('id', $_) } }
            $Policytmp = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings

I can see the Keys and Values are set in the $Settings Variable, but sadly this does not seem to be the only issue.

Now

$Policytmp = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings

still produces the Code 500 Error (now with $VerbosePreference='Continue')

VERBOSE: 11:54:48 [Edit-FalconDeviceControlPolicy] /policy/entities/device-control/v1:patch
VERBOSE: 11:54:48 [ApiClient.Invoke] PATCH https://api.eu-1.crowdstrike.com/policy/entities/device-control/v1
VERBOSE: 11:54:48 [ApiClient.Invoke] ContentType=application/json, Accept=application/json
VERBOSE: 11:54:48 [ApiClient.Invoke] {"resources":[{"settings":[{"delete_exceptions":["id","c6efb87cadbd44a29b1c934031b2e5bb"]},{"delete_exceptions":["id","1c85297c3bb24ff2a0332356ce4dc23a"]},{"delete_exceptions":["id","1902f3aecc1946899c47bbc98c641408"]},{"delete_exceptions":["id","1acb0436ea6f4990aeaf5c96ed89dc0a"]},{"delete_exceptions":["id","45fee029a0844c25ac222707e134ef32"]},{"delete_exceptions":["id","c61b056f905e4b17b5f7eecc0ff7db76"]},{"delete_exceptions":["id","ae4a7443cd7b4893a3c2e1ddfb0117b6"]},{"delete_exceptions":["id","f8f9f56e89194e52b1f5565926f81023"]},{"delete_exceptions":["id","319603c5a3954d68bf652a866933e70a"]},{"delete_exceptions":["id","2a52db8594634555b58d170958d5256c"]},{"delete_exceptions":["id","f04715ee22ce4ad0bf0cda1da88ef8b6"]},{"delete_exceptions":["id","d045150a4c364142802ddb7c7aaaee90"]},{"delete_exceptions":["id","46044bd4d1404fe39d0dcb957809129f"]},{"delete_exceptions":["id","e2872832ee8c4811baeaff07cefbc7a1"]},{"delete_exceptions":["id","381aef577dad4f98a0cccfa5af2c85ad"]},{"delete_exceptions":["id","490bd2eb5bba4c96a3560d5488cc855b"]},{"delete_exceptions":["id","1f1d89ad1f974d57bb24b9b29f5cc1a3"]},{"delete_exceptions":["id","024c8f85aad64983a1104c371ed05c4d"]},{"delete_exceptions":["id","301a89e368c1405cad9b0118f49f4faa"]},{"delete_exceptions":["id","bd9bad30aeb249699a190e82d97384c2"]},{"delete_exceptions":["id","88579ef79a6243069ef98471a1b6f6cb"]},{"delete_exceptions":["id","26924f25e9b8470aacddaab4283ba7ac"]},{"delete_exceptions":["id","2f54dbc66a3e4603989bfddd54ae7210"]},{"delete_exceptions":["id","f452d0af0d7d4aa282639bb17ce2478a"]},{"delete_exceptions":["id","f04e723b002341ed924f5cdafd7ec132"]},{"delete_exceptions":["id","0f634c246f6c499fac5ba5df3e4a05a2"]},{"delete_exceptions":["id","ceeedb74254646d5ab3b7d57f8a1abc3"]},{"delete_exceptions":["id","c9707b43c166458ca76399e11c20680b"]},{"delete_exceptions":["id","4393e3041372480d814fb9980f7d870c"]},{"delete_exceptions":["id","a8928167b78841a08f4cecd73542631f"]},{"delete_exceptions":["id","14c62dc487b04acbb29375c575a39e48"]},{"delete_exceptions":["id","e25592c7218740f3a79920c9438dd380"]},{"delete_exceptions":["id","72fe3f973bc84dd8a76ad7247cbe13e2"]},{"delete_exceptions":["id","ffe173abedaf47429921f129f47ba556"]},{"delete_exceptions":["id","8f502321a1a84d0f9c2fead61d11dd16"]},{"delete_exceptions":["id","374bb8f010b7482e9240f8620908aef8"]},{"delete_exceptions":["id","5bb8deff64b64bdebe84dd74c651417b"]},{"delete_exceptions":["id","ea7c60c785be462d92d776eeb2aa8a26"]},{"delete_exceptions":["id","cd13a308cdb04263bcf522bf0e404bbf"]},{"delete_exceptions":["id","47a2898a59114cf8917d8f4c4b7c6ed2"]},{"delete_exceptions":["id","599aba7468a94cb28f24340fbbd73859"]},{"delete_exceptions":["id","3722e3b20bb14e53b135cda0b3b6eeff"]},{"delete_exceptions":["id","b05631baf71a44b1a7395f5c7d9a8209"]},{"delete_exceptions":["id","556e5a3d03fe40939e98b8497b689903"]},{"delete_exceptions":["id","fc83dede1dac4e9db046bfa9b9d125f8"]},{"delete_exceptions":["id","686c22b4ea264cd5ac27a01ec43c7712"]},{"delete_exceptions":["id","4546c0cc2da4428590fe45d9b62229f9"]},{"delete_exceptions":["id","7bb7131baa8f49f4ad83c4251bdba31c"]},{"delete_exceptions":["id","a5f056a5534444e3ab5408d4f48c239c"]},{"delete_exceptions":["id","0d7f6db7173c410cb4ad1dc3ba32ed92"]}],"id":"0aba781042fa459a91aa3216fcfa614e"}]}
VERBOSE: 11:54:48 [ApiClient.Invoke] 500: InternalServerError
VERBOSE: 11:54:48 [ApiClient.Invoke] Server=nginx, Date=Tue, 07 Nov 2023 10:54:50 GMT, Connection=keep-alive, X-Content-Type-Options=nosniff, X-Cs-Traceid=b7c5ceaa-decf-45d2-bfb9-42ff3bc40fb6, X-Ratelimit-Limit=6000, X-Ratelimit-Remaining=5998, Strict-Transport-Security=max-age=31536000; includeSubDomains
VERBOSE: 11:54:48 [Write-Result] query_time=2,08E-07, powered_by=crowdstrike-api-gateway, trace_id=b7c5ceaa-decf-45d2-bfb9-42ff3bc40fb6
Write-Result: C:\Users\t.steiner\Documents\PowerShell\Modules\PSFalcon\2.2.5\private\Private.ps1:627:17
Line |
 627 |                  Write-Result $Object
     |                  ~~~~~~~~~~~~~~~~~~~~
     | [{"code":500,"message":"Internal Server Error: Please provide trace-id='b7c5ceaa-decf-45d2-bfb9-42ff3bc40fb6' to support"}]

2023-11-07 11_37_48-Clipboard

So now at least we see that it does send the IDs, but I suspect the format does not quite work out? Maybe it should be more like

delete_exceptions = @('id', ID1, ID2, ID3 ....)

or how does the server wants it to be formatted?

bk-cs commented 10 months ago

Try this?

for ($i=0; $i -lt ($Remove | Measure-Object).Count; $i+=$Max) {
  # Aggregate exceptions in groups of $Max
  $Delete = [System.Collections.Generic.List[string]]@()
  [string[]]$IdGroup = $Remove[$i..($i+($Max-1))].ExceptionID
  @($IdGroup).foreach{ $Delete.Add($_) }
  $Setting = [PSCustomObject]@{ delete_exceptions = @($Delete) }
  $Policy = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Setting
  $completedCount.Value = ($completedCount.Value + $IdGroup.Count)
 }
STonEDSTonE commented 10 months ago

I do not see the difference in $Settings, looks the same to me (same structure), but it works: 2023-11-07 17_43_12-Clipboard Can you tell me the difference for learning purposes?

Bonus question: To whitelist a Type of Device (e.g. iPhones) completely, how should I do that with PSFalcon? If I try it with VendorID and ProductID (as I would within the console), it thows errors while it works with CombinedIDs:

            # Aggregate exceptions in groups of $Max
            [Object[]]$IdGroup = $ExceptAny[$i..($i + ($Max - 1))]
            [Object[]]$Exception = @($IdGroup).foreach{ 
                # Check if serialnumber is set or exception should be product wide
                if ($null -ne $_.Serialnumber) {
                    @{ 
                        vendor_name  = $_.Vendor;
                        product_name = $_.Product;
                        combined_id  = $_.CombinedID;
                        action       = $_.Action; 
                        description  = $_.Description
                    }
                }
                else {
                    Write-Host "$($_.CombinedID): Exception will be set product wide..."
                    @{ 
                        vendor_name        = $_.Vendor;
                        product_name       = $_.Product;
                        vendor_id_decimal  = $_.VendorID;
                        product_id_decimal = $_.ProductID;
                        action             = $_.Action; 
                        description        = $_.Description
                    }
                }
            }
            $Settings = [PSCustomObject]@{ classes = @(@{ id = 'ANY'; exceptions = $Exception }) }
            $Policy = Edit-FalconDeviceControlPolicy -Id $Policy.ID -Setting $Settings
1536_159_: Exception will be set product wide...
PS C:\Users\t.steiner\Documents\WindowsPowerShell\Scripts>
VERBOSE: 18:39:19 [Edit-FalconDeviceControlPolicy] /policy/entities/device-control/v1:patch
VERBOSE: 18:39:19 [ApiClient.Invoke] PATCH https://api.eu-1.crowdstrike.com/policy/entities/device-control/v1
VERBOSE: 18:39:19 [ApiClient.Invoke] Accept=application/json, ContentType=application/json
VERBOSE: 18:39:19 [ApiClient.Invoke] {"resources":[{"settings":{"classes":[{"exceptions":[{"product_name":"ClickShare","action":"FULL_ACCESS","vendor_name":"Barco","vendor_id_decimal":1536.0,"description":"Clickshare Wildcarded","product_id_decimal":159.0}],"id":"ANY"}]},"id":"759a1eeedcfe47d78dfd644630fc1610"}]}
VERBOSE: 18:39:20 [ApiClient.Invoke] 500: InternalServerError
VERBOSE: 18:39:20 [ApiClient.Invoke] Server=nginx, Date=Tue, 07 Nov 2023 17:39:20 GMT, Connection=keep-alive, X-Content-Type-Options=nosniff, X-Cs-Traceid=ca1cf43f-2b07-4902-b7f2-d3fa84b49767, X-Ratelimit-Limit=6000, X-Ratelimit-Remaining=5999, Strict-Transport-Security=max-age=31536000; includeSubDomains
VERBOSE: 18:39:20 [Write-Result] query_time=2,55E-07, powered_by=crowdstrike-api-gateway, trace_id=ca1cf43f-2b07-4902-b7f2-d3fa84b49767
Write-Result: C:\Users\t.steiner\Documents\PowerShell\Modules\PSFalcon\2.2.5\private\Private.ps1:627:17
Line |
 627 |                  Write-Result $Object
     |                  ~~~~~~~~~~~~~~~~~~~~
     | [{"code":500,"message":"Internal Server Error: Please provide trace-id='ca1cf43f-2b07-4902-b7f2-d3fa84b49767' to support"}]

2023-11-07 18_38_57-Clipboard

In any case, thank you for your Help!

bk-cs commented 10 months ago

In your example, there were multiple delete_exceptions sub-objects, each with their own id value, plus the a specific id string (which is likely an error):

{
  "resources": [
    {
      "settings": [
        {
          "delete_exceptions": [
            "id","c6efb87cadbd44a29b1c934031b2e5bb"
          ]
        },
        {
          "delete_exceptions": [
            "id","1c85297c3bb24ff2a0332356ce4dc23a"
          ]
        },
       <continued>
      ]
    }
  ]
}

In mine, it was one delete_exceptions sub-object with multiple id values. Example:

{
  "resources": [
    {
      "settings": [
        {
          "delete_exceptions": [
             "c6efb87cadbd44a29b1c934031b2e5bb","1c85297c3bb24ff2a0332356ce4dc23a", <continued>
          ]
        }
      ]
    }
  ]
}

To whitelist a Type of Device (e.g. iPhones) completely, how should I do that with PSFalcon? If I try it with VendorID and ProductID (as I would within the console), it thows errors while it works with CombinedIDs...

This isn't a PSFalcon question, it's more about what the API expects. As far as my understanding, when adding an exception, you can either provide...

combined_id is the better choice because it's more accurate, but if you don't know all the values, you can use the wildcard method. I haven't actually added any exceptions recently, and the use_wildcard thing is new. I think the API developers snuck in some changes and it might work differently than it did when my Edit-FalconDeviceControlPolicy examples were made.

STonEDSTonE commented 10 months ago

Thanks for the clarification. I tried different versions now:

                        combined_id  = "$($_.VendorID)$($_.ProductID)_*";
                        use_wildcard       = $true

returns

[{"code":400,"message":"combined id should have 3 parts"}]

(Suspected, but worth a try)

                        vendor_id_decimal  = $_.VendorID;
                        product_id_decimal = $_.ProductID;
                        serial_number = "*";
                        use_wildcard       = $true

returns

VERBOSE: 16:06:42 [Edit-FalconDeviceControlPolicy] /policy/entities/device-control/v1:patch
VERBOSE: 16:06:42 [ApiClient.Invoke] PATCH https://api.eu-1.crowdstrike.com/policy/entities/device-control/v1
VERBOSE: 16:06:42 [ApiClient.Invoke] ContentType=application/json, Accept=application/json
VERBOSE: 16:06:42 [ApiClient.Invoke] {"resources":[{"settings":{"classes":[{"exceptions":[{"action":"FULL_ACCESS","serial_number":"*","product_id_decimal":159.0,"description":"Clickshare Wildcarded","vendor_id_decimal":1536.0,"product_name":"ClickShare","vendor_name":"Barco","use_wildcard":true}],"id":"ANY"}]},"id":"759a1eeedcfe47d78dfd644630fc1610"}]}
VERBOSE: 16:06:42 [ApiClient.Invoke] 500: InternalServerError
VERBOSE: 16:06:42 [ApiClient.Invoke] Server=nginx, Date=Wed, 08 Nov 2023 15:06:43 GMT, Connection=keep-alive, X-Content-Type-Options=nosniff, X-Cs-Traceid=49f716b2-029a-4372-8b51-2a20b14c07e5, X-Ratelimit-Limit=6000, X-Ratelimit-Remaining=5994, Strict-Transport-Security=max-age=31536000; includeSubDomains
VERBOSE: 16:06:42 [Write-Result] query_time=1,89E-07, powered_by=crowdstrike-api-gateway, trace_id=49f716b2-029a-4372-8b51-2a20b14c07e5
[{"code":500,"message":"Internal Server Error: Please provide trace-id='49f716b2-029a-4372-8b51-2a20b14c07e5' to support"}]

even though this should work in my opinion. You have another tip for me here?

bk-cs commented 10 months ago

I was incorrect about use_wildcard--it doesn't seem to be required. This worked for me:

PS> $Setting = [PSCustomObject]@{
  classes = @(
    [PSCustomObject]@{
      id = 'ANY'
      exceptions = @(
        [PSCustomObject]@{
          class = 'ANY'
          vendor_id_decimal = '1452'
          vendor_name = 'Apple, Inc.'
          product_id_decimal = '4776'
          product_name = 'iPhone'
          serial_number = '*'
          action = 'FULL_ACCESS'
          match_method = 'VID_PID_SERIALWC'
        }
      )
    }
  )
}
PS> Edit-FalconDeviceControlPolicy -Id <id> -Setting $Setting

I figured out the vendor_id_decimal, product_id_decimal and match_method properties and values from mounting a device, checking the Device Control activity for the vendor/product, then adding the exception manually through the UI. Once added, I pulled the policy and copied how the exception looked (minus a few fields that weren't required).

STonEDSTonE commented 10 months ago

Thanks you very much for your help it works now. I also know what my issue was: vendor_id_decimal and product_id_decimal were treated as numbers, so going to CrowdStrike they were send as 1452.0. and 4776.0 respectively which CrowdStrike did not like but also did not bother to tell me either :P Pointing that out for people after me maybe having the same problem :)

PS: not quite sure why there was an

class = 'ANY'

in the inner custom object, I didn't use it and it seems to work fine :)

bk-cs commented 10 months ago

There are different classes that you use when setting exceptions. Mobile phones can show up as several device types, so using class = 'ANY' helps ensure that the exception won't be ignored if the classes don't match. The API might default to ANY if it's not specified.