scan.ps1

# Scan APIs

function New-ICQuery {
  Param(
    [parameter(Mandatory=$True)]
    [String]$TargetGroupId,

    [parameter(Mandatory=$True)]
    [String]$credentialId,

    [String]$sshCredentialId,

    [parameter(Mandatory=$True)]
    [ValidateNotNullorEmpty()]
    [String]$Query,

    [String]$QueryName
    )

  $Credential = Get-ICCredential -CredentialId $CredentialId
  $TargetGroup = Get-ICTargetGroup -TargetGroupId $TargetGroupId
    Write-Host "Creating new Query ($query) in TargetGroup $TargetGroup.name using credential $($Credential.name) [$($Credential.username)]"
  $Endpoint = "queries"
  $data = @{
    value = $query
    targetId = $TargetGroupId
        name = $QueryName
  }
  if ($credentialId) {
    $data['credentialId'] = $CredentialId
  }
  if ($sshCredentialId) {
    $data['sshCredential'] = $sshCredentialId
  }
  $body = @{
    data = $data
  }
  _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method POST
}

function Get-ICQuery ([String]$TargetGroupId) {
  $Endpoint = "queries"
  $filter =  @{
    limit = $resultlimit
    skip = 0
  }
  if ($TargetGroupId) {
    $filter['where'] = @{ targetId = $TargetGroupId }
    Write-Verbose "Getting Queries for Target Group Id: $TargetGroupId"
  }
  #Write-Verbose "Getting all Queries from TargetGroup $TargetGroup"
  _ICGetMethod -url $HuntServerAddress/api/$Endpoint -filter $filter -NoLimit:$true
}

function Remove-ICQuery {
  param(
    [parameter(Mandatory=$true, Position=0)]
    [ValidateNotNullOrEmpty()]
    [String]$QueryId
  )

  $Endpoint = "queries/$QueryId"
  Write-Warning "Removing query [$HuntServerAddress/api/$Endpoint]."
  _ICRestMethod -url $HuntServerAddress/api/$Endpoint -method 'DELETE'
}

