Skip to content

Commit db58126

Browse files
committed
credential: sanitize the user prompt
When asking the user interactively for credentials, we want to avoid misleading them e.g. via control sequences that pretend that the URL targets a trusted host when it does not. While Git learned, over the course of the preceding commits, to disallow URLs containing URL-encoded control characters by default, credential helpers are still allowed to specify values very freely (apart from Line Feed and NUL characters, anything is allowed), and this would allow, say, a username containing control characters to be specified that would then be displayed in the interactive terminal prompt asking the user for the password, potentially sending those control characters directly to the terminal. This is undesirable because control characters can be used to mislead users to divulge secret information to untrusted sites. To prevent such an attack vector, let's add a `git_prompt()` that forces the displayed text to be sanitized, i.e. displaying question marks instead of control characters. Note: While this commit's diff changes a lot of `user@host` strings to `user%40host`, which may look suspicious on the surface, there is a good reason for that: this string specifies a user name, not a <username>@<hostname> combination! In the context of t5541, the actual combination looks like this: `user%[email protected]:5541`. Therefore, these string replacements document a net improvement introduced by this commit, as `user@[email protected]` could have left readers wondering where the user name ends and where the host name begins. Hinted-at-by: Jeff King <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 501d8da commit db58126

File tree

7 files changed

+53
-20
lines changed

7 files changed

+53
-20
lines changed

Documentation/config/credential.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ credential.useHttpPath::
2222
or https URL to be important. Defaults to false. See
2323
linkgit:gitcredentials[7] for more information.
2424

25+
credential.sanitizePrompt::
26+
By default, user names and hosts that are shown as part of the
27+
password prompt are not allowed to contain control characters (they
28+
will be URL-encoded by default). Configure this setting to `false` to
29+
override that behavior.
30+
2531
credential.username::
2632
If no username is set for a network authentication, use this username
2733
by default. See credential.<context>.* below, and

credential.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ static int credential_config_callback(const char *var, const char *value,
130130
}
131131
else if (!strcmp(key, "usehttppath"))
132132
c->use_http_path = git_config_bool(var, value);
133+
else if (!strcmp(key, "sanitizeprompt"))
134+
c->sanitize_prompt = git_config_bool(var, value);
133135

