Ngx_xss: Native support for cross-site scripting in an nginx

Hi, folks!

I’m delighted to announce the first release of our new module,
ngx_xss. This output filter module adds native support for simple
cross-site AJAX to the nginx server. Currently only cross-site GET is
implemented, but cross-site POST support is on our TODO list.

Here’s a small example using our ngx_echo module together:

location /foo {
    default_type "application/json";
    echo '{"errcode":400,"errstr":"Bad Request"}';

    xss_get on; # enable cross-site GET support
    xss_callback_arg callback; # use $arg_callback
}

Then accessing /foo?callback=blah gives the following response:

blah({"errcode":400,"errstr":"Bad Request"}
);

And the ultimate response Content-Type is set to
“application/x-javascript”, which can be overridden by the
“xss_output_type” directive like this

xss_output_type text/javascript;

By default, the ngx_xss module filter will skip responses with
Content-Type set to anything other than “application/json”. If that’s
not what you want, you can use the “xss_input_types” directive to
override that:

xss_input_types text/plain text/css;

This module can also be chained with other output filters like
ngx_rds_json:

xss_get             on;
xss_callback_arg    _callback;

location /query {
    drizzle_query "select name from products limit 0, 10";
    drizzle_backend my_mysql;

    rds_json on;
}

Then you can expect something like this when doing GET
/query?_callback=OpenResty.callback[32]

OpenResty.callback[32]([{"name":"Bike"},{"name":"Book"}]);

Be careful with the order of output filters while building nginx. The
ngx_rds_json filter expects a valid binary stream in the RDS format
while the ngx_xss filter expects some JSON text. If you don’t take the
order right, you’ll see your ngx_xss settings get completely ingored
in the final responses. Because the ngx_xss filter sees RDS first and
it ignores it due to its “application/x-resty-dbd-stream” content
type.

Below is the correct nginx configure command if you want to use
ngx_xss and ngx_rds_json together:

./configure \
      --add-module=/path/to/xss-nginx-module \
      --add-module=/path/to/rds-json-nginx-module \
      # more options omitted here...

You see, the order of adding output filters on the configure time is
just the reversed order that the output filters are actually applied
at runtime. Generally speaking, the nginx output filter chain is a
stack, not a queue :wink:

Only a very limited set of callback values is allowed to prevent
JavaScript injection. Valid callback values can be expressed using the
following (Ragel) grammar:

    identifier = [$A-Za-z] [$A-Za-z0-9_]*;

    index = [0-9]* '.' [0-9]+
          | [0-9]+
          ;

    main := identifier ( '.' identifier )*
              ('[' index ']')?

This is exactly the Ragel grammar used to generate the C validator
used by the ngx_xss module itself.

Sorry for writing long emails. Just be too lazy to write formal
documentation atm hence filling most of the docs into this
announcement :stuck_out_tongue:

Enjoy!
-agentzh

This is fantastic, and will definitely be used :slight_smile: Great work agentzh!

Posted at Nginx Forum:

Hi Agentzh:

Thanks very much for your great work.

I have a question need your help after review your code.

  1. Which field is checked in your function? Do we need to check
    cookie/url/content-length etc.?
  2. A decode function is used to decode the args, do we need deocde
    escape
    type? what about unicode and utf-8?

}

src *=* callback.data; dst *=* p;

ngx_unescape_uri(*&*dst, *&*src, callback.len,
        NGX_UNESCAPE_URI_COMPONENT);

thanks

Nexthop.

On Tue, Jan 26, 2010 at 6:20 PM, agentzh [email protected] wrote:

Enjoy!

Oops, forgot to give the links:

Project home page & code repository:

http://github.com/agentzh/xss-nginx-module

Download page for release tarballs:

http://github.com/agentzh/xss-nginx-module/downloads

Have fun!
-agentzh

agentzh wrote:

I’m delighted to announce the first release of our new module, ngx_xss. This output filter module adds native support for simple cross-site AJAX to the nginx server. Currently only cross-site GET is implemented, but cross-site POST support is on our TODO list.

