OpenTechStrategies / lisc-ttm

LISC TTM code. See https://ttm.lisc-chicago.org/.
GNU Affero General Public License v3.0
1 stars 4 forks source link

Core: Check user authorization before sending protected material. #78

Closed cecilia-donnelly closed 9 years ago

cecilia-donnelly commented 10 years ago

The current authentication process is flawed. We need to implement a library for securely checking user authentication before loading pages.

cecilia-donnelly commented 10 years ago

This one was recommended on Stack Overflow: http://usercake.com/index.php. It does have a reported vulnerability: http://xforce.iss.net/xforce/xfdb/93459, but there are examples of how to fix that in the forums (see http://usercake.com/thread.php?id=712 and http://usercake.com/thread.php?id=757).

This is the first result for "php library login secure": http://ulogin.sourceforge.net/ but it doesn't seem to be referenced anywhere else. That might be because the name "ulogin" is fairly generic and hard to Google.

cecilia-donnelly commented 10 years ago

Discussed in a private call with @kevinrak9 the need for a change in the program-specific access set up, which is relevant for TRP and Enlace. Enlace users need to have access to multiple programs, but not all programs. Currently the user-program relationship is many-to-one such that one user can only have access to one program, though one program may have many users associated with it. We need to change that so it is many-to-many.

While this may not quite belong in the Issue #78 thread, in practice it will probably be part of the same process.

cecilia-donnelly commented 10 years ago

Note that this issue has been expanded somewhat to include checks of access before executing a query to the database. For example, we will add a check of whether the logged-in user has administrative privileges before executing a query that deletes information.

cecilia-donnelly commented 10 years ago

In order to finish this issue, I need to:

  1. Expand auth.php and copy siteconfig.php to cover all groups and core files.
  2. Change UI to add new users to certain site(s) and program(s).
  3. Add access check before all mysqli_query calls (!).
  4. Check program access in Enlace using session cookie.
  5. Check access level and display elements accordingly in all sites (done for TRP and Enlace).
  6. Fix and standardize logout across sites.
cecilia-donnelly commented 10 years ago

@akshayagarwal, check out this issue. Once you have a chance, I'd appreciate it if you could help with part (3) from my previous comment. Since we call mysqli_query throughout the code base it would be a good opportunity for you to get more familiar with the code as a whole. For some background: the system has three different user levels. Administrators can do everything, including adding new users, changing passwords, etc. Data entry users can do most things, including adding information, but cannot delete information. View-only or read-only users cannot change any information. The code currently makes these access differentiations only via showing or hiding elements on the page. We need to add a line of code that checks whether the logged-in user has permission to execute a given command (deletion, insertion) before the mysqli_query line that executes the command.

It's pretty simple, but it will involve a fairly massive code change. We'll talk about it, but I wanted to draw your attention here.

More information: As you might be able to see from looking at the commits, I have created a set of functions in /ajax/session_test.php that check authentication and authorization. All files will include a site-specific siteconfig.php, which calls some of those functions to make sure that the browser has a cookie that matches a session ID stored in a database table. Siteconfig will also determine the level of access that the logged-in user has (this is part 1 above). Files will then show or hide elements based on that level of access (part 5 above). As we complete the issue, files will also check that access level before executing queries to the database. TRP and Enlace have special program-specific access levels that require a little extra work, as I mention in part 4 above. I'll add more information about that in commit messages and comments as I continue to work on this.

akshayagarwal commented 10 years ago

Hey Cecilia,

I would like to discuss this over a call with you to understand in depth. I have different calls scheduled until 3 pm Central Time today so let me know if we could connect at 11.30 pm Central time tomorrow

Thanks! Akshay

On Wed Nov 12 2014 at 11:59:34 PM Cecilia Donnelly notifications@github.com wrote:

@akshayagarwal https://github.com/akshayagarwal, check out this issue. Once you have a chance, I'd appreciate it if you could help with part (3) from my previous comment. Since we call mysqli_query throughout the code base it would be a good opportunity for you to get more familiar with the code as a whole. For some background: the system has three different user levels. Administrators can do everything, including adding new users, changing passwords, etc. Data entry users can do most things, including adding information, but cannot delete information. View-only or read-only users cannot change any information. The code currently makes these access differentiations only via showing or hiding elements on the page. We need to add a line of code that checks whether the logged-in user has permission to execute a given command (deletion, insertion) before the mysqli_query line that executes the command.

It's pretty simple, but it will involve a fairly massive code change. We'll talk about it, but I wanted to draw your attention here.

