• chevron_right

      Michael Catanzaro: Introduction to Injection Vulnerabilities (and Script Worlds!)

      news.movim.eu / PlanetGnome • 6 hours ago • 8 minutes

    Injection vulnerabilities, like cross-site scripting (XSS) or command injection, occur when we fail to properly encode untrusted output when inserting it into a trusted context. Before injecting uncontrolled or untrusted data, it’s essential to encode, escape, or quote the data to prevent it from breaking out of its intended context.

    Some security folks previously used to like to talk about “input sanitization.” In practice, input sanitization is hopeless. Instead, nowadays we do the opposite and think about “output encoding.” When you inject untrusted data into a new context, assume the data is always malicious, and encode, escape, or quote it to make it safe for use in that context. Let’s look at some examples.

    Pango Markup Injection

    Here’s a low-stakes example of Pango markup injection:

    markup = g_strdup_printf ("<b>%s</b>,
                              my_user_provided_data);
    gtk_label_set_markup (GTK_LABEL (label), markup);

    The untrusted data is not escaped and may decide to inject its own Pango markup, or break out of any markup that you used yourself. For example, if the data is </b><span foreground="blue" size="x-large">Hello world!</span><b> then it can decide to be blue and extra large instead of the intended bold. That’s not especially serious and probably not likely to be a security issue, but surely it’s an unintended bug. If you’re injecting an uncontrolled string into a Pango markup context, like a GtkLabel, then use g_markup_escape_text() first. (Pango markup can do other interesting things like hide characters or capitalize them. I’m not sufficiently creative to claim that’s definitely a security problem, but perhaps attackers will be more clever than me.)

    A real-world example: in this GNOME Shell issue report , the title of a desktop notification is able to use Pango markup to manipulate its own formatting. (At least, probably , because the issue report is unconfirmed. Looks plausible, though!)

    Unix Shell Command Injection

    Another good example is the Evince command injection vulnerability from a few months ago , where a malicious filesystem path is able to trick Evince/Atril/Xreader into executing arbitrary code. Evince expects the path of a file to open to be something like /home/foo/hello.pdf , but a malicious PDF instead provides the evil input --gtk-module=/home/foo/evil.so /home/foo/hello.pdf . If not quoted properly, we have a command injection vulnerability where --gtk-module is interpreted as a command line flag rather than as a path:

    Incorrect: /usr/bin/evince --named-dest= --gtk-module=/home/foo/evil.so /home/foo/hello.pdf

    Correct: /usr/bin/evince --named-dest=' --gtk-module=/home/foo/evil.so /home/foo/hello.pdf'

    If you’re constructing a Unix command line, as in the Evince example above, then use g_shell_quote() . Failure to do so is ruinous. (But beware: this isn’t necessarily safe if you’re using an actual Unix shell .)

    XSS for Desktop App Developers

    With that primer out of the way, let’s consider what happens when you inject untrusted content into HTML (or CSS, or JavaScript).

    I used to think XSS matters only for websites, and is surely not something that desktop app developers need to know about, right? Wrong, as I discovered five years ago when, to my surprise, Prakash (@1lastBr3ath) reported that websites could inject scripts into Epiphany’s new tab page (about:overview) via malicious page titles . This on its own is not especially serious, but it’s surely not supposed to be possible.

    If your desktop app uses WebKitGTK or another web engine, you probably do need to think carefully about XSS. For example, before injecting untrusted data into HTML, we need to HTML-encode it, which Epiphany didn’t do anywhere. In the simplest case, that looks like this:

    char *
    ephy_encode_for_html_entity (const char *input)
    {
      GString *str = g_string_new (input);
    
      g_string_replace (str, "&", "&amp;", 0);
      g_string_replace (str, "<", "&lt;", 0);
      g_string_replace (str, ">", "&gt;", 0);
      g_string_replace (str, "\"", "&quot;", 0);
      g_string_replace (str, "'", "&#x27;", 0);
      g_string_replace (str, "/", "&#x2F;", 0);
    
      return g_string_free_and_steal (str);
    }

    Simply replace the few dangerous characters with HTML entities, and you’re good to go. That doesn’t work for HTML attributes, though, where the rules are slightly different. And it definitely doesn’t work for CSS or JavaScript. Carefully review the OWASP Cross Site Scripting Preventing Cheat Sheet to understand what you can and cannot do.

    Recent XSS Bugs in Epiphany

    Anyway, back to the old about:overview bug report. Turns out, Epiphany had many similar vulnerabilities. I attempted to fix them all , but in fact, I had missed a spot. In this old commit , I recognized that a URL is untrusted data that must be encoded before I inject the URL into the error message. But I treated the error message of the GError returned by WebKit as if it’s trusted and does not need to be encoded. In fact, the error message itself may contain a URL! Oops. Fernando Munoz recently noticed and reported several example URLs that could inject content into Epiphany error pages. I’m unable to share my favorite example URL here on WordPress, because WordPress is sanitizing it (yes, that is indeed ironic, considering my above recommendation to not do that). But the result of the injection looks like this:

    So an evil URL can mess up the Epiphany network error page. That’s not particularly serious, but Fernando found a second injection that is much worse , an XSS vulnerability in Epiphany’s autofill implementation. Here, selector is an untrusted DOM element ID provided by the web page itself. Notice that no output encoding is performed before the untrusted value is injected into the JavaScript command:

      page_id = webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (view));
      world_name = ephy_embed_shell_get_guid (ephy_embed_shell_get_default ());
      script = g_strdup_printf ("EphyAutofill.fill(%lu, '%s', %i);",
                                page_id,
                                selector,
                                fill_choice);
    
      webkit_web_view_evaluate_javascript (WEBKIT_WEB_VIEW (view),
                                           script,
                                           -1,
                                           world_name,
                                           NULL,
                                           view->cancellable,
                                           autofill_cb,
                                           NULL);

    Because the untrusted data here is already used as a quoted data value, one of very few cases where it is safe to inject untrusted data into JavaScript , this would actually have been safe if only Epiphany had JavaScript-encoded the value first, following the OWASP rules for JavaScript encoding : “Encode all characters using the Unicode \uXXXX encoding format, where XXXX represents the hexadecimal Unicode code point. For example, A becomes \u0041 . All alphanumeric characters (letters A to Z, a to z, and digits 0 to 9) remain unencoded.” But Epiphany did not do so. (I got confused by the OWASP rules and didn’t realize how easy it was to make this safe, so I fixed it in a more complicated way instead, by removing the need for injecting the form ID .)

    So how bad is this mistake? In Fernando’s example, the ID of the evil form element is "a'); alert('XSS in private world'); var _=('" , allowing the malicious website to run any script it wants. That might not seem so serious, because websites don’t need to exploit any vulnerabilities to execute JavaScript… right?

    Script Worlds

    Websites are only supposed to be able to execute JavaScript in the default script world. Think of a script world as basically just a big namespace for all of your JavaScript: the default world is what the website itself uses, but desktop applications can create their own private script worlds in order to run their own scripts. In a private script world, you can manipulate the page’s DOM as usual, but you have a separate environment for executing JavaScript code, so you don’t have to worry about name clashes or scripts conflicting with each other. Also, website scripts cannot access your scripts.

    In practice, web browsers inject their own scripts into every web page in order to implement various browser features. Epiphany uses a script to find the best web app icon for a web page, for example. These scripts use a private script world that websites should never themselves have access to. But in this XSS attack on Epiphany’s form autofill implementation, the malicious website has managed to execute its script in the private script world. Now it can access whatever internal web browser features are available in that script world.

    Unfortunately, there’s one more relevant Epiphany feature implemented using scripts: the password manager. Epiphany’s password manager is necessarily exposed to its private script world because Epiphany needs to execute JavaScript code in the web page in order to autofill passwords. Although there were no relevant bugs in Epiphany’s password autofill code (which is totally unrelated to its vulnerable generic form autofill feature), this did not matter: if an XSS bug in any Epiphany feature can be abused to execute code in Epiphany’s private script world, that code can access the password manager and exfiltrate all the user’s saved Epiphany passwords for every website. (At least, probably , because I have not set up an attack website to test this. But I don’t see why it wouldn’t work!) So that’s pretty serious.

    Conclusion

    I requested a CVE for the autofill vulnerability earlier today, but nowadays CVE requests usually take a couple of weeks, so I don’t have one yet. It is fixed in Epiphany 50.6 and 49.9. If you don’t have those versions yet, don’t panic. To be exploited, you have to manually trigger form autofill by right clicking on a form and then selecting either “Autofill Personal Fields” or “Fill This Field,” so that makes it much less scary. Even more fortunately, users probably won’t ever do that, because selecting either option always causes Epiphany to reject all further mouse input, becoming unusable . Nobody has reported this bug before, so it seems safe to conclude zero people are using Epiphany’s form autofill feature!