The issue here is that Josh wants to allow certain users (admins) to create content with <script> tags, but ensure that said users can't use JavaScript to gain superuser status or otherwise monkey with things they shouldn't. So while the "don't trust user content" approach is a good default, I don't think it applies in this case. And while this may not be//cross site, per se, it is still request forgery.
_Nik On 5/11/2012 7:13 PM, Russell Keith-Magee wrote: > On Sat, May 12, 2012 at 5:11 AM, Josh Cartmell <joshcar...@gmail.com> wrote: >> I work a lot with Mezzanine which is a CMS that uses Django. A >> security issue was recently revealed where an admin user, lets call >> him A, (they can post rich content) could put a cleverly constructed >> javascript on a page such that if a superuser, let's call her B, then >> visited the page it would elevate A to superuser status (a more >> thorough explanation is here: >> http://groups.google.com/group/mezzanine-users/browse_thread/thread/14fde9d8bc71555b/8208a128dbe314e8?lnk=gst&q=security). >> Apparently any django app which allowed admin users to post arbitrary >> html would be vulnerable. >> >> My first thought was that csrf protection should prevent this but alas >> that is not the case. The only real solution found is to restrict >> admin users from posting any javascript in their content, unless you >> completely trust the admin users. > This isn't a CSRF issue. CSRF stands for Cross Site Request Forgery. A > CSRF attack is characterised by: > > * A user U on site S, who has credentials for the site S, and is logged in. > > * An attacking site X that is visited by U. > > * Site X submits a form (by POST or GET) directly to site S; because > U is logged in on S, the post is accepted as if it came from U > directly. > > CSRF protection ensures that site X can't submit the form on the > behalf of U - the CSRF token isn't visible to the attacker site, so > they can't provide a token that will allow their submission to be > accepted. > > What you're referring to is an injection attack. An injection attack > occurs whenever user content is accepted and trusted on face value; > the attack occurs when that content is then rendered. > > The canonical example of an injection is "little johnny tables": > http://xkcd.com/327/ > > However, the injected content isn't just SQL; all sorts of content can > be injected for an attack. In this case, you're talking about B > injecting javascript onto a page viewed by A; when A views the page, > the javascript will be executed with A's permissions, allowing B to > modify the site as if they A. > > Django already has many forms of protection against injection attacks. > In this case, the protection comes by way of Django's default template > rendering using escaped mode. If you have a template: > > {{ content }} > > and context (possibly extracted from the database): > > <script>alert('hello')</script> > > Django will render this as: > > <script>alert('hello')<script> > > which will be interpreted as text, not as a script tag injected into your > page. > > That said, the protection can be turned off. If you modify the template to > read: > > {{ content|safe }} > > or > > {% autoescape off %} > {{ content }} > {% endautoescape %} > > or you mark the incoming string as "mark_safe" in the template > context, then the content will be rendered verbatim -- which means > that the Javascript will be executed. > > I'm not intimately familiar with Mezzanine or DjangoCMS, but based on > the nature of those tools (i.e., tools for building end-visible > content), I'm guessing they've marked content as safe specifically so > that end users can easily configure their CMS sites by putting HTML > into a field somewhere on the site. The side effect is that they're > implicitly saying that *all* user-supplied content is safe, which > provides the channel by which an attacker can do his/her thing. > > The lesson from this? Even when you think you can trust a user's > content, you can't trust a user's content :-) > >> My question is are there any other solutions to these sorts of >> problems? It seems like allowing an admin user to post javascript is >> reasonable, what is unreasonable is for that javascript to be able to >> elevate a user's privilege. Could improvements be made to the csrf >> mechanism to prevent this sort of user privilege elevation? > As I've indicated, there is a solution, and Django already implements > it. It involves escaping content, and has nothing to do with CSRF. > > In the case of Mezzanine, they've fixed the problem by implementing a > 'cleansing' process - i.e., still accepting the content as 'safe', but > post-processing it to make sure that it really *is* safe, by stripping > out <script> tags or anything else that might provide an injection > channel. > > While I can fully understand why Stephen has taken this approach for > Mezzanine, I'm not convinced it's a good answer in the general case. > CMS solutions are an edge case -- they actually *want* to accept HTML > content from the end user, so that it can be rendered. > > The problem with cleansing is that it doesn't fix the problem -- it > just narrows the attack window. Ok, so lets say your cleanser removes > <script> tags; that's fixed one obvious way to inject. But what about > <a href="…" onclick="alert('hello')"> That's still javascript content > that could be used for an attack; your attacker just needs to socially > engineer the user to click on the link. So, you update your cleaners > to strip onclick attributes -- at which point, the attacker finds a > new way to inject, or they find a bug in your cleansing library, or > they find the one input field on your site that you accidentally > forgot to cleanse… you're now engaged in an arms race with your > attackers. > > The default Django position of "don't *ever* trust user content" is > ultimately the safest approach, which is why Django implements it. > Django does provide a way to disable that protection, but it really > should be done as a last resort. > > That said -- we're always open to suggestions. If anyone has any good > ideas for preventing injection attacks (or any other type of attack, > for that matter), let us know. You can't have enough out-of-the-box > security. > > Yours, > Russ Magee %-) > -- You received this message because you are subscribed to the Google Groups "Django users" group. To post to this group, send email to django-users@googlegroups.com. To unsubscribe from this group, send email to django-users+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/django-users?hl=en.