Jump to content

more security in regards to $page->id


Recommended Posts

Here is a security related feature request.
I am having more and more use of $page->id as a GET or POST parameter, for various workflows in frontend site.
Processwire itself is making use of it at some places related to frontend, eg. for comments submission workflow.
My problem is : This is an absolute AND predictable value : from 0 to N. So, when used for submission by the users, it allows a malicious user to forge requests in order to perform a FULL crawling of the website pages. Even pages that are otherwise not accessible by following the website links. Of course, Processwire access permissions apply ; but then, any site-specific permission weakness will result in information disclosure. Overall, this is not very satisfying.
What would be best, instead, is the ability to make use of an absolute AND NOT predictable value : a $page->encodedId (build with something like http://hashids.org/php/)
Along with a commodity method getDecodedId(), for retreiving the associated $page->id.
Fact is, I am doing something similar to this in the templates which need it. 
And, for easier usage, I plan to generalize this to all templates, with some coding which implies hooks on template creation & on page creation, for automatically adding a $page->encodedId field at each template creation + automatically populating its value at each page creation/cloning.
But before I go into this, I would like to submit this feature request : I would rather have this in Processwire core :-)
Processwire itself would directly benefit from this feature (see comments submission workflow, for instance).
I hope it makes sense for someone else than me :-)

Edited by er314
Fixed hashids link
  • Like 3
Link to comment
Share on other sites

+1 for this request. 

While it's always in the hands of the dev to prevent malicious access to content, even if someone does fiddle with some id's, it's still an added layer of obstruction. This can for example be handy when using id's on the frontend site, like as urlSegments.

  • Like 1
Link to comment
Share on other sites

  • 4 months later...

Or... another solution, for not adding the burden of needing new fields : have PW migrate to using unguessable IDs, instead of the current 1-to-N scheme.

In practice, make all page IDs for newly created pages unguessable, à la youtube hashes.

This would mean, for a fresh install, make all IDs this way. -> As an install option, for compatibility with legacy applications. And probably for some "root" IDs it would be hard and/or too much implications to follow this path.

Yes, I know : even though at first sight things looks clean from the API side for going towards this path ($config->xxxxxPageID , $user->isXxxxx) , I know that things must be much more complex than this :

1. there's the problem of legacy applications, which probably for most of them don't make full use of $config->xxxxxPageID  and $user->isXxxxx

    -> hence being optional at install time and at runtime

2. there must be a huge number of implications in PW core ; and potentially in many modules.

But hey, this is for opening the discussion. Probably another lost cause :-)


Link to comment
Share on other sites

While I liked the idea of having a new method, which would provide an encrypted id (autogenerated salt like for the user passwords) I don't think there's any necessity to change the underlying id system, especially as this is a default behaviour of mysql. Do you have any usecase examples, where this would be needed? 

  • Like 1
Link to comment
Share on other sites

No, I have absolutely no use case where this alternative proposal would be needed in place of the 1st method.

I was just trying to see if it's simpler/cleaner than the 1st method.

My rough guess was that, it might be cleaner from a public API perspective (no new method needed), but much more complex internally. You are just confirming this, in regards to mysql :-)

Link to comment
Share on other sites

TL;DR Everything could already use the paths instead of the ids; it just needs some work to implement. If the ids are so unwelcome, why not go and work on the right kinda complicated stuff instead of adding unnecessary (and probably useless) complexity by inventing an obscure layer of "protection"?


There's already an unambiguous 1-to-1 relationship between paths and ids; not using paths at certain places (e.g. assets and edit pages and whatnot) is not an architectural decision but that of convenience and performance. In other words, nothing stands in the way for the whole system to use the paths everywhere, i.e. /my/awesome/page/edit instead of /admin/page/edit/?id=3811 -- one can just set up a few .htaccess rules, rewrite (or hook?) whatever generates the ugly edit links, and done.

Or, almost. It gets more involved for the asset paths, where there are two options I can think of. A) In the current storage, where the asset directories are addressed by the page id, Apache would need to consult the DB to resolve the paths to the ids. And that needs a module, which raises the bar for hosting. B) Then there is the option to use the page paths within the assets directory, but that may be a bit messy when moving / renaming things (the DB and the file system can get out of sync when something happens, so some journaling would be necessary.)

