目录
简介
信息收集
漏洞发现
文件包含漏洞
漏洞利用
用户chiv的Shell
权限提升
解密ciphertext
用户pain的Shell
用户root的Shell
总结
这是一台练习CTF的好靶机,靶机创作者有很多提示,但是也有很多坑,总体来说有一定的难度,涉及文件包含、加解密、逆向分析等知识。通过提示发现备份站点是关键,然后利用文件包含漏洞收集信息发现FTP登录凭据,使用SSH获得用户chiv的Shell,通过pain用户的SUID执行文件读取备份文件获得用户pain的MySQL数据库凭据,同样可以使用SSH获得Shell,通过枚举攻击解密密文,获得加密磁盘镜像的密码,从而获得root用户的SSH私钥文件,得到root权限Shell。
使用nmap扫描扫描目标主机开放的常用端口及运行的服务,发现开启22和80端口,分别运行着ssh和http服务,且目标主机操作系统可能为ubuntu 2.6.32,如图:
然后访问80端口的http服务,发现网站被重定向到http://forwardslash.htb/,如图:
然后将forwardslash.htb添加到hosts文件再进行访问,如图:
网站名称是反斜杠派,而主机名称是正斜杠,从网页内容中发现来自反斜杠派的嘲讽,服务器使用了XML和FTP自动登录,反斜杠派可能利用这点黑了该网站,且他们是Sharon的忠实追随者。使用Google搜索关键字,只搜索到Apache反斜杠目录遍历漏洞,但对该靶机不适用。使用dirbuster扫描网站目录和php,bak,zip,sql等文件未发现有价值的信息,但在模糊测试时发现存在.php文件(也可能是目录),但是没有权限访问,如图:
使用wfuzz扫描目录的时候发现存在.htaccess和.htpasswd文件,以及server-status目录,但都没有访问权限,如图:
再次扫描网站txt文件发现存在note.txt文件,如图:
查看内容发现反斜杠派有备份站点,如图:
然后使用wfuzz利用Host头枚举子域名,当Host为backup.forwardslash.htb时发生跳转,如图:
将backup.forwardslash.htb添加到hosts文件,然后访问,如图:
页面跳转到登录页面。扫描网站目录及文件发现如下文件,如图:
测试登录框发现不存在SQL注入漏洞,然后注册一个账户,如图:
登录之后跳转到welcome页面,如图:
跳过重置密码,登出和修改用户名功能,查看修改个人资料图片,如图:
但是表单被禁用了,然后定位到submit按钮源码,发现有disabled属性,URL表单同样如此,如图:
将该属性删掉后就可以使用了,如图:
使用BurpSuite拦截数据包,将test.com改为http://127.0.0.1,如图:
页面返回index.php文件内容,如图:
说明此处存在远程文件包含漏洞。后来发现直接使用绝对路径也可以读取文件内容,所以此处也存在本地文件包含漏洞。
利用该漏洞读取/etc/passwd文件,发现存在普通用户pain和chiv,如图:
然后在Kali Linux创建一个包含php代码的txt文件,并利用该漏洞包含此文件,如图:
发现并没有执行该代码,而仅仅是将文件内容显示了出来。然后读取/var/www/html/index.php文件内容,如图:
该文件是forwardslash.htb网站的index.php文件。尝试使用相对路径读取backup.forwardslash.htb网站的index.php文件,如图:
没有权限查看,尝试使用,base64编码读取,如图:
解码之后,如图:
这说明可以执行同级目录下的php文件,但没有权限在同级目录下创建php文件,因此无法利用该漏洞直接getshell。然后读取其他文件,在config.php中包含数据库用户名和密码等信息,如下:
在dev/index.php文件中发现ftp用户chiv的密码,如图:
尝试使用22端口的ssh服务登录成功,如图:
查看内核版本,如图:
内核版本符合CVE-2019-13272,但无法利用成功,使用CVE-2018-18955也无法利用成功。
查看发现user.txt文件在/home/pain目录下,且note.txt文件提示重要文件被加密了,如图:
查看encryptorinator目录下的文件发现密文和python加密程序文件,如图:
python加密程序内容如下:
def encrypt(key, msg):
key = list(key)
msg = list(msg)
for char_key in key:
for i in range(len(msg)):
if i == 0:
tmp = ord(msg[i]) + ord(char_key) + ord(msg[-1])
else:
tmp = ord(msg[i]) + ord(char_key) + ord(msg[i-1])
while tmp > 255:
tmp -= 256
msg[i] = chr(tmp)
return ''.join(msg)
def decrypt(key, msg):
key = list(key)
msg = list(msg)
for char_key in reversed(key):
for i in reversed(range(len(msg))):
if i == 0:
tmp = ord(msg[i]) - (ord(char_key) + ord(msg[-1]))
else:
tmp = ord(msg[i]) - (ord(char_key) + ord(msg[i-1]))
while tmp < 0:
tmp += 256
msg[i] = chr(tmp)
return ''.join(msg)
print encrypt('REDACTED', 'REDACTED')
print decrypt('REDACTED', encrypt('REDACTED', 'REDACTED'))
代码中包含一个加密函数、一个解密函数和一组测试数据,加密算法是对称密码,加密密钥和解密密钥是相同的,但是密钥会在哪里呢。
利用明文是可打印的字符串这一特性,编写python程序进行枚举,但是没有获得明文,这说明要么rockyou.txt中没有密码,要么明文中包含有不可打印的字符,读取密文发现密文长度为165,说明明文也是165个字符,假设可打印字符为160个,然后逐渐减少可打印字符数量,直到有满足条件的明文出现,程序decrypter.py如下:
#!/usr/bin/python
from threading import Thread
import linecache
import string
def decrypt(key, msg):
key = list(key)
msg = list(msg)
for char_key in reversed(key):
for i in reversed(range(len(msg))):
if i == 0:
tmp = ord(msg[i]) - (ord(char_key) + ord(msg[-1]))
else:
tmp = ord(msg[i]) - (ord(char_key) + ord(msg[i-1]))
while tmp < 0:
tmp += 256
msg[i] = chr(tmp)
return ''.join(msg)
def brute(filename,start,end,ciphertext):
global isExit
while start < end:
#If one thread decrypts successfully, all threads are terminated
if isExit:
return 0
#Read the file from start line to end line
password=linecache.getline(filename,start)
plaintext=decrypt(password,ciphertext)
#If the number of printable characters is greater than 160, the decrypted plaintext is output
if sum(i in string.printable for i in plaintext)>160:
print "\033[1;32;1m[+] Password is :",password,"\033[0m"
print "[+] Plaintext is :",plaintext
isExit=True
start+=1
def threadbrute(func,filename,lines,thread):
threadlist=[]
ciphertext=open('ciphertext','r').read()
for i in range(thread-1):
threadlist.append(Thread(target=func,args=(filename,i*lines,lines*(i+1),ciphertext)))
threadlist.append(Thread(target=func,args=(filename,(thread-1)*lines,crows,ciphertext)))
for i in threadlist:
#Set thread as guardian thread,Allow termination via CTRL + C
i.setDaemon(True)
i.start()
try:
while i.isAlive():
pass
except KeyboardInterrupt:
print "[!] Stop by KeyboardInterrupt!"
#Exit flag
isExit=False
wordlist='/usr/share/wordlists/rockyou.txt'
#Get dictionary lines
crows=len(linecache.getlines(wordlist))
#Divide the dictionary into blocks equal to the number of threads
lines,ad=divmod(crows,200)
print("[*] Cracking Password ......")
threadbrute(brute,wordlist,lines,200)
可打印字符大于160时未获得满足条件的输出,当可打印字符大于155时解密成功,明文中告知了/var/backups/recovery目录下加密映像的密钥,如图:
但是当前用户没有权限查看。
然后继续查看其他文件,在/var/backups目录下发现另一个提示和config.php.bak文件,如图:
文件只对所有者pain用户具有读写权限,且提示需要密码才能读取。使用提权辅助脚本LinEnum.sh枚举发现所有者为pain的SUID文件,如图:
使用linux-smart-enumeration枚举,同样发现SUID文件,如图:
尝试运行发现提示并报错,提示还没有读到正确的文件,只有在同一秒钟内执行备份时才有效。且报错内容为32位16进制字符串不存在或不可访问,如图:
验证发现32位16进制字符串是当前时间戳的MD5值,如图:
从报错信息结合/var/backups/note.txt文件提示推测,当前时间戳的MD5值就是Pain所说的密码,backup程序应该是读取文件名为当前时间戳MD5值的文件内容,但是需要在一秒内将当前时间戳MD5的值作为文件名指向/var/backups/config.php.bak,并执行/usr/bin/backup程序,这就需要写个程序来实现了。使用bash命令更加简单:
ln -s /var/backups/config.php.bak $(date | cut -d' ' -f4 | tr -d $'\n' | md5sum | cut -d' ' -f1); backup
使用该命令成功读取到config.php.bak文件内容,但这仅仅是MySQL数据库用户pain的凭据,如图:
很奇怪使用该凭据并不能成功登录MySQL,但是却可以使用ssh进行登录,如图:
但这需要足够细心和很强的逻辑推理能力,我也是看了大佬的提示花了很长时间才明白backup程序的功能。也有大佬使用ltrace和strace调试分析程序的功能,当然我更喜欢使用IDA,如图:
可以清晰的看到,当MD5值的文件不存在是输出错误,否则就读取文件内容。
再次使用提权辅助脚本LinEnum.sh发现无需密码就可使用sudo的命令,但命令有限制,如图:
回到之前解密后的明文中提到的/var/backups/recovery目录,发现加密的备份镜像文件,如图:
使用提权辅助脚本枚举到的无需密码的sudo命令结合前边获取到的加密映像的密钥,解密并映射到/dev/mapper/backup,如图:
然后在具有写权限的目录下创建mnt目录,使用sudo /bin/mount /dev/mapper/backup ./mnt/进行挂载,如图:
查看目录内容发现id_rsa文件,如图:
应该是root用户的ssh私钥,使用该文件成功登录,如图:
在该靶机刚发布不久我没能成功攻破,由于忽略了对txt文件的扫描,没能找到作者提示的备份站点。在该靶机退休后看了下大佬们的思路,然后又学习了一遍。作者给的提示也很多,但是有时候提示太多也容易混乱,而且坑也比较多。首先在文件包含漏洞那里花了很长时间,以为直接是通过文件包含漏洞获得Shell,在发现密文后,解密过程又花了很多时间,研究backup程序的功能也是,猜了很久,最后还是用了IDA分析。