More information: As you might be able to see from looking at the commits, I have created a set of functions in /ajax/session_test.php that check authentication and authorization. All files will include a site-specific siteconfig.php, which calls some of those functions to make sure that the browser has a cookie that matches a session ID stored in a database table. Siteconfig will also determine the level of access that the logged-in user has. Files will then show or hide elements based on that level of access. As we complete the issue, files will also check that access level before executing queries to the database.

— Reply to this email directly or view it on GitHub https://github.com/OpenTechStrategies/lisc-ttm/issues/78#issuecomment-62766874 .

kfogel commented 10 years ago

By the way, I changed the summary (title) of this to more accurately reflect the underlying bug -- that is, to describe it in terms of the problem, not in terms of one of its possible solutions.

cecilia-donnelly commented 10 years ago

Hey @akshayagarwal, let's agree on a list of changes that need to be made to complete this security fix. As I understand it, we need to:

We may also:

Is there anything else I'm missing? Once we have the full list we can make estimates on time and prioritize these changes, then complete them.

akshayagarwal commented 10 years ago

We can avoid the oAuth auth for now and perform the rest of the tasks. I am not sure how many files are using the MySQL queries, I am assuming all of them?

cecilia-donnelly commented 10 years ago

Essentially all of the files are using MySQL queries, yes.

To do:

cecilia-donnelly commented 10 years ago

@akshayagarwal suggests that we break this up into stages:

  1. Replace existing cookie usage in Enlace subsite with session variables.
  2. Make setcookie() call more secure. Add HTTPS-only, ensure they can't be modified with Javascript.
akshayagarwal commented 10 years ago

Need to add captcha validation after a certain amount of wrong password login attempts to prevent bruteforce attacks.

akshayagarwal commented 10 years ago

How about using this https://www.owasp.org/index.php/OWASP_PHPRBAC_Project

Its actively maintained and supported by OWASP

Another offering of theirs is https://github.com/OWASP/PHP-ESAPI/ but that looks pretty old

cecilia-donnelly commented 10 years ago

Per my call with @akshayagarwal last week, here are our next steps:

  1. We will request the data at login (1. logged in status, 2. role, and 3. permissions) and store it in $_SESSION. I added this in commits d0fbeac and 3103d3d.
    • Note: check logged-in-user's IP because otherwise we are vulnerable to session hijacking.
    • Note: add captcha after certain number of wrong passwords, then block IP after certain number of wrong captchas. We may block the IP directly in Apache or by using a firewall (?) server for more security. @kfogel, what do you think about that idea?
  2. @akshayagarwal will work on adding the PHP RBAC library (see http://phprbac.net/) for authorization. He will start with the Enlace subsite.
cecilia-donnelly commented 9 years ago

Hey @akshayagarwal, we still need to work on this piece, right:

Have you begun any work on this? I'll start a draft function if not, because we really need to make that change so we can open source this thing.

akshayagarwal commented 9 years ago

Hey @cecilia-donnelly ,

I tried reaching you on IRC today but I think you were unavailable. I actually discussed this with @kfogel and we decided to try out an ORM implementation to address this. I have created a new Issue #98 to track the progress of that. Towards that end, I have finalized to use Propel ORM and am brainstorming an implementation strategy for the same. I will be doing the actual implementation over the weekend and will keep you updated. I understand the urgency of this task and will do everything in my hands to not let it become a bottleneck in the open sourcing milestone.

Thanks!

cecilia-donnelly commented 9 years ago

@akshayagarwal, oh no! Sorry I missed your IRC messages. Looking forward to talking soon. Have a good rest of the week. Cecilia

cecilia-donnelly commented 9 years ago

@cwebber, here are some notes on the progress on this issue:

Let's talk after you get a bit of a grip on where the code currently is so we can put together a timeline and the next steps for this change. Thanks!

cwebber commented 9 years ago

Okay, so I read the codebase and talked with Cecilia on the phone, and here's the summary of things.

So what's next? There are two major milestones to achieve before closing this out:

1) Log in and have access to only your subsite. This same code should be consistently usable across subsites. Cecilia thinks this is pretty close, just changing the session and using auth.php and siteconfig.php. 1b) Logout is inconsistent currently, also handle that. 2) subsite permissions come next, the read/read-write/admin stuff. These roles are not yet implemented in the DB, though currently javascript does show/hide things so people do see different things, but there's no security around it.

kfogel commented 9 years ago

Conceptually, are there really two levels of access-checking to be done? It could just be one continuum:

