PHP Code Audit 16—Getting Started with ThinkPHP Code Audit

1. Getting to know ThinkPHP for the first time

1. Directory file structure

├─application 应用目录(可设置)
│ ├─common 公共模块目录(可更改)
│ ├─index 模块目录(可更改)
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ └─ … 更多类库目录
│ ├─command.php 命令行工具配置文件
│ ├─common.php 应用公共(函数)文件
│ ├─config.php 应用(公共)配置文件
│ ├─database.php 数据库配置文件
│ ├─tags.php 应用行为扩展定义文件
│ └─route.php 路由配置文件
├─extend 扩展类库目录(可定义)
├─public WEB 部署目录(对外访问目录)
│ ├─static 静态资源存放目录(css,js,image)
│ ├─index.php 应用入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于 apache 的重写
├─runtime 应用的运行时目录(可写,可设置)
├─vendor 第三方类库目录(Composer)
├─thinkphp 框架系统目录
├─build.php 自动生成定义文件(参考)
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件

2. URL and routing

Basic routing:

  • ROOT_PATH => application
  • THINK_PATH => thiinkphp
  • EXTEND_PATH => extend
  • VENDER_PATH => vender

URL access:

www.xxx.com/index.php/index/index/index, the access location is the index function under the index file under the controller directory under the index module under the application directory.

Incoming parameters:

method one:

www.xxx.com/index.php/hello/index/hello/name/word/city/chengdu, for this method of passing in parameters, it means accessing the hello function under the index file under the hello module, and passing in parameters 1 is name, the value passed in is word, the second parameter passed in is city, the value passed in is cehngdu, there is no order requirement for the passing of these two parameters, for example, the request URL is www.xxx.com/ index.php/hello/index/hello/city/chengdu/name/word has the same effect.

Method 2:

www.xxx.com/index.php/hello/index/hello/name=word&citychengdu, this is a common way of passing parameters, which is relatively easy to understand.

3. Request and response

Instead of using traditional global variables such as $_GET, $_POST, $_COOKIE, $_SESSIONetc., a Request object is provided for calling.

The request object of ThinkPHP5 is completed by the think\Request class.

In thinkphp5, there are the following methods to obtain request content through the requests object:

  • Inherit think\Controller
  • Automatic injection of request objects
  • Use helper functions

Get request variables:

  • param() gets the request variable

    $request->param()方法,用于获取所有的变量,对于变量的获取,具有一定的优先级,优先级情况如下:
    	路由变量 > 当前请求变量($_POST变量) > $_GET变量
    使用示例:echo $request->param('name','yujun','stryolower')
    详解:该示例表示获取name变量的值,如果没有获取到,默认为yujun,如果获取到了使用strtolower()函数转换为小写。
    
  • get() gets the $_GET variable

    示例:echo $request->get('name')
    使用助手函数示例:echo input('get.name') // 表示获取get请求的name变量的值,如果使用input('get.')的方式,表示获取所有get请求的变量
    
  • post() gets the $_POST variable

  • file() gets the contents of $_FILE

  • ip() gets the requested IP address

  • method() method to get the request

  • pathInfo() gets path information for controller and method names

    示例:请求www.xxxx.com/index.php/index/index/hello
    echo $request>pathinfo()  //结果为index/index/hello
    
  • rootInfo() gets routing information

response:

The output of the response content, including the following methods:

  • automatic output

    在config.ph中设置default_return_type 即可更改默认返回类型,达到自动输出的效果
    
  • Manual output

    输出json类型:return json($data)
    输出json类型,并设置响应码和http头:
    方法1return json($data,201,['set_cookie'=>'test_cookie'])
    方法2return json($data)->code(201)->gheader(['set_cookie'=>'test_cookie'])
    对于其他的输出类型,只需要更换为xml或者html等函数即可。
    
  • page jump

    示例:
    public function hello($name){
          
          
    	if($name==='thinkphp'){
          
          $this->success("hello,you are thinkphp","admin")}
    	else{
          
           $this->error("ou error!!","test")}
    }
    public function admin(){
          
          
    return "hello,your right";
    }
    public function test(){
          
          
    return "your are error!!";
    }
    此时,我们请求hello方法,传入$name=thinkphp,则会跳转到admin()方法,如果传入错误,则会输出错误信息后,跳转到test()方法。
    
  • page redirection

    示例:
    public function hello($name){
          
          
    	if($name==='thinkphp'){
          
          
        $this->redicret("http://www.baidu.com");
      }else{
          
           
        $this->redict("http://www.163.com")};
    }
    利用的是302功能码的重定向功能。
    也可以设置跳转的功能码,比如设置为301$this->redicret("http://www.baidu.com",301)
    

4. Data interaction

The basic configuration of the database is in database.php.

