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.
An optional MessageBox may also be configured to alert all logged in users.
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)
})