Windows.Remediation.Quarantine

Apply quarantine via Windows local IPSec policy

  • By default the current client configuration is applied as an exclusion using resolved IP address at time of application.

  • A configurable lookup table is also used to generate additional entries using the same syntax as netsh ipsec configuration.

    • DNS and DHCP are entires here allowed by default.
  • An optional MessageBox may also be configured to alert all logged in users.

    • The message will be truncated to 256 characters.
  • After policy application, connection back to the Velociraptor frontend is tested and the policy removed if connection unavailable.

  • To remove policy, select the RemovePolicy checkbox.

  • To update policy, simply rerun the artifact.

NOTE:

  • Remember DNS resolution may change. It is highly recommended to plan policy accordingly and not rely on DNS lookups.

  • Local IPSec policy can not be applied when Domain IPSec policy is already enforced. Please configure at GPO level in this case.

  • This artifact deliberately does not support connecting back on plain http! We only support the https or wss protocols because this is the recommended connectivity mechanism between server and client.


name: Windows.Remediation.Quarantine
description: |
      **Apply quarantine via Windows local IPSec policy**

      - By default the current client configuration is applied as an
        exclusion using resolved IP address at time of application.

      - A configurable lookup table is also used to generate
        additional entries using the same syntax as netsh ipsec
        configuration.

        - DNS and DHCP are entires here allowed by default.

      - An optional MessageBox may also be configured to alert all
        logged in users.

        - The message will be truncated to 256 characters.

      - After policy application, connection back to the Velociraptor
        frontend is tested and the policy removed if connection
        unavailable.

      - To remove policy, select the RemovePolicy checkbox.

      - To update policy, simply rerun the artifact.

      NOTE:

      - Remember DNS resolution may change. It is highly recommended
        to plan policy accordingly and not rely on DNS lookups.

      - Local IPSec policy can not be applied when Domain IPSec policy
        is already enforced. Please configure at GPO level in this case.

      - This artifact deliberately does not support connecting back on
        plain http! We only support the https or wss protocols because
        this is the recommended connectivity mechanism between server
        and client.

author: Matt Green - @mgreen27

reference:
  - https://mgreen27.github.io/posts/2020/07/23/IPSEC.html

required_permissions:
  - EXECVE

precondition: SELECT OS From info() where OS = 'windows'

parameters:
  - name: PolicyName
    default: "VelociraptorQuarantine"
  - name: RuleLookupTable
    type: csv
    default: |
        Action,SrcAddr,SrcMask,SrcPort,DstAddr,DstMask,DstPort,Protocol,Mirrored,Description
        Permit,me,,0,any,,53,udp,yes,DNS
        Permit,me,,0,any,,53,tcp,yes,DNS TCP
        Permit,me,,68,any,,67,udp,yes,DHCP
        Block,any,,,any,,,,yes,All other traffic

  - name: MessageBox
    description: |
        Optional message box notification to send to logged in users. 256
        character limit.

  - name: RemovePolicy
    type: bool
    description: Tickbox to remove policy.

  - name: VelociraptorURL
    description: |
      A URL for allowing connections back to the
      Velociraptor server. If not specified we use the first URL in the
      client's configuration file.

