Re: HTMLPurifier and the "Input" class

Bharat Mediratta <[email protected]> Sun, 7 Apr 2013 11:04:45 -0700
Newsgroups gmane.comp.web.gallery.devel
Message-ID <CAESa+_ktUvWnMw2k0hdbw200dCtgLHqn649p9x+f4k_85qDeJQ@mail.gmail.com>
I just reviewed the Purifier pull request and it looks pretty good.  I'm
not sure which approach to go with either - having Purifier as a module
definitely has some drawbacks especially because we're really considering
it a mandatory and non-overridable part of the codebase.  But let's try it
as a module for now and see where it leads.  Looking good so far!


On Sat, Apr 6, 2013 at 9:00 AM, Shad Laws <shad-xpYdmXCiSuZWk0Htik3J/[email protected]> wrote:

> Hey Bharat,
>
> Congrats on getting a controller going!  I feel like we're getting
> close to having this thing run...
>
> Big picture updates:
> - I went ahead and added formo and pagination.  Since they were
> entering into the conversation, now seemed like a good time to do this
> so we can view them more easily.  Of course, I also removed forge and
> kohana23_compat.
> - I closed the previous pull request for the Input class draft.
> - I opened a new pull request here:
> https://github.com/gallery/gallery3/pull/274
>
> Looking through more code, it does indeed seem like the preferred way
> to interact with $_GET, $_POST, and $_COOKIE is via the request.  This
> is true almost everywhere in the K3 core (except URL::query()) and
> also holds true for...
> - Formo. See Formo::save() and Formo::load(), the later of which
> defaults to request but will accept anything.  This makes
> Formo::load() a good place to reinject raw post values for modules
> that require it.
> - Pagination: See Pagination::setup(), Pagination::url(), and
> Pagination::query(), the last of which is described as a "URL::query()
> replacement for Pagination use only"
>
> That said, *relying* on everyone to do so (and requiring
> Gallery::ready() to jump through hoops to get there) doesn't seem like
> a great policy.  Rather, it seems like an accident waiting to happen.
>
> So, I agree with you: we should overwrite the superglobals after
> they're cleaned, then provide the raw values separately.  Next
> question: how?
>
> --
>
> I spent a lot of time thinking about this yesterday and today, and
> think I've come up with a pretty good solution (see pull referenced
> above).  Here's the gist of it:
> - move purifier code to its own module
> - load this module in bootstrap right after Kohana::init() (and
> therefore after Kohana::sanitize()) but before anything else
> - make a RAW class that defines three static variables
> - clean the superglobals and fill the RAW variables with the
> pre-HTMLPurifier values
>
> Then downstream, we can access the values using:
> - Request::$current->query(), Request::$current->post(), and
> Request::$current->cookie()  (clean; preferred method)
> - $_GET, $_POST, and $_COOKIE (clean, except cookie signing isn't checked)
> - RAW::$_GET, RAW::$_POST, and RAW::$_COOKIE (not HTMLPurified)
>
> Additionally, I've beefed up the Purifier class so module devs can
> load new instances of HTMLPurifier with a different config set.  So,
> to re-process form data, you should be able to do something like this:
>
> Purifier::add_config_group("foo", $foo_settings);  // $foo_settings is
> an array of HTMLPurifier config values
> $bar = Purifier::purify(RAW::$_POST["bar"], "foo");   // purifies bar
> using the foo settings
> Request::$current->post("bar", $bar);  // updates the bar value
> Formo::load();  // uses the new bar value
>
> Summary: we have a nice default path that just works, with enough
> extras to make it pretty extensible.  Kinda cool, eh?
>
> --
>
> Next question: why did I decide to use a separate purifier module and
> load it before everyone else?
>
> I struggled with this a bit, and it's a big cart-before-the-horse
> problem.  It's hard to be first while also being relatively flexible
> to everyone else's whim, it's hard to be special while still mostly
> fitting in, and it's kind of a pain that the superglobals don't work
> with variable variable names (i.e. $foo = "_GET"; $$foo = $something).
>
> I quickly came to the conclusion that I needed to load *something*
> before everyone else (the special part), and it was best if that
> something was a Kohana-compatible module (the fitting in part).  I had
> three options:
> - use the gallery module.  This reduces our module count by one, but
> also doesn't neatly divide out our Purifier class (which is special in
> that, since it's first, it can't be transparently extended).  This
> didn't feel right.
> - use the application folder.  This segregates it nicely and removes
> the need to call Kohana::modules() an extra time, but also makes our
> application folder not so spartan.  This also didn't feel right.
> - use a new purifier module.  This segregates the code, calls it out
> as special (while still mostly fitting in), and leaves our spartan
> application folder intact.
>
> So, I went with option 3.
>
> --
>
> Anyway, thoughts?
>
> Take care,
> Shad
>
>
> On 6 April 2013 04:57, Bharat Mediratta <[email protected]> wrote:
> >
> > Before this email I had mocked up an Input API and have managed to plumb
> a
> > line all the way through to successfully rendering a minimal controller
> > (WOOT!).  But I went back and changed it over to using Request::$current
> > instead.  This causes a problem in that we do some mechanics in
> > Gallery::ready() before the request is created.  The main thing I hit is
> > that Gallery_Hook_GalleryEvent::gallery_ready() calls Theme::load_themes
> > which:
> >
> > 1) Cares about $_GET["kohana_uri"] if there's no $_SERVER["PATH_INFO"].
>  We
> > need this to work when we're using mod_rewrite (see the mappings at the
> > bottom of gallery3/.htaccess)
> >
> > 2) Cares about $_GET["theme"] which is the mechanism we use to let admins
> > preview a theme
> >
> > The request isn't built until we do it at the very end of index.php,
> right
> > after bootstrap.  Any thoughts on how to play this?  I'd rather not make
> > exceptions to our security flow and use $_GET directly...
> >
> > -Bharat
> >
> >
> >
> > On Fri, Apr 5, 2013 at 10:30 AM, Bharat Mediratta <[email protected]>
> > wrote:
> >>
> >>
> >> Outstanding research, Shad.  TL;DR: I agree with you.  Notes inline
> below.
> >>
> >>
> >> On Fri, Apr 5, 2013 at 8:29 AM, Shad Laws <shad-xpYdmXCiSuZWk0Htik3J/[email protected]> wrote:
> >>>
> >>> Hey everyone,
> >>>
> >>> Some updates:
> >>> - HTMLPurifier - Standalone v4.5.0 is included in gallery.
> >>> - Purifier - I wrote a Purifier class and config file (*strongly*
> >>> based on the 3.0.x purifier module) to load a single instance of it
> >>> and purify things (as Purifier::purify($str)).
> >>> - Input::instance()->ip_address - changed to K3's Request::$client_ip,
> >>> so we no longer need the Input library for this.
> >>> - Input - wrote a draft of it
> >>> (http://github.com/gallery/gallery3/pull/269)...
> >>>
> >>> ... but as I learn more about K3 and the form-handling module Formo,
> >>> I'm starting to think it's not the right approach.  I'm pretty happy
> >>> with the first three steps, but think that the fourth step (the Input
> >>> class draft) won't play well with the Request class or the Formo.
> >>>
> >>> tl;dr: I don't think we should add an Input class, and think that it'd
> >>> be a better fit in the K3 framework to add code to Gallery::ready() or
> >>> bootstrap and some overrides to the Request class.
> >>>
> >>> ----
> >>>
> >>> K2 approach with global_xss_filtering = true (i.e. how Gallery 3.0.x
> >>> works)
> >>>
> >>> - PHP fills the GPCS superglobals for us
> >>> - Input::clean() cleans GPCS at init and overwrites the superglobals
> >>> - Input library returns GPS values from these arrays without further
> >>> processing
> >>> - Input library returns C values after running them through
> >>> cookie::get() to check for signed cookies and remove invalid ones
> >>> - nobody downstream has access to the raw values
> >>>
> >>> Cleaning consists of:
> >>> - GPCS: remove control chars, convert to UTF8 and remove incompatible
> >>> chars
> >>> - GPC: clean keys (except cookie $Version, $Path, and $Domain),
> >>> standardize newlines, remove magic_quotes if needed, XSS clean
> >>> - S: none
> >>>
> >>> Details on the filters:
> >>> - clean keys: fast, simple, secure regex in MY_Input
> >>> (preg_replace("/[^0-9a-zA-Z:_.-]/", "_", $str)).  This is okay.
> >>> - XSS clean: less fast, less simple, less secure regex in Input.  This
> >>> is problematic.
> >>>
> >>> ---
> >>>
> >>> K3 approach as-shipped
> >>>
> >>> - PHP fills the GPCS superglobals for us
> >>> - Kohana::sanitize() cleans GPC globals at init and overwrites the
> >>> superglobals
> >>> - Request::factory() stores G as $request->query($_GET)
> >>> - Request::factory() stores P as $request->post($_POST)
> >>> - Request::factory() stores C as $request->cookie($cookies), where
> >>> $cookies is $_COOKIE processed to check signed cookies
> >>> - most things downstream are expected to get their GPC values from
> >>> Request, which is easily accessible from any controller (*)
> >>> - $_SERVER is not touched and always accessed directly
> >>>
> >>> Cleaning consists of:
> >>> - GPC: standardize newlines, remove magic_quotes if needed
> >>> - S: none
> >>>
> >>> Removed from the list:
> >>> - remove control chars, convert to UTF8 and remove incompatible chars.
> >>>  This is handled by UTF8::clean(), which is not called.
> >>> - clean keys
> >>> - XSS clean (obviously)
> >>>
> >>> (*) exception: URL::query() accesses $_GET directly, but nobody else
> >>> seems to use this function.
> >>
> >>
> >> URL::query is a somewhat dangerous function because it allows parameter
> >> pollution attacks.  Ie, if you go to /index.php?action=logout and we use
> >> URL::query to generate any new urls on the page (which is sometimes what
> >> things like the paginator do) then you trick the user into clicking a
> URL
> >> which has an extra bad parameter in it AND that url might have a CSRF
> token
> >> in it so it might get accepted by the server.  Just a thought.
> >>
> >>>
> >>>
> >>> ---
> >>>
> >>> So, given that, here's my current thought on what we should add at
> init:
> >>> - GPCS: call UTF8::clean() to process GPCS, overwrite the original
> values
> >>> - GPC: clean keys (simple, secure regex like before), overwrite the
> >>> original values
> >>> - GPC: clean values using Purifier class, put into Request object but
> >>> not into superglobals
> >>
> >>
> >> So this would mean that the superglobals would contain the raw value?
> >> Would it be safer for us to duplicate the superglobals into $_RAW_GET
> for
> >> example then clean it into $_GET and just use the superglobals?
> >>
> >>>
> >>> - S: clear HTTP_HOST to force folks to use SERVER_NAME instead
> >>>
> >>> This can be done by adding:
> >>> - class Gallery_Request extends Kohana_Request, with post(), query(),
> >>> and cookie() that, if given a full array, purify it before calling its
> >>> parent function.  We can make an exception for csrf to run a faster,
> >>> simpler [0-9a-f] regex.
> >>> - extra stanza in Gallery::ready() (or bootstrap) that calls
> >>> UTF8::clean(), does unset($_SERVER("HTTP_HOST")), and cleans keys.
> >>
> >>
> >> I'd prefer to do this processing earlier in the bootstrap so that it's
> >> very clear when this is happening and it happens before we load modules
> >> (because in K3 when you load modules they can execute code in their
> init.php
> >> file.
> >>
> >>> - (optional) override URL::query() to make it clean $_GET before using
> >>> it.  This isn't strictly needed by Gallery's core, but would be
> >>> security against other devs using it and accidentally introducing XSS
> >>> insecurities.
> >>
> >>
> >> That is probably a good idea.  Or if we just clean the values in $_GET
> >> then it's not necessary to override URL (but it still doesn't protect us
> >> from pollution attacks).
> >>
> >>>
> >>>
> >>> Then, to access them:
> >>> - GPC cleaned: use Request object from controllers ($request->query(),
> >>> $request->post(), $request->cookie())
> >>> - GPC raw: use $_GET, $_POST, and $_COOKIE directly
> >>> - S: use $_SERVER directly
> >>>
> >>> Which means that:
> >>> - Input::instance()->get($key) becomes $request->query($key)
> >>> - Input::instance()->post($key) becomes $request->post($key)
> >>> - Input::instance()->cookie($key) becomes $request->cookie($key)
> >>> - Input::instance()->server($key) becomes $_SERVER($key)
> >>> - Input::instance()->post($key, $default) becomes
> >>> Arr::get($request->post(), $key, $default)    (and likewise for get,
> >>> cookie, and server using defaults)
> >>> - references to these in non-controllers needs to be moved to their
> >>> respective controllers.  I've perused the code, and don't think this
> >>> would be a big deal.
> >>> - K3 and Formo should use our cleaned values by default :-)
> >>
> >>
> >> So do K3 and Formo use the Request class or the superglobals?  I guess
> >> that dictates what we do as well.  We should stick to their convention
> as
> >> best as possible.
> >>
> >> Either way - this looks good to me.  Get the code in and let's start
> using
> >> it!
> >>
> >>>
> >>>
> >>> ---
> >>>
> >>> Whew - that writeup took awhile!  Thoughts?
> >>>
> >>> Take care,
> >>> Shad
> >>>
> >>>
> >>>
> >>> On 2 April 2013 18:23, Shad Laws <shad-xpYdmXCiSuZWk0Htik3J/[email protected]> wrote:
> >>> <snipped>
> >>> >>>
> >>> >>> Input
> >>> >>>
> >>> >>> This library is gone with K3.  Instead, they recommend working with
> >>> >>> $_GET,
> >>> >>> $_POST, and $_SERVER directly.  Handling unset and default values
> can
> >>> >>> be
> >>> >>> handled using Arr as:
> >>> >>> Arr::get($_POST, "foo", "default foo");
> >>> >>>
> >>> >>> This part is pretty straightforward.  The only catch is that they
> >>> >>> need
> >>> >>> cleaning first.
> >>> >>>
> >>> >>> It was recommended to use Security::xss_clean() with K3.0, but
> that's
> >>> >>> since disappeared.  I'm looking for the best alternative, which
> >>> >>> shouldn't be
> >>> >>> too hard to find, but think the bigger question is this: *where* do
> >>> >>> we do
> >>> >>> this so nobody downstream has to think about it?
> >>> >>> - bootstrap?
> >>> >>> - an extension of Route?
> >>> >>> - an extension of Controller?
> >>> >>> - somewhere else?
> >>> >>
> >>> >>
> >>> >> The XSS cleaner in K2 was questionable at best.  We augmented it
> with
> >>> >> our
> >>> >> own tweaks:
> >>> >>
> >>> >>
> https://github.com/gallery/gallery3/blob/master/system/libraries/Input.php#L306
> >>> >>
> >>> >> Even that is pretty crappy and I don't trust it.  We really need to
> be
> >>> >> very
> >>> >> suspicious of any content in the database and treat it very
> carefully
> >>> >> when
> >>> >> we use it.  For K2 we overloaded html::clean to make it safe:
> >>> >>
> >>> >>
> https://github.com/gallery/gallery3/blob/master/modules/gallery/helpers/MY_html.php#L32
> >>> >>
> >>> >> Then we used that (and all the other cleaning methods) everywhere.
>  My
> >>> >> assumption has always been that with html::clean AND the XSS
> cleaning
> >>> >> code
> >>> >> we don't have to catch every single vector of attack, we've got
> enough
> >>> >> defense-in-depth to make hacking hard.
> >>> >>
> >>> >> The downside, though is that we are always possibly cleaning away
> >>> >> something
> >>> >> that we really care about.  And cleaning is expensive.  :-/
> >>> >>
> >>> >> So options:
> >>> >> 1) Don't bother cleaning at all
> >>> >> 2) Create our own simple API for getting GET/POST/SERVER variables
> >>> >> that does
> >>> >> cleaning and handles defaults
> >>> >> 3) Clean everything up front
> >>> >>
> >>> >> I'm in favor of #2 with an API like this:
> >>> >>
> >>> >> Input::GET("foo", "default foo");
> >>> >> Input::GET("foo", "default foo", Input::RAW);  // don't sanitize
> >>> >> Input::POST("foo", "default foo");
> >>> >> Input::SERVER("foo", "default foo");
> >>> >>
> >>> >
> >>> > Me too.  I've been taking a look at how we'd integrate the standalone
> >>> > HTMLPurifier, and it looks like we can be a bit faster if we use a
> >>> > singleton instance() function.  With this, we could *exactly* copy
> the
> >>> > old API (or at least the parts that Gallery used).  The instance
> would
> >>> > initialize the class, fire up and configure HTMLPurifier once, and
> set
> >>> > up an empty cache of purified entries.  Then, we purify them on
> demand
> >>> > and stick them in the cache so we shouldn't need to do it twice.  I
> >>> > haven't yet figured out if it makes sense to do them one superglobal
> >>> > array at a time or one entry at a time, but certainly it doesn't make
> >>> > sense to do all at once (for example, one might load Input to look
> for
> >>> > $_POST but never care about $_SERVER).
> >>> >
> >>> > I also like the Input::RAW idea.  It should come with a big
> >>> > disclaimer, and really shouldn't be needed too often since we can
> >>> > accept HTML markup, but it's still nice to have.
> >>> >
> >>> >> This has some advantages:
> >>> >> 1) It's close to what we have now so conversion is easier
> >>> >> 2) We can lazy-clean the input for efficiency
> >>> >> 3) We can use Input::RAW as a findable symbol for all places where
> we
> >>> >> work
> >>> >> with raw data (set the RAW constant to some wacky value so that API
> >>> >> users
> >>> >> have to use the constant)
> >>> >> 4) profit?
> >>> >
> >>> > Yes, yes, yes, and hell yes.  :-)
> >>> >
> >>> >
> >>> >> Regarding HTMLPurifier - it added so much weigh to the installed
> code
> >>> >> size
> >>> >> that I was loathe to make it a default module.  Unless it's gotten a
> >>> >> lot
> >>> >> smaller, I suspect I'll still feel the same way...
> >>> >
> >>> > The standalone version you found seems to be not *too* bad.  It's
> <1MB
> >>> > (<300k compressed) and seems like it was designed to play nice with
> >>> > caches.
> >>> >
> >>> > My feeling is this: we should build the Input class and make it use
> >>> > HTMLPurifier.  Then, once we're back in fully-running state, we can
> >>> > run some benchmarks to see how heavy it really is.
> >>> >
> >>> > Thoughts?
> >>> >
> >>> > Take care,
> >>> > Shad
> >>> >
> >>> >
> >>> >>
> >>> >> thoughts?
> >>> >> -Bharat
> >>> >>
> >>>
> >>>
> >>>
> ------------------------------------------------------------------------------
> >>> Minimize network downtime and maximize team effectiveness.
> >>> Reduce network management and security costs.Learn how to hire
> >>> the most talented Cisco Certified professionals. Visit the
> >>> Employer Resources Portal
> >>> http://www.cisco.com/web/learning/employer_resources/index.html
> >>> __[ g a l l e r y - d e v e l ]_________________________
> >>>
> >>> [ list info/archive --> http://gallery.sf.net/lists.php ]
> >>> [ gallery info/FAQ/download --> http://gallery.sf.net ]
> >>>
> >>
> >
>
>

------------------------------------------------------------------------------
Minimize network downtime and maximize team effectiveness.
Reduce network management and security costs.Learn how to hire 
the most talented Cisco Certified professionals. Visit the 
Employer Resources Portal
http://www.cisco.com/web/learning/employer_resources/index.html

__[ g a l l e r y - d e v e l ]_________________________

[ list info/archive --> http://gallery.sf.net/lists.php ]
[ gallery info/FAQ/download --> http://gallery.sf.net ]