Hey everyone! Welcome to the first installment of “Code of the Week,” where we dive deep into the lesser-seen side of coding vulnerabilities. We’re skipping past the usual suspects like SQL injections and XSS because, let’s face it, there’s enough out there on those already. Instead, we’re on the lookout for those sneaky, hidden flaws that don’t get enough spotlight but can cause just as much trouble.

The goal here is simple: spot those tricky bugs, understand why they’re a problem, figure out how to fix them, and learn how to catch them automatically next time. This series is built on a simple yet powerful premise: a bug found once should never be found again.

Let’s kick off this adventure with an in-depth look at our first code snippet, ready to unearth the technical intricacies and ensure that once a bug is found, it never finds its way back into our code.

The code

The rule of the game is straightforward: spot the bug! Keep in mind, for this snippet and any others in the series, if something isn’t explicitly shown, you can assume it’s either not within the user’s control or it’s not a source of vulnerability.

const nonAlpha = /[^a-zA-Z]/g;
const hasNonAlpha = (str) => nonAlpha.test(str);

function login(username, password)
{
    if(hasNonAlpha(username)) return false;

    query = `SELECT * FROM users WHERE username='${username}'`
    user = db.query(query)

    /* keep logging in the user */
}

The bug

At first glance, this snippet might look harmless, even diligent, in its attempt to sanitize the username input. But there’s a lurking danger. Did you spot it? The use of .test() in combination with the global (g) regex flag.

The global flag in regex (/g) makes our regex search “stateful”. This means that subsequent uses of .test() continue from where the last match ended, rather than starting from the beginning of the string. In the context of our login function, this behavior introduces a vulnerability where an attacker could bypass the non-alphanumeric filter by simply making repeated login attempts. The .test() method would eventually return false for a malicious username after enough attempts, slipping through the cracks of our validation.

The fix

Addressing the vulnerability in our login function requires a nuanced understanding of how JavaScript’s regex engine handles the global flag (/g). The trick lies in eliminating the “stateful” behavior of our regex pattern to ensure consistent and secure validation across all login attempts.

Here’s how we can patch this issue effectively:

Remove the Global Flag

The simplest and most effective fix is to remove the global flag from our regex pattern. Without the /g flag, the .test() method will always start searching from the beginning of the string, ensuring consistent behavior across multiple invocations.

const nonAlpha = /[^a-zA-Z]/;       // Removed the 'g' flag
const hasNonAlpha = (str) => nonAlpha.test(str);

State Reset

If the use of the global flag is necessary for other parts of your application (though not recommended in this context), ensure the regex’s lastIndex is reset before each .test() call. This approach is more of a workaround and less of a best practice in this particular scenario.

const nonAlpha = /[^a-zA-Z]/g;      // Keeping the 'g' flag
const hasNonAlpha = (str) => {
    nonAlpha.lastIndex = 0;         // Reset the regex state
    return nonAlpha.test(str);
};

The detection

Identifying vulnerabilities like the one highlighted in our JavaScript login function requires a keen eye and the right tools. While manual code reviews are invaluable, integrating automated tools into the development lifecycle can significantly enhance our ability to detect and mitigate security risks early on. Here, we focus on one powerful ally in our quest for secure code: Semgrep.

Note: For those eager to dive deeper into Semgrep and its integration within CI/CD pipelines, the AppSec Guide by Trail of Bits offers a comprehensive resource. This guide not only walks you through the basics of using Semgrep for static analysis but also provides insights into best practices for securing your applications.

Semgrep is a fast, open-source, static analysis tool that’s designed to easily identify buggy patterns in code. It stands out for its ease of use, the speed of analysis, and its ability to customize rules specific to the needs of a project. For the vulnerability discussed, a custom Semgrep rule can be crafted to flag any use of the .test() method in conjunction with a global regex flag (/g), alerting developers to potential stateful regex pitfalls.

rules:
- id: avoid-global-flag-in-test-method
  patterns:
    - pattern: /$REGEX/g.test($STRING)
  message: "Using `.test()` with a global regex flag can lead to unexpected stateful behavior. Consider removing the `g` flag or resetting the regex state before each use."
  languages: [javascript]
  severity: WARNING

The conclusion

As we wrap up this first entry in our “Code of the Week” series, our journey through the hidden vulnerabilities in code has just begun. Today’s exploration of a subtle yet significant security flaw in JavaScript regex usage displays a crucial aspect of software development: the devil is often in the details.

The case of the stateful regex pattern has taught us that vulnerabilities can lurk in the most innocuous lines of code, awaiting the right (or wrong) conditions to reveal their impact. By adopting a proactive approach to code review, leveraging powerful tools like Semgrep into our development process, we can uncover and address these hidden bugs before they become security risks.

Remember, the essence of this series is not just about finding bugs but ensuring that “a bug found once should never be found again.” It’s about building a culture of security-minded development, where every piece of code is scrutinized not just for functionality but for its potential to compromise security.

Stay tuned for our next installment of “Code of the Week,” where we’ll uncover another hidden flaw, dive into its intricacies, and continue our mission to write safer, more secure code.