前言
比较老的靶机了(2014发布),但很经典,确实学到了很多东西。通过这个靶机接触到一些docker逃逸的东西。
知识点
- base64编码
- 目录遍历(双写
../) - Laravel(php框架)
- sqlite泄露
- ssh伪造密匙
- docker逃逸
详细过程
信息搜集
-
常规端口扫描,就不贴图了,只发现了
22,8881两个端口。首先按看一下8881端口的情况,需要输入口令才能开启新的门,但我们不知道口令是什么。 -
先放着,再看
22端口,先试着连接ssh,发现如下不明字符串。末尾为\x3d,推测为base64编码。 -
使用大厨经16次base64解码后得到明文字符串。
tabupJievas8Knoj,输入8881端口,80端口开放。 -
访问80web服务,web框架为Laravel
-
接着想到目录扫描,该web服务有一个问题就是我们输入错误的或者不存在的地址时会跳转到一个可列目录的位置,但我们点击目录则没有反应。所以一些通过返回响应码来判断目录存在的扫描器,无法使用。dirsearch还是可以使用的但确实没有扫到什么有价值的东西。
-
看一下页面功能。似乎是一个上传图片并展示的网站。存在登陆页面,但我们不知道用户名和密码。不过旁边有提示,这就要看自己的悟性了,能不能猜到用户名是
demo。不过这里可以使用cewl收集社工字典。 -
社工字典加数字得到密码。
demo:demo123.登陆后我们可以下载图片,也可以上传图片。 -
这里本来是想要突破上传拿到webshell的。无奈webshell能够上传,但无法解析。应用程序将文件名直接随机编码,无法突破。下载页面有一个参数值得怀疑。
-
使用
../无法突破,但仔细观察会发现它只是替换了../ -
双写
../可绕过限制。接下来进行文件读取。通过fuzz,我们得到web绝对目录。 -
可到github查看Laravel框架历史版本目录结构,可以读取数据库配置文件。
需要说明的是靶机发布时间为2014年,那时的Laravel框架目录结构与如今不同。 -
得到sqlite数据库位置下载之,使用navicat加载sqlite(结合配置文件的数据库用户名和密码),得到用户名和密码。
getshell
-
我们可以读取/etc/passwd文件,看到dean和robin在系统用户,尝试使用得到的密码撞库。dean可成功登陆。
dean:FumKivcenfodErk0Chezauggyokyait5fojEpCayclEcyaj2heTwef0OlNiphAnA(话说不要破解hash吗) -
dean家目录下有文件,可读取robin目录下docker部署文件。
-
尝试读取其他文件不成功。会在后面加上后缀
/Dockerfile.所以我们通过软连接方式将robin家目录下.ssh/id_rsa文件连接到我们(dean)目录下Dockerfile,是否可以读取他的ssh密匙呢?读取成功。
-
以robin用户登陆。
提权
-
下载提权辅助脚本运行,告诉我说我可能在docker环境中。通过ifconfig查看确实有docker虚拟网卡,可能环境配置有docker。robin的sudo权限如下
-
我们无权限查看这个
restart.sh文件。又想到查看一下docker运行着ubuntu,进入docker -
docker内为root权限,但没有用啊。通过查看docker选项可以看到docker允许挂载目录
-v, --volume=[]: Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container) -
挂载目录后进入root得到flag
番外
-
我参考的这篇博客给出了物理机提权的方法。通过docker逃逸,读取/opt/目录下的文件,进而提权。
我也确实复现成功。 -
但我想的是不必这么麻烦,既然们可以挂载
root目录到docker内,也可以挂载opt目录到docker,同样可以实现文件读取。 -
restart.sh
#!/bin/sh /usr/sbin/service apache2 restart /usr/bin/supervisorctl restart all -
start.py
#!/usr/bin/python ''' Simple socket server using threads. Used in the flick CTF Credit: http://www.binarytides.com/python-socket-server-code-example/ ''' import socket import os, sys, signal from thread import * import subprocess # import the directory containing our config, and prevent the bytcode writes sys.dont_write_bytecode = True # see if /tmp has a configuration to load. # Debugging purposes only!!! if os.path.isfile('/tmp/config.py'): sys.path.insert(0, '/tmp') # <=========注意这里 else: sys.path.insert(0, '/etc') # import the config from config import config HOST = '' # Symbolic name meaning all available interfaces PORT = 8881 # Arbitrary non-privileged port s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #Bind socket to local host and port try: s.bind((HOST, PORT)) except socket.error as msg: print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1] sys.exit() #Start listening on socket s.listen(10) #Function for handling connections. This will be used to create threads def clientthread(conn): #Sending message to connected client conn.send('Welcome to the admin server. A correct password will \'flick\' the switch and open a new door:\n> ') #send only takes string #infinite loop so that function do not terminate and thread do not end. while True: #Receiving from client data = conn.recv(1024) reply = 'OK: ' + data if not data: break # check if the password is tabupJievas8Knoj if data.strip() == 'tabupJievas8Knoj': return_code = subprocess.call(config['command'], shell=True) if return_code == 0: reply += '\nAccepted! The door should be open now :poolparty:\n' else: reply += '\nAccepted, but it doesn\'t look like the door opened :<\n' # add the prompt again reply += '\n> ' conn.sendall(reply) #came out of loop conn.close() #now keep talking with the client while 1: #wait to accept a connection - blocking call conn, addr = s.accept() #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function. start_new_thread(clientthread ,(conn,)) s.close() -
看到脚本其实提权方式也就出来了。还记得我们在一开始进行信息搜集时发现的8881端口就是这个脚本捣的鬼。只有我们输对密码,才会执行
/etc/config.py目录下的shell命令,开启web服务,但在源代码中我们看到只有在/tmp目录下没有config.py文件才读取/etc目录下配置文件,所以我们在/tmp下写入config.py文件即可代替原文件。内容如下config = { 'command': 'cp /bin/sh /tmp/pwn; chmod 4777 /tmp/pwn' }在8881端口再次输入密码后
/tmp目录生成pwn可执行文件,运行之可提权至root。
来源:CSDN
作者:adminuil
链接:https://blog.csdn.net/adminuil/article/details/104098222