In a previous blog, we covered how to use a cast function to convert string or integer data types to boolean. Today, we are going to look at another cast function. This one converts a string value to a numeric float and appropriately is called cast.as_float.
To highlight this function, we are going to start with a relatively simple search. We are searching for user login events that contain a regular expression for the principal and src IP address that starts with 10.1.0. For dedicated readers of this blog, you likely recall that the function net.ip_in_range_cidr is a better way to express this.
net.ip_in_range_cidr(principal.ip, "10.1.0.0/24")
net.ip_in_range_cidr(src.ip, "10.1.0.0/24")
Heck, you may even use the strings.starts_with or strings.contains functions.
strings.starts_with(principal.ip, "10.1.0.")
strings.contains(src.ip, "10.1.0.")
The result of these functions focuses our result set on just a narrow range of IP addresses. The re.capture function then extracts the last octet from the two IP addresses. Finally, the outcome variable $diff, found in the outcome section of the search, calculates the difference between these two octets. Here’s the search.
metadata.event_type = "USER_LOGIN"
re.regex(principal.ip, `^10\.1\.0\..*$`)
re.regex(src.ip, `^10\.1\.0\..*$`)
re.capture(principal.ip, `^10\.1\.0\.(.*)$`) = $principal_last_octet
re.capture(src.ip, `^10\.1\.0\.(.*)$`) = $src_last_octet
outcome:
$diff = $principal_last_octet - $src_last_octet
But wait!
According to the syntax checker, there appears to be a problem. On line 8, the outcome variable of $diff is expecting to have data types of integer or float on either side of a subtraction sign. Google Security Operations (SecOps) tries to help us out by reviewing the expression used in the computation. The outcome data types support integers, floats, strings as well as lists of these three data types.
However, we’ve clearly extracted a value from a string field and now we are trying to subtract two strings, so the compiler returns an invalid argument.
This is where the cast.as_float function comes into play. Here’s the same search, but now we have added the cast.as_float function prior to the re.capture function for both of the last octet values. Now we have captured a string value and immediately converted it to a float.
metadata.event_type = "USER_LOGIN"
re.regex(principal.ip, `^10\.1\.0\..*$`)
re.regex(src.ip, `^10\.1\.0\..*$`)
cast.as_float(re.capture(principal.ip, `^10\.1\.0\.(.*)$`)) = $principal_last_octet
cast.as_float(re.capture(src.ip, `^10\.1\.0\.(.*)$`)) = $src_last_octet
outcome:
$diff = $principal_last_octet - $src_last_octet
Notice that we didn’t change the outcome function and yet there are no compilation errors. Google SecOps recognizes these two placeholder variables as floats. It recognizes a subtraction sign between two floats and understands what to do with it. We can see that the result is a -46.
OK, the negative may not be an ideal value, so let’s modify our search by appending the function math.abs (absolute value).
$diff = math.abs($principal_last_octet - $src_last_octet)
Again, since we are dealing with floats, Google SecOps happily subtracts the two floats and then applies the absolute value function and we are left with a difference of 46.
That’s a nice example of converting a string field to a numeric float and how you can then perform a mathematical computation based on float values.
Let’s look at another example but perform these calculations against aggregated values where the conversion is handled in the outcome section.
This time our search is gathering all network connection events where the principal IP address is in the 10.128.0.0/24 netblock. We are capturing the last octet of that address in a placeholder variable called $last_octet (as a string). These values are being grouped by the target IP address.
metadata.event_type = "NETWORK_CONNECTION"
net.ip_in_range_cidr(principal.ip, "10.128.0.0/24")
re.capture(principal.ip, `^10\.128\.0\.(.*)$`) = $last_octet
target.ip = $target_ip
match:
$target_ip
outcome:
$octet_max = max($last_octet)
How do I know that $last_octet is being stored as a string? The syntax checker is letting me know when I try to apply an aggregation function like max. Aggregations functions of max, min and sum will produce this same error.
Instead, we need to use an aggregation function that can work with a string. Count, count_distinct and array all do, as does array_distinct, which is what we will use instead.
$octet_array = array_distinct($last_octet)
Now we can see the target IP address along with the octets.
However, we are left with an array datatype which doesn’t lend itself to performing any mathematical operations. To fix that, let’s use the cast.as_float function in the outcome section to convert our last octet to a numeric. Then we can use the aggregation functions of max and min to find the greatest and least values. With those two outcome variables calculated with aggregate functions, we can then perform subtraction on the two outcome variables to find the difference and sort by that value.
$octet_max = max(cast.as_float($last_octet))
$octet_min = min(cast.as_float($last_octet))
$octet_diff = $octet_max - $octet_min
order: $octet_diff desc
Here’s the complete query with a subset of results. Notice that we have arrays with multiple values and then separate values for the max and min as well as the difference.
As we’ve shown, both the cast.as_float and cast.as_bool functions can be used in search, dashboards and rules. These functions make it very easy to convert from one data type to another and unlock additional capabilities for analysts as they work with their datasets.