0x00 在一切之前
审计lightftp代码中出现0day漏洞。
0x01 描述
附件解压后文件如下
1 | $ ls |
查看dockerfile可知我们的目标是最新版本的ligthftp
1 | wget --no-check-certificate https://codeload.github.com/hfiref0x/LightFTP/zip/refs/tags/v2.2 |
配置文件如下,使用anonymous加任意密码就可以登录,不过只有只读权限。
1 | [ftpconfig] |
0x02 代码审计
从main.c当中的main函数开始看起。程序解析config文件后会进入ftp_main
。
1 | /* Program entry point */ |
在ftp_main函数中监听端口,accept后进入协议处理逻辑,ftp_client_thread
逻辑中
1 | void *ftpmain(void *p) |
在ftp_client_thread
中,读入ftp的命令并解析,随后调用对应的Command
1 | void *ftp_client_thread(SOCKET *s) |
其中ftpprocs
是一个由命令名和对应的函数组成的结构体
1 | static const FTPROUTINE_ENTRY ftpprocs[MAX_CMDS] = { |
0x03 漏洞挖掘思路
这种协议的漏洞无非会出现在两种地方
- 命令的解析部分
- 命令的执行部分
然后使用checksec分析一下程序的保护机制
1 | Arch: amd64-64-little |
发现程序保护全开,那么内存洞的利用可能就很小了,大概率是逻辑问题这种。
那么首先考虑目录穿越,但是发现读文件的时候目录穿越已经被检测到了,做过一些尝试后都不行,因此思路pass。
接着看其他的一些指令功能有没有缺陷可以导致跨目录读这种。此时注意到ftpList函数,该函数功能为查看当前文件夹下的文件列表。读取的操作使用list_thread
去执行。
1 | int ftpLIST(PFTPCONTEXT context, const char *params) |
其中CurrentDIr
是我们当前所在的文件夹,RootDir
是我们的工作目录,FileName
是将前两者拼接后得到的我们要读取的真实目录。但注意到线程里用到了context这个结构,但并没有对这个变量做任何保护。
又注意到ftpUser
函数中将输入直接拷贝到了这个context->FileName
中。因此我们可以通过条件竞争控制FileName达到任意文件读的目的。
1 | int ftpUSER(PFTPCONTEXT context, const char *params) |
0x04 漏洞利用流程
- 执行FtpList(此时会等待客户端再建立一个新的连接,将List后的结果通过这个连接发送)
- 使用FtpUser改掉context->FileName
- 建立新连接,读取根目录下文件列表
- 获取flag文件名后再重复如上操作进行任意文件读
0x05 exp
1 | from pwn import * |