function Invoke-ICFindHosts {
    [cmdletbinding()]
    param(
        [parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$TargetGroupId,

        [String]$QueryId
    )
    $TargetGroup = Get-ICTargetGroup -TargetGroupId $TargetGroupId
    $Queries = Get-ICQuery -TargetGroupId $TargetGroupId
    if (-NOT $Queries) {
        Throw "Target Group not found or does not have any Queries associated with it"
    }
    $Endpoint = "targets/$TargetGroupId/Enumerate"
    $body = @{
        queries = @()
    }
    if ($QueryId) {
        Write-Verbose "Starting Enumeration of $($TargetGroup.name) with Query $QueryId"
        $body['queries'] += $QueryId
        _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method POST
    } else {
        $Queries | foreach {
            Write-Verbose "Starting Enumeration of $($TargetGroup.name) with Query $($Queries.id)"
            $body['queries'] += $Queries.Id
        }
        _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method POST
    }
}

function Invoke-ICScan {
    [cmdletbinding()]
    param(
        [parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [Alias("targetId")]
        [String]$TargetGroupId,

        [PSObject]$ScanOptions,

        [Switch]$PerformDiscovery
    )
    $TargetGroup = Get-ICTargetGroup -TargetGroupId $TargetGroupId
    if (-NOT $TargetGroup) {
        Throw "TargetGroup with id $TargetGroupId does not exist!"
    }
    if ($PerformDiscovery) {
        Write-Host "Performing discovery on $($TargetGroup.name)"
        $stillactive = $true
        $UserTask = Invoke-ICFindHosts -TargetGroupId $TargetGroupId
        While ($stillactive) {
            Start-Sleep 10
            $taskstatus = Get-ICUserTask -UserTaskId $UserTask.userTaskId
            if ($taskstatus.status -eq "Active") {
                    Write-Host "Waiting on Discovery. Progress: $($taskstatus.progress)%"
            } elseif ($taskstatus.status -eq "Completed") {
                    $stillactive = $false
            } else {
                Throw "Something went wrong in enumeration. Last Status: $($taskstatus.status)"
            }
        }

    }
    $Endpoint = "targets/$TargetGroupId/scan"
    Write-Verbose "Starting Scan of TargetGroup $($TargetGroup.name)"
  $body = @{ options = $ScanOptions }
    _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method POST
}

function Invoke-ICScanTarget {
    [cmdletbinding()]
    param(
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$target,

        [String]$TargetGroupId,
        [String]$TargetGroupName = "OnDemand",

        [String]$CredentialId,
        [String]$CredentialName = "DefaultCredential",

    [String]$sshCredentialId,
        [String]$sshCredentialName,

    [PSObject]$ScanOptions
    )

  $body = @{
    target = $target
  }

    # Select Target targetgroup
    if ($TargetGroupId) {
        $TargetGroup = Get-ICTargetGroup -TargetGroupId $TargetGroupId
        if (-NOT $TargetGroup) {
                    Throw "TargetGroup with id $TargetGroupid does not exist!"
        } else {
      $body['targetGroup'] = @{id = $targetGroupId}
    }
    } else {
    Write-Verbose "Using Target Group Name [$TargetGroupName] -- will be created if it does not exist."
    $body['targetGroup'] = @{name = $TargetGroupName}
  }

    # Select Credential
    if ($CredentialId) {
        $Credential = Get-ICCredential -CredentialId $credentialId
        if (-NOT $Credential) {
            Throw "Credential with id $credentialId does not exist!"
        } else {
      $body['credential'] = @{ id = $credentialId }
    }
    } else {
    # Use Credentialname
    $Credential =  Get-ICCredential | where { $_.name -eq $CredentialName }
        if (-NOT $CredentialName) {
            Throw "Credential with name [$CredentialName] does not exist! Please create it or specify a different credential (referenced by id or name)"
      $body['credential'] = @{ name = $CredentialName }
      }
  }

  # Select Credential
  if ($sshCredentialId) {
      $body['sshcredential'] = @{ id = $credentialId }
  } elseif ($sshCredentialName) {
      $body['sshcredential'] = @{ name = $sshCredentialName }
  }

  if ($ScanOptions) {
        $body['options'] = $ScanOptions
    }
  <#
  EXAMPLE PAYLOAD
  {
    target: 'w12-01-infected.ca.galactica.int', // required
    targetGroup: { // optional
      id: null,
      name: '',
      scanAllAccessibleHosts: false
    },
    credential: { // required id or name
      id: null,
      name: 'service'
    },
    sshCredential: { // optional
      id: null,
      name: null
    },
    options: {}
  }
  #>


    # Initiating Scan
    $Endpoint = "targets/scan"
    Write-Verbose "Starting Scan of target $($target)"
    _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method POST
}

function New-ICScanOptions {
    Write-Host 'ScanOption object properties should be set ($True or $False) and then passed into Invoke-ICScan or Add-ICScanSchedule'
    $options = @{
      EnableProcess = $true
        EnableAccount = $true
        EnableMemory = $true
        EnableModule = $true
        EnableDriver = $true
        EnableArtifact = $true
        EnableAutostart = $true
        EnableApplication = $true
        EnableHook = $true
        EnableNetwork = $true
        EnableLogDelete = $true
      EnableSurveyDelete = $true
        LowerPriority = $false
  }
    return $options
}

function Add-ICScanSchedule {
    [cmdletbinding()]
    param(
        [parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$TargetGroupId,

        [parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$CronExpression,

        [PSObject]$ScanOptions
    )
    $TargetGroup = Get-ICTargetGroup -TargetGroupId $TargetGroupId
    if (-NOT $TargetGroup) {
        Throw "No such target group with id $TargetGroupId"
    }
    $Endpoint = "scheduledJobs"
    Write-Verbose "Creating new schedule for TargetGroup: $($TargetGroup.name) with Cron Express: $CronExpression"
  $body = @{
        name = 'scan-scheduled'
        relatedId = $TargetGroupId
        schedule = $CronExpression
        data = @{
            targetId = $TargetGroupId
        }
    }
    if ($ScanOptions) {
        if ($ScanOptions.EnableProcess -eq $True) {
                $body.data['options'] = $ScanOptions
        } else {
            Throw "ScanScheduleOptions format is invalid -- use New-ICScanScheduleOptions to create an options object"
        }
    }
    _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method POST
}

function Get-ICScanSchedule ($TargetGroupId) {
  $Endpoint = "ScheduledJobs"
  $filter =  @{
    order = @("relatedId")
    limit = $resultlimit
    skip = 0
  }
    if ($TargetGroupId) {
        $TargetGroups = Get-ICTargetGroup -TargetGroupId $TargetGroupId
        $ScheduledJobs = _ICGetMethod -url $HuntServerAddress/api/$Endpoint -filter $filter -NoLimit:$true | where { $_.relatedId -eq $TargetGroupId}
    } else {
        $TargetGroups = Get-ICTargetGroup
        $ScheduledJobs = _ICGetMethod -url $HuntServerAddress/api/$Endpoint -filter $filter -NoLimit:$true
    }

    $ScheduledJobs | % {
        if ($_.relatedId) {
             $tgid = $_.relatedId
             $tg = $TargetGroups | where { $_.id -eq $tgid }
             if ($tg) {
                 $_ | Add-Member -MemberType "NoteProperty" -name "targetGroup" -value $tg.name
             } else {
                 $_ | Add-Member -MemberType "NoteProperty" -name "targetGroup" -value $Null
             }
        }
    }
    $ScheduledJobs
}

function Remove-ICScanSchedule {
    [cmdletbinding(DefaultParameterSetName = 'scheduleId')]
    param(
        [parameter(
                Mandatory,
                ParameterSetName  = 'scheduleId',
                Position = 0,
                ValueFromPipeline,
                ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string]$scheduleId,

        [parameter(
                Mandatory,
                ParameterSetName  = 'TargetGroupId'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias("targetId")]
        $targetGroupId
    )

    $Schedules = Get-ICScanchedule
    if ($ScheduleId) {
        $schedule = $Schedules | where { $_.id -eq $ScheduleId}
    }
    elseif ($TargetGroupId) {
        $schedule = $Schedules | where { $_.relatedId -eq $TargetGroupId}
        $ScheduleId    = $schedule.id
    } else {
        throw "Incorrect input!"
    }
    $tgname = $schedule.targetGroup
    if (-NOT $tgname) { throw "TargetGroupId not found!"}
    $Endpoint = "scheduledJobs/$ScheduleId"
    Write-Host "Unscheduling collection for Target Group $tgname"
    _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method DELETE
}

function Import-ICSurvey {
  # example script to upload a survey file to HUNT (2.10+)
  # Script to upload manual .bz2 file to hunt server.
    [cmdletbinding(DefaultParameterSetName = 'Path')]
    param(
        [parameter(
                Mandatory,
                ParameterSetName  = 'Path',
                Position = 0,
                ValueFromPipeline,
                ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path, # <paths of the survey results (.bz2) files to upload>

        [parameter(
                Mandatory,
                ParameterSetName = 'LiteralPath',
                Position = 0,
                ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath')]
        [string[]]$LiteralPath,

        [String]
        $ScanId,

        [String]
        $TargetGroupId,

          [String]
          $TargetGroupName = "OfflineScans"
  )

  BEGIN {
      # INITIALIZE
      $survey = "HostSurvey.json.gz"
      $surveyext = "*.json.gz"

      function Upload-ICSurveys ([String]$FilePath, [String]$ScanId){
          Write-Verbose "Uploading Surveys"
            $headers = @{
            Authorization = $Global:ICToken
                scanid = $ScanId
            }
          try {
              $objects = Invoke-RestMethod $HuntServerAddress/api/survey -Headers $headers -Method POST -InFile $FilePath -ContentType "application/octet-stream"
          } catch {
              Write-Warning "Error: $_"
              throw "ERROR: $($_.Exception.Message)"
          }
          $objects
      }

    if ($ScanId) {
        # Check for existing ScanId and use it
        $scans = Get-ICScan -NoLimit
        if ($scans.id -contains $ScanId) {
            $TargetGroupName = ($Scans | where { $_.scanId -eq $ScanId}).targetList
        } else {
            Throw "No scan exists with ScanId $ScanId. Specify an existing ScanId to add this survey result to or use other parameters to generate one."
        }
    }
    elseif ($TargetGroupId) {
        # Check TargetGroupId and create new ScanId for that group
        Write-Host "Checking for existance of target group with TargetGroupId: '$TargetGroupId' and generating new ScanId"
        $TargetGroups = Get-ICTargetGroup
        if ($TargetGroups.id -contains $TargetGroupId) {
            $TargetGroupName = ($TargetGroups | where { $_.id -eq $TargetGroupId }).name
        } else {
            Throw "No Target Group exists with TargetGroupId $TargetGroupId. Specify an existing TargetGroupId to add this survey to or use other parameters to generate one."
        }
    }
    else {
        Write-Host "No ScanId or TargetGroupId specified. Checking for existance of target group: '$TargetGroupName'"
          $TargetGroups = Get-ICTargetGroup
          if ($TargetGroups.name -contains $TargetGroupName) {
              Write-Host "$TargetGroupName Exists."
            $TargetGroupId = ($targetGroups | where { $_.name -eq $TargetGroupName}).id
          } else {
            Write-Host "$TargetGroupName does not exist. Creating new Target Group '$TargetGroupName'"
            $g = Get-ICControllerGroup
            if ($g.id.count -eq 1) {
                $ControllerGroupId = $g.id
            } else {
                $ControllerGroupId = $g[0].id
            }
            $TargetGroupId = (New-ICTargetGroup -Name $TargetGroupName -ControllerGroupId $ControllerGroupId).id
          }
    }

    # Creating ScanId
    if (-NOT $ScanId) {
        $ScanName = "Offline-" + (get-date).toString("yyyyMMdd-HHmm")
        Write-Host "Creating scan named $ScanName [$TargetGroupName-$ScanName]..."
        $StartTime = _Get-ICTimeStampUTC
        $body = @{
            name = $scanName;
            targetId = $TargetGroupId;
            startedOn = $StartTime
        }
        $newscan = _ICRestMethod -url $HuntServerAddress/api/scans -body $body -Method 'POST'
        Start-Sleep 1
        $ScanId = $newscan.id
    }


    Write-Host "Importing Survey Results into $TargetGroupName-$ScanName [ScanId: $ScanId] [TargetGroupId: $TargetGroupId]"
  }

  PROCESS {
        # Resolve path(s)
        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            $resolvedPaths = Resolve-Path -Path $Path | Select-Object -ExpandProperty Path
        } elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
            $resolvedPaths = Resolve-Path -LiteralPath $LiteralPath | Select-Object -ExpandProperty Path
        }

                # Process each item in resolved paths
                foreach ($file in $resolvedPaths) {
                     Write-Host "Uploading survey [$file]..."
                     if ((Test-Path $file -type Leaf) -AND ($file -like $surveyext)) {
                         Upload-ICSurveys -FilePath $file -ScanId $ScanId
                   } else {
                       Write-Warning "$file does not exist or is not a $surveyext file"
                   }
               }
    }

  END {
      # TODO: detect when scan is no longer processing submissions, then mark as completed
      #Write-Host "Closing scan..."
      #Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$HuntServerAddress/api/scans/$scanId/complete" -Method Post
  }

}