Velociraptor is a highly privileged service with elevated access to thousands of endpoints across the enterprise. It is therefore crucial to secure the deployment as much as possible.
While Velociraptor is designed with security in mind, there are a number of architectural choices you can make related to security.
In this article we begin with a discussion of the communication protocol used by Velociraptor and suggest a number of alternative deployment methods to ensure it can be secured on the network. We then discuss the Velociraptor permission model and suggest some further steps to ensure user actions are audited and controlled.
How do Velociraptor clients communicate with the server? You can read a lot more details about Velociraptor’s encryption scheme and communication protocol in our Velociraptor Communications Blog post, but we will go through the most important aspects here.
Every Velociraptor deployment creates an internal PKI which underpins it. The configuration wizard create an internal CA with an X509 certificate and a private key. This CA is used to
Create initial server certificates and any additional certificates for key rotation.
CA public certificate is embedded in the client’s configuration and is used to verify server communications.
The internal CA is used to create API keys for programmatic access. The server is then able to verify API clients.
The configuration file contains the CA’s X509 certificate in the Client.ca_certificate parameter (it is therefore embedded in the client configuration). The private key is contained in the CA.private_key parameter.
In a secure installation you should remove the CA.private_key section from the server config and keep it offline. You only need it to create new API keys using the velociraptor config api_client command, and the server does not need it in normal operations.
The internal CA will be used to verify the different Velociraptor components in all cases, regardless if other TLS certificates are used. While it is possible to roll server certificates the CA certificate can not be rolled without re-deploying all the clients.
Clients and servers communicate by sending each other messages (which
are simply protocol buffers), for example, a message may contain VQL
queries or result sets. Messages are collected into a list and sent in
a single POST operation in a MessageList protobuf. This protobuf
is encrypted using a session key with a symmetric cipher
(aes_128_cbc
). The session key is chosen by the sending party and is
written into an encrypted Cipher protobuf and sent along with each
message.
This symmetric key is encoded in a Cipher Properties protobuf which is encrypted in turn using the receiving party’s public key and signed using the sending party’s private key.
You might have noticed that MessageList protobufs are encrypted and signed, but they are usually still delivered within a TLS session - therefore there are two layers of encryption.
The internal encryption scheme’s main purpose is not only to encrypt the messages but to sign them. This prevents messages from one client from impersonating another client.
Velociraptor uses HTTPS POST messages to deliver message sets to the server. The server in turn sends messages to the client in the body of the POST request. The client connects to one of the server URLs provided in the Client.server_urls setting in its config file.
Before the client communicates with the server, the client must verify it is actually talking with the correct server. This happens at two levels:
If the URL is a HTTPS URL then the TLS connection needs to be verified
The client will fetch the url /server.pem to receive the server’s internal certificate. This certificate must be verified by the embedded CA.
Note that this verification is essential in order to prevent the client from accidentally talking with captive portals or MITM proxies.
It is important to understand that the server’s internal certificate
is always signed by the Velociraptor internal CA and is always
named with the name VelociraptorServer
. It is completely
independent of the TLS certificates that control the HTTPS
connection (which may be external certificates).
The client will always verify the internal server certificate in order to decrypt the messages as described above. This means that even when a MITM proxy is able to decode the HTTPS connections, there is no visible plain text due to the included messages being encrypted again by the internal server certificate.
The following are some common network deployment scenarios. Simple scenarios are covered by the configuration generation wizard, but for more complex scenarios you will need to tweak the configuration file after generating it.
This is the simplest deployment scenario handled by the configuration wizard.
When deploying in self signed mode, Velociraptor will use its internal
CA to general TLS certificates as well. The TLS server certificate
will be generated with the common name VelociraptorServer
and be
signed with the Velociraptor internal CA:
The setting Client.use_self_signed_ssl
will be set to true. This
causes the client to require that the server certificate have a
common name of VelociraptorServer
and it must be verified by
the embedded CA certificate in Client.ca_certificate
.
This essentially pins the server’s certificate in the client — even if a MITM attacker was able to mint another certificate (even if it was trusted by the global roots!) it would not be valid since it was not issued by Velociraptor’s internal CA which is the only CA we trust in this mode! In this way self signed mode is more secure than when using a public CA.
This mode also has the server use the VelociraptorServer
internal
certificate for securing the HTTPS connection as well.
The GUI is also served using self signed certificates which will generally result in a browser SSL warning. To avoid a MITM attack on a browser the GUI is forced to only bind to the localhost in this configuration. For increased security we recommend using the GUI using SSH to tunnel the connections into the localhost:
ssh user@velociraptor.server -L 8889:127.0.0.1:8889
Now use your browser on https://127.0.0.1:8889/ on your port forwarded workstation.
Alternatively you can decide to expose the GUI on the public interface by changing the server configuration file:
GUI:
bind_address: 0.0.0.0
bind_port: 8889
In this configuration you may use standard port filtering or firewalls to restrict access to the GUI port while allowing clients to connect freely to the frontend port (since these can be different ports).
The next deployment scenario handled by the configuration wizard uses Let’s encrypt to automatically assign certificates to the HTTPS TLS connections.
In this scenario, the server will request Let’s Encrypt to mint certificates for the domain name provided. In order to this to work, Let’s Encrypt will go through a verification protocol requiring one of their servers to connect to the Velociraptor server over port 80 and 443.
Therefore, the server needs to be reachable over ports 80 and 443 (You can not serve over a non standard SSL port with Let’s Encrypt).
If you filter port 80 from the internet then Let’s Encrypt will be unable to verify the domain and will likely blacklist the domain name for a period.
It is crucial that port 80 and 443 be unfiltered to the world. It is difficult to recover from a blacklisting event other than waiting for a long period of time.
In this configuration:
The Client.use_self_signed_ssl
is switched off. This tells the
client that it should verify the TLS connection using public root
CA’s.
The GUI is configured to bind to all interfaces as above. This is required to allow connections from the Let’s Encrypt servers.
In this scenario the GUI must share the same port as the frontend because the TLS certificate can only be issued to port 443. This means that the GUI is accessible from the world as it is sharing the same port as the frontend service and we can not use traditional port filtering to restrict access.
If your administrators normally access the GUI from a predictable network IP block you can add a list of network addresses in the GUI.allowed_cidr part of the config file. This setting will automatically reject connections to the GUI applications from IP addresses outside the allowed range.
This is a common scenario where there is an SSL inspection proxy between the client and server communications. In this case the proxy will present a certificate for the server signed by another CA (usually an internal self signed CA associated with the inspection software).
Alternatively you may choose to buy TLS certificates from a commercial CA (this use case is very similar).
In this scenario, the client needs to verify the TLS connections using this custom CA certificate:
From the client’s point of view it is not in self signed mode,
because Velociraptor itself did not issue this certificate,
therefore Client.use_self_signed_ssl
should be false.
Additionally the certificate should be verified using the
inspection CA’s certificate. Therefore that certificate should be
added to Client.Crypto.root_certs
:
Client:
Crypto:
root_certs: |
-----BEGIN CERTIFICATE-----
<certificate 1>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<certificate 2>
-----END CERTIFICATE-----
The server may need to present custom certificates. Add those to the Frontend section (Certificates need to be in PEM format with unencrypted private keys to allow the server to start without user interaction):
Frontend:
tls_certificate_filename: /etc/cert.pem
tls_private_key_filename /etc/cert.key
Do not change the Frontend.certificate
field as Velociraptor will
still require to verify the server using it’s own CA.
Note that only the TLS communications will be visible to the TLS interception proxy. It will be unable to see any clear text since there are always two layers of encryption.
An additional layer of security can be provided by use of Mutual TLS
(mTLS) authentication between clients and server. This method requires
the client to present a valid client side certifacte
before the
server even allows a connection to the frontend. The client
certificate is included in the configuration file, therefore
connections from the internet to the frontend require to have a valid
configuration file first.
To implement this strategy you need to:
The client side certificate is just a certificate signed by the internal CA - since all clients present the same certificate we do not use it for identify, just to verify its signature!
Therefore this is the same as an API key certificate and we can use the same process to generate one.
velociraptor --config /etc/velociraptor/server.config.yaml config api_client --name "Client" /tmp/api.config.yaml
Creating API client file on /tmp/api.config.yaml.
No role added to user Client. You will need to do this later using the 'acl grant' command.
This will create an API key and an API user called Client
. It
is critical that the user has no roles or permissions on the server to
prevent this key from being used to connect to the API ports.
Once the key is generated you can see it in the yaml file encoded in
PEM format. Simply copy the two blocks (client_private_key
and
client_cert
) into the client’s config file Client.Crypto.client_certificate and Client.Crypto.client_certificate_private_key.
Note that this client certificate will only be used if the server requests it so it is ok to add the client certificates even if you do not intend to require it until later.
On the server you can require frontend connections to present valid client side certificates by setting the Frontend.require_client_certificates to true.
Once this setting is made it will not be possible to connect to the frontend without presenting a relevant client side certificate. This makes troubleshooting a bit more challenging.
For example the following will fail:
$ curl -k https://127.0.0.1:8000/server.pem
curl: (56) OpenSSL SSL_read: error:0A000412:SSL routines::sslv3 alert bad certificate, errno 0
To allow curl to connect you will need to create a PEM file with both the certificate and the private key in it (Copied from the YAML files above):
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
....
-----END RSA PRIVATE KEY-----
Now we can use curl to connect successfully
curl -k https://127.0.0.1:8000/server.pem --cert /tmp/client.pem | openssl x509 -text
In the following section I will discuss how the GUI application can be further secured. Since Velociraptor commands such privileged access to the network it is important to ensure this access can not be misused.
Velociraptor’s GUI is the nerve center for managing a Velociraptor deployment so we need to ensure we secure it with proper user authentication. Within Velociraptor, the user is central and is associated with a set if roles and permissions which control what the user is able to do.
Users and permissions are managed in the GUI’s Users
screen (which
is visible to administrators)
Velociraptor offers a number of options to authenticate users into the GUI. The type of authenticator is specified in the GUI.authenticator.type setting. The config wizard supports some basic authenticators but others have to be manually configured.
To make deployment easier the configuration wizard supports creating
Initial Users
. These user accounts will be created automatically
when the server starts upon first installation and be given the administrator role.
This provides a way to bootstrap the administrator into the server. If
the Basic
authentication method is specified, the password hashes
and salt will be initialized from the configuration file. For other
authentication methods that do not use passwords, the password hashes
are ignored.
GUI:
initial_users:
- name: mic
password_hash: aa3a779......
password_salt: f8707a7......
To add a new user, the GUI can be used by clicking on the +
icon and
setting a user name.
By default the user is added to the selected org with the Read Only
role. You can assign the new user to any number of orgs using the GUI
Velociraptor users are assigned various permissions to control their
actions in different Orgs
. To make it easier to deal with a group of
permissions, Velociraptor has the concept of a role
which can be
thought of as just a predefined set of permissions.
The actual permission check is made against the set of permissions the user has. Assigning a role to a user gives them a set of permissions, but you can also assign permissions separately.
In the above screenshot we see the testuser
has the read only role
which allows them to read already collected results from
clients. Additionally we also gave them the Label Clients
permission
so they can assign labels to different clients (potentially affecting
their membership in hunts).
User roles and permissions extend far beyond the GUI itself though. Since Velociraptor is really a VQL engine and provides powerful capabilities for automation and post processing via notebooks, the VQL engine itself respects the user’s permissions.
Therefore while the VQL query is executed the user’s permissions are checked against the different VQL plugins and functions, and if the user does not have permissions to run these, the plugin will be prevented from running.
In the above screenshot, the testuser
user attempted to run the
SELECT * FROM info()
plugin but that plugin requires the
MACHINE_STATE
permission (because it inspects properties of the
server like hostname etc). Therefore the plugin will be rejected and
an error log emitted.
However the user may still run other plugins like the hunt_results()
plugin to inspect results (because the user has the read permission).
You can check which permission each plugin requires in the reference
site’s VQL Reference
section.
While you can have very fine control over the user’s roles and permissions we suggest that you stick to the built in roles and they way they should be used as much as possible.
This is because sometimes there are unexpected escalation paths
between permissions that you might not be aware of. For example,
giving a user the Server Artifact Writer
role can easily lead to
privilege escalation as the user can modify an existing server
artifact to run VQL to grant them other roles, and trick an
administrator in running that artifact.
This is why we say that some roles are Administrator Equivalent
because it is easy to escalate from them to more powerful
roles. Typically we try to limit access to trusted users anyway and
not rely too much on the user roles.
Some actions in the UI (Or in VQL notebooks) are important for server
security. We call these actions Auditable Actions
because we want to
report them taking place.
Velociraptor records auditable actions in two ways:
Server.Audit.Logs
event artifact.This allows events to be recorded and also be automatically acted
upon with a server event query using the watch_monitoring()
plugin.
You can forward server event logs to a remote syslog server by setting the value in the Logging.remote_syslog_server part of the config. We recommend this be done to archive audit logs.
However, much more interestingly, Velociraptor treats server audit
events as simply another event query called
Server.Audit.Logs
. Therefore you can view it as just another server
event artifact in the GUI.
The audit event is divided into fixed fields (ServerTime
,
operation
and principal
) and a variable column details
with a
per event data.
By having the data recorded as a timed artifact we can apply filtering etc using the notebook. The below query isolates only administrator actions within the time of interest.
Additionally it is possible to forward audit events to external
systems. For example, Elastic.Events.Upload
can forward audit events
to Elastic, while Server.Alerts.Notification
can forward these to a
slack channel.
Users can be authenticated using a variety of ways, including Active
Directory, Client Certificates, Multi-Factor Authentication
etc. Velociraptor offers a number of different authenticators
to
allow flexibility with authenticating users.
The simplest authentication is Basic
authentication. In this mode,
the GUI requires the user to provide a username and password using the
HTTP Basic
Authentication mode. While these credentials are
typically encrypted using HTTPS in transit, basic authentication is
not considered secure and should be avoided because Velociraptor
itself has to manage the passwords.
Velociraptor will store the password hashes and salts in the filestore for each user and verify them on each HTTP request.
When basic authentication is configured, a user can update their own password using the GUI
Sometimes administrators need to update another user’s password. This can be done from the user management screen.
Note that password management is supposed to be very simplistic because for production servers we recommend to use an external authentication service (e.g. OIDC)
A number of public identity providers are directly supported in Velociraptor and based on OAuth2 protocol.
You can read more about setting up GitHub, Azure and Google as an OAuth2 provider.
Many users use Velociraptor for incident response purposes and collecting telemetry. While it is convenient to have Velociraptor already deployed and active in the environment, this may increase the risk for misuse when not used for response.
For this purpose Velociraptor has a lockdown mode
. This mode
prevents Velociraptor from performing any active modification to the
environment.
This is implemented by denying all users from certain powerful permissions - even if the user is an administrator!
The following permissions are denied while in lockdown mode
ARTIFACT_WRITER
SERVER_ARTIFACT_WRITER
EXECVE
SERVER_ADMIN
FILESYSTEM_WRITE
FILESYSTEM_READ
MACHINE_STATE
After initial deployment and configuration, the administrator can set
the server in lockdown by adding the following configuration directive
to the server.config.yaml
and restarting the server:
lockdown: true
Therefore it will still be possible to read existing collections, and continue collecting client monitoring data but not edit artifacts or start new hunts or collections.
During an active IR the server may be taken out of lockdown by removing the directive from the configuration file and restarting the service. Usually the configuration file is only writable by root and the Velociraptor server process is running as a low privilege account which can not write to the config file. This combination makes it difficult for a compromised Velociraptor administrator account to remove the lockdown and use Velociraptor as a lateral movement vehicle.
While Velociraptor allows user to run arbitrary VQL in notebooks it does control access to the things that the queries can do by applying a user’s ACL token to each plugin.
This means that administrators are typically allowed to run all
plugins, even those that might compromise the server (e.g. the
execve()
plugin can run arbitrary shell commands!)
For shared server environments it is better to prevent these plugins from running at all - even for administrators.
Velociraptor allows the configuration file to specify which VQL plugins are allowed using the defaults.allowed_plugins, defaults.allowed_functions and defaults.allowed_accessors
The easiest way to populate these is to answer Yes to Do you want to restrict VQL functionality on the server?
in the configuration
wizard. This will implement the default allow
list
(which you can tweak later as required).
If these lists are populated, only the plugins mentioned are allowed
to be registered at all. This results in an error message like Plugin Info Not Found
when the plugin is used.
The purpose of this security measure is to completely remove functionality from the server, regardless of the permissions model. It is only needed when the server is shared between potentially untrusted users. Usually you should not implement this because it causes a lot of functionality to randomly break (e.g. any artifacts that might depend on a plugin which is not in the allow list will fail).
Velociraptor uses VQL extensively in the form of artifacts and notebooks. Since a VQL query can do many potentially dangerous actions, it is important to restrict the type of actions the query can perform based on the user’s ACL token. When the VQL query is started, the user’s ACL token is loaded into the query environment. As the query continues executing, various VQL plugins and functions are evaluated by the VQL engine. VQL plugins and functions may have requirements as to the type of permission required to run. You can see the required permission for each plugin in the VQL reference page.
This mechanism allows lower privilege users to run VQL safely - those actions that require permissions the user does not have will simply be ignored (and a log message emitted).
ACL checks are always enforced on the server, for example in notebooks
and server artifacts. A user with the Analyst
role, has no
FILESYSTEM_READ
permission, and therefore if they tried to run a
glob()
based query in a notebook they will be denied.
While ACL checks are enforced on the server, on the client all ACL checks are disabled. This means that as long as a user is able to schedule the collection on the client, the collection can do anything at all. This makes sense as we want the VQL to be evaluated the same way regardless of who launched the collection in the first place.
However this may give lower privilege users a lot of power over the entire network. For example the artifact Windows.System.PowerShell allows running arbitrary shell commands on the endpoint. While this is a useful capability in limited situations it may lead to severe compromise if misused!
Velociraptor allows for an artifact to specify the
required_permissions
field.
name: Windows.System.PowerShell
description: |
This artifact allows running arbitrary commands through the system
powershell.
....
required_permissions:
- EXECVE
This field specifies that the server check the user has all of the
required permissions before the server allows the artifact to be
scheduled. If a user with the Investigator
role tries to launch this
artifact, they will be denied (since the usually lack the EXECVE
permission)
Typically we set the required_permissions
field on client artifacts
that can do dangerous things if misused. In particular, if the
artifact parameter can specify running arbitrary code.
The required_permissions
check is only done on the artifact being
launched. It does not apply to any dependent artifacts called from the
launched artifact. This is deliberate as it allows you to create
curated safe versions of the dangerous artifacts to be used by lower
privilege users. For example, while Windows.System.PowerShell
requires an EXECVE
permission because its parameter allows arbitrary
commands to run, we can wrap it with a safe version:
name: Custom.SafePowershellDir
sources:
- query: |
SELECT * FROM Artifact.Windows.System.PowerShell(Command="dir C:/")
This artifact can not be misused because the command passed to
Windows.System.PowerShell
is a fixed string and can not be changed
by the user that initiates the collection.