Skip to main content

Oops I built a feature and created an Open Redirect Vulnerability in a Deno app

Escrito por:
0 minutos de leitura

We will take a step-by-step approach to setting up a simple Deno "Hello World" web application, an increasingly popular runtime for JavaScript and TypeScript. Specifically.

We will create a Deno web application that supports a redirect query parameter and learn how developers may inadvertently open security holes when they follow roadmap plans and build features.

Getting started with a Deno “Hello World” code tutorial

As pre-requisite requirements, ensure you have access to a Deno web development environment with the required tooling to run Deno projects and interact with the web application.

Follow these steps to create your Deno "Hello World" application:

1. Install Deno on your development environment. If you haven't done so already, you can find the installation instructions on the [official Deno website](https://deno.land/#installation).

2. Create a new Deno project. Do this by creating a new directory and initializing it with a deps.ts file. This file will hold our dependencies, although we won't have any since this is a simple application.

   ```bash
    mkdir deno-hello-world
    cd deno-hello-world
    echo "" > deps.ts
    ```

3. Create a new server.ts file. This file will hold our server logic. In this file, we'll import the serve function from the std/http module, set up a server, and listen for incoming requests.

4. Add the server logic. In the server.ts file, add the following code:

    ```typescript
import { serve } from "https://deno.land/std/http/server.ts";

const httpHandler = async (request: Request): Promise<Response> => {

    let bodyContent = 'Hello World';

    return new Response(bodyContent, {
        status: 200,
        headers: {
            "content-type": "text/html"
        }
    })
};

serve(httpHandler, { hostname: "0.0.0.0", port: 8080 });
console.log("HTTP server is running on http://localhost:8080/");
```

5. Run the server. Save the server.ts file and start your server by running the following command in your terminal:

    ```bash
    deno run --allow-net server.ts
    ```

That's it! You now have a simple Deno web application that displays "Hello World!"

Request redirect in a Deno application

Let’s update the working code above to support a query parameter from the main / index route to a /user route that displays the user’s details.

First, add the second /user route. To avoid introducing any substantial dependency or web frameworks we’ll use a simple conditional check on the requested path.

Update the httpHandler code as follows:

```typescript
const httpHandler = async (request: Request): Promise<Response> => {
    let bodyContent = 'Hello World';

    const requestURL = new URL(request.url);
    if (requestURL.pathname === "/user") {
        bodyContent = `
        <h1> User: admin </h1>
        <ul>
          <li> Name: Roberto </li>
          <li> Email: roberto@example.com </li>
        </ul>
        `
        return new Response(bodyContent, {
            status: 200,
            headers: {
                "content-type": "text/html"
            }
        })
    } else {
        return new Response(bodyContent, {
            status: 200,
            headers: {
                "content-type": "text/html"
            }
        })
    }
};
```

Now, we’re ready to implement a redirect feature so that when requests to /?redirect=/user are sent, our web application parses the query parameter redirect and redirects the request to the user details page handling.

At the top of our httpHandler function let’s expand the requestURL variable to also include a conditional check for the redirect query parameter and act accordingly by responding with an HTTP response that includes a Location redirect header:

```js
    const requestURL = new URL(request.url);
    const redirectTo = requestURL.searchParams.get('redirect');
    if (redirectTo) {
        return new Response(null, {
            status: 302,
            headers: {
                'location': redirectTo
            }
        })
    }
```

To test that our redirect feature works as expected, redirect to the user details page from the main index page by requesting the page: /?redirect=/user. Here it is in action by using curl:

```bash
@lirantal ➜ /workspaces/deno-open-redirect-vulnerability-blog-post (main) $ curl "http://localhost:8080/?redirect=/user" -vv
v
*   Trying ::1:8080...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /?redirect=/user HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< location: /user
< vary: Accept-Encoding
< content-length: 0
< date: Mon, 06 Nov 2023 14:19:36 GMT
< 
* Connection #0 to host localhost left intact
```

Ready to ship to production?

You might want to take a closer look…

Open redirect security vulnerability in a Deno application

An Open Redirect Vulnerability, also known as Unvalidated Redirects and Forwards, is a significant security flaw commonly seen in web applications. This vulnerability can lead to severe cybersecurity issues such as phishing attacks, credential theft, and malicious content delivery.

I highly recommend visiting the Snyk Learn lesson on Open Redirect to get more insights and details on secure coding best practices in JavaScript and other languages such as Java, Python, etc.

What is an open redirect vulnerability?

An Open Redirect Vulnerability occurs when an application takes a parameter and redirects a user to the parameter value without any validation. This vulnerability can allow a cyber attacker to redirect victims from the original website to a malicious site of their choosing. Here's a simple example of how an open redirect might look in a Node.js application:

```javascript
app.get('/dashboard, function(req, res){
    var redirect = req.query.redirect;
    res.redirect(redirect);
});
```

In a Deno project, it looks similar:

```javascript
const httpHandler = async (request: Request): Promise<Response> => {
    const requestURL = new URL(request.url);
    const redirectTo = requestURL.searchParams.get('redirect');
    if (redirectTo) {
        return new Response(null, {
            status: 302,
            headers: {
                'location': redirectTo
            }
        })
    }
});

serve(httpHandler);
```

