Skip to main content

Insecure Use of Language/Framework API

Using APIs securely

About insecure use of language/framework APIs

What is insecure use of language/framework APIs?

Insecure use of language or framework API refers to a security vulnerability that arises when an application uses an API in an unsafe or insecure manner. This can result in unintended behavior or access, which can be exploited by attackers to gain unauthorized access, steal data, or perform other malicious actions.

This type of vulnerability can arise due to a lack of understanding on the part of the developer, or it can result from the use of outdated or deprecated APIs that are no longer considered secure.

What is the impact of insecure use of language/framework APIs?

Insecure use of language/framework APIs can have a wide-ranging impact, depending on the type of vulnerability and the attacker's goals.

Here are some potential impacts of insecure use of language or framework API:

  • Unauthorized access: An attacker may be able to gain unauthorized access to a system or application, giving them access to sensitive data or functionality.
  • Data theft: An attacker may be able to steal data from the system, including personally identifiable information, financial data, or other sensitive data.
  • Denial of service: An attacker may be able to launch a denial-of-service attack by exploiting vulnerabilities in the API.
  • System compromise: In some cases, a successful attack on an insecure API can lead to a complete compromise of the system, allowing the attacker to take full control.

How to prevent the insecure use of language/Framework APIs?

Here are some measures that can help prevent insecure use of language or framework API:

  • Use secure APIs: Use up-to-date and secure APIs that are well-maintained and well-documented. This can help prevent the use of deprecated APIs that are no longer considered secure.
  • Regular security audits: Regularly audit your system and application for security vulnerabilities, including insecure use of language or framework API vulnerabilities. Use automated tools and manual testing to identify potential issues and fix them before they can be exploited.
  • Education and training: Educate your development team about the risks of insecure use of language or framework API and the measures that can be taken to prevent them. Ensure that everyone on the team is aware of secure coding practices and understands the importance of security in application development.

Fixing Insecure Use of Buffer()

This rule covers 2 scenarios:

  1. If untrusted input is passed to new Buffer(...) or Buffer.allocUnsafe, it may allow the user to leak memory (NodeJS version <= 8).
  2. If a number is passed into new Buffer(...) or Buffer.allocUnsafe(...), without zeroing the memory afterwards

Both of these scenarios allow a user to potentially leak sensitive information from memory. This occurs because the Buffer constructor that takes an integer parameter does not initialize/zero itself - meaning it can potentially contain sensitive data that is in memory.

Note that anytime new Buffer(...) is referenced in this document, the same information applies to Buffer.allocUnsafe(...).

The new Buffer() function can allow attackers to get full access to your production environment. The new Buffer() function has been deprecated since Node.js v6.0.0.

Rule-specific references:

Option A: Type check your parameters

One way of fixing this vulnerability is to simply type check your parameters. This is implicit in TypeScript, so no solution is needed for TS.

For JS, this option should be used if the parameter to the Buffer constructor is not intended to be a number.

  1. Locate the vulnerable pattern (example below):

    JS:

       function vulnerable(arr) {
    let buff = new Buffer(arr);
    return buff;
    }
  2. Replace it with one of the following patterns (examples below):

    JS, alternative #1:

       function vulnerable(num) {
    if(typeof num === 'number') {
    num = [num]
    }

    let buff = new Buffer(num);
    }

    JS, alternative #2:

       function vulnerable(param) {
    let buff = new Buffer(param);
    if(typeof param === 'number') {
    buff.fill(0);
    }
    }
  3. Test it

  4. Ship it 🚢 and relax 🌴

Option B: Clear the buffer after initialization

Another option is to make sure your buffer is always cleared after initializing. This option should be used if a number is intended to be passed to the constructor of Buffer.

  1. Locate the vulnerable pattern (examples below):

    JS:

       function vulnerable(maybe_number) {
    let buff = new Buffer(maybe_number);
    return buff;
    }

    TS:

       function vulnerable(num: number) {
    let buff = new Buffer(num);
    return buff;
    }
  2. Replace it with one of the following patterns (examples below):

    JS, clearing the Buffer:

       function vulnerable(maybe_number) {
    let buff = new Buffer(maybe_number);
    buff.fill(0);
    return buff;
    }

    TS, clearing the Buffer:

       function vulnerable(num: number) {
    let buff = new Buffer(num);
    buff.fill(0);
    return buff;
    }

    Using Buffer.alloc:

       function vulnerable(maybe_number) {
    let buff = Buffer.alloc(maybe_number);
    return buff;
    }
  3. Test it

  4. Ship it 🚢 and relax 🌴

Object Injection via untrusted input

JavaScript Object Injection (JOI) is a sub-category of code injection. JOI is a type of security vulnerability that occurs when an attacker can manipulate an object in a way that allows them to inject an object and/or object properties into an existing object and/or object properties. When the code (whether existing or injected) is executed, it uses the injected object property values, executing them if they are methods, or using them as part of evaluation if they are simple values. This can happen when untrusted input is not properly validated, filtered, or sanitized before being executed.

An attacker can then use this injected object or object property to steal sensitive information, short-circut authentication, perform actions on behalf of the user, or execute other malicious actions.

JOI can also occur when an attacker can modify the prototype of an object, allowing them to modify, add, or remove properties and methods that were not intended to be accessible.

Option A: Validate, filter, and sanitize untrusted data

Any untrusted data entering or passing through your software needs to be validated, filtered, and sanitized based on the execution context that it is expected to pass through. In regards to your application code, this is the first and most important aspect to ensure you have covered.

