(抄)Python http.server 任意跳转漏洞学习笔记

新年新怠惰,最近怠惰一段时间,昨天看到ph师傅发了一篇文章,就去学习了一波,水一篇笔记来蒙混蒙混自己Orz
python http.server open redirect vulnerability

复现

这个漏洞复现起来还是方便的python3 -m http.server
然后在浏览器中输入http://127.0.0.1:8000//baidu.com/%2f%2e%2e就能跳转到百度了

简单分析

http.server模块用到几个比较重要的类,这几个类的关系就引用ph师傅的话来描述比较清楚

  1. HTTPServer这个类继承于socketserver.TCPServer,说明其实HTTP服务器本质是一个TCP服务器
  2. BaseHTTPRequestHandler,这是一个处理TCP协议内容的Handler,目的就是将从TCP流中获取的数据按照HTTP协议进行解析,并按照HTTP协议返回相应数据包。但这个类解析数据包后没有进行任何操作,不能直接使用。如果我们要写自己的Web应用,应该继承这个类,并实现其中的do_XXX等方法。
  3. SimpleHTTPRequestHandler,这个类继承于BaseHTTPRequestHandler,从父类中拿到解析好的数据包,并将用户请求的path返回给用户,等于实现了一个静态文件服务器。
  4. CGIHTTPRequestHandler,这个类继承于SimpleHTTPRequestHandler,在静态文件服务器的基础上,增加了执行CGI脚本的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
+-----------+          +------------------------+    
| TCPServer | | BaseHTTPRequestHandler |
+-----------+ +------------------------+
^ |
| v
| +--------------------------+
+----------------| SimpleHTTPRequestHandler |
| +--------------------------+
| |
| v
| +-----------------------+
+-----------------| CGIHTTPRequestHandler |
+-----------------------+

为什么会造成跳转就得去看SimpleHTTPRequestHandler的源码了

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#https://github.com/python/cpython/blob/3.6/Lib/http/server.py#L634
def do_GET(self):
"""Serve a GET request."""
f = self.send_head()
if f:
try:
self.copyfile(f, self.wfile)
finally:
f.close()

def do_HEAD(self):
"""Serve a HEAD request."""
f = self.send_head()
if f:
f.close()

def send_head(self):
"""Common code for GET and HEAD commands.
This sends the response code and MIME headers.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.
"""
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
parts = urllib.parse.urlsplit(self.path)
if not parts.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(HTTPStatus.MOVED_PERMANENTLY)
new_parts = (parts[0], parts[1], parts[2] + '/',
parts[3], parts[4])
new_url = urllib.parse.urlunsplit(new_parts)
self.send_header("Location", new_url)
self.end_headers()
return None
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)

...

如果path是一个目录,并且不是以/结尾,那send_head就会在结尾加上/并进行301跳转,这样看上去并没什么问题,如果目录不存在,就会返回404。但是在浏览器中,如果url以//开头,浏览器会默认认为这个url是当前数据包的协议。输入http://127.0.0.1:8000//baidu.com/%2f%2e%2e

1
2
GET //baidu.com/%2f. HTTP/1.1
Host: 127.0.0.1:8000

所以send_head会在头里加入Location //baidu.com/%2f../
浏览器默认为http协议,所以就跳转到http://baidu.com/%2f../

虽然这个在生产环境中不会有人用python3 -m http.server,如果有别的代码引用了这个库,就可以造成漏洞了。
ph师傅也举了个web.py的例子

1
2
3
4
#httpserver.py
def do_GET(self):
if self.path.startswith('/static/'):
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)

这个文件引入了SimpleHTTPRequestHandler,并且在96行的时候调用了SimpleHTTPRequestHandler.do_GET(),但是请求必须由/static/开头,因为当请求网站静态文件时会调用SimpleHTTPRequestHandler,一般网站会将静态文件放入/static/文件夹中,所以一般都是存在的,所以直接访问http://127.0.0.1:8080////static%2fcss%2f@baidu.com/..%2f就能跳转

参考资料

https://www.leavesongs.com/PENETRATION/python-http-server-open-redirect-vulnerability.html
https://www.zybuluo.com/wzhang1117/note/8226
ps: 如果侵权了的话请告知我,我马上删除