By Maciej Domanski, Travis Peters, and David Pokora

We identified 10 security vulnerabilities within the caddy-security plugin for the Caddy web server that could enable a variety of high-severity attacks in web applications, including client-side code execution, OAuth replay attacks, and unauthorized access to resources.

During our evaluation, Caddy was deployed as a reverse proxy to provide access to several of our internal services. We explored a plugin configuration that would allow us to handle authentication and authorization with our Google SSO so that we didn’t have to implement this on a per-app basis.

In this blog post, we will briefly explore the security vulnerabilities we identified in the caddy-security plugin and discuss their potential impact. As with our typical security assessments, for each issue we identified, we present a recommendation for immediately fixing the issue, as well as long-term, more strategic advice for preventing similar issues and improving the overall security posture.

As security experts, our goal is not only to identify vulnerabilities in specific software but also to contribute to the larger community by sharing our recommendations for fixing these problems. We believe that these recommendations can help developers overcome similar challenges in other SSO systems and improve their security.

Caddy Background

Caddy (a.k.a. Caddy Server or Caddy 2) is a modern, open-source web server written in Golang that is designed to be easy to use and highly configurable. Caddy is built to streamline the process of hosting web applications while prioritizing security and performance. It aims to reduce the complexity associated with configuring and deploying web servers.

The caddy-security plugin is a middleware plugin for the Caddy web server. It provides various security-related functionalities to enhance the overall security posture of web applications. Some of the key features offered by the caddy-security plugin include an authentication plugin for implementing Form-Based, Basic, Local, LDAP, OpenID Connect, OAuth 2.0, SAML Authentication, and an authorization plugin for HTTP request authorization based on JWT/PASETO tokens.

Findings

Issue #1: Reflected Cross-Site Scripting (XSS)

Severity: High

Reflected XSS occurs when an application includes untrusted data in the HTML response sent to the user’s browser. In this case, the provided /admin%22%3E%3Cscript%3Ealert(document.domain)%3C/script%3E/admin/login or /settings/mfa/delete/<img%20src=x%20onerror=alert(document.domain)> API calls trigger an alert. An attacker can exploit this vulnerability to execute arbitrary JavaScript code within the target user’s browser, potentially leading to further attacks such as session hijacking.

To immediately address this issue, strategically treat all string values as potentially untrustworthy, regardless of their source, and escape them properly (using the safehtml/template package that generates output-safe HTML).

In addition to that remediation, we also suggest a few different ways to improve defense in depth:

  • Extend unit tests with potentially malicious XSS payloads. Refer to the Cross-site scripting (XSS) cheat sheet for various attack vectors.
  • Consider using the Active Scanner from Burp Suite Professional in a testing environment for all API calls. Additionally, use the scanning with a live task strategy to have underlying requests scanned automatically when interacting with the web interface.
  • Expand the caddy-security documentation to promote security headers—especially the Content Security Policy (CSP) header that controls which resources can be loaded by the browser, limiting the impact of potential XSS attacks.

Issue #2: Insecure Randomness

Severity: High

The caddy-security plugin uses the math/rand Golang library with a seed based on the Unix timestamp to generate strings for three security-critical contexts in the application, which could possibly be predicted via a brute-force search. Attackers could use the potentially predictable nonce value used for authentication purposes in the OAuth flow to conduct OAuth replay attacks. In addition, insecure randomness is used while generating multifactor authentication (MFA) secrets and creating API keys in the database package.

To immediately mitigate this vulnerability, use a cryptographically secure random number generator for generating the random strings. Golang’s library crypto/rand is designed for secure random number generation.

In addition to that fix, we recommend considering the following long-term recommendations:

  • Review the application for other instances where the math/rand package is used for secure context. Create secure wrapping functions and use them throughout the code to serve a cryptographically secure string with the requested length.
  • Avoid duplicating code. Having a single function, such as secureRandomString, rather than multiple duplicate functions makes it easier to audit and verify the system’s security. It also prevents future changes to the codebase from reintroducing issues.
  • Implement Semgrep in the CI/CD. The math-random-used Semgrep rule will catch instances where math/rand is used. Refer to our Testing Handbook on Semgrep for more information.
  • Read textbooks such as Real World Cryptography, as it is a great resource for practical cryptographic considerations.

Issue #3: IP Spoofing via X-Forwarded-For Header

Severity: Medium

By manipulating the X-Forwarded-For header, an attacker can spoof an IP address used in the user identity module (/whoami API endpoint). This could lead to unauthorized access if the system trusts this spoofed IP address.

To resolve this vulnerability, reimplement the application to not rely on user-provided headers when obtaining a user’s IP address. If user-provided headers are required (e.g., X-Forwarded-For for logging purposes), ensure the header is properly validated (i.e., the value is consistent with IP address format through regular expression) or sanitized (to avoid CRLF log injection attacks, for example).

In addition to this immediate fix, we recommend considering these long-term recommendations:

  • Implement appropriate checks for potential IP spoofing and X- headers on the unit testing level. Consider other headers that can rewrite IP sources.
  • Cover the IP spoofing scenarios and user-provided header processing in Golang’s native fuzz tests.
  • Use the dynamic testing approach with Burp Suite Professional and the Param Miner extension to identify the processing of hidden headers.
  • Expand the caddy-security documentation to increase user awareness of this type of threat; show an example of misconfiguration, how to resolve, and how to test it.

Issue #4: Referer-Based Header XSS

Severity: Medium

