back caretBlog

Writing Malicious Code 101: Infinite Loops

How to knock over an ExtraHop with poorly written triggers.

I'm taking a quick hiatus from our Trigger Optimization 101 series to discuss how not to write triggers. I had the good fortune, recently, of crushing an ExtraHop appliance through a subtle mistake in some trigger code. Take a quick peek at the incoming data breakdown.

If you cause an infinite loop in your ExtraHop trigger, you're going to have a bad time. Click image to zoom

If I'm doing my math right, 170Mb/s is less than the advertised 20Gb/s. How did I achieve such poor performance?

Regular Expression + Infinite Loops => Profit

TL;DR: "Do not place the regular expression literal (or RegExp constructor) within the while condition or it will create an infinite loop if there is a match due to the lastIndex property being reset upon each iteration"

--RegExp.prototype.exec()

Both exec and match let us use regular expression to pull fields from a string using capture groups. However, with exec, we can loop to extract multiple matches. Take the following string:

username:kenp username:dillonf

I want to pull out the usernames so I'll use /username:(\w+)/g for my regex. Breaking it down:

  1. / -- start the regex definition
  2. username: -- look for the exact string "username:"
  3. ( -- start capture group
  4. \w -- match word character (all letters, numbers, and the underscore)
  5. + -- keep matching preceding character (word characters) until we hit a non-match (a non-word character)
  6. ) -- end capture group
  7. /g -- end the regex definition and apply the global flag, indicating that we want to find as many matches in the string as possible

If we run the following code:

var str = 'username:kenp username:dillonf';
console.log(/username:(\w+)/g.exec(str));
console.log(str.match(/username:(\w+)/g));

Output:

["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "username:dillonf"]

Comparing the two methods, both returned an array of values but exec pulled out the username "kenp" but did not match "dillonf", while match returned both username matches but didn't extract the actual usernames.

To work around these limitations, exec can be called multiple times in a loop and will continue to find patterns which fit the regex while extracting capture groups. When the string is exhausted and there are no more matches, exec returns null. With this information, I naively put together code like the below:

var str = 'username:kenp username:dillonf';
var match;
while ( (match = /username:(\w+)/g.exec(str)) !== null) {
console.log(match);
}

Did you try it? Did the results look something like this forever?

...
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
...

What Happened?

To understand what's happening, let's break down the while loop

  1. /username:(\w+)/g -- create a new regex
  2. exec(str) -- execute regex against our string
  3. match = -- assign value to match
  4. !== null -- check the match is not null (the regex didn't match anything)
  5. while (...) { -- while the match is not null, keep looping

When we create a new regex for each iteration of the loop, we are losing the index of the previous match. Instead the regex matches the same substring again, causing an infinite loop. The fix is easy enough: scope the regex outside of the loop:

var str = 'username:kenp username:dillonf';
var str_re = /username:(\w+)/g;
var match;
while ( (match = str_re.exec(str)) !== null) {
console.log(match);
}

Output:

["username:kenp", "kenp", index: 0, input: "username:kenp username:dillonf"]
["username:dillonf", "dillonf", index: 14, input: "username:kenp username:dillonf"]

Moral of the story: be careful when using regular expression and loops. That's it for now, until I make another catastrophic mistake. Have a good week everybody!

Start your ExtraHop demo

ExtraHop Reveal(x) Live Activity Map

Stop Breaches 87% Faster

Investigate a live attack in the full product demo of ExtraHop Reveal(x), network detection and response, to see how it accelerates workflows.

Start Demo

Sign Up to Stay Informed