Commit de6fb646f7 for ffmpeg
commit de6fb646f7c4c35973526066081232327c5d08a4
Author: Michael Niedermayer <michael@niedermayer.cc>
Date: Mon Oct 12 04:12:37 2026 +0200
avformat/http: send cookies given without a Domain only to their host
Fixes: cookie disclosure to other hosts
Fixes: u2ys08MO8oEF
Cookie disclosure Replicated through UnModified FFmpeg
Found-by: Yazan Balawneh, Cystack.ps
diff --git a/doc/protocols.texi b/doc/protocols.texi
index aa3b88fd58..287d972f68 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -524,7 +524,8 @@ Exports the HTTP response version number. Usually "1.0" or "1.1".
@item cookies
Set the cookies to be sent in future requests. The format of each cookie is the
same as the value of a Set-Cookie HTTP response field. Multiple cookies can be
-delimited by a newline character.
+delimited by a newline character. A cookie without a Domain attribute is only
+sent to the host of the URL it is set for.
@item icy
If set to 1 request ICY (SHOUTcast) metadata from the server. If the server
@@ -675,7 +676,7 @@ is used for reading large video packets with small audio packets in between.
Some HTTP requests will be denied unless cookie values are passed in with the
request. The @option{cookies} option allows these cookies to be specified. At
-the very least, each cookie must specify a value along with a path and domain.
+the very least, each cookie must specify a value.
HTTP requests that match both the domain and path will automatically include the
cookie value in the HTTP Cookie header field. Multiple cookies can be delimited
by a newline.
diff --git a/libavformat/http.c b/libavformat/http.c
index 488a8eb73d..2f4f522363 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -239,6 +239,7 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
const char *hoststr, const char *auth,
const char *proxyauth);
static int http_read_header(URLContext *h);
+static int bind_cookies_to_host(HTTPContext *s);
static int http_shutdown(URLContext *h, int flags);
void ff_http_init_auth_state(URLContext *dest, const URLContext *src)
@@ -837,6 +838,9 @@ static int http_open(URLContext *h, const char *uri, int flags,
if (!s->uri)
return AVERROR(ENOMEM);
+ if (s->cookies && (ret = bind_cookies_to_host(s)) < 0)
+ goto bail_out;
+
if (options)
av_dict_copy(&s->chained_options, *options, 0);
@@ -1259,6 +1263,49 @@ static int parse_cookie(HTTPContext *s, const char *p, const char *host,
return 0;
}
+/* Cookies without a Domain apply only to the host they are given for. */
+static int bind_cookies_to_host(HTTPContext *s)
+{
+ char host[1024], *cookies, *cookie, *next, *saveptr = NULL, *bound;
+ AVBPrint bp;
+ int ret = 0;
+
+ av_url_split(NULL, 0, NULL, 0, host, sizeof(host), NULL, NULL, 0, s->location);
+
+ next = cookies = av_strdup(s->cookies);
+ if (!cookies)
+ return AVERROR(ENOMEM);
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+ while ((cookie = av_strtok(next, "\n", &saveptr))) {
+ AVDictionary *params = NULL;
+ int len = strlen(cookie), host_only;
+
+ next = NULL;
+ while (len && strchr(WHITESPACES, cookie[len - 1]))
+ len--;
+ if ((ret = parse_set_cookie(cookie, ¶ms)) < 0) {
+ av_dict_free(¶ms);
+ break;
+ }
+ host_only = !cookie_domain(params) && !av_dict_get(params, "@hostonly", NULL, 0);
+ av_dict_free(¶ms);
+ av_bprintf(&bp, "%.*s%s%s\n", len, cookie, host_only ? "; @hostonly=" : "", host_only ? host : "");
+ }
+ av_free(cookies);
+ if (ret >= 0 && !av_bprint_is_complete(&bp))
+ ret = AVERROR(ENOMEM);
+ if (ret < 0) {
+ av_bprint_finalize(&bp, NULL);
+ return ret;
+ }
+ if ((ret = av_bprint_finalize(&bp, &bound)) < 0)
+ return ret;
+ av_free(s->cookies);
+ s->cookies = bound;
+ return 0;
+}
+
static int cookie_string(AVDictionary *dict, char **cookies)
{
const AVDictionaryEntry *e = NULL;