Too many versions only analyze the large version and the version with more users. Currently, the version with the most users is 3.2.3. During the audit, multiple versions were found to be unpublished

Test environment: mysql5.6 / php5.5

First of all, it is clear that the full version of ThinkPHP may have wide byte injection when PDO is not used for parameter binding.

Black box detection method: input test data with header byte greater than 7F, for example:

%88% 5C% 27% 5eupdatexml (1, concat (0x7e, database()), 3)% 23 (% 5E followed by any T-SQL statement)

Whether the global search default format of white box detection method is set to GBK

'default menu charset' = > UTF-8 ', // default output code


("SET NAMES gbk");

Where method

It is also the most commonly used condition query method, and supports query condition preprocessing

1. $Model->where("id=%d
and username='%s' and xx='%f'",array($id,$username,$xx))->select();
2. // or
3. $Model->where("id=%d
and username='%s' and xx='%f'",$id,$username,$xx)->select();

And his preprocessing actually calls the addslashes () method

 *SQL instruction security filtering
 * @access public
 *@ param string $STR SQL string
 * @return string
public function escapeString($str) {
    return ($str);

However, when passing a single parameter, where does not parameterize the statement, and only one parameter is passed in multiple instances of the official document, including the wrong writing methods found in some open source projects.

$result =
$teachers = $Teacher->where('name', 'like', '%'
. $name . '%')

Where code

 *Specify query criteria to support security filtering
 * @access public
 *@ param mixed $where conditional expression
 *@ param mixed $parse preprocessing parameter
 * @return Model
public function where($where,$parse=null){
    if(!($parse) && ($where)) {
        if(!($parse)) {
            $parse = ();
        $parse = (array($this->db,'escapeString'),$parse);
        $where =   ($where,$parse);
    return $this;

Only the $parse parameter is filtered. This writing method is equally valid for query() or similar methods.

This is also a security awareness issue for developers, but such a way of writing affects the full version at the time of audit.


Query() method, execute method

These two methods support more native SQL statements, which are often encountered in complex business scenarios

Both methods call parsesql to parse the SQL statement in versions lower than 3.1.3

 *Parsing SQL statements
 * @access public
 *@ param string $SQL SQL instruction
 *Do @ param Boolean $parse need to parse SQL
 * @return string
protected function parseSql($sql,$parse) {
    //Analysis expression
    if(true === $parse) {
        $options =  $this->_parseOptions();
        $sql    =   $this->db->parseSql($sql,$options);
    }Elseif (($parse)) {// SQL preprocessing
        $sql    =   ($sql,$parse);
        $sql    =   ($sql,array('__TABLE__'=>$this->getTableName(),'__PREFIX__'=>C('DB_PREFIX')));
    return $sql;

However, parsesql queries directly without preprocessing array parameters.

This loophole has been disclosed by the government for a long time, but it can still be seen in the historical version.

Table, find, alias, join, union, group, having, comment method

Both the table and find methods need to select () to query the database, but no filtering is found

If the parameters are controllable, it can be directly used


Enter in URL address

Or array form can avoid value filtering detection

Table is used in the same way


All operations before where can be used in this way

There are also similar

Alias sets the alias of the current data table

Group groups result sets based on one or more columns

Join is used based on the relationship between columns in two or more tables

The union operation is used to merge the result set of two or more select statements

The comment method is used to add comment content to the generated SQL statement

If the parameters are controllable, SQL injection will occur

Order method

The order method has a CVE number cve-2018-16385

SQL injection exists in versions less than 5.1.2. Now the latest version of 3.2.5 has been fixed

Before 3.2.4, the vulnerability still exists, and the update function has changed many upgrades, which may cause more problems






The second figure is the array of all parameters after the patch to prevent SQL injection. Consider the problem to increase array traversal more comprehensively. The general array processing can bypass for example


Select method

The front-end method is to enter select and interact with the database after query data splicing. It’s also an important method to support a parameter passing. In 18 years, a vulnerability is also disclosed. There are also delete, find and other methods to control the same kind of influence of the $options parameter

Just type in the URL


It can be injected successfully because the received array parameters are not verified

*Query data set
* @access public
*@ param array $options expression parameter
* @return mixed   
public function select($options=array()) {
$pk   =  $this->getPk();    
//Analysis expression
 $options    =  $this->_parseOptions($options);     
 return $resultSet;

Although this vulnerability was updated in version 3.2.5, it can still be exploited even though it is not fixed in the official website 3.2.3, which also leads to the exploitation of versions lower than 3.2.5.

When looking at the official security update code, we found that 5. X includes the latest version of 3.2.5, which does filter this vulnerability but causes another exploitation possibility.

$this->data =  $Dat;

Enter id = 1% 20and% 201 = 1 at the URL to see the execution statement

SELECT * FROM `users` WHERE `id` = 1 [ RunTime:0.0007s ]

You can see that there will be no SQL injection, but there is another problem: the particularity of ThinkPHP framework

When you query whether a data exists, the intruder cannot know your IP address. You can pass an array for example


SELECT * FROM `users` [ RunTime:0.0006s ]

When encountering position error, it will automatically skip when splicing where condition, so you can see the data of the whole table. This method can also be used in delete() method

Update method

1. $user – > where (‘id = 5 ‘) – > setinc (‘score’); / / add 1 to the user’s score

2. $user – > where (‘id = 5 ‘) – > setdec (‘score’, 5); / / the user’s score minus 5

When setinc is called, setdec will inject if the parameter is controllable

When you directly call update to instantiate the update data, the same parameters will be injected. The same official also released the security update

For example, building an object

$User = M("users");
$user['id'] = I('id');
$valu = $User->where($user)->save($data);

This is also using exp injection

The SQL statement executed by ID [0] = exp & id = [1] = = 1 is

Select * from users Where id=1

The update here also uses the payload built by bind in this way:


And its code

     *Update records
     * @access public
     *@ param mixed $data data
     *@ param array $options expression
     * @return false | integer
    public function update($data,$options) {
        $this->model  =   $options['model'];
        $table  =   $this->parseTable($options['table']);
        $sql   = 'UPDATE ' . $table . $this->parseSet($data);
        If (($table, ',')) {// multi table update supports join operation
            $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
        $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
            //Single table update supports order and lmit
            $sql   .=  $this->parseOrder(!empty($options['order'])?$options['order']:'')
        $sql .=   $this->parseComment(!empty($options['comment'])?$options['comment']:'');
        return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);

First call from update codeparseSetSet XXXX built is similar to

UPDATE xxx SET user=:0 WHERE id= xx

SQL injection can be caused in the second call splicing of where condition

Else if ('bind '= = $exp) {// use expression
                    $whereStr .= $key.' = :'.$val[1];
                }Else if ('exp '= = $exp) {// use expression
                    $whereStr .= $key.' '.$val[1];

Bypassing can be achieved when passing arrays


This is also bind, exp injection

In the last call to the execute method, the default pair is: 1:0 splice.

This will cause a third injection

For example, we input


In the condition bit, only input: 1 ‘: 0. Modify the bit to place the statement we want to inject, and it can still be injected successfully



Here, the same way to use insert is less than 5.0. This problem has not been fixed officially at present, although the utilization conditions are harsh.

So far, the audit of common interactive query and modification methods is completed. It is also found that there are multiple utilization conditions (endless pits). For SQL injection caused by multiple processing of ThinkPHP when receiving arrays, although the meaning of this design framework is not understood, it is very unsafe. It is easy to receive arrays that cannot be processed, which leads to the disclosure of important information reported by the program and even the server downtime caused by 500.

It’s also the first time to contact PHP code audit. For many features, you need to read official documents. If there are omissions or errors, please add and correct them.