In other words, the starting state is zero -- no access to anything.

That minor conceptual quibble aside, everything you write above makes sense to me!

cwebber commented 9 years ago

@kfogel That makes sense, and good to hear you say that, as it was my initial reaction as well. @cecilia-donnelly expressed something that she originally thought of this as two problems, but was happy to have it changed and was flexible on it.

I'm still trying to account for the ways in which security ends up happening and take record of it to be sure that I am doing this right, but I will keep that in mind.

cwebber commented 9 years ago

BTW, the utilities in common/tools/auth.php currently have the session id passed in to many of them as a function parameter, but the parameter is actually ignored anyway, so I think I will remove the parameter from the arguments in those places. It might make a lot of sense to pass in something where you can specify which user we are talking about, but it probably makes more sense on the user id level than on the session id level.

Moving forward with that as my current thinking, but feel free to correct me!

cwebber commented 9 years ago

So (uncommitted) I began working on switching the user from the cookie to the session... would we prefer to store the user id or the username in the session? I suppose we store the username in the cookie at present, so that should be fine. But I'm not sure since getAllSiteAccess asks for user_id, whereas many existing calls which rely on the cookie rely on the user as username. Not that queries can't be adjusted, but what do you think, @cecilia-donnelly ?

cecilia-donnelly commented 9 years ago

Thanks @cwebber! I generally prefer using the user ID, especially since we have some duplicate usernames that need to be cleaned up (see issue #15). It's true that in many places the queries use the username, but I think it would be straightforward to use the user ID in all of them -- I think generally they actually rely on the User ID to join with the Users_Privileges table, anyway.

I agree with your earlier comment, too. We might want to use the user id as a parameter at some point, but there's no need to pass in the session id if it isn't being used.

cwebber commented 9 years ago

@cecilia-donnelly okay great, thanks for clarifying on that :)

cwebber commented 9 years ago

@xray7224's suggested that we build a User class to do permission checking; after some thinking, I realized that she's absolutely right and this is the simplest way to do checking.

However, I need some clarity at this point in writing the code....

@cecilia-donnelly I'm working on the authentication system refactor. I'm a bit confused, and just want to make sure I'm doing the right thing... I see here:

    $find_site_access_sqlsafe = "SELECT Privilege_ID, Site_Privilege_ID, Program_Access FROM Users_Privileges WHERE User_ID =" . $user_id;
    // ...
    while ($access = mysqli_fetch_row($access_result)){
        $access_return[$access[0]] = array($access[1], $access[2]);
    }

Am I right in reading that:

Is this an accurate description of how things work? I just want to be sure I'm doing the right thing.

cecilia-donnelly commented 9 years ago

@cwebber, that is exactly right. The column names are definitely confusing. Privilege_ID is the site id and is listed in the "Privileges" table. Site_Privilege_ID, as you said, is the permission level (there is another table called Site_Privileges that lists these permission levels).

I'm glad you saw the many-to-many comment on Program_Access, since I thought we might be able to look into changing that while making these other changes. If it's too complicated to do it right now then don't worry about it, but if it's simple to make that change while altering the access set up then we should do so. It's mostly relevant for Enlace, but I think TRP also uses program-specific access privileges.

cwebber commented 9 years ago

@cecilia-donnelly Okay, that's good to have clarified. Re: the "many to many" stuff, I think I won't do that since that isn't the critical part here, but I'll leave the code in such a state that switching in many to many support on the db side can be done in the future, while maintaining the same API as in terms of the methods called, so that multiple program access permissions per person can be added later.

cecilia-donnelly commented 9 years ago

@cwebber: leaving it ready to switch to many-to-many sounds perfect. I agree that it's not critical at the moment.

cwebber commented 9 years ago

Okay, things are working, slowly:

$u = new User(31);
echo("<br />");
echo("Username: " . $u->username . "<br />");
echo("Permissions: ");
print_r($u->site_permissions);
echo("<br />");

function do_we_have_this($user, $site_id, $site_name) {
    if ($user->has_site_access($site_id)) {
        echo("User has $site_name<br/>");
    } else {
        echo("No $site_name for user!<br/>");
    }
}

do_we_have_this($u, $TRP_id, "TRP");
do_we_have_this($u, $LSNA_id, "LSNA");
do_we_have_this($u, $SWOP_id, "SWOP");
do_we_have_this($u, $Enlace_id, "Enlace");

echo("Checking permissions....");

// Only gets past TRP

