PHP代码审计基础

php核心配置

php.ini

基本配置

语法
  • 大小写敏感
  • 运算符
  • 空值的表达式
安全模式
  • 安全模式
    safe_mode = off

用来限制文档的存取,限制环境变量的存取,控制外部程序的执行.PHP5.4.0移除

  • 限制环境变量存取
    safe_mode_allowed_env_vars = string

指定php程序可以改变的环境变量的前缀,当这个选项的值为空时,那么php可以改变任何环境变量,如果
如:safe_mode_allowed_env_vars = PHP_,当这个选项的值为空时,那么php可以改变任何环境变量

  • 外部程序执行目录
    `safe_mode_exec_dir = "e:\loalxxx"
  • 禁用函数
    disable_functions

为了更安全的运行PHP,可以用此指令来禁止一些敏感函数的使用,当你想用本指令禁止一些危险函数时,切记把dl()函数也加到禁止列表,攻击者可以利用dl()函数加载自定义的php扩展突破disable_functions.配置禁止函数时可以使用逗号分隔函数名

  • COM组件
    com.allow_dcom = false

PHP设置在安全模式下(safe_mode),仍允许攻击者使用COM()函数来创建系统组件来还行任意命令,推荐关闭这个函数
使用COM()函数需要在PHP.ini中配置extension=php_com_dotnet.dll,如果PHPversion<5.4.5则不需要

  • 全局变量注册开关
    register_globals = off

php.ini的register_globals选项的默认值为OFF,在4.2版本之前是默认开启的,当设定为On时,程序可以接收来自服务器的各种环境变量,包括表单提交的变量,这是对服务器分厂不安全的,
register_globals = off时,服务器端获取数据的时候用$_GET['name']来获取数据
register_globals = on时,服务端使用POST或GET提交的变量,豆浆自动使用全局变量的值来接受

  • 魔术引号自动过滤
    magic_quotes_gpc = on

PHP5.4.0被移除
magic_quotes_gpc = off 在php.ini中默认是关闭的,如果打开它,将自动把用户提交对sql的查询的语句进行转换,如果设置成ON,php会把所有的单引号,双引号,和反斜杠和空字符(NULL)加上反斜杠()进行转义
它会影响HTTP请求的数据(GET,POST.COOKIE),开启它会提高网站的安全性,当然,也可以使用addslashes来转义提交的HTTP请求数据,或者使用stripslashes来删除转义

  • 是否允许包含远程文件
    allow_url_include = off

该配置为ON的情况下,可以直接包含远程文件,若包含的变量为可控的情况下,可以直接控制变量来执行PHP代码

  • 是否允许打开远程文件
    allow_url_open = on

允许本地PHP文件通过调用url重写来打开或者关闭写权限,默认的封装协议提供的ftp和http协议来访问文件

  • HTTP头部版本信息
    expose_php = off

防止通过http头泄漏php版本信息

  • 文件上传临时目录
    upload_tmp_dir =

上传文件临时保存的目录,如果不设置的话,则采用系统的临时目录

  • 用户可访问目录
    open_basedir = E:\WWW

能够控制PHP脚本只能访问指定的目录,这样能够避免PHP脚本访问不应该访问的文件,一定成都上限制了phpshell的危害

  • 内部错误选项
    display_errors = on

表明实现PHP脚本的内部错误,网站发布后建议关不PHP的错误回显

  • 错误报告级别
    error_reporting = E_ALL & ~Enotice

这个设置的作用是将错误级别调到最高,显示所有问题,方便排错

代码调试及Xdebug的配置使用

代码调试

echo

var_dump

debug_zval_dump

debug_print_backtrace

exit()

Xdebug

//指定Xdebug扩展文件的绝对路径
zend_extension = "E:\php\php_debug.dll"
//启动性能检测分析
xdebug.profiler_enable = on
//启动代码自动跟踪
xdebug.auto_trace = on
//允许手机传递给函数的参数变量
xdebug.collect_params = on
//允许收集函数调用的返回值
xdebug.collect_return = on
//指定堆栈跟踪文件的存放目录
xdebug_trace_output_dir = "E:\php\xdebug"
//指定性能分析文件的存放目录
xdebug.profiler_output_dir = "E:\php\xdebug"
//连接和监听主机和端口的调试客户端
xdebug.remote_enable = on
//启动调试器协议dbgp
xdebug.remote_handler = dbgp
//客户端的主机
xdebug.remote_host = localhost
//客户端的端口
xdebug.remote_port = 9000
//指定DBGp调试器处理程序
xdebug.idekey = PHPSTORM

全局/超全局变量

全局变量

全局变量就是在函数外定义的变量,不能在函数中直接使用,因为它的作用域不会用到函数内部,所以在函数内部使用的时候常常看到类似global $a;

超全局变量

超全局变量作用域在所有脚本都有效,所以,在函数可以直接使用
$_GET,$_SERVER等都是超全局变量
除$_GET,$POST,$_SERVER,$_COOKIE等之外的超全局变量保存在$GLOBALS数组中

  • $GLOBALS
  • $_SERVER
  • $_REQUEST
  • $_POST
  • $_GET
  • $_FILES
  • $_ENV
  • $_COOKIE
  • $_SESSIONSSS

SQL注入

  • magic_quotes_gpc魔术引号只会转义单引号,双引号,反斜线,NULL,数字型和带有编码的和宽字节注入,不受影响
//数字型
url//?id=-1%27union%20select%201,user(),3--%20+
//base64编码
$id=base64_decode($_GET['id']);
url/base64.php?id=JyB1bmlvbiBzZWxlY3QgMSx1c2VyKCksMyAtLSAr
//url编码解码
$id=urldecode($_GET['id']);
url/id=%2527union%20select%201,user(),3--%20+
//宽字节注入
mysql_query("set names 'gbk' ",$conn);
$id=urldecode($_GET['id']);
id=1'->id=1\'->id=1%5c%27
id=1%df'->id=1%df%5c%27->id=1%DF5C%27->id=1運'
  • addslashes()

数字型注入不受影响

addslashes ( string $str ) : string
返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符)。
  • intval()

获取变量的整数值

intval ( mixed $var [, int $base = 10 ] ) : int
通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
var	要转换成 integer 的数量值
base	转化所使用的进制
  • select
  • uptate
  • insert into
  • delete
  • 二次注入

代码执行

常见危险函数

  • eval()

把字符串code作为PHP代码执行.eval(phpinfo());

  • assert()

assert()会检查指定的assertion并在结果为FALSE时采取适当的行动
如果assertion是字符串,它将会被assert()当做PHP代码来执行
assert(phpinfo());

回调函数

  • array_map()

为数组的每个元素应用回调函数

array_map ( callable $callback , array $array1 [, array $... ] ) : array

array_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。
callback 回调函数,应用到每个数组里的每个元素。
array1 数组,遍历运行 callback 函数。
... 数组列表,每个都遍历运行 callback 函数。
返回数组,包含callback函数处理之后array1的所有元素。

  • call_user_func()

第一个参数callback是被调用的回调函数,其余参数是回调函数的参数

call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] ) : mixed

callback 将被调用的回调函数(callable)。
parameter 0个或以上的参数,被传入回调函数。
常见的动态执行函数:

  • call_user_func_array()

把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

call_user_func_array (callable $callback , array $param_arr) : mixed

callback 被调用的回调函数。
param_arr 要被传入回调函数的数组,这个数组得是索引数组。
返回值 返回回调函数的结果。如果出错的话就返回FALSE

动态函数执行

  • $_GET'a'; url/a=eval&b=phpinfo

定义一个函数
将函数名(字符串)赋值给一个变量
使用变量名代替函数名动态调用函数

preg_replace()正则函数

搜索subject中匹配pattern的部分, 以replacement进行替换

preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) : mixed

pattern 要搜索的模式。可以使一个字符串或字符串数组。可以使用一些PCRE修饰符。
replacement 用于替换的字符串或字符串数组。如果这个参数是一个字符串,并且pattern 是一个数组,那么所有的模式都使用这个字符串进行替换。如果pattern和replacement 都是数组,每个pattern使用replacement中对应的 元素进行替换。如果replacement中的元素比pattern中的少, 多出来的pattern使用空字符串进行替换。
replacement中可以包含后向引用\n 或$n,语法上首选后者。 每个 这样的引用将被匹配到的第n个捕获子组捕获到的文本替换。 n 可以是0-99,\0和$0代表完整的模式匹配文本。 捕获子组的序号计数方式为:代表捕获子组的左括号从左到右, 从1开始数。如果要在replacement 中使用反斜线,必须使用4个("\\",译注:因为这首先是php的字符串,经过转义后,是两个,再经过 正则表达式引擎后才被认为是一个原文反斜线)。
当在替换模式下工作并且后向引用后面紧跟着需要是另外一个数字(比如:在一个匹配模式后紧接着增加一个原文数字), 不能使用\1这样的语法来描述后向引用。比如, \11将会使preg_replace() 不能理解你希望的是一个\1后向引用紧跟一个原文1,还是 一个\11后向引用后面不跟任何东西。 这种情况下解决方案是使用${1}1。 这创建了一个独立的$1后向引用, 一个独立的原文1。
当使用被弃用的 e 修饰符时, 这个函数会转义一些字符(即:'、"、 \ 和 NULL) 然后进行后向引用替换。当这些完成后请确保后向引用解析完后没有单引号或 双引号引起的语法错误(比如: 'strlen('$1')+strlen("$2")')。确保符合PHP的 字符串语法,并且符合eval语法。因为在完成替换后, 引擎会将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串。
subject 要进行搜索和替换的字符串或字符串数组。
如果subject是一个数组,搜索和替换回在subject 的每一个元素上进行, 并且返回值也会是一个数组。
limit 每个模式在每个subject上进行替换的最大次数。默认是 -1(无限)。
count 如果指定,将会被填充为完成的替换次数。

<? php
//第一个参数	url/cmd=<\/php>/e
echo $cmd = $_GET['cmd'];
$srt = '<php>phpinfo()</php>';
preg_replace("/<php>(.*?)$cmd","\\1",$srt);
//第二个参数	url/cmd=phpinfo()
preg_replace("/php/e","$cmd",$srt);
//第三个参数	url/cmd=[php]phpinfo()[php]
preg_replace("/\[php\](.*?)\[php\]/e","\\1",$cmd);
?>

修复方案

尽量不要执行外部的应用程序或命令
使用自定义函数或者函数库来替代外部应用程序或者命令的功能
使用escappeshellarg函数来处理命令参数
使用safe_mode_exec_dir来指定可执行的文件路径
将执行函数的参数做白名单限制,在代码或者配置文件中限制某些参数

用户能够控制函数输入

存在可执行代码的危险函数


命令执行

  • 代码层过滤不严
  • 系统的漏洞造成命令注入
  • 调用的第三方组件存在代码执行漏洞

常见函数

system

system ( string $command [, int &$return_var ] ) : string

command 要执行的命令。
return_var 如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。

exec

** 只显示命令执行结果的第一行 (返回string类型,需要echo)**

exec ( string $command [, array &$output [, int &$return_var ]] ) : string
  • exec() 执行 command 参数所指定的命令。

command 要执行的命令。
output 如果提供了 output 参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。 数组中的数据不包含行尾的空白字符,例如 \n 字符。 请注意,如果数组中已经包含了部分元素,exec() 函数会在数组末尾追加内容。如果你不想在数组末尾进行追加, 请在传入 exec() 函数之前 对数组使用 unset() 函数进行重置。
return_var 如果同时提供 output 和 return_var 参数, 命令执行后的返回状态会被写入到此变量。

shell_exec

shell_exec ( string $cmd ) : string
  • 本函数同 执行操作符(``) 命令无回显(返回string类型,需要echo)
  • 反引号运算符在激活了安全模式或者关闭了 shell_exec() 时是无效的

cmd 要执行的命令。

passthru

passthru ( string $command [, int &$return_var ] ) : void

command 要执行的命令。
return_var 如果提供 return_var 参数, Unix 命令的返回状态会被记录到此参数。

  • 同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。

``

<?php
$output = `ls -al`;
echo "<pre>$output</pre>";
?>
  • 调用 shell_exec函数

反引号运算符在激活了安全模式或者关闭了 shell_exec() 时是无效的
与其它某些语言不同,反引号不能在双引号字符串中使用。

修复建议

  • escapeshellcmd() 过滤整条命令
  • escapeshellarg() 过滤整个参数

尽量少用执行命令的函数或者直接禁用参数值尽量使用引号包括
在使用动态函数之前,确保使用的函数是指定的函数之一
在进入执行命令的函数/方法前,对参数进行过滤,对敏感字符进行转义
尽量少使用执行命令的函数
对于可控点是程序参数的情况下,使用escapeshellcmd函数进行过滤
对于可控点是程序参数值的情况下,使用escapeshellarg函数进行过滤
参数的值尽量使用引号包裹,并在拼接前调用addslashes进行转义
针对由特定第三方组件引发的漏洞,我们要做的就是及时打补丁,修改安装时的默认配置


XSS

常见函数

  • print
  • print_r
  • echo
  • printf
  • die
  • var_dump
  • var_export

反射型

  • 直接输出到浏览器页面
  • 直接输出到html便签里
  • 直接输出到<script>代码里

存储型

DOM型

DOM型常见属性

输入点 输出点
document.url eval
document.location document.write
document.referrer document.innterHTML
document.form document.OuterHtml

修复方案

  • htmlspecialchars() &,",',<,>

将特殊字符转换为HTML实体
对所有输入中的script,iframe等字样进行严格检查
验证数据的类型及其格式,长度,范围和内容
客户端做数据的验证与过滤,关键的过滤步骤在服务端进行
检查输出的数据


CSRF

  • javascript提交
//自动提交表单
<script>document.forms[0].submit();</script>
  • XMLHTTPRequest提交
//1.js
function del() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET','/xiong/admin/?r=linklist&delete=6');
    xhr.send(null);
    }
del();

<script src="http://404.com/xiong/1.js"></script>

  • 是否有验证码
  • 是否验证了请求源(referer)
  • 是否验证token,token过期时间

文件上传漏洞

  • 关注函数
move_uploaded_file ( string $filename , string $destination ) : bool

filename 上传的文件的文件名。
destination 移动文件到这个位置。
本函数检查并确保由 filename 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 destination 指定的文件。
文件上传只有一个函数 move_uploaded_file() 一般来说,我们就可以搜索这个函数来回溯,看他的验证方式,是黑名单还是白名单,是否是前端限制,是否只是简单的验证了文件头,是否是能绕过的正则匹配,是否渲染了图片

修复方案

检测文件上传内容

  • 黑名单验证,检测文件扩展名是否合法
  • MIME验证,检测文件的MIME类型
    限制文件大小
    更改临时文件夹的路径
    读取上传文件的绝对路径与文件名称
    隐藏文件路径

目录穿越

相关函数

  • readfile()
readfile ( string $filename [, bool $use_include_path = false [, resource $context ]] ) : int

filename 要读取的文件名。
use_include_path 想要在 include_path 中搜索文件,可使用这个可选的第二个参数,设为 TRUE。
context Stream 上下文(context) resource

绕过方法

  • url编码

点-> %2e 反斜杠-> %2f 正斜杠-> %5c

  • 16位Unicode编码

点-> %u002e 反斜杠-> %u2215 正斜杠-> %u2216

  • 双倍URL编码

点-> %252e 反斜杠-> %u252f 正斜杠-> %u255c

  • 超长UTF-8 Unicode编码

点-> %c0%2e %e0$40%ae %c0ae
反斜杠-> %c0af %e0%80af %c0%af
正斜杠-> %c0%5c %c0%80%5c

修复方案

在URL内不要使用文件名称作为参数
检查使用者输入的文件名是否有".."的目录阶层字符
使用realpath函数来展开文件路径中的"./"和"../"等字符,然后使用绝对路径名称
使用basename函数来返回不包含路径的文件名称


文件包含

本地文件包含(Local File Include)简称 LFI
远程文件包含漏洞(Remote File Inclusion)简称RFI,他需要我们的php.ini中配置allow_url_include=Onallow_url_fopen = On

相关函数

include(),require() 语句包含并运行指定文件

  • include()
  • include_once()
  • require()
  • require_once()

利用方式

本地文件包含

  • %00截断
  • 记录错误日志文件

需要解密base64
利用环境:php<5.3 magic_quotes_gpc取消的
可以使用遍历目录实现效果

远程文件包含

  • %00截断
  • 路径长度截断

(linux-4096,windows-256)(不受GPC限制),5.3以后被修复
url/file=../../1.php[超过限制长度的././././]
点号截断 只在windows下可用
url/file=../boot.ini[超过长度限制的................]

  • ?伪截断,不受GPC和PHP版本限制(<5.2.8),会把?后面的当参数
  • 伪协议

file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

修复方案

关闭远程包含参数开关,彻底切断这个业务相比较
设置类似白名单的方法,筛选固定文件名
对常见目录穿越字符进行过滤,如(./,../,..\等)


任意文件读取

allow_url_fopen选项激活了URL形式的fopen封装协议,是的可以访问URL对象,例如文件,
默认的封装协议提供用ftp和http协议来访问远程文件,一些扩展库,如:zlib可能会注册更多的封装协议

相关函数

  • fopen()
  • file_get_contents()
  • fread
  • fgets
  • fgetss
  • file
  • fpassthru
  • parse_ini_file
  • readfile

修复方案

正则严格判断用户输入参数的格式
检查使用者输入的文件名是否有".."等目录阶层字符
在php.ini文件中设置open_basedir来限定文件的访问范围


任意文件删除

相关函数

  • unlink()

修复方案

同文件读取


任意文件下载

常见函数

  • file_get_contents()

将整个文件读入一个字符串

file_get_contents ( string $filename [, bool $use_include_path = false [, resource $context [, int $offset = -1 [, int $maxlen ]]]] ) : string

filename 要读取的文件的名称

  • readfile()

读取文件并写入到输出缓冲

readfile ( string $filename [, bool $use_include_path = false [, resource $context ]] ) : int

filename 要读取的文件名。
use_include_path 想要在 include_path 中搜索文件,可使用这个可选的第二个参数,设为 TRUE。
context Stream 上下文(context) resource。
返回从文件中读入的字节数。如果出错返回 FALSE 并且除非是以 @readfile() 形式调用,否则会显示错误信息

  • fopen()

fopen() 将 filename 指定的名字资源绑定到一个流上

fopen ( string $filename , string $mode [, bool $use_include_path = false [, resource $context ]] ) : resource

filename 如果 filename 是 "scheme://..." 的格式,则被当成一个 URL,PHP 将搜索协议处理器(也被称为封装协议)来处理此模式。如果该协议尚未注册封装协议,PHP 将发出一条消息来帮助检查脚本中潜在的问题并将 filename 当成一个普通的文件名继续执行下去
mode mode 参数指定了所要求到该流的访问类型
|mode|说明|
|----|----|
|'r'|只读方式打开,将文件指针指向文件头|
|'r+'|读写方式打开,将文件指针指向文件头|
|'w'|写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之|
|'w+'|读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之|
|'a'|写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之|
|'a+'|读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之|
|'x'|创建并以写入方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用失败并返回 FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。这和给 底层的 open(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的|
|'x+'|创建并以读写方式打开,其他的行为和 'x' 一样|
|'c'|Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer is positioned on the beginning of the file. This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file, as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can be used after the lock is requested)|
|'c+'|Open the file for reading and writing; otherwise it has the same behavior as 'c'|

修复方案

同文件读取


变量覆盖

  • 漏洞解释

变量覆盖(Dynamic Variable Evaluation)是指变量未被初始化,我们自定义的参数值可以替换程序原有的变量值

  • 漏洞危害

通常结合程序的其它漏洞实现完整的攻击,比如文件上传页面,覆盖掉原来白名单的列表,导致任意文件上传;用户注册页面控制没覆盖的为初始化变量导致SQL

常见函数

  • $$

常见的遍历方式释放代码,可能导致变量覆盖漏洞

<?php
$a = 'a';	//
echo $a
//url/?a=2
foreach(array('_COOKIE','_POST','_GET') as $_request) {
	foreach($$_request as $kay=>$valus) {
		$$_key = addslashes($_value);	
	}
}
echo $a;
?>

其中$_key的值为a,那么 $a的值就被覆盖为2了。
还有全局注册register_globals这些,php配置默认都是关闭的

  • extract()
extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] ) : int : int

本函数用来将变量从数组中导入到当前的符号表中
array 一个关联数组。此函数会将键名当作变量名,值作为变量的值。 对每个键/值对都会在当前的符号表中建立变量,并受到 flags 和 prefix 参数的影响。
必须使用关联数组,数字索引的数组将不会产生结果,除非用了 EXTR_PREFIX_ALL 或者 EXTR_PREFIX_INVALID。
flags 对待非法/数字和冲突的键名的方法将根据取出标记 flags 参数决定。可以是以下值之一:
EXTR_OVERWRITE 如果有冲突,覆盖已有的变量。
EXTR_SKIP 如果有冲突,不覆盖已有的变量。
EXTR_PREFIX_SAME 如果有冲突,在变量名前加上前缀 prefix。
EXTR_PREFIX_ALL 给所有变量名加上前缀 prefix。
EXTR_PREFIX_INVALID 仅在非法/数字的变量名前加上前缀 prefix。
EXTR_IF_EXISTS 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。 举个例子,以下情况非常有用:定义一些有效变量,然后$_REQUEST 中仅导入这些已定义的变量。
EXTR_PREFIX_IF_EXISTS 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。
EXTR_REFS 将变量作为引用提取。这有力地表明了导入的变量仍然引用了 array 参数的值。可以单独使用这个标志或者在 flags 中用 OR 与其它任何标志结合使用。
如果没有指定 flags,则被假定为 EXTR_OVERWRITE。
prefix 注意 prefix 仅在 flags 的值是 EXTR_PREFIX_SAME,EXTR_PREFIX_ALL,EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS 时需要。 如果附加了前缀后的结果不是合法的变量名,将不会导入到符号表中。前缀和数组键名之间会自动加上一个下划线

<?php
$password = 'pwd';
$arr = array(
	'username' => 'username',
	'password; => 'password',
	'rand' => 'rand'
);
//第一种情况
extract($arr,EXTR_PREFIX_SAME,'pwd')
echo "$username,$password,$rand";
echo "<br>";
echo ".".$pwd_password;
//第二种情况
extract($arr,EXTR_OVERWRITE,'pwd')
echo "$username,$password,$rand";
//第三种情况
extract($arr,EXTR_IF_EXISTS,'pwd')
echo "$username,$password,$rand";
?>
$a = "aa";
echo $a;	//aa
extract($_GET);	//url/?a=1
echo $a;	//1
  • parse_str()

parse_str() 函数用于把查询字符串解析到变量中,如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量。在没有array参数的情况下使用此函数,并且在PHP 7.2中将废弃不设置参数的行为,此函数没有返回值

parse_str ( string $encoded_string [, array &$result ] ) : void

如果 encoded_string 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )
encoded_string 输入的字符串。
result 如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代

<?php
$a = 'a';
parse_str($a = b);
echo $a;
?>
$a = "a";
$id = $_GET['x'];
echo $a;	//a
parse_str($id);	//url/?x=a=1
echo $a;	//1
  • import_request_variables()

此函数只能用于PHP4.1 ~ PHP5.4

import_request_variables ( string $types [, string $prefix ] ) : bool

将 GET/POST/Cookie 变量导入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。
你可以使用 types 参数指定需要导入的变量。可以用字母‘G’、‘P’和‘C’分别表示 GET、POST 和 Cookie。这些字母不区分大小写,所以你可以使用‘g’、‘p’和‘c’的任何组合。POST 包含了通过 POST 方法上传的文件信息。注意这些字母的顺序,当使用“gp”时,POST 变量将使用相同的名字覆盖 GET 变量。任何 GPC 以外的字母都将被忽略。
prefix 参数作为变量名的前缀,置于所有被导入到全局作用域的变量之前。所以如果你有个名为“userid”的 GET 变量,同时提供了“pref_”作为前缀,那么你将获得一个名为 $pref_userid 的全局变量

<? php
$a = 0;
import_request_variables('G');
if($a == 1){
	echo 'success';
}else{
	echo 'fali';
}
?>
$a = 'a';
echo $a;	//a
import_request_variables('GP'); //url/?a=1
echo $a;	//1

修复方案

在php.ini文件中设置register_globals=OFF
使用原始变量的属组,如果$_POST.$_GET等数组变量进行操作
不使用foreach语句来遍历$_GET变量,而改用[(index)]来指定
验证变量是否存在,注册变量前先判断变量是否存在


反序列化漏洞

  • unserialize()

unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值

unserialize ( string $str ) : mixed

str 序列化后的字符串。
若被解序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup() 成员函数(如果存在的话)
反序列化对象中存在魔术方法,而魔术方法中的代码可以被控制,漏洞根据不同的代码可以导致各种攻击,如代码注入,SQL注入,目录遍历等等

序列化的不同结果

  • public
  • private
  • protect

魔术方法

  • __construct()
    __construct ([ mixed $args [, $... ]] ) : void

每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作

  • __destruct()
    __destruct ( void ) : void

某个对象的所有引用都被删除或者当对象被显式销毁时执行

  • __call() //在对象上下文中调用不可访问的方法时触发
  • __callStatic() //在静态上下文中调用不可访问的方法时触发
  • __get() //用于从不可访问的属性读取数据
  • __set() //用于将数据写入不可访问的属性
  • __isset() //在不可访问的属性上调用isset()或empty()触发
  • __unset() //在不可访问的属性上使用unset()时触发
  • __sellp() //使用serialize时触发
  • __wakeup() //使用unserialize时触发
  • __toString() //把类当作字符串使用时触发
  • __invoke() //当脚本尝试将对象调用为函数时触发
  • __set_state()
  • __clone()
  • __debuginfo()

PHP弱类型

php是一款弱类型语言,他在使用==比较字符串的时候会把字符串类型转化成相同的再比较,那么这样也会造成一些问题
他能遇到字符串的0e,0x就会解析成对应的科学计数和16进制

//字符串和数字比较
var_dump(0=="admin");	//true
var_dump(1=="1admin");	//true
var_dump(1=="admin1");	//false
var_dump(0=="admin1");	//true
//数字和数组
$arr = array();
var_dump(0==$arr);	//
//字符串和数组
$arr = array();
var_dump("0"==$arr);	//
//"合法数字+e+合法数字"类型的字符串
var_dump("0e123456"=="0e456789");	//
var_dump("1e1"=="10");	//
//==和===

empty和isset

  • 变量为:0,"0",null,'',false,array()时,使用empty函数,返回的都是true
  • 变量未定义或者为null时,isset函数返回的为false,其他都为true
$a = null;
$b = 0;
$c = "";
var_dump(empty($a));
var_dump(empty($b));
var_dump(empty($c));
var_dump(isset($a));
var_dump(isset($b));
var_dump(isset($c));

md5()函数

md5 ( string $str [, bool $raw_output = FALSE ] ) : string
//示例
$arr1 = array("test","test2","2019");
$arr2 = array("test3","test4","2020");
var_dump(md5($arr1) == md5($arr2));		//true

str 原始字符串。
raw_output 如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。

is_number()

检测变量是否为数字或数字字符串,如果var是数字和数字字符串则返回TRUE,否则返回FALSE

is_numeric ( mixed $var ) : bool

is_number() 函数他会判断变量是否为数字或者数字字符串,假如我们传入的字符串为16进制,那么他也是认定为数字的
我们知道我们向mysql插入数据的时候是可以是16进制的,他取出来就会还原成原始字符串,这样用is_number() 函数检测后肯能就会存在二次注入。

strcmp()函数

比较函数如果两者相等返回0,string1>string2返回>0 反之小于0。在5.3及以后的php版本中,当strcmp()括号内是一个数组与字符串比较时,也会返回0

strcmp ( string $str1 , string $str2 ) : int
//示例	url/pwd[]=xxx
$password = "12345";
if(strcmp($_GET['pwd'],$password) == 0){
	echo "success";
}else{
	echo "fail";
}

注意该比较区分大小写
str1 第一个字符串。
str2 第二个字符串。
返回值 如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0

preg_match()

如果在进行正则表达式匹配的时候,没有限制字符串的开始和结束(^ 和 $),则可以存在绕过的问题。

$ip = '127.0.0.1 and 1=1';
if(preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+),$ip)){
	echo "yes";
	}else{
	echo "no";
	}
}

in_array()函数

in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) : bool
大海捞针,在大海(haystack)中搜索针( needle),如果没有设置 strict 则使用宽松的比较

needle 待搜索的值。
Note: 如果 needle 是字符串,则比较是区分大小写的。
haystack 待搜索的数组。
strict 如果第三个参数 strict 的值为 TRUE 则 in_array() 函数还会检查 needle 的类型是否和 haystack 中的相同

$arr = array(0,1,2,3,4);
$payload1 = '1 and 1=1';
$payload2 = '2 and 1=1';
var_dump(in_array($payload1,$arr));	//true
var_dump(in_array($payload2,$arr));	//false
var_dump(in_array($payload1,$arr,true));	//false

在没设置第三个参数的情况下in_array()函数将会吧1 and 1=1转为数字1比较,那么这样就造成了一些安全问题,在注入或上传的情况下可能绕过。

array_search()函数

array_search ( mixed $needle , array $haystack [, bool $strict = false ] ) : mixed
大海捞针,在大海(haystack)中搜索针( needle 参数)。

needle 搜索的值。
Note:如果 needle 是字符串,则比较以区分大小写的方式进行。
haystack 这个数组。
strict 如果可选的第三个参数 strict 为 TRUE,则 array_search() 将在 haystack 中检查完全相同的元素。 这意味着同样严格比较 haystack 里 needle 的 类型,并且对象需是同一个实例。

switch()

如果switch是数字类型的case的判断时,switch会将参数转换为int类型

$a = "1aaa";
switch($a){
	case 1:
		echo "success";
		break;
	case 2:
		echo "fail";
		break;
}

PHP伪协议

<?php
include($_GET['file']);
?>

file://协议

url/?file://d:\1.txt

用于访问本地文件

php://filter

url/?file=php://filter/read=convert.base64-encode/resource=../../etc/passwd

php://filter读取源代码并进行base64编码输出

php://input

url/?file=php://input
POST:
<?php phpinfo();?>

php://input可以访问请求的原始数据的只读流
allow_url_fopen:off/on
allow_url_include:on

data://协议

url/?file=data://text/plain,<?php phpinfo()?>
`url/file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

