PHP Code Audit 18—Summary of PHP Code Audit


Preface: After a period of code audit learning, here is a brief summary of the knowledge of code auditing, part of which is a personal knowledge summary. In order to more comprehensively summarize the conclusions of code auditing methods, I also refer to A lot of information is written at the end of the article.

1. Preliminary preparation

1. Tool preparation

Here is a summary of some tools that I have used or that I know better.

  • PHP code debugging tool

    • PHPstorm+xdebug
  • code reading tool

    • notePad++
    • cublimeTxt 3
  • Static code audit tool

    • Fortify

      HP出的一款静态代码审计工具,支持21种开发语言,是一款功能强大的商业代码审计工具。
      
    • Seay

      Seay大佬协议的代码审计工具,国产、开源。
      
    • CodeQl

      Githubt推出的代码审计项目,在国外受到众多安全研究着追捧。
      
    • Xcheck

      Xcheck的php引擎支持原生php的安全检查,也支持对国内主流框架编写的web应用进行安全检查,覆盖包括Thinkphp,Laravel,CodeIgniter,Yii,Yaf等web框架。
      
    • RIPS

      轻量级的代码审计工具,来源于国外专业的代码审计公司RIPS。但是RIPS的开源版本已经多年未更新,并且不支持面向对象的代码审计。
      
  • Browsers and Plugins

    • Browser: FireFox
    • Browser plug-ins: HackBar, FoxyProxy, EditThisCookie
  • database management software

    • Phpmyadmin
    • Navicat

2. Audit environment preparation

  • operating system

    一般采用Windows系统进行审计,对于大多数审计工具来说,Windows系统下图形化界面在使用的时候会更加方便。
    
  • PHP integrated environment

    • phpStudy
    • Pagoda panel
  • PHP version

    对于PHP版本的选择,为了使用实际使用环境,应该尽量使用php5.4以后的版本
    
  • database

    对于数据库,对于PHP程序来说,一般都是采用的Mysql,在审计过程中,最好将mysql设置为5.7版本以后。
    

2. Understand the system architecture

For the system architecture, after confirming the audit target, before the audit, we need to understand the basic architecture of the target system, such as the directory situation, whether the framework is used, which routes exist, whether there is a security filter function, and whether there is global parameter filtering etc. Here it is necessary to divide into framed and unframed for analysis.

1. Using the development framework

1) ThinkPHP framework

Here is ThinkPHP5 as an example, the general directory structure of the framework is as follows:

www  WEB部署目录(或者子目录)
├─application           应用目录
│  ├─common             公共模块目录(可以更改)
│  ├─module_name        模块目录
│  │  ├─config.php      模块配置文件
│  │  ├─common.php      模块函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录
│  │  ├─view            视图目录
│  │  └─ ...            更多类库目录
│  │
│  ├─command.php        命令行工具配置文件
│  ├─common.php         公共函数文件
│  ├─config.php         公共配置文件
│  ├─route.php          路由配置文件
│  ├─tags.php           应用行为扩展定义文件
│  └─database.php       数据库配置文件
│
├─public                WEB目录(对外访问目录)
│  ├─index.php          入口文件
│  ├─router.php         快速测试文件
│  └─.htaccess          用于apache的重写
│
├─thinkphp              框架系统目录
│  ├─lang               语言文件目录
│  ├─library            框架类库目录
│  │  ├─think           Think类库包目录
│  │  └─traits          系统Trait目录
│  │
│  ├─tpl                系统模板目录
│  ├─base.php           基础定义文件
│  ├─console.php        控制台入口文件
│  ├─convention.php     框架惯例配置文件
│  ├─helper.php         助手函数文件
│  ├─phpunit.xml        phpunit配置文件
│  └─start.php          框架入口文件
│
├─extend                扩展类库目录
├─runtime               应用的运行时目录(可写,可定制)
├─vendor                第三方类库目录(Composer依赖库)
├─build.php             自动生成定义文件(参考)
├─composer.json         composer 定义文件
├─LICENSE.txt           授权说明文件
├─README.md             README 文件
├─think                 命令行入口文件