In the above code, the application redirects the user to the URL in the query parameter redirect. An attacker could abuse this by providing a malicious URL as the target, redirecting the user to an unintended destination.

If you need a full working example of the Deno application code we’ve put together you can find it here: https://github.com/snyk-snippets/deno-open-redirect-vulnerability-blog-post

Exploiting an open redirect vulnerability

In our simple Deno web application, a malicious user may abuse the redirect feature by crafting a link such as:

```
https://example.com/?dummyParameter1=value1&dummyParameter2=value2&redirect=https://attacker.com/
```

Using a dummy query parameter such as dummyParameter1 doesn’t impact the actual Deno application code but it creates a longer URL and as such it is harder for users to notice the redirect parameter and its value being a red flag.

Furthermore, malicious users can hide their intent by using URL shortening services. Unsuspecting users most likely wouldn’t unfurl the URL to find out the URL being redirected to.

How Snyk can detect an open redirect vulnerability in your Deno web app

Snyk is a powerful developer-first security tool that can help identify and fix vulnerabilities in your codebase and open-source dependencies. It's particularly useful in detecting security weaknesses like open redirect vulnerabilities.

Currently, Snyk supports TypeScript and Node.js projects but not Deno specifically. That said, if you’re building apps with JavaScript you’d want to scan both your package.json file for vulnerable open source dependencies and your own JavaScript code which may introduce code insecurity such as this Open Redirect vulnerability.

To detect an open redirect vulnerability in a JavaScript application with Snyk, you need to integrate Snyk into your development and deployment pipeline. You can do this by installing Snyk CLI and running the Snyk test against your JavaScript, TypeScript or Node.js application. Here's an example of how to do it:

1. Install Snyk CLI globally:

```bash
npm install -g snyk
```

2. Run the Snyk test in your application directory:

```bash
snyk code test
```

Snyk will then scan your application code and report any vulnerabilities it finds, including open redirect vulnerabilities. It also provides detailed information on how to fix the vulnerabilities, making it an essential tool for maintaining a secure application.

Snyk Code finding vulnerabilities in your own JavaScript and Node.js project and displays them with the Snyk CLI output

In fact, Snyk can find insecure code vulnerabilities right in the IDE, while you’re writing the code. In the following Node.js web application code written using the Express library and using a controller middleware to redirect the user after the admin user logs in, we can see Snyk finds many security vulnerabilities, one of which is an open redirect security issue.

On the right pane, you can see Snyk providing insights on the open redirect vulnerability:

  1. Explaining how unsanitized input flows between different files and code paths, detailing the specific line of code that is vulnerable.

  2. Providing a link to learn about this open redirect vulnerability (find the Snyk Lesson here!)

  3. Several code examples of how this open redirect vulnerability was fixed in other open-source projects and provide you with the code diff.

Snyk VS Code extension finds open redirect vulnerability in the IDE while writing Node.js Express route handler code

Remember, prevention is always better than cure, especially in cybersecurity. By understanding the open redirect vulnerability and using tools like Snyk, you can ensure your web applications remain secure and trustworthy.

Configuring the Deno Dev Container

In this section, we'll look at the configuration of the Deno development container within GitHub Codespaces. This will involve the modification of specific configurations such as the postCreateCommand and the forwardedPorts in the devcontainer.json file. Throughout this section, we'll provide some practical examples and suggestions to guide you in setting up a functional Deno web application in your development environment.

Use a Dev Container postCreateCommand Configuration

The postCreateCommand in the devcontainer.json file is a command or series of commands run after the development container has been built and started. This configuration is important because it allows you to automate tasks that you typically perform manually every time you start your container.

For example, you could use the postCreateCommand to install project dependencies, build your project, or start your application server. 

For a Deno project, you can use the postCreateCommand to auto-start the web application inside the development container: Here's an example of what your postCreateCommand configuration might look like:

```json
{
  "postCreateCommand": "deno run --allow-net server.ts"
}
```

Forwarded Port Configuration in Deno

Since this isn’t just a command-line Deno program but an actual web application, we need to expose ports that the Dev Container is hosting requests for and which the host environment should be able to access. This is where the forwarded ports configuration of a Dev Container comes in.

The forwardedPorts configuration in the devcontainer.json file specifies which ports in the container should be exposed to the host machine.

For instance, the Deno web application we outlined in this article listens on port 8080, then we need to forward this port to the host machine using the forwardedPorts configuration.

To set up a forwarded port configuration for your Deno web app, add the forwardedPorts configuration to your devcontainer.json file. Here is an example:

```json
{
  "forwardedPorts": [8080]
}
```

Now, your Deno web application is running on port 8080 in the container, and you can access it at http://localhost:8080 on your host machine. Remember, if you’re running this in a remote cloud environment such as GitHub Codespaces, then you’d need to access it via the Codespace URL itself such as https://orange-invention-pe8918638bd1nf81-8080.app.github.dev/

What’s next?

We learned about Deno, Dev Containers, and most importantly, how to avoid introducing a security vulnerability when building a feature. Phew, that was a lot!

You can find the full working code of this article on the Snyk Snippets code repository at GitHub: https://github.com/snyk-snippets/deno-open-redirect-vulnerability-blog-post