python中socket模块的使用

0x01 原理

在python安全编程的世界当中,socket是和requests并驾齐驱,必须要掌握的一个模块。因为应用层上除了WEB的行为,你基本上都要使用socket去完成。

那么什么是socket呢?简单来说,就是区分同一服务器上不同应用程序的标识。其目的是为了完成从A主机上X进程到B主机上Y进程的通信。因此socket是工作在应用层和传输层之间的。如果你对什么是socket,什么是TCP,UDP不了解,建议阅读这篇文章,如果你从来没学过计算机网络,则请阅读这篇文章

注意,socket和socket模块是不一样的,这篇文章主要介绍socket模块的使用。

0x02 使用

先让我们来看一个小小的Demo

1
2
3
4
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

首先socket.socket是一个类,我们将其实例化之后传递给s,而实例化的参数如下:

1
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

family用于指定套接族,type用于指定套接类型,proto用于指定协议。

family 含义
AF_UNIX unix系统本地通信
AF_INET ipv4通信
AF_INET6 ipv6通信
type 含义
SOCK_STREAM 基于TCP的流式socket通信
SOCK_DGRAM 基于UDP的数据包式通信
SOCK_RAW 原始套接字

以上只是一部分,更多细节请参考官方手册。
完成初始化之后,就可以调用socket类的方法了。

1
s.connect(('127.0.0.1', 12345))

在上面的demo当中,调用了connect方法。该方法传入一个address参数,该参数为元组形式,包括地址以及端口。意思就是建立连接,只有建立连接之后才能有后续动作。
(注:python3.5之后该方法超时不再引发InterruptedError)

1
2
3
4
5
6
while True:
text = input('please input...')
print()
s.send(text.encode('ascii'))
data = s.recv(1024)
print(data.decode('ascii'))

recv方法传入一个bufsize参数,指定接收内容的大小。执行成功返回bytes对象。
send则是相反,发送一个bytes对象到对应客户端。因此,发送或接收时注意不要发送str对象,而应发送bytes对象。
(注:在python2中则为发送接收str对象)

接下来开始服务器端的编写

1
2
3
4
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 12345))

这里调用了bind方法。作用在于绑定主机地址和端口号,建立一个服务端的socket进程。
(注:这里如果写localhost或是127.0.0.1只能本机访问,如果需要让别的主机访问,可以使用socket.gethostname()。)

1
2
3
4
5
6
7
while True:
conn, addr = s.accept()
print('connected by ',addr)
while True:
data = conn.recv(1024)
print(data.decode('ascii'))
conn.send(b'server received you message')

accept的方法是接受客户端发起的connect请求,该方法返回一个元组,包含一个新的socket对象以及地址对。在后续阶段,将使用这个新的socket对象与客户端进行交互。

1
s.close()

最后,在使用完成之后,记得关闭套接字。
至此,一个基本的通过socket通信的程序已经基本完成。

0x03 补充

如果你还对socket的实际应用不太了解,建议多阅读exploit-db上面的一些EXP,相当多都用到了socket。
如前阵子的永恒之蓝,又如14年臭名昭著的心脏滴血

当然socket的用途绝不仅限于编写EXP,但是通过阅读EXP,理解socket的本质,做到举一反三,对你非常有帮助。

如果你对socket的一些细节用法不太了解,请手动查阅官方文档。

0x04 参考

python-socket详细介绍

socket官方文档(3.5)

基于python2的官方tutorial