图片 2

深入理解,PHP弱类型安全问题总结

1.概述

PHP(本文所述案例PHP版本均为7.1.3)作为一门动态脚本语言,其在zend虚拟机执行过程为:读入脚本程序字符串,经由词法分析器将其转换为单词符号,接着语法分析器从中发现语法结构后生成抽象语法树,再经静态编译器生成opcode,最后经解释器模拟机器指令来执行每一条opcode。

在上述整个环节中,生成的opcode可以应用编译优化技术如死代码删除、条件常量传播、函数内联等各种优化来精简opcode,达到提高代码的执行性能的目的。

PHP扩展opcache,针对生成的opcode基于共享内存支持了缓存优化。在此基础上又加入了opcode的静态编译优化。这里所述优化通常采用优化器(Optimizer)来管理,编译原理中,一般用优化遍(Opt
pass)来描述每一个优化。

整体上说,优化遍分两种:

  • 一种是分析pass,是提供数据流、控制流分析信息为转换pass提供辅助信息;
  • 一种是转换pass,它会改变生成代码,包括增删指令、改变替换指令、调整指令顺序等,通常每一个pass前后可dump出生成代码的变化。

本文基于编译原理,结合opcache扩展提供的优化器,以PHP编译基本单位op_array、PHP执行最小单位opcode为出发点。介绍编译优化技术在Zend虚拟机中的应用,梳理各个优化遍是如何一步步优化opcode来提高代码执行性能的。最后结合PHP语言虚拟机执行给出几点展望。

前段时间做了南京邮电大学网络攻防平台上面的题目,写了一个writeup之后,还有必要总结一下。由于做的题目都是web类型的,所有的题目都是使用PHP来写的,所以很多题目并没有考察到传统的如SQL注入,XSS的类型的漏洞,很多都是PHP本身语法的问题。鉴于目前PHP是世界上最好的语言,PHP本身的问题也可以算作是web安全的一个方面。在PHP中的特性就是弱类型,以及内置函数对于传入参数的松散处理。本篇文章主要就是记录我在做攻防平台上面遇到的PHP的函数中存在的问题,以及PHP的弱类型所带来的问题。

《PHP中的字符串、编码、UTF-8》一文中描述了一些列的基础知识,比较枯燥,现在来说点有用的——PHP
字符串处理的最佳实践,本文是“PHP、字符串、编码、UTF-8”相关知识的第二部分。先说结论——
PHP 中的各个方面使用 UTF-8
编码。

2.几个概念说明

PHP弱类型简介

在PHP中,可以进行一下的操作。

$param = 1;
$param = array();
$param = "stringg";

弱类型的语言对变量的数据类型没有限制,你可以在任何地时候将变量赋值给任意的其他类型的变量,同时变量也可以转换成任意地其他类型的数据。

PHP 语言层面是不支持 Unicode字符集的,但是可以通过 UTF-8
编码能处理大部分问题。

1)静态编译/解释执行/即时编译

静态编译(static compilation),也称事前编译(ahead-of-time
compilation),简称AOT。即把源代码编译成目标代码,执行时在支持目标代码的平台上运行。

动态编译(dynamic
compilation),相对于静态编译而言,指”在运行时进行编译”。通常情况下采用解释器(interpreter)编译执行,它是指一条一条的解释执行源语言。

JIT编译(just-in-time
compilation),即即时编译,狭义指某段代码即将第一次被执行时进行编译,而后则不用编译直接执行,它为动态编译的一种特例。

上述三类不同编译执行流程,可大体如下图来描述:

图片 1

类型转换问题

类型转换是无法避免的问题。例如需要将GET或者是POST的参数转换为int类型,或者是两个变量不匹配的时候,PHP会自动地进行变量转换。但是PHP是一个弱类型的语言,导致在进行类型转换的时候会存在很多意想不到的问题。

最佳实践就是明确知道输入编码(不知道就检测),内部统一转换为 UTF-8
编码,输出编码也统一是 UTF-8编码。

2)数据流/控制流

编译优化需要从程序中获取足够多的信息,这是所有编译优化的根基。

编译器前端产生的结果可以是语法树亦可以是某种低级中间代码。但无论结果什么形式,它对程序做什么、如何做仍然没有提供多少信息。编译器将发现每一个过程内控制流层次结构的任务留给控制流分析,将确定与数据处理有关的全局信息任务留给数据流分析。

  • 控制流
    是获取程序控制结构信息的形式化分析方法,它为数据流分析、依赖分析的基础。控制的一个基本模型是控制流图(Control
    Flow
    Graph,CFG)。单一过程的控制流分析有使用必经结点找循环、区间分析两种途径。
  • 数据流
    从程序代码中收集程序的语义信息,并通过代数的方法在编译时确定变量的定义和使用。数据的一个基本模型是数据流图(Data
    Flow
    Graph,DFG)。通常的数据流分析是基于控制树的分析(Control-tree-based
    data-flow analysis),算法分为区间分析与结构分析两种。

比较操作符

PHP 层面如何处理 UTF-8

