
CVE-2026-48710漏洞验证代码
#Principio: Starlette es un framework ASGI ligero y de código abierto para Python. El problema surge de que el framework Starlette, al procesar la estructura de datos de la URL, confía en el host enviado por el cliente y lo concatena directamente a la variable url para el posterior procesamiento de autenticación.
La URL original se procesa en uvicorn\protocols\http\httptools_impl.py
def on_message_begin(self) -> None:
self.url = b""
self.expect_100_continue = False
self.headers = []
self.scope = {
"type": "http",
"asgi": {"version": self.asgi_version, "spec_version": "2.3"},
"http_version": "1.1",
"server": self.server,
"client": self.client,
"scheme": self.scheme,
"root_path": self.root_path,
"headers": self.headers,
"state": self.app_state.copy(),
}
# Parser callbacks
def on_url(self, url: bytes) -> None:
self.url += url #self.url设置为原始url
def on_header(self, name: bytes, value: bytes) -> None:
name = name.lower()
if name == b"expect" and value.lower() == b"100-continue":
self.expect_100_continue = True
self.headers.append((name, value))
def on_headers_complete(self) -> None:
http_version = self.parser.get_http_version()
method = self.parser.get_method()
self.scope["method"] = method.decode("ascii")
if http_version != "1.1":
self.scope["http_version"] = http_version
if self.parser.should_upgrade() and self._should_upgrade():
return
parsed_url = httptools.parse_url(self.url)#使用httptools拆分原始url
raw_path = parsed_url.path #raw_path设置为原始url拆分出的path(访问的path)
path = raw_path.decode("ascii") #path设置为使用ascii解码以后的原始path值
if "%" in path: #处理url解码以后的中文从重编码
path = urllib.parse.unquote(path)
full_path = self.root_path + path
full_raw_path = self.root_path.encode("ascii") + raw_path
self.scope["path"] = full_path
self.scope["raw_path"] = full_raw_path
self.scope["query_string"] = parsed_url.query or b""
(Comentarios que escribí manualmente)
Este código se encuentra dentro del método de class URL:.
Código: \starlette\datastructures.py
host_header = None for key, value in scope["headers"]: if key == b"host": host_header = value.decode("latin-1") break if host_header is not None: url = f"{scheme}://{host_header}{path}" #基于用户传入的请求头二次定义url
En starlette\routing.py:
route_path = get_route_path(scope)
if scope["type"] == "http" and self.redirect_slashes and route_path != "/":
redirect_scope = dict(scope)
if route_path.endswith("/"):
redirect_scope["path"] = redirect_scope["path"].rstrip("/")
else:
redirect_scope["path"] = redirect_scope["path"] + "/"
for route in self.routes:
match, child_scope = route.matches(redirect_scope)
if match != Match.NONE:
redirect_url = URL(scope=redirect_scope) #调用URL方法
response = RedirectResponse(url=str(redirect_url))
await response(scope, receive, send)
return
Esto hace que un usuario envíe una solicitud malformada con el encabezado host a un endpoint que requiere autenticación; en la capa de enrutamiento, la url se redefine como un endpoint que no requiere autenticación.
Por ejemplo, en http://127.0.0.1:9999/admin
En esta URL, admin es un endpoint que requiere autenticación.
Envío del paquete de datos:
url = http://127.0.0.1:9999/admin
header{
host = 123?
}
La url se redefine como http://123?/admin
En este momento se usa la función urlsplit para analizar la url
Resultado: SplitResult(scheme='http', netloc='123', path='', query='/admin', fragment='')
Debido a que path='', la autenticación posterior considera que el usuario visita la raíz del sitio, por lo que se permite el acceso directamente; en realidad, se devuelve el contenido del directorio admin.