Repository navigation
Expand file tree
/
Copy pathhttp_client.py
More file actions
170 lines (128 loc) · 6.11 KB
/
Copy pathhttp_client.py
File metadata and controls
170 lines (128 loc) · 6.11 KB
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import sys
import ssl
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY2:
from httplib import HTTPConnection, HTTPSConnection
if PY3:
from http.client import HTTPConnection, HTTPSConnection
# NOTE: the URL may be relative to host, or may be full URL.
conn = HTTPConnection("example.com") # $ clientRequestUrlPart="example.com"
conn.request("GET", "/") # $ clientRequestUrlPart="/"
url = "http://example.com/"
conn.request("GET", url) # $ clientRequestUrlPart=url
# kwargs
conn = HTTPConnection(host="example.com") # $ clientRequestUrlPart="example.com"
conn.request(method="GET", url="/") # $ clientRequestUrlPart="/"
# using internal method... you shouldn't but you can
conn._send_request("GET", "url", body=None, headers={}, encode_chunked=False) # $ clientRequestUrlPart="url"
# low level sending of request
conn.putrequest("GET", "url") # $ clientRequestUrlPart="url"
conn.putheader("X-Foo", "value")
conn.endheaders(message_body=None)
# HTTPS
conn = HTTPSConnection("host") # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url"
# six aliases
import six
conn = six.moves.http_client.HTTPConnection("host") # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url"
conn = six.moves.http_client.HTTPSConnection("host") # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url"
# ==============================================================================
# Certificate validation disabled
# ==============================================================================
# default SSL context is the one given by `_create_default_https_context`
context = ssl._create_default_https_context()
assert context.check_hostname == True
assert context.verify_mode == ssl.CERT_REQUIRED
conn = HTTPSConnection("host", context=context) # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url"
# `_create_default_https_context` is currently just an alias for `create_default_context`
# which creates a context for SERVER_AUTH purpose.
context = ssl.create_default_context()
assert context.check_hostname == True
assert context.verify_mode == ssl.CERT_REQUIRED
conn = HTTPSConnection("host", context=context) # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url"
# however, if you supply your own SSLContext, you need to set it manually
context = ssl.SSLContext()
assert context.check_hostname == False
assert context.verify_mode == ssl.CERT_NONE
conn = HTTPSConnection("host", context=context) # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled
# and if you misunderstood whether to use server/client in the purpose, you will also
# get a context without hostname verification.
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
assert context.check_hostname == False
assert context.verify_mode == ssl.CERT_NONE
conn = HTTPSConnection("host", context=context) # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled
# NOTICE that current documentation says
#
# > Enabling hostname checking automatically sets verify_mode from CERT_NONE to
# > CERT_REQUIRED. It cannot be set back to CERT_NONE as long as hostname checking is
# > enabled.
# - https://docs.python.org/3.10/library/ssl.html#ssl.SSLContext.check_hostname
context = ssl.SSLContext()
context.check_hostname = True
assert context.verify_mode == ssl.CERT_REQUIRED
conn = HTTPSConnection("host", context=context) # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url"
# only setting verify_mode is not enough, since check_hostname is not enabled
context = ssl.SSLContext()
context.verify_mode = ssl.CERT_REQUIRED
assert context.check_hostname == False
conn = HTTPSConnection("host", context=context) # $ clientRequestUrlPart="host"
conn.request("GET", "url") # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled
# ==============================================================================
# taint test
# ==============================================================================
from flask import request
def taint_test():
host = request.args['host']
url = request.args['url']
conn = HTTPConnection(host) # $ clientRequestUrlPart=host
conn.request("GET", url) # $ clientRequestUrlPart=url
resp = conn.getresponse()
ensure_tainted(
# see
# https://docs.python.org/3.10/library/http.client.html#httpresponse-objects
# https://docs.python.org/3/library/http.client.html#http.client.HTTPResponse
# a HTTPResponse itself is file-like
resp, # $ tainted
resp.read(), # $ tainted
resp.getheader("name"), # $ tainted
resp.getheaders(), # $ tainted
# http.client.HTTPMessage
resp.headers, # $ tainted
resp.headers.get_all(), # $ tainted
# Alias for .headers
# http.client.HTTPMessage
resp.msg, # $ tainted
resp.msg.get_all(), # $ tainted
# Alias for .headers
resp.info(), # $ tainted
resp.info().get_all(), # $ tainted
# although this would usually be the textual version of the status
# ("OK" for 200), it is possible to put your own evil data in here.
resp.reason, # $ tainted
# the URL of the recourse that was visited, if redirects were followed.
# I don't see any reason this could not contain evil data.
resp.url, # $ tainted
resp.geturl(), # $ tainted
)
ensure_not_tainted(
resp.status,
resp.code,
resp.getcode(),
)
# check that only setting either host/url is enough to propagate taint
conn = HTTPConnection("host") # $ clientRequestUrlPart="host"
conn.request("GET", url) # $ clientRequestUrlPart=url
resp = conn.getresponse()
ensure_tainted(resp) # $ tainted
conn = HTTPConnection(host) # $ clientRequestUrlPart=host
conn.request("GET", "url") # $ clientRequestUrlPart="url"
resp = conn.getresponse()
ensure_tainted(resp) # $ tainted