enforceUserHasAccess($u, $TRP_id);
echo("Has TRP<br/>");
enforceUserHasAccess($u, $SWOP_id);
echo("Has SWOP<br/>");
enforceUserHasAccess($u, $LSNA_id);
echo("Has LSNA<br/>");
enforceUserHasAccess($u, $Enlace_id);
echo("Has Enlace<br/>");

Spits out:

31
Username: hachanzar
Permissions: Array ( [4] => Array ( [0] => 1 [1] => a ) )
User has TRP
No LSNA for user!
No SWOP for user!
No Enlace for user!
Checking permissions....Has TRP
User does not have permissions to access this site.

Which is correct, since the user doesn't have permissions to access SWOP. The page bails out at that point.

I'm going to make it so that a $USER is set up on pretty much every page which is a User object, whenever such a user is logged in. Through this we should be able to put a enforceUserHasAccess($u, $this_site_id); at the top of each page and fail out when things aren't working.

cecilia-donnelly commented 9 years ago

By the way, my only quibble with the above is that we might want the do_we_have_this() function to have a more descriptive name. I assume that's just your draft name for it anyway, but figured I would mention it.

cwebber commented 9 years ago

@cecilia-donnelly That was just my test code above, do_we_have_this was just at throwaway function to make sure I was getting the right output and so I could repeat things a few times. It seems you guessed that though, in which case your guess was correct! :)

cwebber commented 9 years ago

(That close was totally unintentional!)

cwebber commented 9 years ago

Okay, so I've been working hard on this; here's an update of where things stand so far:

This still leaves some questions:

Next steps seem to be:

I know that sounds like a lot of remaining steps, but I think it's much closer. :)

cecilia-donnelly commented 9 years ago

@cwebber thank you so much for writing this up, and for doing all this work! I am excited about how close we're getting.

Here are my thoughts on our permission-denied interfaces. The cases where we deny access:

For each of the first three cases, we should die at the top of the page, and the existing system works great. It's the final case that is a little different. The actions we're concerned with are inserting, updating, and deleting data. In all (?) places in the system, these actions are accomplished by a POST/ajax-y thing.

Currently:

In the future, we should:

  1. Hide the UI element(s)
  2. Check access when they attempt to insert, update, or delete data (almost every POST in the codebase, except a few search/reporting features)
  3. Give them an error in the screen or as a pop-up if they don't have permission to perform the action
  4. Block the query from being executed

I think that should do it. So, we need one different error message ("You don't have permission to ...perform this action? ...change this piece of information? ... See your system administrator for details.") and, more significantly, a way to check site access level and stop the action mid-page if they don't have correct permissions.

We could check in the destination page, and send the error message as a "response" to the ajax initiator.

post(
    'some_page.php',
{
    'some info'
},
    function (response){
        alert(response); // or html_in_element, or what have you
    }
);

and on 'some_page.php':

enforce_has_access($site, $access_level_needed); // imagining that second arg
// Add the following as another piece of enforce_has_access?
if ($access_level > $access_level_needed){ //remembering that greater permission comes with a lower $access_level int
    echo "You don't have permission to change this information";
    exit; 
}

Does that make sense? It would be a lot of changes to the many 'some_page.php' destination pages, but many of the posting pages are already set up to show the response.

cecilia-donnelly commented 9 years ago

Update after a conversation with @cwebber:

Examples of the cases in my last comment:

cwebber commented 9 years ago

I believe the branch is at the point where it needs to be to start updating things, or at least discuss it.

Unfortunately, though I made some demo files to drop into the checkout, I wasn't sure where to put them since this repo is not public yet, so scp'ing them to my own site did not make sense, github lacks an attachment feature, etc... so I have created an "evil" branch. Check out the Issue-78-personalized-withsandbox branch.

There were a few other things I could have done; I considered emailing a tarball to @cecilia-donnelly and @kfogel, but then that's lost to the issue tracker.

I'll find out what @kfogel thinks is TRTTD later :)

cecilia-donnelly commented 9 years ago

@cwebber As you can see in commit 17635da, I've started adding the new security apparatus to some Enlace pages. Based on that exercise, I think we need to add:

  1. A way to quickly check whether a person has access to "all" programs. That might be deprecated with the issue #121 change(s), but it would make access to person and program profiles simpler.
  2. A standard way to show or hide UI elements. This doesn't necessarily need to be a method in the API, but I want to make sure that we document it clearly.

What do you think?

kfogel commented 9 years ago

Hey @cwebber,