Among them, the applicatinon directory and our Public directory make our application file directory and operation data storage directory belong to important attention objects.

At the same time, config.php, database.php, and route.php in the application directory are system configuration files, database operation files, and system routing files, which need to be carefully analyzed.

For applications developed by the ThinkPHP framework, the URL to access the system usually looks like this:/index.php/模块名/控制器名/函数名/[参数名/参数值]

The module name corresponds to the name of the folder under the application directory. The controller name corresponds to the php file name under the controller folder under the module name directory. Parameters are optional, and /参数名/参数值multiple parameters can be passed in at the same time according to the method, or ?参数1=参数值1&参数2=参数值2variables can be passed in in the traditional way.

2) laravel framework

Basic directory structure:

 |——app  包含了站点的controllers(控制器),models(模型),views(视图)和assets(资源
 |——bootstrap 存放系统启动时的必要文件,这些文件会被index.php这样的文件调用。
 |——public 系统运行的公开数据,包括静态资CSS文件、js文件等
 |——vender 第三方类库

Same as thinkPHP, the main application files are stored in the app directory.

2. No development framework is used

In the case of not using the development framework, we need to determine whether the application adopts the MVC mode. If so, we need to check the routing file of the system to see how the control program is located through routing. For example, PHPCMS uses the MVC controller developed by itself for routing:

Visit index.php, you can see that it contains phpcms/base.php

define('PHPCMS_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR);

include PHPCMS_PATH.'/phpcms/base.php';

Enter the base.php file, and you can see that there are a lot of routes and some initialized classes and methods defined here:

//PHPCMS框架路径
define('PC_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR);

if(!defined('PHPCMS_PATH')) define('PHPCMS_PATH', PC_PATH.'..'.DIRECTORY_SEPARATOR);

//缓存文件夹地址
define('CACHE_PATH', PHPCMS_PATH.'caches'.DIRECTORY_SEPARATOR);
//主机协议
define('SITE_PROTOCOL', isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://');
//当前访问的主机名
define('SITE_URL', (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ''));
//来源
define('HTTP_REFERER', isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '');
//定义网站根路径
define('WEB_PATH',pc_base::load_config('system','web_path'));
//js 路径
define('JS_PATH',pc_base::load_config('system','js_path'));
//css 路径
define('CSS_PATH',pc_base::load_config('system','css_path'));
//img 路径
define('IMG_PATH',pc_base::load_config('system','img_path'));
//动态程序路径
define('APP_PATH',pc_base::load_config('system','app_path'));
//应用静态文件路径
define('PLUGIN_STATICS_PATH',WEB_PATH.'statics/plugin/');
class pc_base {
    
    
  ......
}

For programs that do not use a framework or a custom MVC model, you only need to follow the traditional script format access method, use index.php as the entry point, and analyze the relevant codes in turn.

3. Parameter filtering analysis

Traditional parameter filtering analysis usually filters in the form of functions, but some systems may also perform global parameter filtering after parameters are passed in, so we need to know which filtering methods are used by the application before auditing , whether to use global parameter filtering.

1. Analysis of filtering situation in MVC mode

First of all, in the case of ThinkPHP, there are two ways to obtain parameters, one is to obtain parameters through native $_GETmethods, and the other is to obtain parameters through the $Request() object. When auditing the thinkPHP program, it is necessary to audit these two ways of obtaining parameters.

In ThinkPHP, a default global filter is defined in the config file:

// 默认全局过滤方法 用逗号分隔多个
'default_filter'         => '',

For example, above, the default filter is empty. That is to say, when using $_GETthe original method to obtain parameters, it will not be filtered.

Then let's take a look at how the $request object gets parameters. For example, the get method of the erquest object is used to get the parameters passed in by GET:

public function get($name = '', $default = null, $filter = '')
    {
    
    
        if (empty($this->get)) {
    
    
            $this->get = $_GET;
        }
        if (is_array($name)) {
    
    
            $this->param      = [];
            return $this->get = array_merge($this->get, $name);
        }
        return $this->input($this->get, $name, $default, $filter);
    }

As you can see, three formal parameters are defined here, namely name, default, and filter. Among them, name is the name of the variable we want to obtain, and filter is the filter we want to use, which is empty by default, that is, no filtering is performed.

Therefore, when auditing, we need to determine whether the default global filtering option is set, or whether a new filter is set when obtaining parameters.

2. Filter analysis in native PHP mode

For applications developed in native PHP, filtering rules are often set through functions, so you need to check in the place where the parameters are obtained to see if the parameter filtering function is called to filter, and judge whether it is effective filtering.

Here we take Imperial CMS as an example to conduct a simple analysis. The filter function of Empire CMS defines three parameter processing functions in e/class/connect.php. Here we take one of them as an example for analysis:

//参数处理函数
function RepPostVar($val){
    
    
	if($val!=addslashes($val))
	{
    
    
		exit();
	}
	CkPostStrChar($val);
	$val=str_replace("%","",$val);
	$val=str_replace(" ","",$val);
	$val=str_replace("`","",$val);
	$val=str_replace("\t","",$val);
	$val=str_replace("%20","",$val);
	$val=str_replace("%27","",$val);
	$val=str_replace("*","",$val);
	$val=str_replace("'","",$val);
	$val=str_replace("\"","",$val);
	$val=str_replace("/","",$val);
	$val=str_replace(";","",$val);
	$val=str_replace("#","",$val);
	$val=str_replace("--","",$val);
	$val=RepPostStr($val,1);
	$val=addslashes($val);
	//FireWall
	FWClearGetText($val);
	return $val;
}

It can be seen that %, space, `, %20, %27, ', ", etc. are filtered here.

Then we can enter the specific file to see if all the parameters passed in have called this function for filtering.

//审核评论
{
    
    
	$plid=$_POST['plid'];
	$id=$_POST['id'];
	$bclassid=$_POST['bclassid'];
	$classid=$_POST['classid'];
	CheckPl_all($plid,$id,$bclassid,$classid,$logininid,$loginin);
}

function CheckPl_all($plid,$id,$bclassid,$classid,$userid,$username){
    
    
	global $empire,$class_r,$dbtbpre,$public_r;
	//验证权限
	$restb=(int)$_POST['restb'];
	$count=count($plid);
	if(empty($count)||!$restb){
    
    
		printerror("NotCheckPlid","history.go(-1)");
	}
	if(!strstr($public_r['pldatatbs'],','.$restb.',')){
    
    
		printerror("NotCheckPlid","history.go(-1)");
	}
	$add='';
	$docheck=(int)$_POST['docheck'];
	$docheck=$docheck?1:0;
	for($i=0;$i<$count;$i++){
    
    
		$add.="plid='".intval($plid[$i])."' or ";
	}
	$add=substr($add,0,strlen($add)-4);
	$sql=$empire->query("update {
      
      $dbtbpre}enewspl_{
      
      $restb} set checked='$docheck' where ".$add);
	if($sql)
	{
    
    
		....
  }
}

For example, here, in the place where comments are managed in the background, parameters such as comment ID are obtained, and the CheckPl_all() function is passed in without filtering. It can be seen that none of the parameters here are processed by calling the filter function, but fortunately, all An int cast was used for these parameters. It is relatively safe.

4. Summary of Common Vulnerability Audit Methods

1. SQL injection

First, you need to understand the common business scenarios and vulnerability types of SQL injection:

  • User login - universal password
  • Data Search—Search Injection
  • Get HTTP header
  • Commodity purchase—insert, update injection, etc.
  • Information query—union joint injection, error injection, etc.

It can be said that SQL injection may exist in any place that interacts with the database, and there are various scenarios for its vulnerability, so it is not a good example here.

For the SQL injection audit method in code audit, the first thing to pay attention to is the keywords of database operations.

For example, in PHP native code, you can pay more attention to these keywords:

select
mysqli_connect
mysqli_query
mysqli_fetch_row
mysqli_fetch_array
update
indert into
delete
.......

In a CMS or framework, you need to pay more attention to the following keywords or methods:

name()
where()
find()
select()
.....

By locating these keywords, we can locate the place where the SQL statement is executed, and then determine whether the parameters in the SQL statement are executed by splicing SQL. If so, determine whether there is parameter filtering and whether the parameters are controllable.

If the parameter is uncontrollable and comes from the query result of a certain SQL statement, we need to focus on whether the parameter comes from other user input. If so, we need to consider the situation of the master's secondary injection.

2. XSS vulnerability

The focus of XSS vulnerabilities is some output functions, such as the following:

print()
echo 
print_f()
die()
var_dump()
print_r()
......

Then judge whether there are controllable variables in the output content, and detect whether these variables use Html entity encoding when inputting or outputting, or whether the input is filtered. If there is none, there is likely to be an XSS vulnerability when the output content is controllable.

3. Code execution

The code execution here includes two parts: code execution and command execution. Similarly, there are some functions that need to be focused on:

代码执行:
eval()
assert()
preg_replace()
array_map()
call_user_funcn()
.....
命令执行:
system()
exec()
shell_exec()
passthru()
popen()
proc_open()
.....

For code execution functions, the eval() function is the most common code execution function and needs to be paid attention to.

The assert() function is similar to eval in PHP, but it can only execute one line of code. In PHP7, the function of executing dynamic code of this function is canceled, that is to say, it executes fixed code.

Other code execution functions, most of which are callback functions, have the function of calling php code.

For the command execution vulnerability, it mainly exists in some places to obtain system information, which may be obtained by executing commands. If the executed commands are controllable, it may lead to command execution.

For several methods of command execution, there are certain differences:

  • system() is to execute the system command and return the execution result.
  • exec() returns a result handle after executing the command, and does not directly return the result.
  • shell_exec() does not return any information after the command is executed.

But for code execution and command execution, it should be noted that in PHP, {}the code can also be executed through the method, and at the same time, the PHP code can also be executed by dynamically splicing code. By using double backticks, it is also possible to wait for the command execution effect of the system.

4. File upload, delete, download

1) File upload vulnerability

Common business scenarios:

  • Avatar upload
  • Backup file upload
  • Configuration file upload

For the file upload vulnerability, the function that needs attention is: move_uplaod_file() function.

When auditing, you only need to search for the Osso function, and then judge whether to restrict the format of file uploads, or whether you can bypass the file suffix restriction.

If it cannot be bypassed, it is necessary to detect whether there is a file parsing vulnerability or a file inclusion vulnerability, and then bypass it.

2) File deletion vulnerability

For the file deletion vulnerability, there is only one function that triggers the vulnerability, that is the unlink() function. When we are auditing, we judge whether the file name is controllable, and at the same time check whether the path crossing character is allowed in the file hit. If it is allowed, it means that it exists. The vulnerability of arbitrary file deletion is more harmful.

3) File download vulnerability

File reading or file downloading vulnerabilities, common functions that trigger vulnerabilities are as follows:

file_get_content()
fopen()
readfile()
fread()
file()
......

For this kind of vulnerability, you can first find the function point of file reading and downloading in the case of a black box, and then analyze the specific code corresponding to the function through the requested URL, and then judge whether the file name to be read is valid. Control whether to allow path crossing characters in the file name, etc.

If there is a loophole, use the method to read the system file for a while, but read the source code of the website. When reading, we can use the php pseudo-protocol to read the source code.

5. XXE vulnerability

XXE vulnerability, the function we need to care about is: simplexml_load_string(), we need to judge whether the parsed XML data allows us to input externally, and whether it allows us to substitute external entities, and then conduct detailed analysis and utilization.

6. SSRF vulnerability

The trigger function of the SSRF vulnerability is mainly some functions that can be used to request resources remotely, such as:

file-get_content()
curl()
fopen()
readline()
......

Common vulnerability scenarios are:

  • social sharing
  • transcoding service
  • online translation
  • Remote image loading without img tag

The detection method is mainly to determine whether the URL or file name obtained by these functions is controllable. If it is controllable, it may cause a loophole, and it is also easy to cause the above-mentioned arbitrary file reading loophole.

V. Summary of related auditing skills

1. Take advantage of the loopholes of GPC

First, let’s briefly introduce GPC. magic_quotes_gpc is a configuration option in php.ini. It is enabled by default in versions before php5.2, and it is disabled by default in versions between php 5.2 and 5.4. In versions after PHP 5.4, this configuration item is directly canceled.

The function of this configuration item is to escape the characters such as ', ", \, etc. in front of the POST, GET, and COOKIE variables we pass in .NULL\

In PHP, there are two methods for khaki GPC. One is to configure the value of the magic_quotes_gpc option in PHP. addslashes() function for escaping. For example:

if (!get_magic_quotes_gpc()) {
    
      //magic_quotes_gpc 配置为 ON 则返回1,如果配置为OFF 则返回0
	$_POST['message'] = addslashes($_POST['message']);
} else {
    
    
	.......
}

In PHP, we mentioned that variables such as POST will be affected by the global configuration of GPC, but there is a variable that will not be affected, that is, $_SERVER, where the obtained http header is not protected by GPC. If the obtained HTTP If the header exists in the behavior of database interaction, it may lead to the generation of SQL injection.

At the same time, because GPC is used to escape special characters, we all know the principle of wide-byte injection vulnerabilities. If GPC is enabled and the database encoding is set to GBK mode, it may lead to wide-byte injection. exist. There are two ways to set the database encoding in PHP:

方法1mysqli_set_charset($connnect,'GBK')
方法2mysqli_query("set names 'gbk'")

These two methods can set the encoding mode of the database. When GPC is enabled, the problem of wide byte injection is prone to occur.

2. Use characters to deal with problems

1) Use the character processing function to report an error

Before understanding this problem, we need to know that in PHP, error messages are divided into multiple levels. You can configure display_error =on in PHP.ini or use error_reporting() in the code to set the error level. Some common error levels as follows:

1E_ERROR             //致命的运行时错误。这类错误一般是不可恢复的情况,例如内存分配导致的问题。后果是导致脚本终止不再继续运行。
2E_WARNING          //运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。
3E_PARSE            //编译时语法解析错误。解析错误仅仅由分析器产生
4E_NOTICE           //运行时通知。
5E_USER_ERROR       //用户产生的错误信息。
6E_USER_WARNING     //用户产生的警告信息。
7E_USER_NOTICE
8E_STRICT           //启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
9E_ALL              //E_STRICT除外的所有错误和警告信息。

In PHP, most errors will display the wrong file path and collective location. In penetration testing, we often encounter the situation of uploading or writing to webshell and need to know the absolute path of the website. At this time, we can consider using PHP to report errors to get the website path.

For PHP programs, most developers will use the trim() function to remove the air conditioning at the beginning and end of the parameter, but when the parameter we pass in is an array, for example, if it is used to remove the spaces of the a parameter at this time /index.php?a[]=test, trim($_GET('a'))It will cause the program to report an error.

There are many similar functions, such as:

addcslashes()
bin2hex()
chr()
exho()
explode()
crypto()
md5()
..........

2) Utilize string truncation

String truncation is most used when uploading files, but the %00 empty character is NULL, which will be affected and cannot be used normally when GPC is turned on. At the same time, after PHP5.3, this problem has been fixed, so the usage scenarios are relatively rare compared to the current ones. Just a brief mention here.

However, there is another case of string truncation, that is, when the iconv() function is used for encoding conversion, for example, UTF-8 ring is encoded as GBK, there will always be some differences, resulting in garbled characters in the conversion. Therefore, when the iconv() function is used for conversion, if an error occurs, the conversion will not be performed, resulting in the problem that the string is truncated.

A big guy has tested related issues and found out whether the characters between cahr(128)—chr(255) may be truncated when using the iconv() function to encode the UTF-8 encoding warrant officer GBK.

3. Irregular regular expressions

For example, the following situation:

$ip=$_SERVER('HTTP_CLIENT_IP');
if(preg_match('/\d+\.\d+\.\d+\.\d+/',$ip)){
    
    
	echo $ip;
}

Here if we pass in client_ip as 127.0.0.1</scritp>alert(xss)</scritp>

It will successfully pass the regular check and output the IP information, resulting in the generation of XSS vulnerabilities.

5. References

Guess you like

Origin blog.csdn.net/qq_45590334/article/details/126517767