Skip to content

Commit 7725b81

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%40@127.0.0.1:5541`. Therefore, these string replacements document a net improvement introduced by this commit, as `user@host@127.0.0.1` could have left readers wondering where the user name ends and where the host name begins. Hinted-at-by: Jeff King <peff@peff.net> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent c903985 commit 7725b81

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
@@ -14,6 +14,12 @@ credential.useHttpPath::
1414
or https URL to be important. Defaults to false. See
1515
linkgit:gitcredentials[7] for more information.
1616

17+
credential.sanitizePrompt::
18+
By default, user names and hosts that are shown as part of the
19+
password prompt are not allowed to contain control characters (they
20+
will be URL-encoded by default). Configure this setting to `false` to
21+
override that behavior.
22+
1723
credential.username::
1824
If no username is set for a network authentication, use this username
1925
by default. See credential.<context>.* below, and

credential.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ static int credential_config_callback(const char *var, const char *value,
6767
}
6868
else if (!strcmp(key, "usehttppath"))
6969
c->use_http_path = git_config_bool(var, value);
70+
else if (!strcmp(key, "sanitizeprompt"))
71+
c->sanitize_prompt = git_config_bool(var, value);
7072

7173
return 0;
7274
}
@@ -179,7 +181,10 @@ static char *credential_ask_one(const char *what, struct credential *c,
179181
struct strbuf prompt = STRBUF_INIT;
180182
char *r;
181183

182-
credential_describe(c, &desc);
184+
if (c->sanitize_prompt)
185+
credential_format(c, &desc);
186+
else
187+
credential_describe(c, &desc);
183188
if (desc.len)
184189
strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
185190
else

credential.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ struct credential {
119119
configured:1,
120120
quit:1,
121121
use_http_path:1,
122-
username_from_proto:1;
122+
username_from_proto:1,
123+
sanitize_prompt:1;
123124

124125
char *username;
125126
char *password;
@@ -132,6 +133,7 @@ struct credential {
132133
#define CREDENTIAL_INIT { \
133134
.helpers = STRING_LIST_INIT_DUP, \
134135
.password_expiry_utc = TIME_MAX, \
136+
.sanitize_prompt = 1, \
135137
}
136138

137139
/* 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
@@ -45,6 +45,10 @@ test_expect_success 'setup helper scripts' '
4545
test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
4646
EOF
4747
48+
write_script git-credential-cntrl-in-username <<-\EOF &&
49+
printf "username=\\007latrix Lestrange\\n"
50+
EOF
51+
4852
PATH="$PWD:$PATH"
4953
'
5054

@@ -825,4 +829,20 @@ test_expect_success 'credential config with partial URLs' '
825829
test_i18ngrep "skipping credential lookup for key" stderr
826830
'
827831

832+
BEL="$(printf '\007')"
833+
834+
test_expect_success 'interactive prompt is sanitized' '
835+
check fill cntrl-in-username <<-EOF
836+
protocol=https
837+
host=example.org
838+
--
839+
protocol=https
840+
host=example.org
841+
username=${BEL}latrix Lestrange
842+
password=askpass-password
843+
--
844+
askpass: Password for ${SQ}https://%07latrix%20Lestrange@example.org${SQ}:
845+
EOF
846+
'
847+
828848
test_done

t/t5541-http-push-smart.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ test_expect_success 'push over smart http with auth' '
351351
git push "$HTTPD_URL"/auth/smart/test_repo.git &&
352352
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
353353
log -1 --format=%s >actual &&
354-
expect_askpass both user@host &&
354+
expect_askpass both user%40host &&
355355
test_cmp expect actual
356356
'
357357

@@ -363,7 +363,7 @@ test_expect_success 'push to auth-only-for-push repo' '
363363
git push "$HTTPD_URL"/auth-push/smart/test_repo.git &&
364364
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
365365
log -1 --format=%s >actual &&
366-
expect_askpass both user@host &&
366+
expect_askpass both user%40host &&
367367
test_cmp expect actual
368368
'
369369

@@ -393,7 +393,7 @@ test_expect_success 'push into half-auth-complete requires password' '
393393
git push "$HTTPD_URL/half-auth-complete/smart/half-auth.git" &&
394394
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/half-auth.git" \
395395
log -1 --format=%s >actual &&
396-
expect_askpass both user@host &&
396+
expect_askpass both user%40host &&
397397
test_cmp expect actual
398398
'
399399

t/t5550-http-fetch-dumb.sh

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ test_expect_success 'http auth can use user/pass in URL' '
9090
test_expect_success 'http auth can use just user in URL' '
9191
set_askpass wrong pass@host &&
9292
git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass &&
93-
expect_askpass pass user@host
93+
expect_askpass pass user%40host
9494
'
9595

9696
test_expect_success 'http auth can request both user and pass' '
9797
set_askpass user@host pass@host &&
9898
git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both &&
99-
expect_askpass both user@host
99+
expect_askpass both user%40host
100100
'
101101

102102
test_expect_success 'http auth respects credential helper config' '
@@ -114,14 +114,14 @@ test_expect_success 'http auth can get username from config' '
114114
test_config_global "credential.$HTTPD_URL.username" user@host &&
115115
set_askpass wrong pass@host &&
116116
git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user &&
117-
expect_askpass pass user@host
117+
expect_askpass pass user%40host
118118
'
119119

120120
test_expect_success 'configured username does not override URL' '
121121
test_config_global "credential.$HTTPD_URL.username" wrong &&
122122
set_askpass wrong pass@host &&
123123
git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 &&
124-
expect_askpass pass user@host
124+
expect_askpass pass user%40host
125125
'
126126

127127
test_expect_success 'set up repo with http submodules' '
@@ -142,7 +142,7 @@ test_expect_success 'cmdline credential config passes to submodule via clone' '
142142
set_askpass wrong pass@host &&
143143
git -c "credential.$HTTPD_URL.username=user@host" \
144144
clone --recursive super super-clone &&
145-
expect_askpass pass user@host
145+
expect_askpass pass user%40host
146146
'
147147

148148
test_expect_success 'cmdline credential config passes submodule via fetch' '
@@ -153,7 +153,7 @@ test_expect_success 'cmdline credential config passes submodule via fetch' '
153153
git -C super-clone \
154154
-c "credential.$HTTPD_URL.username=user@host" \
155155
fetch --recurse-submodules &&
156-
expect_askpass pass user@host
156+
expect_askpass pass user%40host
157157
'
158158

159159
test_expect_success 'cmdline credential config passes submodule update' '
@@ -170,7 +170,7 @@ test_expect_success 'cmdline credential config passes submodule update' '
170170
git -C super-clone \
171171
-c "credential.$HTTPD_URL.username=user@host" \
172172
submodule update &&
173-
expect_askpass pass user@host
173+
expect_askpass pass user%40host
174174
'
175175

176176
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
'
@@ -199,7 +199,7 @@ test_expect_success 'clone from auth-only-for-objects repository' '
199199
echo two >expect &&
200200
set_askpass user@host pass@host &&
201201
git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth &&
202-
expect_askpass both user@host &&
202+
expect_askpass both user%40host &&
203203
git --git-dir=half-auth log -1 --format=%s >actual &&
204204
test_cmp expect actual
205205
'
@@ -224,14 +224,14 @@ test_expect_success 'redirects send auth to new location' '
224224
set_askpass user@host pass@host &&
225225
git -c credential.useHttpPath=true \
226226
clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth &&
227-
expect_askpass both user@host auth/smart/repo.git
227+
expect_askpass both user%40host auth/smart/repo.git
228228
'
229229

230230
test_expect_success 'GIT_TRACE_CURL redacts auth details' '
231231
rm -rf redact-auth trace &&
232232
set_askpass user@host pass@host &&
233233
GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
234-
expect_askpass both user@host &&
234+
expect_askpass both user%40host &&
235235
236236
# Ensure that there is no "Basic" followed by a base64 string, but that
237237
# the auth details are redacted
@@ -243,7 +243,7 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
243243
rm -rf redact-auth trace &&
244244
set_askpass user@host pass@host &&
245245
GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace &&
246-
expect_askpass both user@host &&
246+
expect_askpass both user%40host &&
247247
248248
# Ensure that there is no "Basic" followed by a base64 string, but that
249249
# the auth details are redacted
@@ -256,7 +256,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE
256256
set_askpass user@host pass@host &&
257257
GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \
258258
git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
259-
expect_askpass both user@host &&
259+
expect_askpass both user%40host &&
260260
261261
grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace
262262
'
@@ -568,7 +568,7 @@ test_expect_success 'http auth remembers successful credentials' '
568568
# the first request prompts the user...
569569
set_askpass user@host pass@host &&
570570
git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
571-
expect_askpass both user@host &&
571+
expect_askpass both user%40host &&
572572
573573
# ...and the second one uses the stored value rather than
574574
# prompting the user.
@@ -599,7 +599,7 @@ test_expect_success 'http auth forgets bogus credentials' '
599599
# us to prompt the user again.
600600
set_askpass user@host pass@host &&
601601
git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
602-
expect_askpass both user@host
602+
expect_askpass both user%40host
603603
'
604604

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

0 commit comments

Comments
 (0)