http://qs321.pair.com?node_id=577062


in reply to Basics: CGI MySQL security

I'm not quite an expert on this yet myself, but I do need to improve in this area, so I'll make some suggestions and see if other monks shoot me offer improvements... 8^)

On the one hand, there are mysql user accounts that can be set up with a varied assortment of permissions for what they may do with specific databases, specific tables and even specific columns within tables. I would tend to use the mysql user accounts to establish the "roles" that blue_cowdawg spoke of:

On the other hand, you have the web login procedure, with user names and passwords assigned to any number of people out in the world. The relationship between these user accounts and the mysql "role" user accounts is something that needs to be managed: someone has to be responsible for approving new people (new web login accounts) who want to take on any of the more privileged (mysql account) roles.

With that set up, the cgi scripts simply need to maintain and use the session data to know, from one page/transaction to the next, what sort of client they are dealing with on each request.

Suppose that there's a cgi script that provides a form for updating table data (something only an "expert" can do). If an unauthorized user happens to get hold of an appropriate URL string to send a request to that script, the script will check for suitable "expert" credentials in the session data for the request: if the user is known/logged-in, query the sort of table that blue_cowdawg described, relating login names to database roles. If the appropriate role assignment isn't there, or if the user is not logged in, the cgi script will return a "permission denied" response.

As for the query activity itself, you must of course use the necessary precautions equally for all users when putting together the SQL statements: do taint checking on all user input, use placeholders for query parameter values, and don't use user-supplied strings as a direct part of any SQL string that you pass to the database. That is, instead of something like this, which would be outrageously risky:

$table = param("table"); $column = param("column"); $value = param("value"); $rows = $dbh->selectall_arrayref( "select $column from $table where ke +y = '$value'" );
you should be doing something like this, to eliminate the risk:
my %table_cols = ( foo_table => 'foo_column', bar_table => 'bar_column', # ... ); my $table = param( "table" ); my $column; if ( defined( $table ) and $table =~ /^(\w+)$/ and exists( $table_cols{$1} ) my $sth = $dbh->prepare( "select $table_cols{$1} from $1 where key += ?" ); $sth->execute( $param('value')); my $rows = $sth->fetchall_arrayref; }
Except that even this last snippet does not provide the amount of error checking that would be prudent: for each DBI call that could fail, you wan the cgi script to check for failure, and in that case, do something coherent in terms of returning some sort of feedback to the client.