Cmd triggers are powerful and flexible, but only when precisely designed and thoroughly tested. This page addresses some common challenges to successful trigger configuration, and suggests best practices. 

This page contains three examples of how to fix triggers that don't work as intended. The examples are intended to illustrate the thinking needed to develop effective triggers.

This will make more sense if you are familiar with basic triggers. You may also want to refer to the CQL glossary to understand all the CQL properties used in these examples. Before you attempt to build complex triggers, we also recommend learning about Cmd CQL's character matching syntax.


Overview

  • Summary of best practices

  • Examples:
        - 1: Package installation
        - 2: Unsafe Mysql execution
        - 3: File download without sudo

Summary of best practices

  • Think about restricting behaviors, rather than just particular commands.

  • Include wildcards where necessary (e.g. around parameters).

  • Account for each CQL query's syntax.

  • Have multiple members of your team review new triggers.

  • Test new triggers. 

  • Double-check your use of parentheses. 

  • Refer to our CQL documentation to make sure you understand the nuances of each CQL value in your trigger. 

  • Think carefully about how to account for potential workarounds.

 

Example 1

Trigger idea: ‘Block installation of packages on an Ubuntu host’
Trigger type: Command
Trigger CQL:   cmd = 'apt-get install *'  
Question: Why is this only working once in a while?
Answer: Several reasons.

First, the CQL value ‘cmd’ refers to the entire command, as entered.  So this trigger will fire if the root user runs a command such as ‘apt-get install ntop’. However, it won’t fire if a user enters ‘sudo apt-get install ntop’ (because of the unexpected ‘sudo’). It won’t fire on ‘apt-get -y install ntop’ (because of the ‘-y’), and it won’t fire if a user runs the command as part of a script that was called, for example, ‘install_ntop.sh’.

Let’s break down why not, and some best practices that can help avoid similar problems.

The CQL ‘cmd’ refers to shell commands precisely as entered. This enables powerful whitelisting and blacklisting, however other CQL statements are often better suited for providing comprehensive control over user behavior. Consider the same example:

cmd = 'apt-get install *'

 
An inefficient approach to protecting against the cases not captured by this query would involve adding more complexity, e.g.: 

cmd = 'apt-get install *' OR cmd = 'sudo apt-get install *' OR 
cmd = 'apt-get -y install' OR cmd= 'sudo apt-get -y install' OR...

 
Instead, we suggest using CQL that more directly targets the user behavior you intend to monitor or control. The CQL value cmd refers to an entire command as typed, and represents the combination of two other CQL values: ‘cmd_root’, and ‘cmd_parameters’. Thus the query “cmd = ‘apt-get install’ ” represents two other CQL queries: 

1.   cmd_root = 'apt-get'  

2.  cmd_parameters = 'install *'   

By combining these two simple queries, we can target any usage of ‘apt-get’ with the ‘install’ parameter:   cmd_root = 'apt-get' AND cmd_parameters = '*install*'  

‘cmd_root’ refers to the base of a command, so it can help to enable enforcement across cronjobs, scripts, sudo commands, etc.  

‘cmd_parameters’ refers to a command’s parameters. It’s good practice to put wildcards around parameters used with this CQL value, since many parameters don’t have to follow a particular order. This helps create robust triggers which fire regardless of how many additional parameters are present, and their order.

Additionally, if you decide to block package installation via apt-get, you likely need to block other ways to achieve the same thing. A decent engineer might try installing packages with dpkg. In order to block that too, you could use a similar query:

cmd_root = 'dpkg' AND cmd_parameters = '*-i*'   

 
You could block the use of dpkg by using "cmd_root = 'dpkg' "  However, this would also stop your team from using dpkg to investigate your systems while troubleshooting. You could also create a trigger that creates an alert when someone uses dpkg to view your installed packages with the ‘-l’ parameter (since they may be fingerprinting your installed applications). For example:

cmd_root = 'dpkg' AND cmd_parameters = '*-l*'

 
Similarly, you could use "cmd_root = 'apt-get' " to create a trigger that fires on any apt-get commands. Instead, we recommend using the power of in-depth CQL statements to block particular usages of commands, giving your engineers more latitude. As in the dpkg example, you could create triggers that fire only on specific ‘apt-get’ usages which pose security risks. For example, an adversary who knew they couldn’t install packages via ‘apt-get install’ might attempt to use something like ‘apt-get --download-only’, before installing with dpkg - but you could implement a simple trigger query to stop them: 

