Full Report
Astro is a web framework that has gained a lot of popularity in recent years - 50K stars and 800K downloads per week. It renders components on the server side, similar to NextJS but sends lightweight HTML to the browser with no JavaScript by default. Because of it's popularity, the authors of this post decided to look into the framework more. In Node's adapter function createRequest, it takes in a protocol, host name + port and path. A recent vulnerability labeled that the x-forwarded-host header was used to construct the URL without any validation. Although this was fixed, it seemed to open a pandora's box around these types of issues. Notably, x-forwarded-proto was NOT fixed. Since the protocol lives at the beginning of the URL, it's possible to use this header to change the rest of the URL being parsed. This is a very powerful primitive: the real URL isn't being modified but the information being rendered is. This could lead to cache poisoning XSS or SSRF issue for all websites, which is pretty wild. Their first exploits revolves around bypassing middleware protections (similar to a bug they found in NextJS). For example, the configuration of the website redirects all users to a different path unless they are logged in as admin; this check is done via middleware. Unfortunately, it's as simple as putting data into the x-forwarded-proto and rerouting; if we do this, THIS is the final path. Time for some trickery. The authors spent a lot of time looking at the WHATWG URL specification to learn about interesting edge cases. After a while, they came to the payload x:admin?. First, the parser sees the protocol x. Since there is no /, the path is parsed next - not an authority; the path is admin. If the URL is special, such as http, then the slashes can actually be skipped. http:admin? would have the domain be admin and contain an empty path name. In the original code example, an exact check is performed on the path - /admin. In the case of x:admin? the path is admin! The missing forward slash creates an incorrect string comparison that allows for bypassing the verification. The question mark is required for the path in order to eat the real host and path. To turn this into XSS, it relies on cache poisoning. Many CDNs do not include the presence of the x-forwarded-proto header as part of the cache key. So, if the application generates dynamic links based upon Astro.url, this can now lead to XSS. The link can be poisoned to all who access the page. The bug was fixed by doing some validation on the input of the headers. However, there was another parsing issue. The validation was only ran if x-forwarded-host was included. By setting this to an empty string, JavaScript treats this as null on the check and no validation is done. Later on, the empty domain is used and concatenated with the path. By setting the path to be the domain the URL becomes controllable once again. A neat bug! An amazing blog post! I loved the technical rigor of the parser and the bypassed patch as well. Both were very complicated issues to exploit but fairly obvious bugs by themselves. I'll leave with a direct quote from the article "It goes to show that sometimes it’s just a matter of timing: having the right details freshly in mind, along with the perfect situation to put them into practice."
Analysis Summary
# Vulnerability: URL Reconstruction via Unsanitized Forwarded Headers in Astro
## CVE Details
- **CVE ID**: CVE-2025-64525 (and a complete bypass of CVE-2025-61925)
- **CVSS Score**: 6.5 (Medium/Moderate) - *Note: Researchers argue for a "High" classification due to exploitation potential.*
- **CWE**: CWE-20 (Improper Input Validation), CWE-444 (Inconsistent Interpretation of HTTP Requests)
## Affected Systems
- **Products**: Astro Web Framework (specifically Node adapter and Internationalization module)
- **Versions**: Versions prior to v5.15.5
- **Configurations**: Applications using Server-Side Rendering (SSR) with the Node.js adapter or i18n features.
## Vulnerability Description
The vulnerability exists in how Astro constructs a URL object during server-side rendering. The framework uses template literals to concatenate the protocol, hostname, and path:
`url = new URL(`${protocol}://${hostnamePort}${req.url}`);`
The `protocol` and `hostnamePort` components were derived from standard HTTP headers (`x-forwarded-proto`, `x-forwarded-host`, and `x-forwarded-port`) without sufficient validation. Because `x-forwarded-proto` is placed at the very beginning of the string, an attacker can inject a full URL into that header (e.g., `https://malicious.com/?ignore=`). This causes the URL parser to treat the intended legitimate host and path as part of the query string, effectively hijacking the internal `Astro.url` object.
A second issue involved a bypass of the original fix for CVE-2025-61925: if an attacker provided an empty string for `x-forwarded-host`, the validation logic treated it as null and skipped checks, allowing host manipulation.
## Exploitation
- **Status**: PoC available; technically Rigorous research published.
- **Complexity**: Medium
- **Attack Vector**: Network
- **Scenarios**:
- **Middleware Bypass**: Using special characters like `x:admin?` to trick path-based authorization checks that expect a leading `/`.
- **Cache Poisoning to XSS**: Injecting malicious URLs into `Astro.url`. If a CDN does not include `x-forwarded-proto` in the cache key, the poisoned page (containing attacker-controlled links) is served to all users.
- **SSRF**: Redirecting server-side requests to internal or malicious endpoints.
## Impact
- **Confidentiality**: Low (Depends on application logic)
- **Integrity**: Low (Can lead to Stored XSS via Cache Poisoning)
- **Availability**: Low
## Remediation
### Patches
- **Astro v5.15.5**: This version includes the fix for the protocol injection and the bypass for the host validation.
### Workarounds
- Configure reverse proxies/load balancers to strip or overwrite `x-forwarded-proto`, `x-forwarded-host`, and `x-forwarded-port` headers before they reach the Astro application.
- Implement strict "Allowed Domains" lists in the Astro configuration.
## Detection
- **Indicators of Compromise**: HTTP logs showing unusual values in `x-forwarded-proto` (e.g., values containing `://`, `?`, or multiple characters beyond `http`/`https`).
- **Detection Methods**: Monitor for inconsistencies where `x-forwarded-host` is present but empty, or where the protocol header contains URL delimiters.
## References
- **Vendor Advisory**: hxxps://github[.]com/withastro/astro/security/advisories/GHSA-hr2q-hp5q-x767
- **Original Research**: hxxps://zhero-web-sec[.]github[.]io/research-and-things/astro-framework-and-standards-weaponization
- **Related Fix**: hxxps://github[.]com/withastro/astro/security/advisories/GHSA-5ff5-9fcw-vg88 (CVE-2025-61925)