134136
return 0;
135137
}
@@ -242,7 +244,10 @@ static char *credential_ask_one(const char *what, struct credential *c,
242244
struct strbuf prompt = STRBUF_INIT;
243245
char *r;
244246

245-
credential_describe(c, &desc);
247+
if (c->sanitize_prompt)
248+
credential_format(c, &desc);
249+
else
250+
credential_describe(c, &desc);
246251
if (desc.len)
247252
strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
248253
else

credential.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ struct credential {
168168
multistage: 1,
169169
quit:1,
170170
use_http_path:1,
171-
username_from_proto:1;
171+
username_from_proto:1,
172+
sanitize_prompt:1;
172173

173174
struct credential_capability capa_authtype;
174175
struct credential_capability capa_state;
@@ -195,6 +196,7 @@ struct credential {
195196
.wwwauth_headers = STRVEC_INIT, \
196197
.state_headers = STRVEC_INIT, \
197198
.state_headers_to_send = STRVEC_INIT, \
199+
.sanitize_prompt = 1, \
198200
}
199201

200202
/* Initialize a credential structure, setting all fields to empty. */

t/t0300-credentials.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ test_expect_success 'setup helper scripts' '
7676
test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
7777
EOF
7878
79+
write_script git-credential-cntrl-in-username <<-\EOF &&
80+
printf "username=\\007latrix Lestrange\\n"
81+
EOF
82+
7983
PATH="$PWD:$PATH"
8084
'
8185

@@ -1007,4 +1011,20 @@ test_expect_success 'credential config with partial URLs' '
10071011
test_grep "skipping credential lookup for key" stderr
10081012
'
10091013

1014+
BEL="$(printf '\007')"
1015+
1016+
test_expect_success 'interactive prompt is sanitized' '
1017+
check fill cntrl-in-username <<-EOF
1018+
protocol=https
1019+
host=example.org
1020+
--
1021+
protocol=https
1022+
host=example.org
1023+
username=${BEL}latrix Lestrange
1024+
password=askpass-password
1025+
--
1026+
askpass: Password for ${SQ}https://%07latrix%[email protected]${SQ}:
1027+
EOF
1028+
'
1029+
10101030
test_done

t/t5541-http-push-smart.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ test_expect_success 'push over smart http with auth' '
343343
git push "$HTTPD_URL"/auth/smart/test_repo.git &&
344344
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
345345
log -1 --format=%s >actual &&
346-
expect_askpass both user@host &&
346+
expect_askpass both user%40host &&
347347
test_cmp expect actual
348348
'
349349

@@ -355,7 +355,7 @@ test_expect_success 'push to auth-only-for-push repo' '
355355
git push "$HTTPD_URL"/auth-push/smart/test_repo.git &&
356356
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
357357
log -1 --format=%s >actual &&
358-
expect_askpass both user@host &&
358+
expect_askpass both user%40host &&
359359
test_cmp expect actual
360360
'
361361

@@ -385,7 +385,7 @@ test_expect_success 'push into half-auth-complete requires password' '
385385
git push "$HTTPD_URL/half-auth-complete/smart/half-auth.git" &&
386386
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/half-auth.git" \
387387
log -1 --format=%s >actual &&
388-
expect_askpass both user@host &&
388+
expect_askpass both user%40host &&
389389
test_cmp expect actual
390390
'
391391

t/t5550-http-fetch-dumb.sh

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ test_expect_success 'http auth can use user/pass in URL' '
111111
test_expect_success 'http auth can use just user in URL' '
112112
set_askpass wrong pass@host &&
113113
git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass &&
114-
expect_askpass pass user@host
114+
expect_askpass pass user%40host
115115
'
116116

117117
test_expect_success 'http auth can request both user and pass' '
118118
set_askpass user@host pass@host &&
119119
git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both &&
120-
expect_askpass both user@host
120+
expect_askpass both user%40host
121121
'
122122

123123
test_expect_success 'http auth respects credential helper config' '
@@ -135,14 +135,14 @@ test_expect_success 'http auth can get username from config' '
135135
test_config_global "credential.$HTTPD_URL.username" user@host &&
136136
set_askpass wrong pass@host &&
137137
git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user &&
138-
expect_askpass pass user@host
138+
expect_askpass pass user%40host
139139
'
140140

141141
test_expect_success 'configured username does not override URL' '
142142
test_config_global "credential.$HTTPD_URL.username" wrong &&
143143
set_askpass wrong pass@host &&
144144
git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 &&
145-
expect_askpass pass user@host
145+
expect_askpass pass user%40host
146146
'
147147

148148
test_expect_success 'set up repo with http submodules' '
@@ -163,7 +163,7 @@ test_expect_success 'cmdline credential config passes to submodule via clone' '
163163
set_askpass wrong pass@host &&
164164
git -c "credential.$HTTPD_URL.username=user@host" \
165165
clone --recursive super super-clone &&
166-
expect_askpass pass user@host
166+
expect_askpass pass user%40host
167167
'
168168

169169
test_expect_success 'cmdline credential config passes submodule via fetch' '
@@ -174,7 +174,7 @@ test_expect_success 'cmdline credential config passes submodule via fetch' '
174174
git -C super-clone \
175175
-c "credential.$HTTPD_URL.username=user@host" \
176176
fetch --recurse-submodules &&
177-
expect_askpass pass user@host
177+
expect_askpass pass user%40host
178178
'
179179

180180
test_expect_success 'cmdline credential config passes submodule update' '
@@ -191,7 +191,7 @@ test_expect_success 'cmdline credential config passes submodule update' '
191191
git -C super-clone \
192192
-c "credential.$HTTPD_URL.username=user@host" \
193193
submodule update &&
194-
expect_askpass pass user@host
194+
expect_askpass pass user%40host
195195
'
196196

197197
test_expect_success 'fetch changes via http' '

t/t5551-http-fetch-smart.sh

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ test_expect_success 'clone from password-protected repository' '
181181
echo two >expect &&
182182
set_askpass user@host pass@host &&
183183
git clone --bare "$HTTPD_URL/auth/smart/repo.git" smart-auth &&
184-
expect_askpass both user@host &&
184+
expect_askpass both user%40host &&
185185
git --git-dir=smart-auth log -1 --format=%s >actual &&
186186
test_cmp expect actual
187187
'
@@ -221,7 +221,7 @@ test_expect_success 'clone from auth-only-for-objects repository' '
221221
echo two >expect &&
222222
set_askpass user@host pass@host &&
223223
git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth &&
224-
expect_askpass both user@host &&
224+
expect_askpass both user%40host &&
225225
git --git-dir=half-auth log -1 --format=%s >actual &&
226226
test_cmp expect actual
227227
'
@@ -246,14 +246,14 @@ test_expect_success 'redirects send auth to new location' '
246246
set_askpass user@host pass@host &&
247247
git -c credential.useHttpPath=true \
248248
clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth &&
249-
expect_askpass both user@host auth/smart/repo.git
249+
expect_askpass both user%40host auth/smart/repo.git
250250
'
251251

252252
test_expect_success 'GIT_TRACE_CURL redacts auth details' '
253253
rm -rf redact-auth trace &&
254254
set_askpass user@host pass@host &&
255255
GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
256-
expect_askpass both user@host &&
256+
expect_askpass both user%40host &&
257257
258258
# Ensure that there is no "Basic" followed by a base64 string, but that
259259
# the auth details are redacted
@@ -265,7 +265,7 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
265265
rm -rf redact-auth trace &&
266266
set_askpass user@host pass@host &&
267267
GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace &&
268-
expect_askpass both user@host &&
268+
expect_askpass both user%40host &&
269269
270270
# Ensure that there is no "Basic" followed by a base64 string, but that
271271
# the auth details are redacted
@@ -278,7 +278,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE
278278
set_askpass user@host pass@host &&
279279
GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \
280280
git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
281-
expect_askpass both user@host &&
281+
expect_askpass both user%40host &&
282282
283283
grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace
284284
'
@@ -592,7 +592,7 @@ test_expect_success 'http auth remembers successful credentials' '
592592
# the first request prompts the user...
593593
set_askpass user@host pass@host &&
594594
git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
595-
expect_askpass both user@host &&
595+
expect_askpass both user%40host &&
596596
597597
# ...and the second one uses the stored value rather than
598598
# prompting the user.
@@ -623,7 +623,7 @@ test_expect_success 'http auth forgets bogus credentials' '
623623
# us to prompt the user again.
624624
set_askpass user@host pass@host &&
625625
git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
626-
expect_askpass both user@host
626+
expect_askpass both user%40host
627627
'
628628

629629
test_expect_success 'client falls back from v2 to v0 to match server' '

0 commit comments

Comments
 (0)