Query expression:

  • selec * from table_bame where id='$id'

    方法1:$result=Db::query("select * from test_table where id='$id'")
    方法2:$result=Db::name("test_table")->where('id',1)->find();
    方法3:$result=Db::name("table_name")->where('id',$id)->select;
    方法4(参数绑定):$result=Db::name('table_name')->where("id=:id",["id"=>$id])->select()
    
  • select * from table_name where id>'$id' limit 0,10

    方法1:$result=Db::query("select * from test_table where id >'$id' limit 0,10")
    方法2:$result=Db::name("test_table")->where('id','>',$id)->limit(10)->find();
    方法3:$result=Db::name("table_name")->where('id','>',$id)->limit(10)->select();
    
  • select * from table_name wheere id='$id' and password=​'$passwd'

    方法1:$result=Db::query("select * from table_name wheere id='$id' and password='$passwd'")
    方法2:$result=Db::name("test_table")->where('id',$id)->where('password',$passwd)->find();
    方法3:$result=Db::name("test_table")->where(['id'=>[$id],'passwd'=>[$passwd]])->find();
    
  • select user_name from table_name where id='$id'

    方法1: $result=Db::name(table_name)->column('user_name')->where('id',$id)->find()
    方法2(参数绑定):$result::Db::name(table_name)-culomn('user_name')->where("id=:id",["id"=>$id])->select();
    

2. ThinkPhp framework audit case 1

Audit system: hsyCMS v3.0

Vulnerabilities involved: XSS, SQL injection, file deletion.

1. Familiar with the website structure

Familiar with the structure of the website, you need to do the following:

  • Understand the website directory structure
  • Understand system functions
    • Foreground function
    • background function
  • Analyze possible test points
    • Foreground test point analysis
      • message
      • search
    • Background test point analysis
      • Login, Registration, Password Retrieval
      • File upload, download, read
      • SQL injection, http header injection, code injection

2. Determine routing and filtering

Routing: app/route.php

use think\Route;
//前端路由配置
if (is_file(APP_PATH.'common/install.lock')) {
    
    
  $routeNav  = db('nav')->field('entitle')->order('sort,id')->select();  //从nav表中查询entitle
  $routeCate = db('cate')->field('entitle')->order('sort,id')->select(); //从cate表中查询entitle
  Route::rule('search','index/Search/index');   //将search 路由到 index模块的Search控制器下的index方法下
  foreach ($routeNav as $key=>$v) {
    
    
	  Route::rule($v['entitle'],'index/Article/index');  //将从nav表中查询出的entitle循环路由到index/Article/index
	  Route::rule($v['entitle'].'/:id','index/Show/index'); 
  }
  foreach ($routeCate as $key=>$v) {
    
    
	  Route::rule($v['entitle'],'index/Article/index');  //将从cate表中查询出的rntitle循环路由到index/Artitle/index
  }
}

Parameter filtering situation:

Parameter filtering that needs to be understood:

  • Native parameters: GET, POST, RERUEST
  • System external variable acquisition functions: get(), post(), Request()

Requet class function analysis: libs\libray\thibk\Request.php

  • get()

    public function get($name = '', $default = null, $filter = '')
        {
          
          
            if (empty($this->get)) {
          
          
                $this->get = $_GET;  //将$_GET中的参数赋值到$this—>get变量
            }
            if (is_array($name)) {
          
           //如果传入的$name是数组
                $this->param      = [];
                $this->mergeParam = false;
                return $this->get = array_merge($this->get, $name);  //将传入的GET参数和$name合并为一个数组
            }
            return $this->input($this->get, $name, $default, $filter);  //调用input函数
        }
    
  • input()

    public function input($data = [], $name = '', $default = null, $filter = '')
        {
          
          
            if (false === $name) {
          
           // 获取原始数据
                return $data;
            }
            $name = (string) $name;
            if ('' != $name) {
          
           // 解析name
                if (strpos($name, '/')) {
          
            //如果name中存在“/”
                    list($name, $type) = explode('/', $name);//将$name拆分为$name和$type
                } else {
          
          
                    $type = 's';
                }
                foreach (explode('.', $name) as $val) {
          
           // 按.拆分成多维数组进行判断
                    if (isset($data[$val])) {
          
          
                        $data = $data[$val];
                    } else {
          
          
                        return $default; // 无输入数据,返回默认值
                    }
                }
                if (is_object($data)) {
          
          
                    return $data;
                }
            }
            $filter = $this->getFilter($filter, $default);// 调用解析过滤器,$default为空
            if (is_array($data)) {
          
            //如果输入的数据是数组,调用array_walk_recursive()并使用$filter作为过滤器
                array_walk_recursive($data, [$this, 'filterValue'], $filter);
                reset($data);
            } else {
          
          
                $this->filterValue($data, $name, $filter);  //调用filtervalue()
            }
            if (isset($type) && $data !== $default) {
          
           //没有设置$type,也就是$name中不存在“/”
                $this->typeCast($data, $type);  // 强制类型转换
            }
            return $data;
        }
    
  • getFilter()

    protected function getFilter($filter, $default)
        {
          
          
            if (is_null($filter)) {
          
            //默认为空,所以并不会进行过滤
                $filter = [];
            } else {
          
           //不为空,
                $filter = $filter ?: $this->filter;
                if (is_string($filter) && false === strpos($filter, '/')) {
          
          
                    $filter = explode(',', $filter);
                } else {
          
          
                    $filter = (array) $filter;
                }
            }
            $filter[] = $default;
            return $filter;
        }
    
  • post()

    public function post($name = '', $default = null, $filter = '')
        {
          
          
            if (empty($this->post)) {
          
          
                $content = $this->input;
                if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) {
          
          
                    $this->post = (array) json_decode($content, true);
                } else {
          
          
                    $this->post = $_POST;
                }
            }
            if (is_array($name)) {
          
          
                $this->param       = [];
                $this->mergeParam  = false;
                return $this->post = array_merge($this->post, $name);
            }
            return $this->input($this->post, $name, $default, $filter);  //调用inout函数
        }
    
  • Request()

     public function request($name = '', $default = null, $filter = '')
        {
          
          
            if (empty($this->request)) {
          
          
                $this->request = $_REQUEST;  //获取$_request
            }
            if (is_array($name)) {
          
            //如果$name为数组,返回合并后的数组
                $this->param          = [];
                $this->mergeParam     = false;
                return $this->request = array_merge($this->request, $name);
            }
            return $this->input($this->request, $name, $default, $filter); //调用inout
        }
    