P.S. I did use SHA based page ids before, to make scraping super hard (random access impossible) so I do agree that it has legit use cases. "Security" (i.e. "I don't want the baddies know too much about how I store my data" and the like) isn't one of them. Or am I missing something?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Similar Content

    • By MoritzLost
      I've been working with ProcessWire for a while now, and I've noticed that using Composer to manage dependencies and autoload external libraries isn't as prevalent in ProcessWire development as in other areas of PHP programming. I started out by using the default setup recommend in this blogpost. However, one major problem I have with this approach is that all external dependencies live in the webroot (the directory the server points to), which is unfavourable from a security standpoint and, in my opinion, just feels a bit messy.
      In this tutorial, I want to go through a quick setup of Composer and ProcessWire that keeps the dependencies, all custom-written code and other source material outside of the webroot, and makes full usage of the Composer autoloader. This setup is pretty basic, so this tutorial is probably more useful to beginners (this is why I'll also include some general information on Composer), but hopefully everyone can take something away from this for their personal workflow.
      Site structure after setup
      This is what the directory structure can look like after the setup:
      . ├── composer.json ├── composer.lock ├── node_modules │   └── ... ├── public │   ├── index.php │   ├── site │   ├── wire │   └── ... ├── packacke-lock.json ├── package.json ├── sass │   ├── main.scss │   ├── _variables.scss │   └── ... ├── src │   ├── ContentBag.php │   └── ... └── vendor ├── autoload.php ├── composer ├── league ├── symfony └── ... As mentioned, the main point of this setup is to keep all external libraries, all other custom source code and resources out of the webroot. That includes Composer's vendor folder, your node_modules and JavaScript source folder if you are compiling JavaScript with webpack or something similar and including external scripts via NPM, or your CSS preprocessor files if you are using SASS or LESS. In this setup, the public directory acts as the webroot (the directory that is used as the entry point by the server, DocumentRoot in the Apache configuration). So all other files and directories in the mysite folder aren't accessible over the web, even if something goes wrong.
      One caveat of this setup is that it's not possible to install ProcessWire modules through Composer using the PW Module Installer (see Blogpost above), but that's just a minor inconvenience in my experience.
      You'll need to have composer installed on your system for this. Installation guides can be found on getcomposer.org.
      First, open up your shell and navigate to the mysite folder.
      $ cd /path/to/mysite/ Now, we'll initialize a new Composer project:
      $ composer init The CLI will ask some questions about your projects. Some hints if you are unsure how to answer the prompts:
      Package names are in the format <vendor>/<project>, where vendor is your developer handle. I use my Github account, so I'll put moritzlost/mysite (all lowercase). Project type is project if you are creating a website. Author should be in the format Name <email>. Minimum Stability: I prefer stable, this way you only get stable versions of dependencies. License will be proprietary unless you plan on sharing your code under a FOSS license. Answer no to the interactive dependencies prompts. This creates the composer.json file, which will be used to keep track of your dependencies. For now, you only need to run the composer install command to initialize the vendor directory and the autoloader:
      $ composer install Now it's time to download and install ProcessWire into the public directory:
      $ git clone https://github.com/processwire/processwire public If you don't use git, you can also download ProcessWire manually. I like to clean up the directory after that:
      $ cd public $ rm -r .git .gitattributes .gitignore CONTRIBUTING.md LICENSE.TXT README.md Now, setup your development server to point to the /path/to/mysite/public/ directory (mind the public/ at the end!) and install ProcessWire normally.
      Including & using the autoloader
      With ProcessWire installed, we need to include the composer autoloader. If you check ProcessWire's index.php file, you'll see that it tries to include the autoloader if present. However, this assumes the vendor folder is inside the webroot, so it won't work in our case.
      One good place to include the autoloader is using a site hook file. We need the autoloader as early as possible, so we'll use init.php:
      EDIT: As @horst pointed out, it's much better to put this code inside the config.php file instead, as the autoloader will be included much earlier:
      // public/site/config.php <?php namespace Processwire; require '../../vendor/autoload.php'; The following also doesn't apply when including the autoloader in the config-file.
      This has one caveat: Since this file is executed by ProcessWire after all modules had their init methods called, the autoloader will not be available in those. I haven't come across a case where I needed it this early so far; however, if you really need to include the autoloader earlier than that, you could just edit the lines in the index.php file linked above to include the correct autoloader path. In this case, make sure not to overwrite this when you update the core!
      Now we can finally include external libraries and use them in our code without hassle! I'll give you an example. For one project, I needed to parse URLs and check some properties of the path, host et c. I could use parse_url, however that has a couple of downsides (specifically, it doesn't throw exceptions, but just fails silently). Since I didn't want to write a huge error-prone regex myself, I looked for a package that would help me out. I decided to use this URI parser, since it's included in the PHP League directory, which generally stands for high quality.
      First, install the dependency (from the project root, the folder your composer.json file lives in):
      $ composer require league/uri-parser This will download the package into your vendor directory and refresh the autoloader.
      Now you can just use the package in your own code, and composer will autoload the required class files:
      // public/site/templates/basic-page.php <?php namespace Processwire; use \League\Uri\Parser; // ... if ($url = $page->get('url')) { $parser = new Parser(); $parsed_url = $parser->parse($url); // do stuff with $parsed_url ... } Wiring up custom classes and code
      Another topic that I find really useful but often gets overlooked in Composer tutorials is the ability to wire up your own namespace to a folder. So if you want to write some object-oriented code outside of your template files, this gives you an easy way to autoload those using Composer as well. If you look at the tree above, you'll see there's a src/ directory inside the project root, and a ContentBag.php file inside. I want to connect classes in this directory with a custom namespace to be able to have them autoloaded when I use them in my templates.
      To do this, you need to edit your composer.json file:
      { "name": "moritzlost/mysite", "type": "project", "license": "proprietary", "authors": [ { "name": "Moritz L'Hoest", "email": "info@herebedragons.world" } ], "minimum-stability": "stable", "require": {}, "autoload": { "psr-4": { "MoritzLost\\MySite\\": "src/" } } } Most of this stuff was added during initialization, for now take note of the autoload information. The syntax is a bit tricky, since you have to escape the namespace seperator (backslash) with another backslash (see the documentation for more information). Also note the PSR-4 key, since that's the standard I use to namespace my classes.
      The line "MoritzLost\\MySite\\": "src/" tells Composer to look for classes under the namespace \MoritzLost\MySite\ in the src/ directory in my project root. After adding the autoload information, you have to tell composer to refresh the autoloader information:
      $ composer dump-autoload Now I'm ready to use my classes in my templates. So, if I have this file:
      // src/ContentBag.php <?php namespace MoritzLost\MySite; class ContentBag { // class stuff } I can now use the ContentBag class freely in my templates without having to include those files manually:
      // public/site/templates/home.php <?php namespace Processwire; use MoritzLost\MySite\ContentBag; $contentbag = new ContentBag(); // do stuff with contentbag ... Awesome!
      By the way, in PSR-4, sub-namespaces correspond to folders, so I can put the class MoritzLost\MySite\Stuff\SomeStuff in src/Stuff/SomeStuff.php and it will get autoloaded as well. If you have a lot of classes, you can group them this way.
      With this setup, you are following secure practices and have much flexibility over what you want to include in your project. For example, you can just as well initialize a JavaScript project by typing npm init in the project root. You can also start tracking the source code of your project inside your src/ directory independently of the ProcessWire installation. All in all, you have good seperation of concerns between ProcessWire, external dependencies, your templates and your OOP-code, as well as another level of security should your Server or CGI-handler ever go AWOL. You can also build upon this approach. For example, it's good practice to keep credentials for your database outside the webroot. So you could modify the public/site/config.php file to include a config or .env file in your project root and read the database credentials from there.
      Anyway, that's the setup I came up with. I'm sure it's not perfect yet; also this tutorial is probably missing some information or isn't detailed enough in some areas depending on your level of experience. Feel free to ask for clarification, and to point out the things I got wrong. I like to learn as well 🙂
      Thanks for making it all the way to the bottom. Cheers!
    • By humanafterall
      I would like to set an admin template to 'https only' as recommended in the Processwire security docs.
      However if I do this it forces this setting locally too, resulting in https://localhost requests which result in an error page.
      Is there a simple way round this? Setting https for templates in the config?
    • By nuel
      Dear PW Community
      Let me shout out my question here, I really don't know where to start and hope someone can give me a hint or tell me to resign and go home and cry.
      I want to create a subpage that is only accessible to people with unique access codes. It's gonna be an online concert streaming page (thanks Corona!). People who buy tickets through a local ticketing service should be able to access and stream the show with their individual access code. These codes should work only for this person and show. If someone in the «audience» closes and reopens the page, they should get in again, but not their friends who were given the code of course, basically just like in a club with a ticket and a stamp on the wrist.
      Now, is there a possibility to achieve that with more or less basic Processwire skills? In my imagination I have a field where I list the given access codes, another two to add start and ending date/time of the show, maybe one for a unique ID/title of the show.
      Is there an existing module for something like that? Should I get into the module development field and create that? How?? Haha. Any comments are welcome here.
    • By theoretic
      Hi guys and ladies! And thanks for Processwire!
      It appears i've got an interesting issue concerning the template-settings-based PW redirects dealing with access control. Any PW template has some access control options i.e. "Login redirect URL or page ID to render". If this option is used for a page having a template with this option filled, a redirect will occur if user is not logged in and/or has insufficient access rights.
      I like to hook PW events. In one of my current projects i decided to write an addHookBefore('Session::redirect', ...) which should store the page we are being redirected from. With "regular" redirects like $session->redirect('/somewhere/') this hook works like a charm. But it was strange to see that it doesn't work with the template-settings-based redirect.
      I'm too dumb to dive deep inside PW and to examine the whole PW session mechanism. But it could be rather logical if ANY redirect ( no matter template-settings-based or using $session->redirect() ) could be hooked in the same manner.
      Okay okay i can forget about template-settings-based redirect and write my own. Just a couple of lines of code, and it works. But it's less elegant than hooking the template-settings-based redirects.
      So am i missing something? It this behavior a bug, or is it intended by PW team? Thanks in advance for any comment!
    • By Guy Incognito
      What's the best process for adding another user with TfaTotp 2FA? Just using it for the first time.
      Should I supply them with them with the secret when I first create their account? Seems like a security risk?
      Otherwise how do I create a 2FA user and let them login for the first time?
  • Create New...