Preventing XSS in Actix-web

Hello

I have a Actix/Web project using Askama for HTML templates.
When outputting a value in Askama using {{ example_variable }} it is automatically encoded in a way to prevent XSS.

The problem I have now is that I also often request data using AJAX and parse the response in my HTML, this is prone to XSS-attacks.

I somehow need to encode my response to not allow XSS-queries.

For example this function which gets called by AJAX:

pub async fn get_restaurant(req: HttpRequest, mysql: web::Data<MySQL>) -> Result<HttpResponse> {
    let restaurant_id = req.match_info().get("id").unwrap().parse::<u64>().unwrap();
    let restaurant = data::get_restaurant(Selector::ById(restaurant_id), &mysql)
        .await
        .unwrap();

    Ok(HttpResponse::Ok()
        .content_type("application/json")
        .append_header(("Cache-Control", "max-age=604800"))
        .body(serde_json::to_string(&restaurant).unwrap()))
}

If restaurant.name is <script>alert(0)</script> the Javascript gets executed. Is there a way to encode this in a way that prevents XSS?

I tried for example the sanitize_html crate.

sanitize_str(&DEFAULT, &order.restaurant.name).unwrap()

Which works. But then no string is shown while I'd rather have that the XSS-string is displayed but does not get executed/interpreted. And if I have to do this for each output I'm afraid I may miss a line somewhere. Is there a more efficient way?

Hmm, the function you provided won't execute anything, because its content type is JSON, not HTML. It's your template interpolations that are vulnerable (but I'm sure you already knew that).

Askama should be doing this sanitization for you already. In fact, I see some clear indicators just with a brief look through the docs: Escaper in askama_escape - Rust (docs.rs) There's a whole crate called askama_escape that it uses for this purpose.

Yes Askama does this automatically. But I am asking about data I am requesting outside Askama (from AJAX for example) and putting that in the DOM.

This might be more about how you handle the response in the client, and the right solution depends on whether you already have some frameworks you’re working with.

With nothing special, you can use innerText to avoid XSS, like

.then((data) => {
  const span = document.createElement('span');
  span.innerText = data.name;
  document.querySelector('#dest').appendChild(span);
});

but if you’re looking for something to do templating on the browser, things like Lit HTML will use the content safely:

.then((data) => {
  const dest = document.querySelector('#dest');
  render(html`<span>${data.name}</span>`, dest);
});

How are you putting it in the DOM (if not with askama)?

And what if I do something like this?

let html = `
            <tr>
                <td>`+row.value+`</td>
            </tr>
        `;

how to escape row.value?

It’s not really safe to construct HTML using concatenation, on either the server or the client. If you don't want to use a template library on the browser, you can do something like

let td = document.createElement('td');
td.innerText = row.value;
let html = '<tr>' + td.outerHTML + '</tr>';

but you’re going to have a horrible time keeping track of which strings are safe and which are not.

My preference if you don't go for a full framework like Vue, Svelte, React, Lit, etc., is to use something standalone like Lit HTML:

let content = html`<tr><td>${row.value}</td></tr>`;

You can use Lit HTML from a pre-built bundle as described at https://lit.dev/docs/getting-started/#use-bundles, like

import {html, render} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';

as long as you load your code with <script type="module">.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.