当操作 Unicode 字符集的时候,请务必安装 mbstring
扩展,并使用相应的函数代替原生的字符串函数。举个例子,一个文件编码为
UTF-8 的 PHP 代码,假如使用 strlen() 函数是错误的,请使用 mb_strlen()
函数代替。

mbstring
扩展大部分的函数都需要基于一个编码(内部编码)来处理,请务必统一使用
UTF-8 编码,这个大部分可以在 PHP.INI 中配置。

从 PHP 5.6 开始,default_charset 配置可以替换
mbstring.http_input,mbstring.http_output 。
另外一个重要的配置就是 mbstring.language,这个默认值是
Neutral(UTF-8)。

注意文件编码和 mbstring 扩展的内部编码不是同一个概念。

概括的说来:

  • PHP.INI 中涉及到 mbstring 扩展的部分尽量使用 UTF-8。
  • 请用 mbstring 扩展函数代替原生字符串操作函数。
  • 在使用相关函数的时候,请务必了解你操作的字符的编码是什么,在使用对应函数的时候,显示的写上
    UTF-8 编码参数,比如 htmlentities() 函数的第三个参数显示写上 UTF-8。

3)op_array

类似于C语言的栈帧(stack
frame)概念,即一个运行程序的基本单位(一帧),一般为一次函数调用的基本单位。此处,一个函数或方法、整个PHP脚本文件、传给eval表示PHP代码的字符串都会被编译成一个op_array。

实现上op_array为一个包含程序运行基本单位的所有信息的结构体,当然opcode数组为该结构最为重要的字段,不过除此之外还包含变量类型、注释信息、异常捕获信息、跳转信息等。

类型转换

在$a==$b的比较中

$a=null;$b=flase ; //true
$a='';$b=null;      //true

这样的例子还有很多,这种比较都是相等。

使用比较操作符的时候也存在类型转换的问题,如下:

0=='0'        //true
0 == 'abcdefg'  //true
0 === 'abcdefg' //false
1 == '1abcdef'  //true

当不同类型的变量进行比较的时候就会存在变量转换的问题,在转换之后就有可能会存在问题。

文件 IO 操作 如何处理 UTF-8

这里举个例子,假如你要打开一个文件,但是不知道文件内容是什么编码的,那么如何处理呢?

最佳实践就是,在打开的时候统一转换成
UTF-8,修改内容后就再转回原来的编码并保存到文件。看代码吧:

if ( mb_internal_encoding()!="UTF-8") {
        mb_internal_encoding("UTF-8");
}

$file = "file.txt"; //一个编码为gbk的中文文件
$str= file_get_contents($file);
//不管来源是什么编码,统一显示的时候转换为 UTF-8
 if (mb_check_encoding($str,"GBK")) 
    $str =  mb_convert_encoding($str,"UTF-8",“GBK”); 

$str ="修改内容";
$str =  mb_convert_encoding($str,$srcbm,"UTF-8"); //原样转回去
file_put_contents($file,$str);

4)opcode

解释器执行(ZendVM)过程即是执行一个基本单位op_array内的最小优化opcode,按顺序遍历执行,执行当前opcode,会预取下一条opcode,直到最后一个RETRUN这个特殊的opcode返回退出。

这里的opcode某种程度也类似于静态编译器里的中间表示(类似于LLVM
IR),通常也采用三地址码的形式,即包含一个操作符,两个操作数及一个运算结果。其中两个操作数均包含类型信息。此处类型信息有五种,分别为:

  • 编译变量(Compiled
    Variable,简称CV),编译时变量即为php脚本中定义的变量。
  • 内部可重用变量(VAR),供ZendVM使用的临时变量,可与其它opcode共用。
  • 内部不可重用变量(TMP_VAR),供ZendVM使用的临时变量,不可与其它opcode共用。
  • 常量(CONST),只读常量,值不可被更改。
  • 无用变量(UNUSED)。由于opcode采用三地址码,不是每一个opcode均有操作数字段,缺省时用该变量补齐字段。

类型信息与操作符一起,供执行器匹配选择特定已编译好的C函数库模板,模拟生成机器指令来执行。

opcode在ZendVM中以zend_op结构体来表征,其主体结构如下:

图片 2

Hash比较

除了以上的这种方式之外在进行hash比较的时候也会存在问题。如下:

"0e132456789"=="0e7124511451155" //true
"0e123456abc"=="0e1dddada"  //false
"0e1abc"=="0"     //true

在进行比较运算时,如果遇到了0e\d+这种字符串,就会将这种字符串解析为科学计数法。所以上面例子中2个数的值都是0因而就相等了。如果不满足0e\d+这种模式就不会相等。这个题目在攻防平台中的md5
collision
就有考到。

Mysql 和 UTF-8 的最佳实践

这个相对简单,首先保证你的 Mysql 都是 UTF-8。然后 Mysql
客户端连接的时候也保持 UTF-8,具体到 PHP 中,就是 imysql 或者 PDO
扩展连接 Mysql 的时候都设置 UTF-8
作为连接编码,二边保持一致,一般就不会遇到问题。

有兴趣可以看看这篇文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

标签:, , , , , , , , ,
网站地图xml地图