Lucene search

K
talosTalos IntelligenceTALOS-2024-1995
HistoryJul 22, 2024 - 12:00 a.m.

Ankitects Anki Flask Invalid Path Reflected Cross-Site Scripting (XSS) vulnerability

2024-07-2200:00:00
Talos Intelligence
www.talosintelligence.com
9
cross-site scripting
vulnerability
ankitects anki
flask
arbitrary file read

CVSS3

8.2

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

LOW

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N

AI Score

7.1

Confidence

High

EPSS

0.001

Percentile

36.5%

Talos Vulnerability Report

TALOS-2024-1995

Ankitects Anki Flask Invalid Path Reflected Cross-Site Scripting (XSS) vulnerability

July 22, 2024
CVE Number

CVE-2024-32484

SUMMARY

An reflected XSS vulnerability exists in the handling of invalid paths in the Flask server in Ankitects Anki 24.04. A specially crafted flashcard can lead to JavaScript code execution and result in an arbitrary file read. An attacker can share a malicious flashcard to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Ankitects Anki 24.04

PRODUCT URLS

Anki - <https://apps.ankiweb.net/&gt;

CVSSv3 SCORE

7.4 - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N

CWE

CWE-80 - Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)

DETAILS

Anki is an open-source program that helps with memorization of information through the use of flash cards. It supports syncing of these cards across multiple computers as well as sharing cards with other users. It supports multiple different content types such as images, audio, videos, and scientific notation (via LaTeX).

Anki offers users the option to publicy share their decks, and it is normal behaviour to use them; there are no warnings or checks in place to prevent using cards from someone else. A malicious user could share a deck to trigger the following vulnerability.

Anki provides an internal flask server to manage the app, including importing new media for flashcards and changing deck options. You can find the exposed backend methods here. These are designed to only be accessed by the Anki program and not through user-imported content. However, due to a reflected XSS vulnerability in the web server, a precisely crafted card can access the methods exposed.

When processing a request, the web server checks the referer header to see where it’s being sent from (Anki calls this the “context”).

def _extract_page_context() -&gt; PageContext:
    "Get context based on referer header."
    from urllib.parse import parse_qs, urlparse

    referer = urlparse(request.headers.get("Referer", ""))
    if referer.path.starts with("/_anki/pages/") or is_sveltekit_page(referer.path[1:]):
        return PageContext.NON_LEGACY_PAGE
    elif referer.path == "/_anki/legacyPageData":
        query_params = parse_qs(referer.query)
        id = int(query_params.get("id", [None])[0])
        return aqt.mw.mediaServer.get_page_context(id)
    else:
        return PageContext.UNKNOWN

It then checks if the method is a GET, in which case any context is fine—this is for rendering HTML pages like the reviewer and card content in the “reviewer” page, or if it’s a POST, and therefore, the context has to be checked to make sure it’s authorised to call the API. Note the comment [1] about containing third-party JavaScript.

def _check_dynamic_request_permissions():
    if request.method == "GET":
        return
    context = _extract_page_context()

    def warn() -&gt; None:
        show_warning(
            "Unexpected API access. Please report this message on the Anki forums."
        )

    # check content type header to ensure this isn't an opaque request from another origin
    if request.headers["Content-type"] != "application/binary":
        aqt.mw.taskman.run_on_main(warn)
        abort(403)

    if (
        context == PageContext.NON_LEGACY_PAGE
        or context == PageContext.EDITOR
        or context == PageContext.ADDON_PAGE
        or os.environ.get("ANKI_API_PORT")
    ):
        pass
    elif context == PageContext.REVIEWER and request.path in (
        "/_anki/getSchedulingStatesWithContext",
        "/_anki/setSchedulingStates",
    ):
        # reviewer is only allowed to access custom study methods
        pass
    else:
        # other legacy pages may contain third-party JS, so we do not [1]
        # allow them to access our API
        aqt.mw.taskman.run_on_main(warn)
        abort(403)

One of Anki’s features that makes it so extendable is its ability to include custom JavaScript & CSS in the flashcards rendered as HTML. When the user reviews the card, it embeds a QTWebEngine to render the page with the flashcard code injected. We, of course, can make HTTP requests with JavaScript and, therefore, can make a POST request to the webserver API. However, as previously mentioned, the author is aware of this and has implemented the check for the context of where the JavaScript is being run, which in our case is the review page (PageContext.REVIEWER), so the execution of the API call gets aborted.

Before checking anything, the Flask web server first ensures that the resource requested is valid and whether or not it’s a file being requested or an API method. If it’s not, it will return an HTTP status. not found response with the path location - embedded directly [2] into the HTML and, therefore, is interpreted as such, creating a reflected XSS vulnerability.

else:
            print(f"Not found: {path}")
            return flask.make_response(
                f"Invalid path: {path}", [2]
                HTTPStatus.NOT_FOUND,
            )

To create a valid context for the API request, the URL requested should begin with /anki/pages/. By abusing the reflected XSS vulnerability, we can create a link like /anki/pages/&lt;img src=x onerror=alert(1). This loads the Invalid path page, injects our HTML and runs the malicious JavaScript.

To utilise this with our card located in the denied listed context, we can create an invisible iframe with the malicious URL to load. When the user reviews the card, the iframe will be added, and the reflected XSS will execute, giving us JavaSscript access in an authorised context.

&lt;script&gt;
    const iframe = document.createElement('iframe');
    iframe.style.display = 'none'; // Hide the iframe
    document.body.appendChild(iframe);
    
    iframe.src = 'http://' + window.location.hostname  + ':' + window.location.port + '/_anki/pages/<img src />';
&lt;/script&gt;

Anki has a feature for flashcards that lets the user attach an image and cover it up to memorise. To achieve this, however, the image must first be moved into Anki’s media folder for it to be accessed by the cards JavaScript to render. When adding an image occlusion note, the addImageOcclusionNote API method is called and given a path to the image to import and create the note. However, no check is done here to ensure it is a valid image; therefore, any file can be given [3], which Anki will happily add to the media folder [4].

pub fn add_image_occlusion_note(
        &mut self,
        req: AddImageOcclusionNoteRequest,
    ) -&gt; Result&lt;OpOutput&lt;()&gt;&gt; {
        // image file
        let image_bytes = read_file(&req.image_path)?;
        let image_filename = Path::new(&req.image_path) [3]
            .file_name()
            .or_not_found("expected filename")?
            .to_str()
            .unwrap()
            .to_string();

        let mgr = MediaManager::new(&self.media_folder, &self.media_db)?; [4]

Once this is done, we can easily access the file through JavaScript, as the webserver provides a direct link to the media folder assets (Located at 127.0.0.1:anki_port/image_name.png), where image_name.png is the file you want to access. We can then read this file and send its contents to the attacker’s webserver.

TIMELINE

2024-05-27 - Vendor Disclosure
2024-06-24 - Vendor Patch Release
2024-07-22 - Public Release

Credit

Discovered by Autumn Bee Skerritt of Cisco Duo Security and Jacob B


Vulnerability Reports Next Report

TALOS-2024-1994

Previous Report

TALOS-2024-1911

CVSS3

8.2

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

LOW

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N

AI Score

7.1

Confidence

High

EPSS

0.001

Percentile

36.5%