数据
allow_url_fopen:on
allow_url_include:on


会话认证漏洞

  • 遇到的比较多的就是出现在cookie验证上面,通常是没有使用session来认证,直接将用户信息保存在cookie中

session固定攻击

  • session固定攻击是因为黑客固定住目标用户的sessionID,因此目标用户所使用的session可由攻击者指定

修复方案

不要从GET/POST变量中接受sessionID
调用session_start()函数后,立即产生新的sessionID,并删除旧的session
将sessionID存放在cookie内
注销后即销毁session的所有数据
使用时间戳来记录session的使用时间,如果两次session的相差时间太长,就销毁session的所有数据
检查用户的IP地址,如果IP地址改变就产生一个新的sessionID,并删除就的session

session劫持攻击

  • session劫持攻击是指黑客劫持目标用户的sessionID来获取网站服务器上未经许可的存取信息,特别是窃取目标用户等cookie数据,来去的网站的认可

修复方案

使用随机而且长度够大的数字或字符串来当作sessionID
将网页之间传递的数据使用某种形式的封装,特别是sessionID
更改session的名称
注销后即销毁session的所有数据


逻辑漏洞

逻辑漏洞是指由于程序逻辑不严,或者函数使用不当,导致发生越权访问,cookies绕过,越权密码修改,重复安装等等问题。一般逻辑漏洞的挖掘需要对代码有一定阅读能力

越权

越权一般是对cookies的验证不严或者没有验证,一般我们审计后台发现某个功能没有包含验证文件,那么很有可能发生越权操作,当然越权有很多不仅仅局限于一个后台访问的问题。在众多大型网站越权问题也时常发生的,这也是漏洞挖掘中大家都比较喜欢的,有些越权在黑盒测试中或许更加容易发现,所以代码审计大家灵活运用,不要局限了你的思路。越权是个大的专题,我应该是讲不了多少还是请大家多看看文章

  • 垂直越权
  • 水平越权

cookies验证不严


posted @ 2019-12-03 09:55  mrhonest  阅读(1288)  评论(0编辑  收藏  举报