ThinkPHP framework security implementation analysis, thinkphp Framework Implementation

Source: Internet
Author: User

ThinkPHP framework security implementation analysis, thinkphp Framework Implementation

The ThinkPHP framework is one of the popular PHP frameworks in China. Although it cannot be compared with those frameworks outside China, it has the advantage that the Chinese manual is comprehensive. I recently studied SQL injection. I used to use the TP framework because the underlying layer provides security functions, so I didn't think about security issues during the development process.

I. I have to say I Function

The TP system provides the I function for filtering input variables. The significance of the entire function body is to obtain data in various formats, such as I ('get. '), I ('Post. and then use the htmlspecialchars function (by default) for processing.

If you need to use other methods for security filtering, you can set it in/ThinkPHP/Conf/convention. php:

'Default _ filter' => 'strip _ tags', // You can also set multiple filtering methods 'default _ filter' => 'strip _ tags, stripslashes ',

You can find the I function from/ThinkPHP/Common/functions. php. The source code is as follows:

/*** Input parameters can be filtered and default values * usage: * <code> * I ('id', 0 ); get the id parameter to automatically determine get or post * I ('Post. name ', '', 'htmlspecialchars'); get $ _ POST ['name'] * I ('get. '); obtain the $ _ GET * </code> * @ param string $ name variable name. The variable name supports the specified type * @ param mixed $ default. The default value is * @ param mixed $ filter. * @ param mixed $ additional data source to be obtained by datas * @ return mixed */function I ($ name, $ default = '', $ filter = null, $ datas = null) {static $ _ PUT = null; if (strpos ($ na Me, '/') {// specifies the modifier list ($ name, $ type) = explode ('/', $ name, 2 );} elseif (C ('var _ AUTO_STRING ') {// It is forcibly converted to the string $ type = 's' by default;}/* Get data in the format of $ name: first determine the source of the parameter, and then obtain data in various formats */if (strpos ($ name ,'. ') {list ($ method, $ name) = explode ('. ', $ name, 2) ;}// specify the parameter source else {$ method = 'param';} // set to automatically obtain the switch (strtolower ($ method )) {case 'get': $ input = & $ _ get; break; case 'post': $ input = & $ _ post; break; case 'put ': /* omitted here */ Case 'param':/* skipped here */case 'path ': /* omit */}/* here to filter the obtained data */if (''// get all variables $ data = $ input; $ filters = isset ($ filter )? $ Filter: C ('default _ filter'); if ($ filters) {if (is_string ($ filters) {$ filters = explode (',', $ filters);} // supports foreach ($ filters as $ filter) for multiple filtering methods {$ data = array_map_recursive ($ filter, $ data ); // cyclic filtering }}elseif (isset ($ input [$ name]) {// value operation $ data = $ input [$ name]; $ filters = isset ($ filter )? $ Filter: C ('default _ filter'); if ($ filters) {/* filters parameters, regular Expression verification is supported * // * skipped */} if (! Empty ($ type) {// If the forced conversion type switch (strtolower ($ type) {case 'A': $ data = (array) $ data; break; is set; // array case 'D': $ data = (int) $ data; break; // number case 'F': $ data = (float) $ data; break; // floating point case 'B': $ data = (boolean) $ data; break; // boolean case 's': // string default: $ data = (string) $ data ;}} else {// default value of the variable $ data = isset ($ default )? $ Default: null;} is_array ($ data) & array_pai_recursive ($ data, 'Think _ filter'); // If $ data is an array, use think_filter to filter the array return $ data ;}

Well, the function is basically divided into three parts:
FirstTo obtain data in various formats.
SecondAnd cyclically encode the obtained data, whether it is a two-dimensional array or a three-dimensional array.
Part 3That is, the second to the last line calls think_filter to perform a mysterious processing of the data.

Let's first trace the think_filter function:

// The latest function think_filter (& $ value) is added to row 1536 in version 3.2.3) {// filter and 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. It can be seen at a glance that a space is added after some specific keywords.

But this is called the think_filter function. It only adds a space. What filtering function does it play?

We all know important logic verification, such as verifying whether a user has logged on, whether the user can buy a product, etc. It must be verified on the server side. If it is verified on the front end, it is easy to bypass. In the same way, in/exp logic structures in a program should also be controlled by the server.

When the data transmitted to the server is like this: id [0] = in & id [1] =, 3, if the think_filter function is not available, it will be parsed into 1 in the following table, it will be regarded as the server-side logic parsing. However, if it is changed to the following table 2, a space is added, and the parsing cannot be matched, the vulnerability is avoided.

$ Data ['id'] = array ('in' => '1, 2, 3 ') // After filtering by think_filter, it will look like: $ data ['id'] = array ('in' => '1, 2, 3 ')

Ii. SQL Injection

Related Files:/ThinkPHP/Library/Think/Db. class. php (changed to/ThinkPHP/Library/Think/Db/Driver in 3.2.3. class. php) And/ThinkPHP/Library/Think/Model. class. php. Among them, the Model. class. php file provides functions directly called by curd and interfaces are provided externally. Functions in Driver. class. php are indirectly called by curd operations.

// The main analysis is as follows: M ('user')-> where ($ map)-> find (); // retrieve a piece of data from the user table based on the $ map Condition

Let's talk about it.TP processing ideas:

First, instantiate the Model class as a user object, and then call the where function in the user object to process $ map, that is, after formatting $ map, assign the value to the member variable $ options of the user object (if there are other coherent operations, assign the value to the corresponding member variable of the user object first, instead of directly splicing SQL statements, you do not need to consider the order of keywords like splicing SQL statements when writing a coherent operation.) Next, call the find function.

The find function calls the select function in the driver class to obtain data. The select function is another story.

In addition to curd operations, select also processes pdo binding. We only care about curd operations here. Therefore, buildSelectSql is called in select to process paging information, in addition, parseSQL is called to assemble the SQL statements in the set sequence.

Although all the parameters required for splicing SQL statements are included in the member variables, the format is not uniform. It may be in string format or in array format, it may also be a special Query format provided by TP, such as: $ data ['id'] = array ('gt ', '000000');, so before splicing, you also need to call their respective processing functions for unified formatting. I chose parseWhere as a complex example for analysis.

For security, if I function is used to obtain data, htmlspecialchars will be processed by default to effectively defend against xss attacks, but it has little impact on SQL injection.

When filtering out symbols related to SQL injection, TP is witty: first, user input is processed according to the normal logic, then, perform security processing in functions like parseWhere and parseHaving that are closest to the final SQL statement. This sequence avoids injection during processing.

Of course, the processing method is the most common addslashes. It is recommended to use mysql_real_escape_string for filtering, according to the waves on the beach, however, this function can only be used when the database is connected.

I feel that TP can be optimized in this place. After all, the database is connected at this step.

Well, the analysis begins:

Let's talk about the member variables in several Model objects:

// Primary key name protected $ pk = 'id'; // field information protected $ fields = array (); // data information protected $ data = array (); // query expression parameter protected $ options = array (); // list of chain operation methods protected $ methods = array ('strict ', 'order', 'Alias ', 'having', 'group', 'lock', 'stinct ', 'auto', 'filter', 'validate', 'result', 'Token', 'index ', 'force') Next, analyze the where function: public function where ($ where, $ parse = null) {// if it is not in the array format, that is, where ('Id = % d & name = % s', array ($ id, $ name ), The array passed to the string calls 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 ;} // 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 very simple. If it is in the format of where ('Id = % d & name = % s', array ($ id, $ name), then $ id, the $ name variable calls the escapeString in mysql for processing. The essence of escapeString is to call functions such as mysql_real_escape_string and addslashes for processing.

Finally, assign the array after analysis to the member function -- $ where of the Model object for further processing.

Analyze the find function again:

// Model. class. php line 721 version 3.2.3public function find ($ options = array () {if (is_numeric ($ options) | is_string ($ options )) {/* If the transmitted data is a string, not an array */$ where [$ this-> getPk ()] = $ options; $ options = array (); $ options ['where'] = $ where;/* extracts the query condition and assigns a value */} // searches for the record based on the primary key $ pk = $ this-> getPk (); if (is_array ($ options) & (count ($ options)> 0) & is_array ($ pk) {/* construct a composite primary key query condition, skipped */} $ options ['limit'] = 1; // always search for a record $ options = $ this-> _ parseOptions ($ options ); // analysis expression if (isset ($ options ['cache']) {/* cache query, skipped */} $ resultSet = $ this-> db-> select ($ options); if (false = $ resultSet) {return false ;} if (empty ($ resultSet) {return null;} // the query result is null if (is_string ($ resultSet) {return $ resultSet ;} // The query result is a string // The processing result after the data is read. The abbreviation $ this-> data = $ this-> _ read_data ($ resultSet [0]) is omitted here. return $ this-> data ;}

$ Pk is the primary key and $ options is the expression parameter. This function completes the member variable -- options array, calls the select function at the db layer to query data, and then returns data after processing.

Follow-up _ parseOptions functions:

Protected function _ parseOptions ($ options = array () {// analysis expression if (is_array ($ options) {$ options = array_merge ($ this-> options, $ options);}/* get the table name. Skipped here * // * Add the alias of the data table, and skipped */$ options ['model'] = $ this-> name; // record the operation model name/* checks the field type of the array query condition. If it is within a reasonable range, filter it; otherwise, an exception is thrown or the corresponding field */if (isset ($ options ['where']) & is_array ($ options ['where']) is deleted. &! 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, filter and force type conversion if (is_scalar ($ val )) {/* is_scalar checks whether it is a scalar. Scalar is a variable of integer, float, string, and boolean, while array is not a scalar. */$ This-> _ parseType ($ options ['where'], $ key) ;}} elseif (! Is_numeric ($ key )&&'_'! = Substr ($ key,) & false = strpos ($ key ,'. ') & false === strpos ($ key,' (') & false === strpos ($ key,' | ') & false = strpos ($ key, '&') {// If $ key is not a number and the first character is not _, it does not exist. (| & and other special characters if (! Empty ($ this-> options ['strict ']) {// in strict mode, an error E (L (' _ ERROR_QUERY_EXPRESS _ ') is thrown _'). ':['. $ key. '=> '. $ val. ']');} unset ($ options ['where'] [$ key]); // unset the corresponding value }}$ this-> options = array (); // clear the SQL expression Assembly after the query to avoid affecting the next query $ this-> _ options_filter ($ options); // The expression filters return $ options ;}

The structure of this function is probably that the table name and model name are obtained first, and then the data is processed: if the data is not in the database field, then, an exception is processed or the data is deleted. Otherwise, perform _ parseType processing. ParseType is not followed up here. Its function is data type detection. Forced type conversion includes three types of data: int, float, and bool.

When the function runs here, the processed data should be transferred to the select function at the db layer. In this case, int, float, and bool data in the query condition $ options have been forcibly converted. strings in the where () function (queries in non-array format) addslashes is also processed.

Continue to trace the select function and get to the driver object. You should first list several useful member variables:

// Database expression protected $ exp = array ('eq '=>' = ', 'neq' =>' <> ', 'gt' => ', 'egt' => '> =', 'lt '=>' <', 'elt' =>' <= ', 'notlike' =>' NOT like ', 'like' => 'like', 'in' => 'in', 'notin' => 'not in', 'not in' => 'not in ', & apos; between & apos; = & apos; BETWEEN & apos;, not between & apos; = & apos; not between & apos;, notbetween & apos; = & apos; not between & apos '); // query expression protected $ selectSql = 'select % DISTINCT % FIELD % FROM % TABLE % FORCE % JOIN % WHERE % GROUP % HAVING % ORDER % LIMIT % UNION % LOCK % COMMEN T % '; // The current SQL command protected $ queryStr = ''; // The parameter is bound to 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 ;}

Version 3.2.3 has been improved, and the select statement has been simplified a lot. The parseBind function is a binding parameter used for pdo queries. No table is specified here.

The buildSelectSql () function and its subsequent calls are as follows:

Public function buildSelectSql ($ options = array () {if (isset ($ options ['page']) {/* page number calculation and processing, skipped */} $ SQL = $ this-> parseSql ($ this-> selectSql, $ options); return $ SQL ;} /* replace the expression */public function parseSql ($ SQL, $ options = array () {$ SQL = str_replace (array ('% TABLE % ', '% DISTINCT %', '% FIELD %', '% JOIN %', '% WHERE %', '% GROUP %', '% HAVING % ', '% ORDER %', '% LIMIT %', '% UNION %', '% LOCK %', '% COMMENT %', '% FORCE % '), array ($ th Is-> parseTable ($ options ['table']), $ this-> parseDistinct (isset ($ options ['distinct '])? $ Options ['did']: false), $ this-> parseField (! Empty ($ options ['field'])? $ Options ['field']: '*'), $ this-> parseJoin (! Empty ($ options ['join'])? $ Options ['join']: ''), $ this-> parseWhere (! Empty ($ options ['where'])? $ Options ['where']: ''), $ this-> parseGroup (! Empty ($ options ['group'])? $ Options ['group']: ''), $ this-> parseHaving (! Empty ($ options ['having '])? $ Options ['having']: ''), $ this-> parseOrder (! Empty ($ options ['order'])? $ Options ['order']: ''), $ this-> parseLimit (! Empty ($ options ['limit'])? $ Options ['limit']: ''), $ this-> parseUnion (! Empty ($ options ['join'])? $ Options ['Union ']: ''), $ this-> parseLock (isset ($ options ['lock'])? $ Options ['lock']: false), $ this-> parseComment (! Empty ($ options ['comment'])? $ Options ['comment']: ''), $ this-> parseForce (! Empty ($ options ['force'])? $ Options ['force']: ''), $ SQL); return $ SQL ;}

As you can see, parseSql concatenates SQL statements with regular expressions, but does not directly process various data formats. Instead, multiple functions are called During Variable parsing, here we use parseWhere as an example.

Protected function parseWhere ($ where) {$ whereStr = ''; if (is_string ($ where) {// directly use the string condition $ whereStr = $ where ;} else {// use an array expression/* To set logical rules, such as or and xor. The default value is and. */$ operate = 'and' is omitted here '; /* parse expressions in special format and format the output */foreach ($ where as $ key => $ val) {if (0 = strpos ($ key, '_') {// parse Special Condition expression $ whereStr. = $ this-> parseThinkWhere ($ key, $ val);} else {// security filter for query fields $ multi = is_array ($ val) & isset ($ val ['_ multi']); // Determine whether a composite query $ key = trim ($ key);/* processing field contains | & logic */if (strpos ($ key, '| ')) {// supports name | title | the nickname method defines the query field/* to convert | to or, and format the output. */} elseif (strpos ($ key, '&') {/* replace & with and format the output. Here, the */} else {$ whereStr is omitted. = $ this-> parseWhereItem ($ this-> parseKey ($ 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 it is a $ map ['id'] = array ('eq ', 100) structure, if (preg_match ('/^ (eq | neq | gt | egt | lt | elt) $/', $ exp) {$ whereStr. = $ key. ''. $ this-> exp [$ exp]. ''. $ this-> parseValue ($ val [1]);} // If the fuzzy search format is elseif (preg_match ('/^ (notlike | like) $/', $ ex P) {// fuzzy search, $ map ['name'] = array ('like', 'thinkphp % '); if (is_array ($ val [1]) {// resolution format: $ map ['B'] = array ('notlike', array ('% thinkphp %', '% tp'), 'AND '); $ likeLogic = isset ($ val [2])? Strtoupper ($ val [2]): 'OR'; // if no logical structure is set, the default value is OR if (in_array ($ likeLogic, array ('and ', 'OR', 'xor ') {/* combines statements based on the logical structure. The */$ whereStr is omitted here. = '('. implode (''. $ likeLogic. '', $ like ). ')' ;}} else {$ whereStr. = $ key. ''. $ this-> exp [$ exp]. ''. $ this-> parseValue ($ val [1]) ;}} elseif ('bind' ==$ exp) {// use an expression to bind pdo data to $ whereStr. = $ key. '= :'. $ val [1];} elseif ('exp '= $ exp) {// use the 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. ')';} e Lseif (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, an error E (L ('_ EXPRESS_ERROR _') is thrown _'). ':'. $ val [0]) ;}} else {// resolution example: $ map ['status & score & title'] = array ('1', array ('gt ', '0'), 'thinkphp', '_ mul' => 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 {// use fuzzy match for string 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) {// condition for parsing special formats $ whereStr = ''; switch ($ key) {case '_ string': $ whereStr = $ val; break; // string mode query condition case '_ complex': $ whereStr = substr ($ this-> parseWhere ($ val), 6); break; // composite query condition case '_ query': // string mode query condition/* process the logical structure and format the output string. */} return '('. $ whereStr. ')';}

The two functions above are very long. Let's look at them in a concise way: parseWhere first checks whether the query data is a string. If it is a string, it returns a string directly. Otherwise, it traverses the array of the query condition and parses it one by one.

Because TP supports Special queries such as _ string, _ complex, parseThinkWhere is called for processing. For common queries, parseWhereItem is called.

In their respective processing processes, parseValue is called. After tracing, addslashes is used for filtering. Although addslashes may cause wide byte injection in pages not encoded in UTF-8, however, if the page and database are correctly encoded, there is still no problem.

Articles you may be interested in:
  • ThinkPHP php framework learning notes
  • PHP is invisible to a single-line backdoor, And the ThinkPHP framework is used to add a password Program (base64_decode)
  • ThinkPHP framework implements a solution to cross-domain session Problems
  • Detailed analysis of an SQL injection vulnerability in earlier versions of the ThinkPHP framework
  • Use of arbitrary code execution vulnerability in the ThinkPHP framework and its repair methods
  • ThinkPHP Framework Design and extension
  • ThinkPHP, ZF2, Yaf, and Laravel frameworks
  • How to replace the framework entry file or application entry file with the Lite file in thinkphp3.2
  • Use Thinkphp framework to develop mobile Interfaces
  • ThinkPHP development framework function explanation: C method

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.