thinkphp Framework Security Implementation Analysis, thinkphp Framework Implementation _php Tutorial

Source: Internet
Author: User
Tags comparable scalar

thinkphp Framework Security Implementation Analysis, thinkphp framework implementation


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. Recently, when we studied SQL injection, we used the TP framework to provide security features and did not consider security issues during the development process.

I have to say the I function

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

If you need to use a different method for security filtering, you can set it from/thinkphp/conf/convention.php:

' Default_filter ' + '    strip_tags ',//can also set a variety of filtration methods ' default_filter '    = ' strip_tags,stripslashes ',

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

/** * Get input parameters to support filtering and default values * How to use: * * I('id',0); 获取id参数 自动判断get或者post * I('post.name','','htmlspecialchars'); 获取$_POST['name'] * I('get.'); 获取$_GET *  * @param string $name The name of the variable supports the specified type * @param mixed $default not present when the default value * @param mixed $filter parameter filtering method * @param mixed $datas  Extra data source to get * @return mixed */function I ($name, $default = ", $filter =null, $datas =null) {static $_put = null;  if (Strpos ($name, '/')) {//Specify modifier list ($name, $type) = explode ('/', $name, 2);  }elseif (C (' var_auto_string ')) {//By default cast to String $type = ' s '; */* Get data according to $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 the parameter source else{$method = ' param ';}    Set to automatically get switch (Strtolower ($method)) {case ' get ': $input =& $_get;break;    Case ' post ': $input =& $_post;break; Case ' put ':/* omitted here */case ' param ':/* omitted here */Case ' path ':/* omitted here */}/* To filter the data obtained */if ('//Get all variables $    data = $input;    $filters = Isset ($filter)? $filter: C (' default_filter '); if ($filters) {if (is_string ($filters)) {$filters = explode (', ', $filters)}//provides support for multiple filtering methods foreach ($filters as $f  Ilter) {      $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) {/* Filters the parameters, supports regular expression validation */* omitted here */} if (!empty ($type)) {//If the cast type switch is set (Strtolower ($type  ) {case ' a ': $data = (array) $data;  Array case ' d ': $data = (int) $data;  Number case ' F ': $data = (float) $data;  Floating point case ' B ': $data = (Boolean) $data;      Boolean case ' s '://string Default: $data = (string) $data;  }}}else{//variable default value $data = Isset ($default)? $default: null; } is_array ($data) && array_walk_recursive ($data, ' think_filter '); If the $data is an array, then filter the return $data with think_filter arrays;}

Well, the function is basically divided into three blocks:
The first block , get data in various formats.
In the second block , the retrieved data is cyclic encoded, whether it is a two-dimensional array or a three-dimensional array.
The third , which is the penultimate line, calls the Think_filter to take the last step of the mysterious processing of the data.

Let's start by tracing the Think_filter function:

1536 line 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 is a simple function that can be seen at a glance, with a space behind some specific keywords.

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

We all know important logic validation, such as verifying whether a login is logged in, whether the user can purchase a product, etc., must be verified from the server side, if verified from the front, it is easy to bypass. The same principle, in the program, In/exp a class of logical structure, preferably also 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 the table of 1, will be treated as a server-side logical parsing. But if it turns out to look like the following table 2, because there is a space, can not be resolved by matching, also avoids the vulnerability.

$data [' id ']=array (' in ' + = ')//after think_filter filtering, it will look like this: $data [' id ']=array (' in ' = ') '

Second, SQL injection

The relevant 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. Where the Model.class.php file provides a function that is called directly by curd, directly provides an interface to the outside, Driver.class.php function is called indirectly by the curd operation.

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

Think about the idea of TP processing :

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 $map to the member variable $options of the user object after some formatting processing (if there are other coherent operations, It is also the assignment of the corresponding member variables to the user object instead of directly stitching the SQL statements, so when writing a coherent operation, it is not necessary to consider the order of the keywords in the same way as the stitching SQL statements, then call the Find function.

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

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

Although the parameters required to splice SQL statements have all been placed in the member variables, but the format is not uniform, it is possible to format the string, it is possible to format the array, there may be a special query format TP, such as: $data [' id ']=array (' GT ', ' 100 '); So before stitching, also call the respective processing function, for uniform format processing. I chose Parsewhere, a complex model to analyze.

On the security side, if I use the I function to get the data, the Htmlspecialchars processing will be done by default, which is effective against XSS attacks, but it has little effect on SQL injection.

When filtering the symbols related to SQL injection, the TP approach is witty: first, the user's input is processed in the normal logic, and then the Parsewhere, parsehaving, and so on, which are closest to the final SQL statement, are handled safely. This sequence avoids the injection in the process.

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

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

Well, next, the analysis begins:

First, say a few of the member variables in the model object:

The primary key name protected $PK = ' id ';//field information protected $fields = array ();//data information protected $data = array ();//query expression parameter protected $op tions = Array ();//Chain Operation method List protected $methods = Array (' strict ', ' order ', ' Alias ', ' having ', ' group ', ' lock ', ' distinct ', ' Auto ', ' filter ', ' Validate ', ' result ', ' token ', ' index ', ' Force ') next parse the WHERE function: Public function where ($where, $parse =null ) {//If non-array format, that is, where (' id=%d&name=%s ', Array ($id, $name)), to the array passed to the string, call the escapestring in MySQL to process 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; }//assigns $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 very simple, and if it is the format of where (' id=%d&name=%s ', Array ($id, $name)), then the $id, $name variable, is called to the MySQL escapestring for processing. The essence of escapestring is to call mysql_real_escape_string, addslashes and other functions for processing.

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 line 721 version 3.2.3public function find ($options =array ()) {if (Is_numeric ($options) | | is    _string ($options)) {/* If the data passed is a string, not an array */$where [$this->getpk ()] = $options;    $options = Array (); $options [' where '] = $where;  /* Extract the query criteria and assign the value */}//According to the primary key to find records $PK = $this->getpk (); if (Is_array ($options) && (count ($options) > 0) && is_array ($PK)) {/* Constructs a composite primary key query condition, omitted */} here $options                 [' limit '] = 1;   Always find a record $options = $this->_parseoptions ($options);  Parse expression if (Isset ($options [' cache ']) {/* cache query, omitted here */} $resultSet = $this->db->select ($options);  if (false = = = $resultSet) {return false;}      if (empty ($resultSet)) {return null;}  The query result is empty if (is_string ($resultSet)) {return $resultSet;}  The query result is a string//read the data after processing, here omit shorthand $this->data = $this->_read_data ($resultSet [0]); return $this->data;} 

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

Follow the _parseoptions function:

protected function _parseoptions ($options =array ()) {//Parse expression if (Is_array ($options)) {$options = Array_merge ($this  Options, $options); }/* Get the table name, omit/* Here to add the data table alias, omit */$options [' model '] = $this->name;//The name of the record operation */* field type check for array query conditions, if within reasonable range, filter Otherwise throw an exception or delete the corresponding field */if (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 is in the database field, filtering and forcing the type conversion of if (Is_scalar ($val)) {/*is_scalar detection is scalar. Scalar is a variable of integer, float, String, Boolean, and array is not scalar.        */$this->_parsetype ($options [' where '], $key);  }}elseif (!is_numeric ($key) && ' _ '! = substr ($key, 0,1) && false = = = Strpos ($key, '. ') && false = = = Strpos ($key, ' (') && false = = = Strpos ($key, ' | ') && false = = = Strpos ($key, ' & ')) {//if $key is not Number and the first character is not _, does not exist. (|& and other specialCharacter if (!empty ($this->options[' strict ')) {//If it is strict mode, throw an exception E (L (' _error_query_express_ '). ': ['. $key. '        = '. $val. '); } unset ($options [' where '] [$key]);      unset drop the corresponding value}}} $this->options = Array ();    Empty SQL expression assembly after query to avoid affecting the next query $this->_options_filter ($options); The expression filters the return $options;}

The structure of this function is probably the first to get the table name, model name, and then processing the data: if the data is not in the database field, then make exception processing or delete the bar data. Otherwise, do _parsetype processing. Parsetype is not followed up here, the function is: data type detection, forced type conversion including the type of Int,float,bool three kinds of data.

function to run here, it's time to pass the processed data to the SELECT function in the DB layer. At this point the query condition $options in the Int,float,bool type of data has been forced type conversion, the where () function in the string (non-array-formatted query) has also been addslashes and other processing.

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

Database expression protected $exp = Array (' eq ' = ' = ', ' neq ' = ' <> ', ' gt ' = ' > ', ' egt ' = ' >= ', ' lt ' = ' + ' < ', ' elt ' = ' <= ', ' notlike ' = ' not like ', ' like ' and ' like ', ' in ' ' and ' ', ' notin ' = ' not ', ' no in ' = ' Not in ', ' between ' = ' between ', ' not between ' = ' not 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, select streamlines a lot. The Parsebind function is a binding parameter that is used for PDO queries, not tables here.

The Buildselectsql () function and its subsequent invocation are as follows:

Public Function Buildselectsql ($options =array ()) {if (Isset ($options [' page])) {/* page number calculation and processing, omitted here */} $sql = $this-&G  T;parsesql ($this->selectsql, $options); return $sql;} /* Replace the expression in SQL statement */public function parsesql ($sql, $options =array ()) {$sql = Str_replace (Array ('%table% ', '%distinct% ', '%F    ield% ', '%join% ', '%where% ', '%group% ', '%having% ', '%order% ', '%limit% ', '%union% ', '%lock% ', '%comment% ', '%force% ', Array ($this->parsetable ($options [' table ']), $this->parsedistinct (isset ($options [' distinct '])? $options [' Di Stinct ']:false), $this->parsefield (!empty ($options [' field '])? $options [' Field ']: ' * '), $this->parsejoin (!emp Ty ($options [' join ']) $options [' Join ']: ' $this->parsewhere ($options [' where '])? $options [' WHERE ']: ') , $this->parsegroup (!empty ($options [' group])? $options [' Group ']: '), $this->parsehaving (!empty ($options [' H Aving ')? $options [' Having ']: '), $this->parseorder (!empty ($options [' Order '])? $options [' Order ']: '), $this->parselimit (!empty ($options [' limit ']), $options [' Limit ']: '), $this->parseunion (!empty ($options [' U Nion '])? $options [' Union ']: $this->parselock (Isset ($options [' Lock '])? $options [' Lock ']:false), $this->pa Rsecomment (!empty ($options [' comment ']), $options [' comment ']: '), $this->parseforce (!empty ($options [' Force '])?  $options [' Force ']: '), $sql); return $sql;}

As you can see, the SQL statements are spliced with regular expressions in the Parsesql, but not directly to the various socialize of your data format, but rather in the process of parsing variables called several functions, here take parsewhere example.

protected function Parsewhere ($where) {$whereStr = ';  if (is_string ($where)) {//Use string condition directly $whereStr = $where;    } else{//Use array expression/* To set logical rules, such as OR and XOR, default to and, omitted here */$operate = ' and ';   /* Parse Special-format expressions and format output */foreach ($where as $key + $val) {if (0===strpos ($key, ' _ ')) {//parse special conditional expression $WHERESTR      . = $this->parsethinkwhere ($key, $val); else{//Query field security Filter $multi = Is_array ($val) && isset ($val [' _multi ']);//Determine if there is a compound query $        Key = Trim ($key); /* processing the fields contained in the | & Logic */if (Strpos ($key, ' | '))          {//Support Name|title|nickname method definition query Field/* will be | replaced with OR, and formatted output, omitted here */} elseif (Strpos ($key, ' & ') { /* Change & to and, and format the output, omit */} else{$whereStr. = $this->parsewhereitem ($this->parsekey ($key        ), $val);    }} $whereStr. = $operate;  } $whereStr = substr ($whereStr, 0,-strlen ($operate)); } return empty ($WHERESTR)? ': ' WHERE '. $whereStr;} where childUnit analysis protected function Parsewhereitem ($key, $val) {$whereStr = ';      if (Is_array ($val)) {if (is_string ($val [0])) {$exp = Strtolower ($val [0]); If it is $map[' id ']=array (' eq ', 100) A class of structure, then parse into the database executable format if (Preg_match ('/^ (eq|neq|gt|egt|lt|elt) $/', $exp)) {$whereSt R. = $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% ');          The IF (Is_array ($val [1])) {//parse format is 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 the OR if (In_array ($likeLogic, Array (' and ', ' OR ', ' XOR ')) {/* * Based on logical structure, combined statement, omitted here/ $whereStr. = ' ('. Implode ('. $likeLogic. ')                       ', $like). '; }} else{$whereStr. = $key. ' '. $this->exp[$exp]. '        '. $this->parsevalue ($val [1]); }}elseif (' bind ' = = $exp) {//using an expression, PDO numberAccording to the 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 ', ' 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])? EX        Plode (', ', $val [1]): $val [1]; $whereStr. = $key. ' '. $this->exp[$exp]. ' '. $this->parsevalue ($data [0]). '      and '. $this->parsevalue ($data [1]);      }else{//Throws 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) {//parse conditions for special formats $whereStr = ';                 Switch ($key) {case ' _string ': $whereStr = $val;  String pattern query criteria case ' _complex ': $whereStr = substr ($this->parsewhere ($val), 6); Compound query conditions case ' _query '://String pattern query Condition/* Handle logical structure, and format output string, omit */} return ' ('. $whereStr. ') ';}

The above two functions are very long, we will simplify some of the following: Parsewhere first determine whether the query data is not a string, if it is a string, directly return a string, otherwise, iterate through the array of query conditions, parse it one by one.

Because TP supports special queries such as _string,_complex, called Parsethinkwhere to handle, for ordinary queries, it calls the Parsewhereitem.

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

Articles you may be interested in:

    • thinkphp PHP Framework Learning Notes
    • PHP Stealth A word back door, and thinkphp framework plus password program (base64_decode)
    • A solution to the problem of cross-domain of session in thinkphp framework
    • Detailed analysis of a SQL injection vulnerability for earlier versions of the thinkphp framework
    • Exploitation of arbitrary Code execution vulnerability in thinkphp framework and its repairing method
    • thinkphp Framework Design and extension
    • thinkphp, ZF2, YAF, laravel framework routing big battle
    • How to replace a frame entry file or apply a portal file in the thinkphp3.2 Lite file
    • Developing the mobile interface using the thinkphp framework
    • thinkphp Development Framework Functions: C method

http://www.bkjia.com/PHPjc/1110081.html www.bkjia.com true http://www.bkjia.com/PHPjc/1110081.html techarticle thinkphp Framework Security Implementation Analysis, thinkphp Framework Implementation thinkphp Framework is one of the most popular PHP framework in China, although it is not comparable with those of foreign countries, but the advantage is that, en, ...

  • 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.