Am I the only one wondering what’s the use of this module? It seems to
just add a string and a pair of parentheses around the response. Can’t
you do that on the backend, assuming you have some sort of backend? Or
on the client side, if the response is to be parsed by some client-side
javascript?

I don’t mean to belittle your effort, I’m just curious!

Tobia

It is probably best to interact with ajax - then there is no middle
tier of php, python, whatever. Slimming the stack down a bit… That
being said I would be really cautious as to what you expose through it
of course.

Sent from my iPhone

On Jan 28, 2010, at 1:11 PM, Tobia C. [email protected]

On Thu, Jan 28, 2010 at 4:45 PM, quan nexthop [email protected]
wrote:

  1. Which field is checked in your function? Do we need to check
    cookie/url/content-length etc.?

I don’t think that I understand your question. I’m guessing that you
mean access control mechanism? If yes, then that’s for another module
to work together with it, like ngx_encrypted_cookie.

  1. A decode function is used to decode the args, do we need deocde escape
    type? what about unicode and utf-8?

It’s based on octet decoding, no charset is involved here. Either
Unicode or UTF-8 should be fine, as well as other charsets. It depends
on the client side to interpret the charset, not ngx_xss :slight_smile:

Cheers,
-agentzh

On Fri, Jan 29, 2010 at 5:11 AM, Tobia C.
[email protected] wrote:

Am I the only one wondering what’s the use of this module?

The initial motivation of writing this module is to build a
full-fledged blog app that is powered completely by nginx.conf and
client-side JavaScript. I already have something runnable now. Here’s
the nginx.conf that I’ve got so far if you’re interested:

http://agentzh.org/misc/nginx.conf

It seems to just add a string and a pair of parentheses around the response.
Can’t you do that on the backend,

In the demo app mentioned above, the only backend is the mysql
database. Are you sure you want mysqld to do this?

Also, even if you have a real backend app running an upstream, then
you still have a chance to do this in C, also in a streaming fashion
:wink:

(Just as I mentioned in another thread here, we often have to handle
tens of millions of requests per day on 1 or 2 machines, so
eliminating unnecessary cost is very important.)

assuming you have some sort of backend?

Not really in your definition of “backend” :wink:

Or on the client side, if the response is to be parsed by some client-side javascript?

This is the classic cross-site GET trick for JavaScript programmers.

I don’t mean to belittle your effort, I’m just curious!

Well, it’s understandable :slight_smile:

Cheers,
-agentzh

2010/1/29 agentzh [email protected]:

  http://agentzh.org/misc/nginx.conf
You can set “document.domain” in JS and then have a domain
blog.xyz.com, say static pages with header and footer, for example on
a CDN, and a domain api.xyz.com which does your actual magic.

Therefore drizzle and rds_json module (btw, see my issue on Github)
seem to me being the main parts. xss would cover the case where the
blog’s (2nd level) domain differs from the one to serve the JSON
responses.

Thanks for sharing!


Mark

[1] NGINX 3rd Party Modules | NGINX
[2] NGINX 3rd Party Modules | NGINX

On Sat, Jan 30, 2010 at 4:05 AM, W-Mark K.
[email protected] wrote:

Therefore drizzle and rds_json module (btw, see my issue on Github)

I think I’ve fixed that compilation issue on x86_64 in the v0.04
release:

http://github.com/agentzh/rds-json-nginx-module/downloads

Could you please confirm the fix? Thanks for the report :slight_smile:

seem to me being the main parts. xss would cover the case where the
blog’s (2nd level) domain differs from the one to serve the JSON
responses.

Yup, indeed :slight_smile:

Thanks for sharing!

You’re very welcome :slight_smile:

Cheers,
-agentzh

agentzh wrote:

The initial motivation of writing this module is to build a full-fledged blog app that is powered completely by nginx.conf and client-side JavaScript. I already have something runnable now.

Wow!
This sounds very cool.

Can’t you do that
on the client side, if the response is to be parsed by some client-side javascript?

This is the classic cross-site GET trick for JavaScript programmers.

I guess this is the part I’m not clear about… I usually just fetch
stuff with jQuery and then process it on the client-side as I see fit.
Also, looking up xss on Google only gives results about browser
vulnerabilities.

Tobia