Improving Your Zero-Day Readiness in JavaScript

Data breaches are a massive issue. Beyond reputational damage and user data loss, financial costs must also be considered. With the need for extra staff, legal counsel, and even credit-monitoring services for those involved, the Ponemon Institute estimated the global average cost of a data breach in 2020 was $3.86 million. Given that, it’s clear investing in zero-day readiness should be top of mind for security engineers  and developers alike.

What Does “Zero-Day” Mean?

“Zero-day” is a broad term that refers to an unpatched security flaw unknown to the public. It can also be a recently discovered vulnerability in the application. In either case, the developer has “zero days” to address and to fix it before it can be potentially exploited. Attackers make use of such flaws to intrude and to attack the system. Most times, these flaws are spotted by bounty hunters and are promptly patched. However, sometimes the attackers get there first and exploit the flaw before it is fixed.

In the context of web application security, a zero-day vulnerability is often used in cross-site scripting (XSS) and other types of attacks. Attackers take advantage of these vulnerabilities to inject malicious code into webpages viewed by other users. The code then runs on the user’s browser and can perform various actions, such as stealing sensitive information or redirecting the user to a malicious website (often owned by the attacker).

One of the most notable zero-day attacks was the 2014 attack on Sony Pictures Entertainment.  Sony was the victim of a devastating cyber attack that led to the release of sensitive information, including employee data and financial records. The attackers used a zero-day vulnerability in the company’s network to gain access to its systems, which allowed them to steal large amounts of data. The Sony Pictures hack was a major wake-up call for many organizations, as it showed just how vulnerable they could be to cyber attacks, and how costly the reputational and financial damages were.

To better illustrate zero-day problems, let’s examine some zero day terminology and demonstrate with the circumstance of an SQL injection. This is a type of attack where the culprit uses SQL commands to steal or to manipulate data in SQL databases.

Zero-Day Vulnerability

A zero-day vulnerability is a security flaw in the software, such as the operating system, application, or browser. The vendor, software developer, or antivirus manufacturer has not yet discovered or patched this software vulnerability. Although the flaw might not be widely known, it could already be known to attackers, who are exploiting it covertly.

In the case of SQL injection, the vulnerability would be the lack of input sanitization. In this instance, the developer has skipped the step of validating the input data and verifying whether it can be stored or if it contains harmful data.

Zero-Day Exploit

If security is compromised at any stage, attackers can design and implement code to exploit that zero-day vulnerability. The code these attackers use to gain access to the compromised system is referred to as a zero-day exploit.

Attackers can inject malware to a computer or other device, using the exploit code to gain access to the system and bypass the software’s security by leveraging the zero-day vulnerability. Think of it like a burglar entering a home through a damaged or unlocked window.

In terms of SQL injection, the zero-day exploit is the code or manipulated input data the attackers use to infiltrate the vulnerable system.

Zero-Day Attack

A zero-day attack is when a zero-day exploit is actively used to disrupt or steal data from the vulnerable system. Such attacks are likely to succeed because there are often no patches readily available. This is a severe security drawback.

In SQL injection, the zero-day attack occurs when the exploit code is injected at avulnerable point in the software(where no input validation was done).

3 Best Practices for Zero-Day Readiness

  1. Always Sanitize Input Data

Input validation is perhaps the most cost-effective way of improving application security. For any vulnerability to be exploitable, the attacker must first be able to bypass certain checks or validation. The absence of input sanitization is like leaving a door unlocked for the attacker to walk right through.

A solid regular expression (regex) can be designed to cover all the edge cases while validating an input.

For instance, if an input accepts a valid American mobile number, the validation can be performed as follows:

const number = /^(0|1|+1)?\s?\d{10}$/

if(input.match(number)){...}

The above regex considers all the corner cases and also makes it easier to write all the cases as a single expression.

This validation should not be done only on the client side. As a layer of added security, validation needs to be performed on the back end as well. Since the validation on the client side can be manipulated, always recheck for the validation on the server side before performing operations on it.

  1. Intercept Object Behavior

If the attacker manages to bypass the validation, there needs to be some code that can still validate and handle data by default. This can be done using proxy objects.

Proxies are objects in JavaScript that intercept the behavior of other objects. This can be used either to extend or to limit a property’s usability.

To get a clearer insight, consider the code snippet below:

class Vehicle{
constructor(wheels,seats){
this.wheels = wheels
this.seats = seats
}
}

class Car extends Vehicle{
#secret_number
constructor(wheels,seats,power){
super(wheels,seats);
this.wheels = wheels
this.seats = seats
this.power = power
this.#secret_number = Math.random()*Math.random()
//Assume the secret number to be 0.052

}

getSecretNumber(){
const self = this
const proxy = new Proxy(this,{
get(target,prop,handler){
if(prop==='secret_number'){
return self.#secret_number*500
                        //returns 26
}
else {
return target[prop]
}
}
})
    return proxy['secret_number']
}
}

const car_1 = new Car(4,5,450)
car_1.getSecretNumber()

 

In the above snippet, the class “car” inherits from class “vehicle.”

The class “car” contains a method “getSecretNumber,” which is generated when an instance of the class is created. To avoid direct access, declare it a private field, and from the getter method, intercept the behavior (or value) using proxies.

In the example above, there is no need to use a proxy. However, what if there were a single getter function to return the desired value by passing an argument? In such cases, proxies would be of great benefit by masking or intercepting only the selected properties.

  1. Maintain Recent Dependencies

Using external libraries in any JavaScript-based project is quite common because redeveloping an already created utility is a waste of time, energy, and resources. Additionally, many open source libraries have continuous support and user-contributed updates. While such dependencies provide numerous advantages, most developers find it quite painful to keep track of and to update them. 

Security is one of the main reasons to be vigilant about dependency updates; automating the application of security patches can fix over 90% of new vulnerabilities before any issue is publicly disclosed. 

Because many developers find tracking and updating dependencies so difficult, they automate the task using a tool like Mend Renovate. It is an open-source tool for developers that automatically creates pull requests (PRs) for dependency updates.

Renovate first scans the entire repository and detects all the dependencies used. It then proceeds to check if there are any updates available on those dependencies. If so, it raises a pull request containing relevant information, such as release notes, adoption data, crowdsourced test status, and commit histories. Insights into such data further help in deciding if an update needs to be merged or not.

Automating dependency updates could save up to 20% of your development time.

To get started, do the following: 

  1. Navigate to GitHub marketplace (linked above), and install the Renovate app.
  2. Select all the repositories you would like Renovate to scan.
  3. Before it starts scanning for updates, an onboarding PR is raised. This contains a preview of the actions Renovate will take. Merge it.
  4. Leave the rest to Renovate.

Updating dependencies with Renovate minimizes the risk of breaking code during an update because the merge confidence is crowdsourced. This can be used to evaluate whether an update can be safely merged or if it contains potential risk.

In the case of zero-day vulnerability, rather than dealing with accumulated tech debt and deciding whether to jump a few major releases, you’ll only need to apply a security patch by updating to the next invulnerable version.

Updating dependencies is a crucial step in preventing security issues, and apps like Renovate can simplify the process and save developers time.

Conclusion

For any application, protecting its users’ data and other confidential information is of prime importance. While no system is 100 percent foolproof, vendors and software developers must always seek to find and to fix vulnerabilities before they’re exploited.

At the very least, there needs to be an incident response plan ready to minimize the impact if an attack does occur.

As the saying goes, however, an ounce of prevention is worth a pound of cure. For the best results, make sure you have implemented enough preventative measures to minimize the chance of exploitation in the first place.