cmd_root = 'apt-get' AND cmd_parameters = '*download*'   

 

Example 2

Question: How can we set up triggers that create alerts when users attempt unsafe mysql executions?

A majority of our customers wonder about this, since Mysql allows users to enter passwords into the command line rather than into a password prompt (at the risk of storing unsecured passwords in bash_history, etc). Although Mysql provides warnings about the risky options, it still allows them.

For a trigger to fire in response to all unsafe usages, it must account for the multiple ways of entering passwords in mysql. For a password of ‘badsecuritypractice’ and the root user, all of the following are valid commands:

  1. mysql --user=root -p

  2. mysql --user=root -pbadsecuritypractice

  3. mysql --user=root --password

  4. mysql --user=root --password=badsecuritypractice

Options #1 and #3 are considered secure since they initiate a password prompt. So, how to fire a trigger on options #2 and #4? With a combination of parameter rules, as in the following query:

 cmd_root = 'mysql' AND (( cmd_parameters = '* -p*' AND 
 cmd_parameters != '* -p *' AND cmd_parameters != '*-p') OR (cmd_parameters = '*--password=*' ))

 
Please note the space before the first ‘-p’, and the spaces between the second ‘-p' and the wildcards that surround it.

There’s a lot to digest here, so let’s break it down. First, we’ve grouped our parameter checks with parentheses. This works just like any logical statement—we recommend careful attention to your AND’s and OR’s, and thorough testing.

In order to fire a trigger when users use the ‘-pPASSWORD’ syntax (#2 above), our query has to fire when the following conditions are true (in order of how the CQL is written):

  1. There was some data entered after the -p.

  2. There was no space after the -p.

  3. There wasn’t a newline immediately after the -p.  

Number 3 is important because neither of the following mysql commands should be blocked:

mysql --user=root localhost -p
mysql --user=root localhost -p $DATABASETABLE

 
Finally, the last part of the query simply identifies the use of ‘--password=’. Since mysql requires an equal sign (‘=’) after the ‘--password’ parameter in use case #4 above, that is the only use case we need to test for.

With this CQL statement, you can choose to block a poorly written mysql execution, post a message to the terminal, and/or create an alert, depending on your organization’s preferences.
 

Example 3

Trigger idea: 'Fire when server operators try downloading files without using sudo'
Trigger type: Command
Trigger CQL:

 session_interactive = ‘true’ AND cmd_root != 'sudo' AND 
 cmd_root IN 'wget,nc,scp,curl' OR
 cmd_exec_path IN 'wget,nc,scp,curl'

There are four problems with this trigger:

  1. There is a dangling OR at the end. It should be grouped with the previous cmd_root clause.

  2. The values for cmd_exec_path are relative paths, but should be absolute paths (ex: /usr/bin/wget). 

  3. Execution paths are defined too narrowly (without wildcards).

  4. Because of how sudo works,  cmd_root != ‘sudo’   will never be true while    cmd_root IN ‘wget, nc, scp, curl’   is also true. (Behind the scenes, sudo executes first, escalating privileges before the next command — e.g. wget — can execute.)

Without these problems, the trigger would look as follows:

  session_interactive = 'true' AND cmd_exec_user != 'root' AND 
( cmd_root IN 'wget,nc,scp,curl' OR
  cmd_exec_path IN '*/wget,*/nc,*/scp,*/curl' )

 
In addition, since  cmd_exec_path  refers to mutable file locations, you should consider limiting the linking, moving, and copying of binaries in your /bin directories. This would mitigate the inherent fragility of relying on exec-path-based controls. For example, you could create a trigger like this: 

 cmd_user_typed = 'true' AND cmd_exec_user != 'root' AND 
 cmd_root IN 'ln,cp,mv' AND cmd_parameters IN  
 '*/bin*,*/sbin*,*/usr/bin*,*/usr/sbin*'


The following best practices help to avoid similar problems:

  • Double-check your use of parentheses. 

  • Get a second set of expert eyes on each new trigger.

  • Refer to our CQL documentation to make sure you understand the nuances of each CQL value used in your trigger. 

  • Think carefully about how to account for potential workarounds.

Takeaways:
We hope the examples illustrate the importance of following these best practices. To refresh your memory, here are the takeaways from the first two examples, above:

  • Think about restricting behaviors, rather than just particular commands.

  • Include wildcards where necessary (e.g. around parameters).

  • Account for each CQL query's syntax.

  • Have multiple members of your team review new triggers.

  • Test new triggers. 

 

Related resources:


 

Did this answer your question?