thinkphp Framework Security Implementation Analysis _php example

Source: Internet
Author: User
Tags foreach explode mixed scalar sql injection string format throw exception

Thinkphp Framework is one of the most popular PHP frameworks in China, although it is not comparable with those of foreign countries, but the advantage is that, well, the Chinese manual is very comprehensive. A recent study of SQL injection, which used the TP framework as a security feature at the bottom, did not give much thought to security issues during the development process.

I have to say the I function

The TP system provides the I function for filtering the input variables. The whole meaning of the body of the function is to obtain data in various formats, such as I (' Get. '), I (' post.id '), and then use the Htmlspecialchars function (by default) for processing.

If you need to use other methods for security filtering, you can set from/thinkphp/conf/convention.php:

' Default_filter '    => ' strip_tags ',
//You can also set up a variety of filtering methods
' Default_filter '    => ' strip_tags, Stripslashes ',

I can find the I function from the/thinkphp/common/functions.php, the source code is as follows:

/** * Get input parameter support filter and default value * Use method: * <code> * I (' id ', 0); Gets the ID parameter to automatically judge get or post * I (' post.name ', ', ', ' htmlspecialchars '); Gets the $_post[' name '] * I (' get. '); Get $_get * </code> * @param string $name variable name supports the specified type * @param mixed $default does not exist the default value * @param mixed $filter parameters Filtering method * @param mixed $datas Additional data sources to be obtained * @return mixed/function I ($name, $default = ', $filter =null, $datas =null) {stat
  IC $_put = null;
  if (Strpos ($name, '/')) {//Specify modifier list ($name, $type) = explode ('/', $name, 2);
  }elseif (C (' var_auto_string ')) {//default cast to String $type = ' s '; }/* To obtain the data according to the $NAME format: first determine the source of the parameter, and then obtain the data according to various formats */if (Strpos ($name, '. ')) {List ($method, $name) = Explode ('. ', $name, 2);} Specify parameter source else{$method = ' param ';}
    Set to automatically obtain switch (Strtolower ($method)) {case ' get ': $input =& $_get;break;
    Case ' post ': $input =& $_post;break; 
    Case "put":/* Here omitted */Case ' param ':/* HERE omitted * * * case ' path ':/* here omitted */} * To filter the data obtained * */if ('//Get all the variables   $data = $input;
    $filters = Isset ($filter) $filter: C (' default_filter '); if ($filters) {if (is_string ($filters)) {$filters = explode (', ', $filters),}//To provide support for multiple filtering methods ($filters as $filter) {$data = Array_map_recursive ($filter, $data);//Loop Filter}}}elseif (Isset ($input [$name])) {//
    Value operation $data = $input [$name];
    $filters = Isset ($filter) $filter: C (' default_filter '); if ($filters) {/* To filter the parameters, support regular expression validation////////////////} if (!empty ($type)) {//If the cast type switch is set (Strtolower ($  Type) {case ' a ': $data = (array) $data;  Array case ' d ': $data = (int) $data;  Digital case ' f ': $data = (float) $data;  Floating case ' B ': $data = (Boolean) $data;
      Boolean case ' s '://string Default: $data = (string) $data;
  }}}else{//variable defaults $data = Isset ($default)? $default: null; Is_array ($data) && array_walk_recursive ($data, ' think_filter '); If $datais an array, then the return $data is filtered by a think_filter array;
 }

Well, the function is basically divided into three pieces:
The first block gets the data in various formats.
The second block encodes the obtained data, whether it is a two-dimensional array or a three-dimensional array.
The third , which is the penultimate line, calls the Think_filter to the last step of the mysterious processing of the data.

Let's start by tracking the Think_filter function:

1536 row version 3.2.3 latest add
function Think_filter (& $value) {//filter query special characters  
  if (Preg_match ('/^ exp| neq| Gt| Egt|lt| elt|or| xor| like| notlike| Not between| notbetween| between| notin| Not in| IN) $/i ', $value)) {    
    $value. = ';  
  }
}

This function is very simple, you can see at a glance, add a space after some specific keywords.

But this function called Think_filter, just add a space, in the end what is the role of filtering?

We all know that important logical validation, such as verifying that you are logged in, whether a user can buy a product, and so on, must be validated from the server side, which is easily bypassed if validated from the front end. The same reason, in the program, In/exp a class of logical structure, preferably by the server side to control.

When the data passed to the server side is this: id[0]=in&id[1]=1,2,3, if there is no think_filter function, it will be parsed into 1 of the table below, and will be interpreted as a server-side logic. However, if it becomes as shown in table 2 below, because there is more than one space, cannot be matched parse, also avoids the flaw.

$data [' id ']=array (' => ' 1,2,3 ') 
//After filtering by Think_filter, it becomes an interface:
$data [' id ']=array (' in ' => ' 1,2,3 ']

Second, SQL injection

The related documents are:/thinkphp/library/think/db.class.php (changed to/thinkphp/library/think/db/driver.class.php in 3.2.3) and/ThinkPHP/ library/think/model.class.php. The Model.class.php file provides the function directly called by curd, provides interface directly, and functions in Driver.class.php are indirectly called by curd operation.

The main analysis of the following statements:
M (' user ')->where ($map)->find ();  Retrieves a data from the user table based on the $MAP condition

About the processing of TP thought :

You first instantiate the model class as a user object, and then call the where function in the user object to handle $map, which is to assign the $map to the member variable $options of the user object after some formatting processing (if there are other consecutive operations, is also first assigned to the user object of the corresponding member variables, rather than directly splicing the SQL statements, so when writing a coherent operation, without the same as the concatenation of SQL statements to consider the order of the keyword, then call the Find function.

The find function calls the underlying, function--select in the driver class to get the data. To the Select function, it's another story.

In addition to handling curd operations and handling PDO bindings, we only care about curd operations, so we call Buildselectsql in Select, process paging information, and call Parsesql assemble SQL statements in the order in which they are set.

Although the parameters required to splice the SQL statement are all in the member variable, but the format is not uniform, it may be a string format, may be an array format, there may be a special query format TP provide, such as: $data [' id ']=array (' GT ', ' 100 '); So in the stitching before, but also to call their own processing functions, a unified format processing. I chose Parsewhere, a complex model to analyze.

On the security side, if you use the I function to get the data, then the default is Htmlspecialchars processing, which is effective against XSS attacks, but does not have much impact on SQL injection.

TP is witty when filtering the symbols associated with SQL injection: First, the user's input is processed in the normal logic, then the Parsewhere, parsehaving and other functions closest to the final SQL statement are handled safely. Such an order avoids the injection during processing.

Of course the approach is the most common addslashes, according to the dead on the beach before the waves, said the recommended use of mysql_real_escape_string to filter, but this function can only be connected to the database under the premise of use.

Feel TP in this place can do optimization, after all, go to this step is connected to the database.

Well, then, the analysis begins:

Let's say a few of the member variables in the model object:

Primary Key Name protected $PK = ' id ';
Field information protected $fields = Array ();
Data information protected $data = Array ();
Query expression parameter protected $options = Array (); Chain Action Method List protected $methods = Array (' strict ', ' order ', ' Alias ', ' Have ', ' group ', ' lock ', ' distinct ', ' auto ', ' filter ', ' Validate ', ' result ', ' token ', ' index ', ' Force ') next analyze the WHERE function: Public function where ($where, $parse =null) {//If not array format, That is, where (' id=%d&name=%s ', Array ($id, $name)), escapestring in MySQL is invoked on the array passed to the string if (!is_null ($parse) &&
    Is_string ($where)) {if (!is_array ($parse)) {$parse = Func_get_args (); Array_shift ($parse);}
    $parse = Array_map (Array ($this->db, ' escapestring '), $parse); $where = vsprintf ($where, $parse);
  The vsprintf () function writes the formatted string to the variable}elseif (is_object ($where)) {$where = Get_object_vars ($where);
    } if (Is_string ($where) && '!= $where) {$map = array ();
    $map [' _string '] = $where;
  $where = $map;     
  ///Assign $where to $this->where if (isset ($this->options[' where ')) {  $this->options[' where '] = Array_merge ($this->options[' where '), $where);
  }else{$this->options[' where ' = $where;
return $this;
 }

The logic of the where function is simple, if it is the format of the WHERE (' id=%d&name=%s ', Array ($id, $name), then $id, $name variable calls the escapestring in MySQL. The essence of escapestring is to call mysql_real_escape_string, addslashes and other functions to deal with.

Finally, the parsed array is assigned to the member function of the model object-$where for next processing.

Re-analyze the Find function:

//model.class.php row 721 version 3.2.3 Public function find ($options =array ()) {if (is_numeric ($options) | |
    Is_string ($options)) {/* If the data passed over is a string, not an array * * * $where [$this->getpk ()] = $options;
    $options = Array (); $options [' where '] = $where; if (Is_array ($options) && (count ($options) > 0) && is_array ($PK)) {/* Constructs compound primary key query condition, omit/} here $opti                 ons[' limit '] = 1;   Always look for a record $options = $this->_parseoptions ($options);
  Parse expression if (Isset ($options [' cache ']) {* * cache query, omit/} $resultSet = $this->db->select ($options);
  if (false = = $resultSet) {return false;}      if (empty ($resultSet)) {return null;}  The query result is an empty if (is_string ($resultSet)) {return $resultSet;}
  The query result is the processing of the data after reading the string//, abbreviated $this->data = $this->_read_data ($resultSet [0]);
return $this->data; }

$Pk the primary key, $options as an expression parameter, the function is to perfect the member variable--options array, and then call the DB Layer Select function query data, processing and return data.

Follow up the _parseoptions function:

protected function _parseoptions ($options =array ()) {//Parse expression if (Is_array ($options)) {$options = Array_merge ($this-&
  Gt;options, $options); /* Get the table name, here omit//* Add data table alias, here omitted * * * $options [' model '] = $this->name;//record operation models name/* Array query condition for field type check, if within reasonable range, enter Row filter processing, otherwise throw an exception or delete the corresponding field * * (Isset ($options [' where ']) && is_array ($options [' where ']) &&!empty ($
      Fields) &&!isset ($options [' join ']) {foreach ($options [' where '] as $key => $val) {$key = Trim ($key); if (In_array ($key, $fields, True)) {//if $key in the database field, filter and force type conversion if (Is_scalar ($val)) {/*is_scalar Detect whether is scalar. A scalar is an integer, float, string, Boolean variable, and array is not a scalar.
        * * $this->_parsetype ($options [' where '], $key); }elseif (!is_numeric ($key) && ' _ '!= substr ($key, 0,1) && false = = Strpos ($key, '. ') && Fals E = = Strpos ($key, ' (') && false = = = Strpos ($key, ' | ') && false = = = Strpos ($key, ' & ')) {//if $key Not a number and the first wordCharacter is not _, does not exist. (|& special characters if (!empty ($this->options[' strict ')) {//If it is strict mode, throw an exception E (' _error_query_express_ '). ' : ['. $key. '
        => '. $val. ']; } unset ($options [' where '] [$key]);      Unset out the corresponding value}} $this->options = Array ();    Empty SQL expression assembly after query to avoid affecting the next query $this->_options_filter ($options);
Expression filter return $options;

 }

The structure of this function is probably the first to obtain the table name, the model name, and then processing the data: if the data is not in the database field, then make exception processing or delete the data. Otherwise, perform _parsetype processing. Parsetype here no longer follow up, the function is: data type detection, mandatory type conversion including Int,float,bool type of three kinds of data.

function to run here, it's time to upload the processed data to the DB layer's Select function. In this case, the Int,float,bool type of data in the query condition $options has been coerced type conversion, and the string in the Where () function (not in the array format) is also handled addslashes.

Continue to trace to the Select function, to the driver object, or to first enumerate a few useful member variables:

Database expression
Protected $exp = Array (' eq ' => ' = ', ' neq ' => ' <> ', ' GT ' => ' > ', ' egt ' => ' >= ', ' lt ' => ' < ', ' ELT ' => ' <= ', ' notlike ' => ' not like ', ' like ' => ' like ', ' in ' => ' ', ' notin ' => ' "Not in ', ' don't in ' = > ' Not in ', ' between ' => ' between ', ' not between ' => ' don't between ', ' notbetween ' => ' not between ');
Query expression
protected $selectSql = ' select%distinct%%field% from%table%%force%%join%%where%%group%%having%%order%% limit%%union%%lock%%comment% ';
Current SQL instruction
protected $queryStr  = ';
Parameter binding
protected $bind     =  Array ();
Select function: Public
function Select ($options =array ()) {
  $this->model =  $options [' model '];
  $this->parsebind (!empty ($options [' bind ']) $options [' Bind ']:array ()];
  $sql  = $this->buildselectsql ($options);
  $result  = $this->query ($sql,!empty ($options [' fetch_sql '])? true:false);
  return $result;
}

After the version 3.2.3 has been improved, the select has been streamlined. The Parsebind function is a binding parameter that is used to PDO a query and is not listed here.

The Buildselectsql () function and subsequent calls are as follows:

Public Function Buildselectsql ($options =array ()) {if isset ($options [' page ']) {/* page number calculation and processing, here omitted */} $sql = $th
  Is->parsesql ($this->selectsql, $options);
return $sql; /* Replace expression in SQL statement/Public function parsesql ($sql, $options =array ()) {$sql = Str_replace (Array ('%table% ', '%distinct% ', '%field% ', '%join% ', '%where% ', '%group% ', '%having% ', '%order% ', '%limit% ', '%union% ', '%lock% ', '%comment% ', '%force % '), array ($this->parsetable ($options [' table ']), $this->parsedistinct (isset ($options [' distinct '])? $ options[' distinct ']:false, $this->parsefield (!empty ($options [' field '])? $options [' Field ']: ' * ', $this->p Arsejoin (!empty ($options [' join ']), $options [' Join ']: '), $this->parsewhere ($options [' where '])? $options [' WHERE ']: ', $this->parsegroup ($options [' Group ']) $options [' Group ']: '), $this->parsehaving (!em Pty ($options [' having]] "$options [' having ']:" $this->parseorder ($options [' Order '])?$options [' Order ']: '), $this->parselimit (!empty ($options [' limit '])? $options [' Limit ']: '), $this->parseun Ion (!empty ($options [' Union '])? $options [' Union ']: '), $this->parselock ($options [' Lock '])? $options [' Lock ']:false, $this->parsecomment (!empty ($options [' comment ']) $options [' comment ']: '), $this->parseforc
  E (!empty ($options [' Force ']) $options [' Force ']: '), $sql);
return $sql;

 }

You can see that in parsesql with the regular expression concatenation of the SQL statement, but not directly to deal with all kinds of Sussu your data format, but in the process of parsing variables called a number of functions, here take parsewhere example.

protected function Parsewhere ($where) {$whereStr = ';
  if (is_string ($where)) {//directly using string conditions $WHERESTR = $where;
    else{///use array expression/* To set logical rules, such as OR and XOR, default to and, here omit */$operate = ' and '; /* Parse special format expressions and format Output/foreach ($where as $key => $val) {if (0===strpos ($key, ' _ ')) {//parse special conditional expression $where
      Str. = $this->parsethinkwhere ($key, $val);
        else{//query field security filtering $multi = Is_array ($val) && isset ($val [' _multi ']);//To determine if there is a compound query
        $key = Trim ($key); /* processing fields included in the | & Logic */if (Strpos ($key, ' | '))
          {//Support Name|title|nickname method to define query Field/* convert | To or, and format output, omit/} elseif (Strpos ($key, ' & ')) { /* Convert & to and, and format output, omit/} else{$whereStr. = $this->parsewhereitem ($this->parsek
        EY ($key), $val);
    }} $whereStr. = $operate;
  $WHERESTR = substr ($whereStr, 0,-strlen ($operate)); return empty ($whereStr)? '': ' WHERE '. $whereStr;
  }//Where sub-unit analysis protected function Parsewhereitem ($key, $val) {$whereStr = ';
      if (Is_array ($val)) {if (is_string ($val [0])) {$exp = Strtolower ($val [0]); If the structure is a class of $map[' ID ']=array (' eq ', 100), then parse into the database executable format if (Preg_match ('/^ (eq|neq|gt|egt|lt|elt) $/', $exp)) {$where Str. = $key. ' '. $this->exp[$exp]. '
      '. $this->parsevalue ($val [1]);
        //If it is a fuzzy Lookup format elseif (Preg_match ('/^ (notlike|like) $/', $exp)) {//Fuzzy Lookup, $map [' Name ']=array (' like ', ' thinkphp% ');
          if (Is_array ($val [1])) {//parse format as follows: $map [' B '] =array (' Notlike ', Array ('%thinkphp% ', '%tp '), ' and ');  $likeLogic = Isset ($val [2]) Strtoupper ($val [2]): ' or ';
            If no logical structure is set, the default is OR if (In_array ($likeLogic, Array (' and ', ' or ', ' XOR ')) {/* According to the logical structure, combined statements, omitted here/* $whereStr. = ' ('. Implode ('. $likeLogic.             
          ', $like). ') '; }} else{$whereStr. = $key. '. $this->exp[$exp]. ' '. $this->parsevalue ($val [1]);
        }}elseif (' bind ' = = $exp) {//Use an expression, PDO data binding $whereStr. = $key. ' =: '. $val [1];
        }elseif (' exp ' = = $exp) {//Use expression $map [' id '] = Array (' exp ', ' in (1,3,8) '); $whereStr. = $key. '
      '. $val [1];
        }elseif (Preg_match ('/^ (notin|not in|in) $/', $exp)) {//in operation $map [' id '] = array (' Not in ', ' 1,5,8 '); if (Isset ($val [2]) && ' exp ' = = $val [2]) {$whereStr. = $key. ' '. $this->exp[$exp]. '
        '. $val [1];
          }else{if (is_string ($val [1])) {$val [1] = explode (', ', $val [1]);
          $zone = Implode (', ', $this->parsevalue ($val [1])); $whereStr. = $key. ' '. $this->exp[$exp]. '
        ('. $zone. ') '; }}elseif (Preg_match ('/^ (notbetween|not between|between) $/', $exp)) {//between Operation $data = is_string ($val [1])?
        Explode (', ', $val [1]): $val [1]; $whereStr. = $key. ' '. $this->exp[$exp]. ' '. $this->parsevalue ($data [0]). '
      and '. $this->parsevalue ($data [1]); }else{//Otherwise throw exception E(L (' _express_error_ '). ': '. $val [0]);
      } else{//parsing such as: $map [' Status&score&title '] =array (' 1 ', Array (' GT ', ' 0 '), ' thinkphp ', ' _multi ' =>true);
      $count = count ($val); $rule = Isset ($val [$count-1])? 
      (Is_array ($val [$count-1]) strtoupper ($val [$count -1][0]): Strtoupper ($val [$count-1])]: ";
      if (In_array ($rule, Array (' and ', ' or ', ' XOR ')) {$count = $count-1;
      }else{$rule = ' and ';
        For ($i =0 $i < $count $i + +) {$data = Is_array ($val [$i])? $val [$i][1]: $val [$i]; if (' Exp ' ==strtolower ($val [$i][0])) {$whereStr. = $key. ' '. $data. ' '. $rule. '
        '; }else{$whereStr. = $this->parsewhereitem ($key, $val [$i]). ' '. $rule. '
        ';
    } $whereStr = ' ('. substr ($whereStr, 0,-4). ') ';
    {else {//To use fuzzy matching for string type fields $likeFields = $this->config[' db_like_fields ']; if ($likeFields && preg_match ('/^ $likeFields. ') $/i ', $key)) {$whereStr. = $key. ' LikE '. $this->parsevalue ('% '. $val '% ');
    }else {$whereStr. = $key. ' = '. $this->parsevalue ($val);
} return $WHERESTR;
  } protected function Parsethinkwhere ($key, $val) {//parsing special form conditions $whereStr = ';                 Switch ($key) {case ' _string ': $whereStr = $val;  String mode query condition case ' _complex ': $whereStr = substr ($this->parsewhere ($val), 6);
Compound query condition case ' _query '://String mode query condition/* Handle logical structure, and format output string, here omit/} return ' ('. $whereStr. ') ';

 }

The above two functions are very long, we are more concise to see: Parsewhere first to determine whether the query data is a string, if it is a string, directly return the string, otherwise, traversing the array of query conditions, one by one parsing.

Because TP support special query such as _string,_complex, call Parsethinkwhere to handle, for ordinary query, call Parsewhereitem.

In their respective processing, are called PARSEVALUE, tracking, is actually used addslashes to filter, although addslashes in UTF-8 encoded pages will cause wide byte injection, but if the page and the database are correctly encoded, there is no problem.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.