Forum OpenACS Q&A: Bugtraq: cross site scripting

Collapse
Posted by Jade Rubick on
Has anybody tried finding CSS vulnerabilities on OpenACS? Jon?
Date: Sat, 23 Mar 2002 21:38:30 +0100
From: Berend-Jan Wever 
To: bugtraq 
Subject: Cross-site scripting.

This messages assumes basic knowledge about Cross-site scripting (CSS) and
it's implications. For a quick summary of its implications see the
bottom of
this message first.

I have recently done a "CSS marathon" and found _allmost_ every page I
tried
vulnerable within an half an hour. These include microsoft, altavista,
google, cnet, time, ebay, amazon, netscape, yahoo and redhat. This list
probably could have gone on forever if I had taken the time. I have
contacted every one of them about this (except for yahoo and ebay
because I
was unable to find a contact emailaddress or feedback form; if it takes
longer to find the contact info than to find the CSS, f#ck 'em) I am now
awaiting their respondses.

But the ease with which I CSS-ed the hell out of everyone of them got me
thinking. I'm not going to be the "beta-tester" slave for the whole
internet. The sites I contacted will probably just patch the one hole I
found so I will probably be able to find others and what about all the
sites
I haven't tried yet? Maybe there should be a "general advisory" going
out to
every webdesigner out there that CSS is as dangerous as it is common.
Feedback on the usefullness (or futility) of a "general CSS advisory"
would
be appreciated.


Berend-Jan Wever

--------------------------------------------
CSS implications

By opening a specially crafted URL in the targetted user's web browser
(for
instance when he visits your website or reads an email you sent him).
- read anything that user can read from the CSS-vulnerable site.
(addressbook, personal info, etc...)
- do whatever that user can do on the CSS-vulnerable site (send messages,
order stuff, change personal settings and passwords)
- spoof the contents of the CSS-vulnerable site (make somebody think he is
looking at www.foo.com while the contents of the page actually comes from
your site www.bar.com)
Collapse
Posted by Jon Griffin on
I haven't tried in a while, but I believe that we are NOT vulnerable since we don't allow most html and the request processor eliminates these problems.

I think there was another thread about this a while ago.

Collapse
Posted by Rich Graves on
If you use ad_page_contract religiously and don't drop protections
with :allhtml you should be pretty safe.

ACS also wins because of a page flow we usually find very annoying.
Give bad input, it tells you to hit your Back button and fix it.
Most web apps will helpfully give you a prefilled form with bad
input highlighted, sometimes forgetting to sanitize it first.

Starting with ACS 4.x and 3.4.10, ad_page_contract got an ingenious
-verify parameter. It should be used on sensitive pages.

Ben did a good cleanup job a while back, but it's possible that
openacs 3.2.5 could have some lingering problems.

CSS problems can crop up in unexpected places... ever use analog to
help visualize site traffic? http://www.analog.cx/security4.html

(Is it possible that some UNICODE tricks would slip pass the
ad_page_contract filters? How paranoid has the internationalization
team been?)

Collapse
Posted by Don Baccus on
ACS also wins because of a page flow we usually find very annoying. Give bad input, it tells you to hit your Back button and fix it. Most web apps will helpfully give you a prefilled form with bad input highlighted, sometimes forgetting to sanitize it first.

Proper use of the form builder in OpenACS 4.5 gives you the form back with highlighted errors, and making proper use requires that you bypass ad_page_contract.

This is something to investigate. We already know that we need to better integrate ad_page_contract and the form builder/template system, and flesh out the form builder so it knows about verified forms, etc.

Collapse
Posted by russ m on

This only loosely falls under the umbrella of CSS, but it is an attack that sprung to mind while thinking about this problem -

  • I register at the victim.com acs4 based site, and find out that my user_id is 9876
  • I craft a popup ad that contains 2 frames - a big irritating flashing frame that says "MAKE MEGABUCKS" or whatever, and a 1-pixel tall frame at the bottom that loads "http://victim.com/permissions/grant-2?object_id=0&party_id=98 76&privilege=admin"
  • I entice the victim.com site admin to visit a page containing this ad
  • I take advantage of my new admin rights on victim.com
  • If victim.com has some facility like the old portals module that allows admins to input executable TCL, I take advantage of my new admin rights on the machine that hosts victim.com

the point of the popup ad and frames is tht nobody looks at popups long enough before closing them to notice the 1-pixel tall bit of window that contains either an "unauthorised" error message or a "permissions updated on object 0" result

the lesson of CSS is that not only can't we trust input from users, but we can't trust that input from a user is actually from that user.

the only general solution to this attack I can see is to move the session and auth data from cookies into the URL, which would tend to suck - anybody have other ideas?

Collapse
Posted by Jon Griffin on
IF that works (and I am not sure it will), it was a serious breach of programming know for several years. All the code that I ever checked had a magic key from the preceding form that was passed thereby authenticating the submitted form.
Collapse
Posted by russ m on
well, here's grant-2.tcl (slightly mangled by <textarea>) -

# packages/acs-core-ui/www/acs_object/permissions/grant-2.tcl

ad_page_contract {

  @author rhs@mit.edu
  @creation-date 2000-08-20
  @cvs-id $Id: grant-2.tcl,v 1.2 2001/04/22 16:13:15 stevew Exp $
} {
  object_id:integer,notnull
  party_id:integer,notnull
  privilege
}

ad_require_permission $object_id admin

db_exec_plsql grant {
  begin
    acs_permission.grant_permission(:object_id, :party_id, 
:privilege);
  end;
}

ad_returnredirect "one?[export_url_vars object_id]"

which doesn't seem to require anything more than a valid auth cookie. and having just tried it myself, i can tell you it works.

patching this particular page sequence doesn't however fix the broader problem - a global solution would be preferable to having to code such checks into every page that carries out potentially dangerous operations (and audit that it had been done and done right).

i'm not sure how i'd approach putting together such a global solution, but i'm pretty confident it deserves further thought...

Collapse
Posted by Jon Griffin on
Definitly bad and needs to be fixed globally.

One way I can think of is to have all dml pages (i.e. ???-2.tcl) check to see that it was called from its expected url. Or another solution is to have all admin pages (for all packages) have checks in them (I haven't thought this through yet though), maybe a global random pool or something not guessable.

Thanks for finding this as it is almost show stopper from my point of view and I really think it needs a fix before release. I may be overruled on this point but it is a BIG security problem.

Collapse
Posted by Don Baccus on
One way I can think of is to have all dml pages (i.e. ???-2.tcl) check to see that it was called from its expected url.

Yeah, I think so...recently I added a page to acs-subsite/www/shared that will update a session property when referred by a link or "yes/no" confirmation page.

Check it out:

# acs-subsite/www/shared/session-update.tcl

ad_page_contract {

    @author Don Baccus (dhogaza@pacifier.com)
    @creation_date 2-Feb-2002

    @param session_property The array which describes the new session
           property.  The required elements are package, key, value, and
           referrer.  The referrer element should be set [ad_conn url].
    @param return_url The page to return to

    Update the given session parameter with the given value and
    redirect to the caller.

    Note that a session property should never be used alone to drive
    any action in the system.  Always use permissions or an equivalent
    check!

    In order to reduce the potential for harm that might follow from
    forgetting this principle the session_property array passed to this page
    is signed and verified.
} {
    session_property:array,verify
    return_url
} -validate {
    referrer_error {
        if { ![string equal $session_property(referrer) [get_referrer]] } {
            ad_complain "Expected referrer does not match actual referrer"
        }
    }
}

ad_set_client_property $session_property(package) $session_property(key) $session_property(value)
ad_returnredirect $return_url
Note that it checks to make sure the AOLserver referrer value matches the one signed and passed in (and it's only called internal to the toolkit). And other things are signed, too.

Now ... can one spoof referrer values in a way that breaks this?

We should make more use of signing/verification and checking that we're called from reasonable URLs.

Still, it's basically a client-side weakness. The fact that the user's machine can read things the user is allowed to read isn't the problem, the problem is that the hacker is allowed to send the resulting informatino back to themselves.

Collapse
Posted by David Walker on
Another reason administrators (or anyone else) should not be using an email
program that loads graphics from the internet for html mail.  This vulnerability
could be attacked that way.
Collapse
Posted by David Walker on
We need to be very careful what html we allow in the bboards as this same
trick could be exploited by placing a 1x1 graphic in any of these bboard
messages.  The administrators will be logged in when they read those
messages.
Collapse
Posted by russ m on
ad_page_contract's :verify (mentioned above) would do the trick
if we were willing to accept an extra confirmation page to sign
form vars and display them to the user before doing any DML.

The problem with a global no-programmer-effort-required
solution (if one is possible) is that it'd break user's ability to do
things like bookmark or link to specific bboard threads or
whatever, which is functionality that is well worth keeping. At the
very least the site developer needs to specify per-page "it
does/doesn't matter where the form values I'm using here came
from".

One possibility I've been thinking of is a required -secure or
-insecure option for ad_page_contract (basically integrating
magic token style validation into ad_page_contract). Pages
specified -secure would need to have an aditional form var
carrying a token previously retrieved by the user. As far as I can
see the only data that would need to be tracked/verified is the
token value and the user_id that it was issued to.

The problem with signing and verifying everything is that it
requires the stressed/tired/time-limited developer to say "this
page does something potentially dangerous, so I need to
include an extra verification step". I like the idea outlined above
as it requires no change to existing pageflow to secure what
already exists (just time to audit existing pages and flag them
appropriately), and it forces developers to at least think about
doing the right thing in the future.

Collapse
Posted by Tom Jackson on

Assuming that javascript is used to popup a form submission, it could also popup and read the form itself. That implies that any hidden form var used to validate the form would also be available to the javascript program.

One way around this, used on Altavista and other sites now is to require the user to decode a gif image of some badly distorted letters and numbers. Machines can't do that yet, so requesting the initial form will not help the hacker. Maybe an easier method, would be to require gif images of every user, or for every user to supply an image. You have to click on the correct image, matched with a random string, so the form can only be used once. The image could be randomly distorted with each view so you could not easily track the image.

Hmm, how about this: a big gif form button where some spot is marked, and the computer knows the coordinates, since a form submission will give the clicked coordinates, it might be possible to have a click inside some shape to qualify as a correct response. This could also be combined with a unique random variable that allows the form to only be used once.

Collapse
Posted by defunct defunct on
Related Security Issues

Folks, on a related note, when the ArsDigita guys came over to our offices about 18months ago, we discussed a particularly nasty denial-of-service vulnerability with them. They were in the process of providing a solution, but I've since lost track of whether it was ever addressed.

Basically, I seem to remember it being related to the use of system wide unique ids i.e. acs_object_id_seq.

There ar emany circumstances whereby one page creates a new id, passed somewhere else and the eventually recorded in the DB. As I understand it is was possible to return the contents of a page and alter such id values randomly.
Its of course difficult to do specific damage, but after a while the database becomes full of IDs which have yet to be extracted from the sequence, cuasing problems down the line.

A colleague of mine at the time created an AOLServer instance which acted as the client side, supplying and receiving requests from the service and returning spurious values back.

Does this problem still exist or is there an fix/approach I should be ware of?

Thanks
Simon

Collapse
Posted by David Walker on
I am of the opinion that creating an id and placing it in the html code is a bad
thing.  I once created 10 users (in OpenACS 3.2.5) and sent out confirmation
email for each one only to find out that since I had used the back button to do
it each one overwrote the previous one and I had really only added one user
and then overwritten them 7 times.

I think these types of values should either be written to a server side variable
or returned in an encrypted format.  The encrypted format method wouldn't
have stopped my problem but it could prevent people from replacing the id
with their own and overwriting important data.

Collapse
Posted by Dave Bauer on
Does workflow partially solve this problem, by storing the application state in the database? That is, to complete a certain action, certain steps must be taken. You have to pass through all the screens to get where you are going.
Collapse
Posted by Jon Griffin on
I still think the easiest way is to simply use a random number stored in a table that must be present for the dml to succeed.

Although it increases the use of the DB, the reality is mostly only admin pages need this security.

Collapse
Posted by Jonathan Ellis on
One way around this, used on Altavista and other sites now is to require the user to decode a gif image of some badly distorted letters and numbers. Machines can't do that yet, so requesting the initial form will not help the hacker.

If anyone's interested in doing something like this, I can give you the code I wrote for this in 3.2.

Collapse
Posted by Tom Jackson on

Jon,

I think the problem is that the javascript can execute anything in the context of the admin user, thus this script can first grab the page which creates the random value in the db. You have only made the attack slightly more difficult. The guy that broke the security of the sites above would probably not be detered by this, just add another half hour to write the javascript to extract the form variable.

The most secure solution is the AltaVista method. If Jonathan would provide the code, I would like to take a hack at a solution.

Also, the problem of passing object_id's around that are unused has a neat solution. The signed variable solution is not good, because although it validates the data, it still allows these values to be generated before they are used. I came up with a single page solution that prevents duplicate data from being inserted into the db, here it is:

# Test of uniqueness constraint
proc uniqueness_protection { input_value {property_name "form" } {module "up"} {error_message "Data Already Entered"} } {
  set session_id [ad_conn -get session_id]
  set sha_of_data [ns_sha1 $input_value]
  set session_form_p [db_0or1row session_property_query "select property_value from sec_session_properties
where session_id = :session_id AND
module = :module AND
property_name = :property_name"]

  if {$session_form_p && [string match $property_value $sha_of_data]} {
    ad_return_complaint 1 $error_message
    return -code return
  }
  # guess data is unique enough
  if {$session_form_p} {
    db_dml update_property_dml "update sec_session_properties 
set property_value = :sha_of_data 
where session_id = :session_id AND
module = :module AND
property_name = :property_name"
  
    } else {
  
    db_dml insert_property_dml "insert into sec_session_properties 
(session_id,module,property_name,property_value) values
(:session_id,:module,:property_name,:sha_of_data)"

    } 
  
    return 
}


uniqueness_protection $package_name $property_name $module $error_message


Collapse
Posted by Dave Bauer on
A simpler solution than the scrambled image one is just to ask for the password again.
Collapse
Posted by Jonathan Ellis on
I've put my code here
Collapse
Posted by Jon Griffin on
Another even better solution is to use gpg to sign admin pages (i.e. expect the key of the admin to complete the DML).

Of course there is no interface with gpg yet, so I will look into it as a priority on my part.

Collapse
Posted by David Walker on
If the admin's key is sent automatically that might not help this particular
problem.

nsopenssl does support client certs.  It is a instance-wide setting so I'm
looking into running nsopenssl twice on the same server.  Once for normal
users and once (at a different port or different IP) for administrators and
requiring administrators to supply a client cert.

Collapse
Posted by Rich Graves on
Of course, once you've typed the passphrase for your SSL cert once,
it provides no more protection than a cookie...

As cheesy as it is, I think checking Referer might actually be
helpful. It is trivial to spoof Referer, so you should not use
Referer alone for security, but I think it is nontrivial for a CSS
attacker to cause you to lie about Referer.

It might be as simple as overloading db_dml to check [ns_conn header
referer]. If it doesn't match [ad_parameter SystemURL] you present a
form with a bunch of [export_entire_form] hidden fields and make the
user click a "Yes I understand I'm submitting a form to
[ad_system_name]" button.

Very rough untested idea, but it might have some merit...

Collapse
Posted by Tom Jackson on

I think any solution that relies on information inside, or obtainable by, the client browser is doomed to failure. That should include client certs, unless the user has to type in a password for each use.

What I would like to know is if there is a method inside javascript of turning off or disabling javascript. Maybe certain operations can be removed. You can do this in TCL, by renaming, etc. This would allow a very simple solution to the problem: just include a javascript in the header that disables javascript. Then, unless the user with privliges is complicit, nothing will happen behind the scenes.

Collapse
Posted by Jade Rubick on
Here's CERT's advisory on cross site scripting:

http://www.cert.org/advisories/CA-2000-02.html