I didn't understand your last comment. How does " this repo is not public yet" affect what you were trying to do? What are these demo files and why would you not just add them to the regular issue branch?

cecilia-donnelly commented 9 years ago

@cwebber also mentioned in IRC that we should be sure to close db connections when we exit a page because of insufficient permission.

cwebber commented 9 years ago

@kfogel Probably I didn't really need to ping you about it, I overworried about the method of distribution, since all felt suboptimal.

So there's a .gitignore'd "sandbox/" directory in the git repo where you can dump test files and mess with things as a "poor person's REPL". I wrote some demos for @cecilia-donnelly to test, but they hardcode make public various properties of users, whether you're logged in or not, which would clearly be dangerous in production, so I didn't want to commit to any branch where that might accidentally happen.

In a public repo, I would have just put together a tarball of these demo .php files and dumped them on http://dustycloud.org/tmp/ and asked @cecilia-donnelly to download that, but since the project isn't released I worried that isn't kosher. But it's probably no problem really

Anyway I was overworrying.

cwebber commented 9 years ago

@cecilia-donnelly I think that your first requirement is fulfilled, as per our recent conversations on IRC. Correct me if wrong!

Thinking about the standard show/hide UI stuff now.

cwebber commented 9 years ago

So I think regarding showing/harding UI elements, it partly depends on the scenario right? Things are pretty different if you're getting a JSON response and then editing the DOM in your ajax'y handler than if you're just rendering things once or using .innerHTML() to replace things after you get a response from the server.

But, the TTM way of doing things does seem to focus around doing one of two things: rendering the page the first time, or doing a .innerHTML() response replacing with whatever you got from the server... right?

So there's really two routes:

If TTM works differently than the above, then maybe a different technique will be needed. The fact that UI elements are "hidden" is okay.

@cecilia-donnelly have I caught things right?

cecilia-donnelly commented 9 years ago

@cwebber, yup, first requirement fulfilled! We can check for access to all programs using site_access_check() as updated in c71088b.

As far as the UI stuff goes, that seems reasonable. There actually are two css classes ("hide_on_view" and "no_view") that currently hide elements that shouldn't be visible to data entry and read-only users, respectively. Currently, those elements are literally just hidden (".").hide() is called in the header). It's my impression that we should do better than that and, as you suggest in your third bullet above, check access before rendering the HTML section. What do you think?

cecilia-donnelly commented 9 years ago

As we split up the work of applying the new API , here is a preliminary list of things that we need to check and change in each file:

TL;DR:

What else?

cwebber commented 9 years ago

We also need to add "database cleanup" functions so that when the enforcement code runs and decides it needs to bail out, all connections are shut down appropriately

My suggestion is the following:

(We could then make use of run_cleanup() more globally too?)

cwebber commented 9 years ago

There are some other things worth cleaning up too once we splat out all these updates:

cwebber commented 9 years ago

I've added new tools in core/tools/db.php which handle setting up and shutting down connections. Working on splatting them out in the right places.

Luckily, this is a lot easier than putting users everywhere, because we can leverage the existing include files that we have. Due to the way the "copy from template include files" work, whis will require updating the dbconnopen files on everyones' servers, but this will be a one-time task.

cwebber commented 9 years ago

Okay! I've set up a connection manager thingy and set up all the dbconnopen.php things to use it. Still need to get to the close and etc stuff. But yeah, good week! :)

cecilia-donnelly commented 9 years ago

@cwebber, @kfogel, I've converted the Enlace subsite to use the new access API and it seems to be working as expected. You can see my notes on making the access changes and testing them in all my commit messages, but especially in commit 82847fd. To convert pages to the new API:

  1. Add include_once() of the user setup file and the core db connection file to the top of each page.
  2. Run enforce_user_has_access() with the appropriate subsite. If the file is an invoked page in an AJAX call (usually just the files in the ajax/ directory), then add the appropriate permission level for "insert," "update," and "delete" statements as a second argument to the call.
  3. Search for ".post(" on each page, and at the end of the parenthetic expression add .fail(failAlert), a function that pops out a failure message if the invoked page returns a 401 (as it will if the enforce_user_has_access() function returns an error).
  4. Search for $_COOKIE['user'] and $cnnLISC on all pages. These are indications that some specialized access tests are already being run and adjustments will need to be made. The user cookie is going away, so we definitely have to remove references to it wherever they appear.
  5. Where UI elements have classes "hide_on_view" and "no_view", treat them as in my comments from Feb 20 and 24 (the latter of which is an earlier version of this).