It can be seen that our get(), post(), and request() functions all call the input() method for parameter checking, but the filters we pass in are all empty, that is, no checking is performed by default, so it does not Safety. Let's briefly analyze a few examples.

3. Foreground SQL injection analysis

Vulnerability description :

在prevNext()函数中,未经过任何过滤就将参数直接拼接到了SQL语句中,造成了SQL注入。

Vulnerability analysis:

First enter the code location where the vulnerability is located: the preNext() function of /app/index/common.php

//获取上下篇
function prevNext($id,$entitle,$one){
    
     
	  //上一篇
	  $prev=db('article')->field("id,title")->where("id < {
      
      $id} and nid={
      
      $one['nid']} and cid={
      
      $one['cid']}")->order('id desc')->limit('1')->find();  
  //执行的Sql语句: select id,title from sy_article where ( id < $id and uid=$one['nid'] and cid=$one['cid'] ) oeder by id desc limit 0,1 
	  if($prev){
    
    
		  $prev['url'] = '/'.$entitle.'/'.$prev['id'].'.html';
	  }else{
    
    
		  $prev['url'] = "javascript:void(0)";
		  $prev['title'] = "没有了";
	  }
	  $data['prev'] = $prev; 
	  
	  //下一篇
	  $next = db('article')->field("id,title")->where("id > {
      
      $id} and nid= {
      
      $one['nid']} and cid = {
      
      $one['cid']}")->order('id asc')->limit('1')->find();  
	  if($next){
    
    
		  $next['url'] =  '/'.$entitle.'/'.$next['id'].'.html';
	  }else{
    
    
		  $next['url'] = "javascript:void(0)";
		  $next['title'] = "没有了";
	  }
	  $data['next'] = $next;
	  return $data;
}

It can be seen that when the SQL statement is executed, the judgment condition is set through the execution of the where function, and parameters such as id are spliced ​​into the SQL statement, so there is a risk of SQL injection.

Then we searched in reverse and found that the index() method in app/index/controller/Show.php called this method. Let's analyze it:

public function index()
    {
    
    
		$id = input('id');  //通过input助手函数获取传入的参数id(并没有经过过滤)
		$one  = db('article')->where('id',$id)->find();			
		if(empty($one)){
    
     exit("文章不存在");}		
		$navrow = db('nav')->where('id',$one['nid'])->find();		
		//省略n行..........			
		if($data['showcate']==1){
    
    			
			//省略n行......
			$data['pn'] = prevNext($id,$navrow['entitle'],$one);		
		}		
		$data['one'] = $one;
		$data['nid'] = $one['nid'];
		$data['site'] = getseo($one['nid'],$id,$one['cid']);
		$this->assign($data);
  	//省略n行......
   

Since the system does not filter the incoming parameters, you can directly construct SQL injection statements for injection here. For example, construct such a payload:

http://www.xxx.com/index.php/index/show/index?id=123) and (select 1 from (select count(*),concat(user(),0x7e,database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+

You can successfully use the error injection to obtain the user name and database information in the system.

3. References

Supongo que te gusta

Origin blog.csdn.net/qq_45590334/article/details/126485532
Recomendado
Clasificación