打开url发现有三个链接,点进去都是.pl文件,且只有files可以上传文件。
.pl文件都是用perl编写的网页文件
这里上传了又将文件的内容全部打印出来,那么猜想后台应该用了param()函数。
param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的接收变量中。如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。对正常的上传文件进行修改,可以达到读取任意文件的目的:
这里附上网上大佬们猜测的后台代码:
直接先读取file.pl文件,盲猜在/var/www/cgi-bin/file.pl或者/var/www/cgi-bin/file.pl两个拿去试
发现确实使用了param()函数,然后我们利用bash来进行读取当前目录下的文件
payload为:
/cgi-bin/file.pl?/bin/bash%20-c%20ls${IFS}/|
%20为空格,可换成+号
bp进行抓包,将上传的文件类型及文件内容处复制再粘贴一行,将filename去掉,然后内容填入ARGV
然后盲猜flag文件,读取试试
总结:perl文件遇到上传可配合ARGV文件使用造成任意文件读取,然后任意文件读取可利用bash执行一定的命令。
本体Perl源码的构造与分析
在看源码之前,如果你没有学过Perl语言。你可以参考https://qntm.org/perl_cn
那么本题中Perl后端到底是怎么写的呢?为此我找了很久
1.要使用Perl CGI 首先加载CGI模块
**use CGI;**
2.CGI模块具有与编程功能,可用于internet,但在使用前必须创建CGI的句柄=>允许我们访问相关的函数
**my $cgi = CGI->new();**
现在$cgi正在加载CGI标准函数,有关CGI标准函数的内容可以参考
http://www.freeoa.net/development/perl/perl-cgi-fun-intro_2195.html
3.本体是一个文件上传,而上面的连接有讲到upload()这个CGI标准函数
upload() 函数是用于处理文件上传的标准函数 , 参数为在构造表单时 ** <input type="file" name=" ... ” />**
设置的 name 的值
回到之前抓的数据包,name参数的值为”file”,文件上传的过程应该是这样的:
**if ( $cgi->upload("file") ) { ... }**
if 语句肯定了这是一个文件上传操作,后面的步骤都是在这个if语句中进行的
4.获取到参数
在服务器处理之前,需要先检索输入,再对他们进行处理
在Perl CGI标准函数中,param()函数用于获取传入的参数(可以接受GET和POST传递的参数,类似的php中的$REQUEST全局变量)
根据之前抓的数据包,所以代码应该是这样的
**my $file= $cgi->param('file' );**
下面就可以读取文件了
5.读取文件
Perl使用一种叫做文件句柄类型的变量来操作文件,从文件读取或者写入数据都需要使用到文件句柄
文件句柄是一个I/0连接的名称,Perl中提供了三种文件句柄,STDIN,STDOUT,STDERR,分别代表标准输入,标准输出,标准错误
这里就要使用Perl中的尖括号运算符了(<>),他有如下用途:
i,如果尖括号中间是文件句柄,尖括号运算符允许读取文件句柄
** ii,如果尖括号中间是搜索模式,尖括号运算符能返回该模式相匹配的文件列表,这被称为一个glob,比如<.bat>*
iii.如果尖括号内没有任何内容,那么它可以读取命令行上所有的文件内容,如果没有文件名,则可以读取标准输出
这里需要使用文件句柄这个功能
6.输出文件内容
前面读到了文件句柄,那么如何输出文件内容呢?
这里用到Perl中的特殊变量**$_**
**$_ 是默认参数的意思,指的是在不指定的情况下,程序处理的上一个变量
**
比如打开一个文本文件,读取每一行,在没有指定参数的情况下,$_指向从文件中读取的每一行
因此,若想要读某个文件的内容,可以写一个循环,逐行读取文件的内容
**while ( <$file> ) { print "$_"; }**
后端源码
综上所述 ,后端源码就构造完成了 , 这个源码在很多 WriteUp 中都给出了 , 但这里是站在初学者的角度 , 具体的阐述了每一行代码是怎么来的
漏洞分析
1.首先要了解Perl中ARGV全局特殊文件句柄
菜鸟教程上给出的解释是这样的
这个 @ARGV 是个全局 数组特殊变量 , 教程里是这样解释的
Perl会将perl命令行参数列表放入到数组@ARGV中,而默认情况下,这些命令行是Perl的数据输入源,也就是Perl会依次将他们当做文件进行读取
2.param()函数会返回一个列表的文件
param()函数会返回一个列表的文件 , 但是根据后端代码 , 只有第一个文件会被放入到 **<file>**
变量中
这里就存在可利用的点!
如果在原来的数据包中新增一个文件上传项,并且删除其filename参数,看一看$file变量的值(也就是文件名)会是怎么样的?
后端会将第一个上传项的内容作为$file参数的值,因此我们可以$file变量的值
如果$file变量的值是ARGV文件句柄,读取后的结果是怎样的呢?@ARGV数组的内容又是怎样的呢?
正如之前所说的,ARGV文件句柄会将@ARGV数组的每一项作为文件名并读取他们的内容
那么如何控制@ARGV数组的内容呢?请看下面两张图
i.正常上传文件
ii.在URL后添加路径
可以看到,在URL后添加的路径会被放到@ARGV数组中,配合之前引入的ARGV文件句柄,我们就可以读取任意文件!
写个DEMO , 这里以 **/etc/passwd**
为例 , 因为大部分系统都有这个文件 , 且任何用户都可读
成功读取到 **/etc/passwd**
漏洞利用
回到题目环境
**成功读取到了 /etc/passwd !**
那么 Flag 文件在哪呢? 这里还需要用到一个知识点 : Perl Open() 函数
Perl Open()函数可以用于打开管道, 用户可以使用”|”作为分隔符,因为Perl会寻找”|”来表示Open()正在打开一个管道,我们可以劫持Open()调用,从而执行系统命令
当然也可以执行 ls
命令
执行 cat
命令读取 flag