Re: HTMLPurifier and the "Input" class

Shad Laws <shad-xpYdmXCiSuZWk0Htik3J/[email protected]> Sun, 7 Apr 2013 15:50:02 +0200
Newsgroups gmane.comp.web.gallery.devel
Message-ID <CA+z51A6RXHdT=EvRYDDAkVy=V37Gwuh5FCzg_TvbVqcs40LGXQ@mail.gmail.com>
Hey everyone,

Update: I tweaked/debugged the pull a bit more, and it seems to work!
https://github.com/gallery/gallery3/pull/274

As a quick test, I hacked some var_dump() calls into the bootstrap and
saw how the raw and clean values are populated for various get queries
(e.g. "index.php?foo|bar=hello_w<o>rld" gives "foo_bar=hello_w<o>rld"
as raw and "foo_bar=hello_wrld" as clean).

Thoughts?

Take care,
Shad

On 6 April 2013 18:00, 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 ]