commit bea337786b3132773ae0963262162ffd54b785b8
Author: Aleksander Bułanowski <abulanowski@google.com>
Date:   Tue Sep 7 14:50:16 2021 +0200

    Remove 'Authentication' headers on redirects
    
    This fixes CVE-2021-31879 / bug 56909.
    
    * Added `--keep-auth-header` flag to preserve old behavior.
    * Added test for testing new behavior.
    * Added test for testing old behavior (with flag).

diff --git a/src/http.c b/src/http.c
index 373a5eb6..0787f0d2 100644
--- a/src/http.c
+++ b/src/http.c
@@ -3896,6 +3896,17 @@ gethttp (const struct url *u, struct url *original_url, struct http_stat *hs,
                 CLOSE_INVALIDATE (sock);
             }
 
+          /* Unless user specifies that explictly, we should not proliferate
+           * their Authentication header to other servers.  */
+          if (!opt.keep_auth_header && opt.user_headers)
+            {
+              int i;
+              for (i = 0; opt.user_headers[i]; i++)
+                if (strncmp("Authentication:", opt.user_headers[i],
+                            strlen("Authentication:")) == 0)
+                  opt.user_headers[i] = "";
+            }
+
           /* From RFC2616: The status codes 303 and 307 have
              been added for servers that wish to make unambiguously
              clear which kind of reaction is expected of the client.
diff --git a/src/init.c b/src/init.c
index 935584a0..cffa7c4d 100644
--- a/src/init.c
+++ b/src/init.c
@@ -247,6 +247,7 @@ static const struct {
   { "inputmetalink",    &opt.input_metalink,    cmd_file },
 #endif
   { "iri",              &opt.enable_iri,        cmd_boolean },
+  { "keepauthheader",   &opt.keep_auth_header,  cmd_boolean },
   { "keepbadhash",      &opt.keep_badhash,      cmd_boolean },
   { "keepsessioncookies", &opt.keep_session_cookies, cmd_boolean },
   { "limitrate",        &opt.limit_rate,        cmd_bytes },
diff --git a/src/main.c b/src/main.c
index 7c27b0c3..5e0b3739 100644
--- a/src/main.c
+++ b/src/main.c
@@ -358,6 +358,7 @@ static struct cmdline_option option_data[] =
     { "input-metalink", 0, OPT_VALUE, "inputmetalink", -1 },
 #endif
     { "iri", 0, OPT_BOOLEAN, "iri", -1 },
+    { "keep-auth-header", 0, OPT_BOOLEAN, "keepauthheader", -1 },
     { "keep-badhash", 0, OPT_BOOLEAN, "keepbadhash", -1 },
     { "keep-session-cookies", 0, OPT_BOOLEAN, "keepsessioncookies", -1 },
     { "level", 'l', OPT_VALUE, "reclevel", -1 },
@@ -839,6 +840,8 @@ HTTP options:\n"),
        --auth-no-challenge         send Basic HTTP authentication information\n\
                                      without first waiting for the server's\n\
                                      challenge\n"),
+    N_("\
+       --keep-auth-header          preserve authentication header when being redirected\n"),
     "\n",
 
 #ifdef HAVE_SSL
diff --git a/src/options.h b/src/options.h
index a0e9c60d..105eb76f 100644
--- a/src/options.h
+++ b/src/options.h
@@ -346,6 +346,8 @@ struct options
 
   const char *homedir;          /* the homedir of the running process */
   const char *wgetrcfile;       /* the wgetrc file to be loaded */
+
+  bool keep_auth_header;        /* Preserve authentication headers when redirecting */
 };
 
 extern struct options opt;
diff --git a/testenv/Makefile.am b/testenv/Makefile.am
index 6e3362f6..69402607 100644
--- a/testenv/Makefile.am
+++ b/testenv/Makefile.am
@@ -67,6 +67,8 @@ DEFAULT_TESTS = \
   Test-recursive-redirect.py                      \
   Test-redirect.py                                \
   Test-redirect-crash.py                          \
+  Test-redirect-keep-auth.py                      \
+  Test-redirect-strip-auth.py                     \
   Test--rejected-log.py                           \
   Test-reserved-chars.py                          \
   Test--spider-r.py                               \
