内容纲要

今天碰到一道 Perl 的题目 , 以前没有接触过 Perl , 所以这里专门记录下来


I-got-id-200

题目是这个样子的

  • 点击 Hello World , 跳转到下面的页面

    看到提示说后端使用的是 Perl 语言 , 从 URL 可以得知是 CGI( 公共网关接口 ) 应用

  • 点击 Forms , 跳转到下面的页面

    有个提交的表单 , 随便填写后抓包分析

    仅返回了用户输入的信息 ,并且我又测试了一些特殊字符 ,都能正常输出 , 暂时找不到利用点

  • 点击 Files , 跳转到下面的页面

    是一个上传页面 , 随意上传一个文件然后抓包分析 .

    看上去也仅仅是输出了用户上传的文件内容

想了一会儿没什么思路 , 不会做 , 于是请教大佬 , 才会做这道题


学习过程

本文主要内容是一个学习的流程 , 所以先让自己的 Apache 开启 CGI 支持 , 后面可以写测试脚本

Apache2 开启 Perl CGI 支持

  • 首先要确保你的 Apache2 存在 CGI 这个MOD , 可以通过如下命令来检查

  • 开启 CGI 支持 , 并重启 Apache2 服务

  • 编辑 Apache2.conf 文件 , 并添加相关配置

    # Perl CGI Support
    ScriptAlias /cgi-bin/ /var/www/cgi-bin/
     <Directory "/var/www/cgi-bin">
         AllowOverride all      
         Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
         Order allow,deny
         Allow from all
     </Directory>
     AddHandler cgi-script .cgi .pl
 此时 , /var/www/cgi-bin/ 目录就是你存放 Perl CGI 脚本的目录 , 如果不存在就新建该目录 , 并且重启 Apache2 服务
  • 编写测试脚本 , 并在浏览器上显示

    然后赋予 Perl 脚本可执行权限( 如果没有的话 ).

    最后在浏览器访问 http://127.0.0.1/cgi-bin/test.pl

    若成功输出测试语句 , 则 Perl CGI 环境已经搭建完成

    本文中没用到这个环境 , 因为用到的知识点并不多 , 如果您想搭建自己的环境 , 可以从这开始


本题 Perl 源码的构造与分析

在看源码之前 , 如果你没有学过 Perl 语言 , 可以参考下 Sam Hughes 大佬的 Learn Perl in about 2 hours 30 minutes , 这篇文章写的非常非常棒 !

那么本题中 Perl 后端到底是怎么写的呢 ? 为此我找了很久 == Google 上关于 Perl CGI 的文章并不多 , 而且关于文件上传的更是寥寥无几 .

最终我在 WikiBook 上找到了有关 Perl CGI 的相关内容

  • 要使用 Perl CGI , 首先要加载 CGI 模块

    use CGI;

  • CGI模块具有预编程功能 , 可用于Internet . 但在使用前必须创建CGI的句柄 => 允许我们访问相关的函数

    my $cgi = CGI->new();

    现在 $cgi 正在加载 CGI 标准函数 , 有关 CGI 标准函数的内容可以参考 Perl CGI 主要函数功能简介

  • 本题的环境是一个文件上传 , 而在上面的链接中有讲到 upload() 这个CGI标准函数

    upload() 函数是用于处理文件上传的标准函数 , 参数为在构造表单时 <input type="file" name=" ... ” /> 设置的 name 的值 .

    回到之前抓到的数据包 , name 参数的值为 “ file ” , 文件上传的过程应该是这样的 :

    if ( $cgi->upload("file") ) { ... }

    if 语句确定了这是一个文件上传操作 , 后面的步骤都是在这个 if 语句中进行的 .

  • 获取到参数

    在服务器处理之前 , 需要先检索输入 . 再对它们进行处理 .

    在 Perl CGI 标准函数中 , param() 函数用于获取传入的参数( 可以接收 GET方法 和 POST方法 传递的参数, 类似 PHP 中的 $REQUEST 全局变量 )

    根据之前抓到的数据包 , 这里的参数应该为 " file " , 所以代码应该是这样的 .

    my $file= $cgi->param('file' );

    下面就可以读取文件内容了

  • 读取文件

    Perl 使用一种叫做文件句柄类型的变量来操作文件 , 从文件读取或者写入数据都需要使用到文件句柄 .

    文件句柄( file handle )是一个I/O连接的名称 , Perl 中提供了三种文件句柄 : STDIN , STDOUT , STDERR , 分别代表标准输入 , 标准输出 , 标准错误输出

    这里就要使用到 Perl 中的尖括号运算符( <> )了 , 它有如下的用途

    1. 如果尖括号中间是文件句柄 , 尖括号运算符允许读取文件句柄

    2. 如果尖括号中间是搜索模式 , 尖括号运算符能返回与该模式相匹配的的文件列表 ,这被称为一个 glob , 比如 <*.bat>

    3. 如果尖括号内没有任何内容 , 那么它可以读取命令行上所有的文件内容 , 如果没有文件名 , 则可以读取标准输出

    这里需要使用 读取文件句柄 这个功能

  • 输出文件内容

    前面读取到了文件句柄 , 那么如何输出文件的内容呢 ?

    这里要用到 Perl 中的特殊变量 $_

    $_ 是默认参数的意思,指的是在不指定的情况下,程序处理的上一个变量 .

    比如打开一个文本文件 , 读取每一行 ,在没有指定参数的情况下 , $_ 指向从文件中读取的每一行 .

    因此 , 若想要读取某个文件的内容 , 可以写一个循环 , 逐行读取文件的内容

    while ( <$file> ) { print "$_"; }


