freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

漏洞扫描器卡住的bug复盘
leveryd 2021-05-15 15:08:06 106145
所属地 重庆

问题背景

之前线上的漏洞扫描遇到一个奇怪的问题:requests.get即使设置了timeout,仍然卡住。

看lijiejie大佬 requests.get 异常hang住也碰到过这个问题。

所以,我想要探究以下问题:

requests库中timeout参数的具体含义是什么?

为什么requests.get时timeout参数"失效"?

分析过程

requests库中timeout参数的具体含义是什么?

requests库中timeout参数是什么?

根据官网文档所说,timeout可以表示(connect超时时间,read超时时间)

什么是connect超时?

客户端需要connect系统调用来和服务端做tcp三次握手,当服务地址在互联网上不存在时,connect系统调用耗时就会比较长。

比如请求1.1.2.3 过一段时间后会返回一个connect tiemout:

python -c 'import requests;requests.get("http://1.1.2.3")'

在上面请求1.1.2.3这个不存在的ip时,客户端发出的 syn 包没有任何响应,于是客户端会重传syn包

重传次数在 /proc/sys/net/ipv4/tcp_syn_retries 可以配置

重传间隔时间并不是固定的,在Linux系统上测试结果是 [1,3,7,15,31]s,似乎就是 2^(n+1)-1

如果重试完后仍然没有收到ack包,就会出现connect timeout

而request的timeout参数就可以减少这个等重传的时间。

python -c 'import requests;requests.get("http://1.1.2.3", timeout=(1, 100))' # 1s的connect超时设置

怎么实现的connect超时控制?

connect、read等系统调用是没有参数可以控制超时时间的,那connect超时控制是怎么实现的呢?

在Modules/socketmodule.c可以找到connect函数的实现

1. socket设置成非阻塞模式

...
sock_call_ex(...,_PyTime_t timeout) {
  ...
  interval = timeout;
  ...
  res = internal_select(s, writing, interval, connect);
  ...
}

static int internal_select(PySocketSockObject *s, int writing, _PyTime_t interval,
            int connect){
  ...
  ms = _PyTime_AsMilliseconds(interval, _PyTime_ROUND_CEILING);
  ...
  n = poll(&pollfd, 1, (int)ms);  2. poll系统调用,如果超时,poll系统调用就会返回

流程如下:

* 设置socket为非阻塞模式后,调用connect系统调用
* 使用poll系统调用来判断是否超时

实际上这是一种很通用的对connect做超时控制的方式,在其他tcp客户端中也可以这么实现超时控制。

什么是read超时?

客户端需要调用read系统调用来读取服务端发送的数据,如果服务端一直不发送数据,读数据时就会卡住。

比如我们用nc命令开启一个服务端只负责监听建立链接,不发送数据.

nc -l 8081

客户端请求nc开启的服务,代码如下,3s后会出现读超时

import requests
requests.get("http://127.0.0.1:8081", timeout=(1,3)) # read超时时间设置成3s

为什么requests.get时timeout参数"失效"?

requests.get在 dns解析、connect、read 这些阶段都有可能耗时比较久。下面分别说一下timeout在这三个阶段中是否生效。

文档中只说了timeout控制connect、read两个阶段,说明dns解析耗时很久时timeout是管不了的。
我自己实验,也得出相同的结论:dns解析时间即使超过timeout,也不会抛出异常。

connect阶段在上面已经分析过,timeout是可以控制这一阶段最多花费多长时间的。

Python中的read超时不是一个全局的时间,它只是在每一次读socket时不能超过这个时间。而一次响应的读取可能有多次read操作。这儿可能和其他的http客户端(比如curl)等超时时间含义不同。

如果服务端能够让客户端read非常多次,且每一次时间都不超过read timeout值,这个时候客户端会卡住。

所以,在下面两种情况下是会造成read timeout参数“失效”的:

响应中content-length是一个特别大的数,服务端缓慢的每次响应1字节

服务端返回的响应码是100,同时服务端持续不断地返回响应头,也会导致客户端持续不断的read

比如下面的服务端持续不断地返回响应头,会导致客户端卡住。

# coding:utf-8
from socket import *
from multiprocessing import *
from time import sleep

def dealWithClient(newSocket,destAddr):
    recvData = newSocket.recv(1024)
    newSocket.send(b"""HTTP/1.1 100 OK\n""")

    while True:
        # recvData = newSocket.recv(1024)
        newSocket.send(b"""x:a\n""")

        if len(recvData)>0:
            # print('recv[%s]:%s'%(str(destAddr), recvData))
            pass
        else:
            print('[%s]close'%str(destAddr))
            sleep(10)
            print('over')
            break

    # newSocket.close()


def main():

    serSocket = socket(AF_INET, SOCK_STREAM)
    serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR  , 1)
    localAddr = ('', 8085)
    serSocket.bind(localAddr)
    serSocket.listen(5)

    try:
        while True:
            newSocket,destAddr = serSocket.accept()

            client = Process(target=dealWithClient, args=(newSocket,destAddr))
            client.start()

            newSocket.close()
    finally:
        serSocket.close()

if __name__ == '__main__':
    main()

更多的讨论可以见提交的bug urllib http client possible infinite loop on a 100 Continue response

总结

请求在 dns解析、connect、read 这些阶段都有可能耗时很久,其中:

dns解析阶段 不受timeout参数控制

connect阶段 受timeout参数控制

read阶段 timeout不是全局的,如果服务端让客户端有很多次read操作,就有可能让客户端卡住

阻塞时的connect系统调用是有默认的最大时间限制,这个和系统配置有关;可以用"非阻塞connect+select/poll"来实现connect的超时控制。

在排查这个case原因时,发现这里存在潜在的dos攻击问题,也上报给Python官方,很快被修复了。

# 安全产品 # 漏洞扫描器 # Python工具
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 leveryd 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
leveryd LV.3
公众号leveryd
  • 7 文章数
  • 4 关注者
基于kubernetes和工作流的漏扫实现
2023-03-24
基于netfilter的后门
2022-09-13
聊一聊基于"ebpf xdp"的rootkit
2022-08-18
文章目录