diff --git a/testenv/Test-redirect-keep-auth.py b/testenv/Test-redirect-keep-auth.py
new file mode 100644
index 00000000..874814ae
--- /dev/null
+++ b/testenv/Test-redirect-keep-auth.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+from sys import exit
+from test.http_test import HTTPTest
+from test.base_test import HTTP, HTTPS
+from misc.wget_file import WgetFile
+
+"""
+    This test ensures Authentication headers are kept on redirects,
+    if the --keep-auth-header flag is provided.
+"""
+############# File Definitions ###############################################
+File2 = "Would you like some Tea?"
+
+File1_rules = {
+    "ExpectHeader"      :  {
+        "Authentication" : "Some authentication"
+    },
+    "Response"          : 301,
+    "SendHeader"        : {"Location" : "/File2.txt"}
+}
+
+File2_rules = {
+    "ExpectHeader"      :  {
+        "Authentication" : "Some authentication"
+    }
+}
+
+# /File1.txt is only a redirect, and so has no file content.
+File1_File = WgetFile ("File1.txt", "", rules=File1_rules)
+# File1_Retrieved is what will be retrieved for URL /File1.txt.
+File1_Retrieved = WgetFile ("File1.txt", File2)
+File2_File = WgetFile ("File2.txt", File2, rules=File2_rules)
+
+WGET_OPTIONS = "--header='Authentication: Some authentication' --keep-auth-header"
+WGET_URLS = [["File1.txt"]]
+
+Servers = [HTTP]
+
+Files = [[File1_File, File2_File]]
+Existing_Files = []
+
+ExpectedReturnCode = 0
+ExpectedDownloadedFiles = [File1_Retrieved]
+
+################ Pre and Post Test Hooks #####################################
+pre_test = {
+    "ServerFiles"       : Files,
+    "LocalFiles"        : Existing_Files
+}
+test_options = {
+    "WgetCommands"      : WGET_OPTIONS,
+    "Urls"              : WGET_URLS
+}
+post_test = {
+    "ExpectedFiles"     : ExpectedDownloadedFiles,
+    "ExpectedRetcode"   : ExpectedReturnCode
+}
+
+err = HTTPTest (
+                pre_hook=pre_test,
+                test_params=test_options,
+                post_hook=post_test,
+                protocols=Servers
+).begin ()
+
+exit (err)
diff --git a/testenv/Test-redirect-strip-auth.py b/testenv/Test-redirect-strip-auth.py
new file mode 100755
index 00000000..27db6de6
--- /dev/null
+++ b/testenv/Test-redirect-strip-auth.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+from sys import exit
+from test.http_test import HTTPTest
+from test.base_test import HTTP, HTTPS
+from misc.wget_file import WgetFile
+
+"""
+    This test ensures Authentication headers are stripped on redirects.
+"""
+############# File Definitions ###############################################
+File2 = "Would you like some Tea?"
+
+File1_rules = {
+    "ExpectHeader"      :  {
+        "Authentication" : "Some authentication"
+    },
+    "Response"          : 301,
+    "SendHeader"        : {"Location" : "/File2.txt"}
+}
+
+File2_rules = {
+    "RejectHeader"      :  {
+        "Authentication" : "Some authentication"
+    }
+}
+
+# /File1.txt is only a redirect, and so has no file content.
+File1_File = WgetFile ("File1.txt", "", rules=File1_rules)
+# File1_Retrieved is what will be retrieved for URL /File1.txt.
+File1_Retrieved = WgetFile ("File1.txt", File2)
+File2_File = WgetFile ("File2.txt", File2, rules=File2_rules)
+
+WGET_OPTIONS = "--header='Authentication: Some authentication'"
+WGET_URLS = [["File1.txt"]]
+
+Servers = [HTTP]
+
+Files = [[File1_File, File2_File]]
+Existing_Files = []
+
+ExpectedReturnCode = 0
+ExpectedDownloadedFiles = [File1_Retrieved]
+
+################ Pre and Post Test Hooks #####################################
+pre_test = {
+    "ServerFiles"       : Files,
+    "LocalFiles"        : Existing_Files
+}
+test_options = {
+    "WgetCommands"      : WGET_OPTIONS,
+    "Urls"              : WGET_URLS
+}
+post_test = {
+    "ExpectedFiles"     : ExpectedDownloadedFiles,
+    "ExpectedRetcode"   : ExpectedReturnCode
+}
+
+err = HTTPTest (
+                pre_hook=pre_test,
+                test_params=test_options,
+                post_hook=post_test,
+                protocols=Servers
+).begin ()
+
+exit (err)