后端源码

综上所述 ,后端源码就构造完成了 , 这个源码在很多 WriteUp 中都给出了 , 但这里是站在初学者的角度 , 具体的阐述了每一行代码是怎么来的


漏洞分析

  • 首先要了解 Perl 中的 ARGV 全局特殊文件句柄

    菜鸟教程上给出的解释是这样的

    这个 @ARGV 是个全局 数组特殊变量 , 教程里是这样解释的

    Perl 会将 perl 命令行参数列表放入到数组 @ARGV 中 , 而默认情况下 , 这些命令行参数是 Perl 的数据输入源 , 也就是 Perl 会以依次将他们当作文件进行读取

  • param()函数会返回一个列表的文件 , 但是根据后端代码 , 只有第一个文件会被放入到 <file> 变量中

    这里就存在可利用的点!

  • 如果在原来的数据包中新增一个文件上传项 , 并且删除其 filename 参数 , 看一看 $file 变量的值( 也就是文件名 )会是怎么样的?

    后端会将第一个上传项的内容作为 $file 参数的值 , 因此我们可以控制 $file 变量的值

  • 如果 $file 变量的值是 ARGV 文件句柄 , 读取后的结果是怎样的呢? @ARGV 数组的内容又是怎样的呢?

    正如之前所说的 , ARGV 文件句柄会将 @ARGV 数组的每一项作为文件名并读取它们的内容.

  • 那么如何控制 @ARGV 数组的内容呢 ? 请看下面两张图

    1. 正常上传文件

    2. 在 URL 后添加路径

    可以看到 , 在 URL 后添加的路径会被放入到 @ARGV 数组中 , 配合之前引入的 ARGV 文件句柄 , 我们就可以读取任意文件 !

  • 写个DEMO , 这里以 /etc/passwd 为例 , 因为大部分系统都有这个文件 , 且任何用户都可读

    成功读取到 /etc/passwd


漏洞利用

回到题目中的环境

成功读取到了 /etc/passwd !

那么 Flag 文件在哪呢? 这里还需要用到一个知识点 : Perl Open() 函数

Perl Open() 函数可用于打开管道 , 用户可以使用 " | " 作为分割符 , 因为 Perl 会寻找 " | "来表示 Open() 正在打开一个管道 , 我们可以劫持 Open() 调用 ,从而执行系统命令

当然也可以执行 ls 命令

执行 cat 命令读取 flag

成功拿到 Flag !


总结

我真是太菜了 , 感觉网上有关 Perl 的文档真的非常难用 == 很多内容找不到 , 读起来非常吃力

这道题还是有些地方不太清楚 , 要更加努力啊 !

最后修改日期:2020年6月28日

作者

留言

你菜个锤子,能像你这么细心写出的东西,值得学习。

谢谢啦,网上找了很多资料,就你这个讲的最好了!!非常感谢

非常好的一篇文章,但一直有一个疑问,为什么要在原来的数据包中新增一个文件上传项呢,我本地测的时候不加文件上传就出不来flag,加上就成功了,但是不太清楚为什么要这么做,可以解答一下吗?非常感谢!!

    因为在 Perl 中 , ” $_ ” 变量代表默认输入和模式匹配内容 . 默认情况下, 后端会将获取到的第一个值赋值给 $file 变量 , 而我们想要控制 $file 变量的值 . 因此新增一个文件上传项 , 并去除文件名 , 这样后端获取到的第一个值就是文件内容 , 然后将文件内容赋值给 $file 变量 , 此时我们就能控制 $file 变量的值了.

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。