sources:
    - query: |
        LET AllURLs <= filter(list=config.server_urls + VelociraptorURL, regex='.+')

        // If a MessageBox configured truncate to 256 character limit
        LET MessageBox <= parse_string_with_regex(
                  regex='^(?P<Message>.{0,255}).*',
                  string=MessageBox).Message

        // Normalise Action
        LET normalise_action(Action)=if(condition= lowcase(string=Action)= 'permit',
              then= 'Permit',
              else= if(condition= lowcase(string=Action)= 'block',
                  then= 'Block'))

        // extract configurable policy from lookuptable
        LET configurable_policy <= SELECT
                  normalise_action(Action=Action) AS Action,
                  SrcAddr,SrcMask,SrcPort,
                  DstAddr,DstMask,DstPort,
                  Protocol,Mirrored,Description
              FROM RuleLookupTable

        // Parse a URL to get domain name.
        LET get_domain(URL) = split(string=url(parse=URL).Host, sep=":")[0]

        // Parse a URL to get the port or use 443. We deliberately do
        // not support plain http!
        LET get_port(URL) = if(condition=url(parse=URL).Host =~ ":",
            then=split(string=url(parse=URL).Host, sep=":")[1],
            else="443")

        // extract Velociraptor config for policy
        LET extracted_config <= SELECT * FROM foreach(
                  row= AllURLs,
                  query={
                      SELECT
                          'Permit' AS Action,
                          'me' AS SrcAddr,
                          '' As SrcMask,
                          '0' AS SrcPort,
                          get_domain(URL=_value) AS DstAddr,
                          '' As DstMask,
                          get_port(URL=_value) AS DstPort,
                          'tcp' AS Protocol,
                          'yes' AS Mirrored,
                          'VelociraptorFrontEnd' AS Description,
                          _value AS URL
                      FROM scope()
                  })

        // build policy with extracted config and lookuptable
        LET policy <= SELECT *
              FROM chain(
                  a=extracted_config,
                  b=configurable_policy
              )
              WHERE Action =~ '^(Permit|Block)$'

        // Removes empty options from the command line
        LET clean_cmdline(CMD) = filter(list=CMD, regex='^(\\w+|\\w+=.+)$')

        LET delete_cmdline = clean_cmdline(
             CMD=('netsh','ipsec','static','delete','policy', 'name=' + PolicyName))

        LET create_cmdline = clean_cmdline(
             CMD=('netsh','ipsec','static','add','policy', 'name=' + PolicyName))

        LET action_cmdline(Action) = clean_cmdline(
             CMD=('netsh','ipsec','static','add','filteraction',
                  'name=' + PolicyName + ' ' + Action + 'Action',
                  'action=' + Action))

        LET rule_cmdline(Action) = clean_cmdline(
             CMD=('netsh','ipsec','static','add','rule',
                  'name=' + PolicyName + ' ' + Action + 'Rule',
                  'policy=' + PolicyName,
                  'filterlist=' + PolicyName + ' ' + Action + 'FilterList',
                  'filteraction=' + PolicyName + ' ' + Action + 'Action'))

        LET enable_cmdline = clean_cmdline(
             CMD=('netsh','ipsec','static','set','policy',
                   'name=' + PolicyName, 'assign=y'))

        // Emit the message if no output is emitted, otherwise emit the output.
        LET combine_results(Stdout, Stderr, ReturnCode, Message) = if(
              condition=Stdout =~ "[^\\s]", then=Stdout,
              else= if(condition=Stderr =~ "[^\\s]", then=Stderr,
              else= if(condition= ReturnCode=0,
                    then=Message )))

        // delete old or unwanted policy
        LET delete_policy = SELECT
              timestamp(epoch=now()) as Time,
              PolicyName + ' IPSec policy removed.' AS Result
          FROM execve(argv=delete_cmdline, length=10000)

        // first step is creating IPSec policy
        LET create_policy = SELECT
              timestamp(epoch=now()) as Time,
              combine_results(Stdout=Stdout, Stderr=Stderr,
                  ReturnCode=ReturnCode,
                  Message=PolicyName + ' IPSec policy created.') AS Result
          FROM execve(argv=create_cmdline, length=10000)

        LET entry_cmdline(Action, SrcAddr, SrcPort, SrcMask,
                   DstAddr, DstPort, DstMask, Protocol,
                   Mirrored, Description) = clean_cmdline(
              CMD=('netsh','ipsec','static','add','filter',
                   format(format='filterlist=%s %sFilterList', args=[PolicyName, Action]),
                   format(format='srcaddr=%v', args=SrcAddr),
                   format(format='srcmask=%v', args=SrcMask),
                   format(format='srcport=%v', args=SrcPort),
                   format(format='dstaddr=%v', args=DstAddr),
                   format(format='dstmask=%v', args=DstMask),
                   format(format='dstport=%v', args=DstPort),
                   format(format='protocol=%v', args=Protocol),
                   format(format='mirrored=%v', args=Mirrored),
                   format(format='description=%v', args=Description)))

        // second step is to create policy filters
        LET create_filters = SELECT * FROM foreach(row=policy,
                  query={
                      SELECT
                          timestamp(epoch=now()) as Time,
                          combine_results(Stdout=Stdout, Stderr=Stderr,
                               ReturnCode=ReturnCode,
                               Message='Entry added: ' +
                                 join(array=entry_cmdline(Action=Action,
                                   SrcAddr=SrcAddr, SrcPort=SrcPort, SrcMask=SrcMask,
                                   DstAddr=DstAddr, DstPort=DstPort, DstMask=DstMask,
                                   Protocol=Protocol, Mirrored=Mirrored,
                                   Description=Description), sep=" ")) AS Result
                      FROM execve(argv=entry_cmdline(Action=Action,
                                   SrcAddr=SrcAddr, SrcPort=SrcPort, SrcMask=SrcMask,
                                   DstAddr=DstAddr, DstPort=DstPort, DstMask=DstMask,
                                   Protocol=Protocol, Mirrored=Mirrored,
                                   Description=Description), length=10000)
                  })

        // third step is to create policy filter actions
        LET create_actions = SELECT * FROM foreach(
                  row= {
                      SELECT Action
                      FROM policy
                      GROUP BY Action
                  },
                  query={
                      SELECT
                          timestamp(epoch=now()) as Time,
                          combine_results(Stdout=Stdout, Stderr=Stderr,
                               ReturnCode=ReturnCode,
                               Message='FilterAction added: ' +
                                 join(array=action_cmdline(Action=Action), sep=" ")) AS Result
                      FROM execve(argv=action_cmdline(Action=Action), length=10000)
                  })

        // fourth step combines action lists and actions in a Rule
        LET create_rules = SELECT * FROM foreach(
                  row= {
                      SELECT Action
                      FROM policy
                      GROUP BY Action
                  },
                  query={
                      SELECT
                          timestamp(epoch=now()) as Time,
                          combine_results(Stdout=Stdout, Stderr=Stderr,
                               ReturnCode=ReturnCode,
                               Message='Rule added: ' +
                                 join(array=rule_cmdline(Action=Action), sep=" ")) AS Result
                      FROM execve(argv=rule_cmdline(Action=Action), length=10000)
                  })

        // fith step is to enable our IPSec policy
        LET enable_policy = SELECT
              timestamp(epoch=now()) as Time,
              combine_results(Stdout=Stdout, Stderr=Stderr,
                  ReturnCode=ReturnCode,
                  Message=PolicyName + ' IPSec policy applied.') AS Result
              FROM execve(argv=enable_cmdline, length=10000)

        // test connection to a frontend server
        LET test_connection = SELECT * FROM foreach(
                  row={
                      SELECT * FROM policy
                      WHERE Description = 'VelociraptorFrontEnd'
                  },
                  query={
                      SELECT *
                          Url,
                          response
                      FROM
                          -- Always use https even when configured for wss
                          http_client(url=url(
                              scheme='https',
                              host=DstAddr + ':' + DstPort,
                              path='/server.pem').String)

                      WHERE Response = 200
                      LIMIT 1
                  })

        // final check to keep or remove policy
        LET final_check = SELECT * FROM if(condition= test_connection,
                  then={
                      SELECT
                          timestamp(epoch=now()) as Time,
                          if(condition=MessageBox,
                              then= PolicyName + ' connection test successful. MessageBox sent.',
                              else= PolicyName + ' connection test successful.'
                              ) AS Result
                      FROM if(condition=MessageBox,
                          then= {
                              SELECT * FROM execve(argv=['msg','*',MessageBox])
                          },
                          else={
                              SELECT * FROM scope()
                          })
                  },
                  else={
                      SELECT
                          timestamp(epoch=now()) as Time,
                          PolicyName + ' failed connection test. Removing IPSec policy.' AS Result
                      FROM delete_policy
                  })

        // Execute content
        SELECT * FROM if(condition=RemovePolicy,
                  then=delete_policy,
                  else={
                      SELECT * FROM chain(
                          a=delete_policy,
                          b=create_policy,
                          c=create_filters,
                          d=create_actions,
                          e=create_rules,
                          g=enable_policy,
                          h=final_check)
                  })