The following are examples of untrusted input being used within Express.js route handlers. Similar examples can be applied anywhere that untrusted data is used directly without validation, filtering, and sanitization.

  1. Locate one of the following vulnerable patterns:

    Accessing an existing object property using untrusted data within bracket notation, or simple assignment of untrusted code to a property:

    import express from 'express';
    const app = express();

    app.get('/users', async (req, res) => {
    try {
    const obj = {};

    // Accessing (assign or read) an object using untrusted data within bracket notation.
    obj[req.body.name] = true;

    // Assigning untrusted code to a property.
    obj['doSomething'] = JSON.parse(req.body.behaviour);
    obj.doSomething = JSON.parse(req.body.behaviour);

    res.send('injected');
    client.close();
    } catch (err) {
    console.error(err);
    res.status(500).send({ error: 'An error occurred' });
    }
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });

    Using Object static methods to inject objects:

    import express from 'express';
    const app = express();

    app.get('/users', async (req, res) => {
    try {
    const obj = {};

    // Adding a property with an untrusted value to an existing object.
    Object.defineProperty(obj, 'newProp', { value: Bourne.parse(req.body.behaviour), writable: false });

    // Adding an object of untrusted properties to an existing object.
    Object.defineProperties(obj, JSON.parse(req.body.json));
    Object.assign(obj, JSON.parse(req.body.json));

    // Setting the prototype of an existing object to that of an untrusted object.
    Object.setPrototypeOf(obj, JSON.parse(req.body.json));

    // Creating a new object that has the prototype of an untrusted object.
    Object.create(JSON.parse(req.body.json));

    res.send('injected');
    client.close();
    } catch (err) {
    console.error(err);
    res.status(500).send({ error: 'An error occurred' });
    }
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });

    Injection of untrusted behaviour into a deprecated with statement, or using the spread operator to inject untrusted object properties:

    import express from 'express';
    const app = express();

    app.get('/users', async (req, res) => {
    try {
    const obj = {};

    // Deprecated with statement used to add untrustedBehaviour to the scope chain when evaluating an untrusted method.
    const untrustedBehaviour = JSON.parse(req.body.behaviour);
    // req.body.behaviour may be: '{ "innocentPropertyNotReally": "() => alert(\\"Attack!\\")" }';
    // For the above to work, one option is to pass a reviver function to JSON.parse and a replacer function to JSON.stringify.
    with (untrustedBehaviour) { innocentPropertyNotReally(); }

    // Adding an untrusted object's properties via the spread operator.
    const objFromSpread = { ...JSON.parse(req.body.json), prop: 'arbitrary value'};

    res.send('injected');
    client.close();
    } catch (err) {
    console.error(err);
    res.status(500).send({ error: 'An error occurred' });
    }
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });

    Applying the built-in apply, call, or bind functions to untrusted objects:

    import express from 'express';
    const app = express();

    app.get('/users', async (req, res) => {
    try {

    // Calling (applying) an existing function with an untrusted object as its this value.
    //const req.body.json = '{ "name": "John", "age": 50, "website": "https://evilsite.com" }';
    const userFunc = function (/* possible parameters */) { window.location.replace(this.website); };
    userFunc.apply(Bourne.parse(req.body.json) /*, [possible args]*/);
    userFunc.call(JSON.parse(req.body.json) /*,possible, args*/);
    const boundUserFunc = userFunc.bind(JSON.parse(req.body.json) /*,possible, args*/);

    res.send('injected');
    client.close();
    } catch (err) {
    console.error(err);
    res.status(500).send({ error: 'An error occurred' });
    }
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });

    Using the built-in Proxy or Reflect objects to intercept behaviour with untrusted behaviour:

    import express from 'express';
    const app = express();

    app.get('/users', async (req, res) => {
    try {
    const obj = {};

    // Providing an untrusted object to proxy an existing object.
    const proxied = new Proxy(obj, JSON.parse(req.body.json));

    // Similar to proxy handlers Reflect is a built-in object with methods that intercept operations.

    // req.body.behaviour may be: '{ "innocentPropertyNotReally": "() => alert(\\"Attack!\\")" }';
    // For the above to work, one option is to pass a reviver function to JSON.parse and a replacer function to JSON.stringify
    // Setting a target object's property (whether it exists previously or not) to an untrusted value
    Reflect.set(obj, innocentPropertyNotReally, Bourne.parse(req.body.behaviour).innocentPropertyNotReally);

    // Calling (applying) an existing function with an untrusted object as its this value.
    const innocentPropertyNotReally = function(){untrustedBehaviour.innocentPropertyNotReally};
    Reflect.apply(innocentPropertyNotReally, JSON.parse(req.body.behaviour), []);

    // Instantiate a TargetClass and replace an instance method with an untrusted implementation.
    function TargetClass(name, age, func) { this.name = name; this.age = age; this.func = func; };
    const instanceOfTargetClass = Reflect.construct(
    TargetClass,
    ['John', 50, JSON.parse(req.body.behaviour).innocentPropertyNotReally]
    );
    // Similar to the previous example, but replace the TargetClass instance's prototype with one that's untrusted
    Reflect.construct(TargetClass, ['John', 50, () => {}], JSON.parse(req.body.behaviour));

    // Reflect.defineProperty: Similar to Object.defineProperty.
    // Reflect.get: passing UntrustedBehaviour as the third argument to use as the this value to the call of the target (the first argument to Reflect.get).
    // Reflect.setPrototypeOf: Setting the prototype of the target (the first argument to Reflect.setPrototypeOf) with untrustedBehaviour (second argument to Reflect.setPrototypeOf).

    res.send('injected');
    client.close();
    } catch (err) {
    console.error(err);
    res.status(500).send({ error: 'An error occurred' });
    }
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });
  2. Be sure to validate, filter, and sanitize all untrusted data before it makes its way into any of the above example functions

  3. Test it

  4. Ship it 🚢 and relax 🌴