本文的标题预示着接下来我们要做一件有意思的事,之前我们写的客户端程序,也可以拿来做服务器使用。
1. 程序路径
代码托管在 gitos 上,请使用下面的命令获取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。本节程序所使用的程序路径是 unp/program/echo_udp/basic
以及 unp/program/echo_udp/verify
。
2. 实验思路
- 找到客户端绑定的套接字地址
要想让客户端成为服务器很简单,只要客户端绑定了套接字地址即可。然而,客户端绑定套接字地址是隐式发生的,即在 sendto 的时候发生的,系统为随机会客户端分配一个端口号。只要我们知道了在 sendto 时的端口号,就可以给客户端发送数据了。
- 让客户端阻塞到 recvfrom 上
这种我们在上一篇文章中已经学会了,只要给一个不存在的服务器发数据,客户端就能阻塞到 recvfrom 上了。
3. 实验步骤
- 在 sun 主机开启 tcpdump
sun $ sudo tcpdump -ttt -i ens33 "host mars and host flower" and udp
- sun 主机上启动客户端,让客户端阻塞到 recvfrom
// mars 主机上的服务器不能启动 sun $ ./echo -h mars
启动客户端后,随便输入一些数据。
图1 向 mars 发送 udp 数据报
由于 mars 并没有启动,所以客户端阻塞到了 recvfrom 上。观察 tcpdump 输出,客户端绑定的端口号是 55904.
- 在 flower 主机上再启动一个客户端
// 准备给 sun 上的 55904 端口发数据 flower $ ./echo -h sun -p 55904
图2 sun 的客户端阴差阳错的收到了 flower 发来的数据
4. 结果分析
不知道对此你能联想到什么,漏洞?攻击?
是的,这绝对是客户端的漏洞。如果别人知道了这个客户端的端口号(比如遍历端口),就完全可以给这个客户端发送一些带有恶意的数据,是不是?
幸亏 recvfrom 有一个参数指定的缓冲区的大小,不然还会弄出一个溢出攻击呢。
作为客户端,对收到的数据做验证是相当重要的,因为客户端并不能保证给它发数据的那个人就是它指定的服务器,而是可以是任何人。具体方法很简单,我们只要将 recvfrom 接收到的 ip 地址进行比对即可。
具体我将其封装成了一个 verify 函数:
// 返回 1 表示验证成功,0 表示失败 int verify(struct sockaddr_in *a, int len1, struct sockaddr_in *b, int len2) { if (len1 != len2 || memcmp(a, b, len1) != 0) { return 0; } return 1; }
修正后的程序可以在 unp/program/echo_udp/verify
找到。
图3 修正后的程序,对于未知的套接字地址发来的数据不予理会
5. 总结
- 为什么客户端也能做服务器
- 如何验证客户端收到的数据确实是指定的服务器发来的