An XSS vulnerability can be triggered by rewriting the Referer header. Although the Referer header is sanitized by escaping some characters that can allow XSS (e.g., [&], [<], [>], ["], [']), it does not account for the attack based on the JavaScript URL scheme (e.g., javascript:alert(document.domain)// payload). Exploiting this vulnerability may not be trivial, but it could lead to the execution of malicious scripts in the context of the target user’s browser, compromising user sessions.

The mitigation for this issue is identical to issue #1.

Issue #5: Open Redirection Vulnerability

Severity: Medium

When a logged-in user clicks on a specially crafted link with a redirect_url parameter, the user can be redirected to an external website. The user must take an action, such as clicking on a portal button or using the browser’s back button, to trigger the redirection. This could lead to phishing attacks, where an attacker tricks users into visiting a malicious website by crafting a convincing URL.

To mitigate this vulnerability, perform proper redirect_url parameter validation to ensure that the redirection URLs are allowed only within the same domain or from trusted sources.

In addition, we also recommend the following long-term fixes:

  • Implement robust unit tests with different bypassing scenarios of redirect_url parameter validation. Refer to the potential URL Format Bypasses. Keep in mind that different components can use different URI parsers, which can lead to parsing confusion.
  • Use Burp Suite Professional with a scanner with both these settings enabled:
    • Audit coverage – maximum: to use the most extensive set of payload variations and insertion point options
    • Audit coverage – thorough: to try more payload variations

Issue #6: X-Forwarded-Host Header Manipulation

Severity: Medium

The caddy-security plugin processes the X-Forwarded-Host header, which could lead to various security vulnerabilities (web cache poisoning, business logic flaws, routing-based server-side request forgery [SSRF], and classic server-side vulnerabilities). Additionally, the caddy-security plugin generates QR codes based on this header, which extends the attack surface.

To mitigate this issue, do not rely on the Host and X-Forwarded-Host headers in the caddy-security plugin logic. Instead, use the current domain manually specified in the configuration file to generate a QR code.

In addition, we recommend the following:

  • Use Burp Suite Professional with the Param Miner extension to identify the processing of hidden headers.
  • Extend the caddy-security documentation to increase user awareness of the HTTP Host header attacks.

Issue #7: X-Forwarded-Proto Header Manipulation

Severity: Low

The processing of the X-Forwarded-Proto header results in redirection to the injected protocol. While this scenario may have limited impact, improper handling of such headers could result in unpredictable security risks, such as bypass of security mechanisms or confusion in handling TLS.

To address this issue, do not rely on the X-Forwarded-Proto header. If it is required, validate the value of the X-Forwarded-Proto header against an allowlist of accepted protocols (e.g., HTTP/HTTPS) and reject unexpected values.

In addition, consider the long-term recommendations from issue #3.

Issue #8: 2FA Bypass by Brute-Forcing Verification Codes

Severity: Low

The current implementation of the application’s two-factor authentication (2FA) lacks sufficient protection against brute-force attacks. Although the application blocks the user after several failed attempts to provide 2FA codes, attackers can bypass this blocking mechanism by automating the application’s full multistep 2FA process.

To address this issue effectively, enforce a minimum six-digit code length in the MFA configuration. Additionally, to reduce the risk of automated brute-forcing, implement an account locking mechanism that triggers after a specified number of invalid 2FA code attempts. Finally, enforce reauthentication for critical actions involving sensitive account information or security settings. For actions such as changing passwords or disabling 2FA, users should be required to reauthenticate, either with their password or a 2FA token. An exception can be made for reauthentication if the user has logged in within the last 10 minutes. Check out Getting 2FA Right in 2019 at the Trail of Bits Blog for more information.

Issue #9: Lack of User Session Invalidation on Logout

Severity: Low

The caddy-security plugin lacks proper user session invalidation upon clicking the “Sign Out” button; user sessions remain valid even after requests are sent to /logout and /oauth2/google/logout. Attackers who gain access to an active but supposedly logged-out session can perform unauthorized actions on behalf of the user.

To address this issue, review the sign-out process to identify the cause of the unexpected behavior. Ensure that the /oauth2/google/logout endpoint correctly terminates the user session and invalidates the associated tokens.

For more defense in depth, use the OWASP Application Security Verification Standard (V3 Session Management) to check whether the implementation handles sessions securely.

Issue #10: Multiple Panics when Parsing Caddyfile

Severity: Low

Multiple parsing functions do not validate whether their input values are nil before attempting to access elements, which can lead to a panic (index out of range). Panics during the parsing of a Caddyfile may introduce ambiguity and vulnerabilities, hindering the correct interpretation and configuration of the web server.

To address these issues, integrate nil checks for input values before element access across all relevant functions.

To prevent similar issues of this type, add Golang’s native fuzz tests for Caddyfile parsing functions.

Golang Security for the Community

We love writing and reviewing Golang codebases at Trail of Bits. Indeed, we are constantly working on Golang-related (Semgrep) resources, rules, and blog posts and look forward to any opportunity to take on pet audits (like this) and client projects where we examine Golang codebases.

Our aim in publishing our findings is to help protect others who may consider implementing a solution similar to the one we explored and to help them make informed decisions about their security infrastructure.

If you’re actively implementing a codebase in Golang or have questions, concerns, or other recommendations on open-source software you think we should look at, please contact us.

Coordinated Disclosure Timeline

As part of the disclosure process, we reported the vulnerabilities to the caddy-security plugin maintainers first. The timeline of disclosure is provided below:

    • August 7, 2023: We reported our findings to the caddy-security plugin maintainers.
    • August 23, 2023: The caddy-security plugin maintainers confirmed that there were no near-term plans to act on the reported vulnerabilities.
    • September 18, 2023: The disclosure blog post was released and issues were filed with the original project repository.