Skip to content

Commit 22c8463

Browse files
committed
gitweb: plug various command-line option injection holes
When running Gitweb and loading a blobdiff with the "hpb" ("hash parent base") query parameter set to a valid diff-tree option, say, "--output=/tmp/pwned", Gitweb will faithfully execute "diff-tree" internally (via "sub git_blobdiff") and blindly pass in the "hpb" query parameter. In other words, visiting a URL like: http://127.0.0.1:1234/?p=<PROJECT_NAME>;a=blobdiff;f=*;hpb=--output=/tmp/pwned;hb=HEAD will result in the file "/tmp/pwned" being created. This happens as a result of gitweb executing something like: git diff-tree -r -M --output=/tmp/pwned HEAD -- , where "--output=/tmp/pwned" is substituted in as the value of "$hash_parent_base". There are various other spots in Gitweb which are too eager to pass untrusted query parameter values as command-line arguments, leading to at least the above option-injection attack, and likely many others. Since 51b4594 (parse-options: allow --end-of-options as a synonym for "--", 2019-08-06), we have the "--end-of-options" command-line flag as a standard mechanism to indicate that any further argument should not be interpreted as command-line options. Guard agains this and other option-injection attacks by placing the "--end-of-options" flag before any untrusted user-input in any place that gitweb spawns Git as a sub-process. Reported-by: Moritz Sanft <[email protected]> Signed-off-by: Taylor Blau <[email protected]>
1 parent 2462961 commit 22c8463

File tree

1 file changed

+52
-25
lines changed

1 file changed

+52
-25
lines changed

gitweb/gitweb.perl

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2722,7 +2722,7 @@ sub git_get_hash {
27222722
my $retval = undef;
27232723
$git_dir = "$projectroot/$project";
27242724
if (open my $fd, '-|', git_cmd(), 'rev-parse',
2725-
'--verify', '-q', @options, $hash) {
2725+
'--verify', '-q', @options, '--end-of-options', $hash) {
27262726
$retval = <$fd>;
27272727
chomp $retval if defined $retval;
27282728
close $fd;
@@ -2737,7 +2737,9 @@ sub git_get_hash {
27372737
sub git_get_type {
27382738
my $hash = shift;
27392739

2740-
open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
2740+
open my $fd, "-|", git_cmd(), "cat-file", '-t', '--end-of-options',
2741+
$hash
2742+
or return;
27412743
my $type = <$fd>;
27422744
close $fd or return;
27432745
chomp $type;
@@ -2885,7 +2887,8 @@ sub git_get_hash_by_path {
28852887

28862888
$path =~ s,/+$,,;
28872889

2888-
open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
2890+
open my $fd, "-|", git_cmd(), "ls-tree", "--end-of-options", $base,
2891+
"--", $path
28892892
or die_error(500, "Open git-ls-tree failed");
28902893
my $line = <$fd>;
28912894
close $fd or return undef;
@@ -2912,7 +2915,8 @@ sub git_get_path_by_hash {
29122915

29132916
local $/ = "\0";
29142917

2915-
open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
2918+
open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z',
2919+
'--end-of-options', $base
29162920
or return undef;
29172921
while (my $line = <$fd>) {
29182922
chomp $line;
@@ -3334,6 +3338,7 @@ sub git_get_last_activity {
33343338
'--format=%(committer)',
33353339
'--sort=-committerdate',
33363340
'--count=1',
3341+
'--end-of-options',
33373342
map { "refs/$_" } get_branch_refs ()) or return;
33383343
my $most_recent = <$fd>;
33393344
close $fd or return;
@@ -3390,6 +3395,7 @@ sub git_get_references {
33903395
# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
33913396
# c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
33923397
open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
3398+
"--end-of-options",
33933399
($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
33943400
or return;
33953401

@@ -3410,7 +3416,8 @@ sub git_get_references {
34103416
sub git_get_rev_name_tags {
34113417
my $hash = shift || return undef;
34123418

3413-
open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
3419+
open my $fd, "-|", git_cmd(), "name-rev", "--tags", "--end-of-options",
3420+
$hash
34143421
or return;
34153422
my $name_rev = <$fd>;
34163423
close $fd;
@@ -3472,7 +3479,9 @@ sub parse_tag {
34723479
my %tag;
34733480
my @comment;
34743481

3475-
open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
3482+
open my $fd, "-|", git_cmd(), "cat-file", "tag", "--end-of-options",
3483+
$tag_id
3484+
or return;
34763485
$tag{'id'} = $tag_id;
34773486
while (my $line = <$fd>) {
34783487
chomp $line;
@@ -3600,6 +3609,7 @@ sub parse_commit {
36003609
"--parents",
36013610
"--header",
36023611
"--max-count=1",
3612+
"--end-of-options",
36033613
$commit_id,
36043614
"--",
36053615
or die_error(500, "Open git-rev-list failed");
@@ -3624,6 +3634,7 @@ sub parse_commits {
36243634
("--max-count=" . $maxcount),
36253635
("--skip=" . $skip),
36263636
@extra_options,
3637+
"--end-of-options",
36273638
$commit_id,
36283639
"--",
36293640
($filename ? ($filename) : ())
@@ -3784,6 +3795,7 @@ sub git_get_heads_list {
37843795
($limit ? '--count='.($limit+1) : ()),
37853796
'--sort=-HEAD', '--sort=-committerdate',
37863797
'--format=%(objectname) %(refname) %(subject)%00%(committer)',
3798+
'--end-of-options',
37873799
@patterns
37883800
or return;
37893801
while (my $line = <$fd>) {
@@ -3998,7 +4010,8 @@ sub run_highlighter {
39984010

39994011
close $fd;
40004012
my $syntax_arg = (defined $syntax) ? "--syntax $syntax" : "--force";
4001-
open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
4013+
open $fd, quote_command(git_cmd(), "cat-file", "blob",
4014+
"--end-of-options", $hash)." | ".
40024015
quote_command($^X, '-CO', '-MEncode=decode,FB_DEFAULT', '-pse',
40034016
'$_ = decode($fe, $_, FB_DEFAULT) if !utf8::decode($_);',
40044017
'--', "-fe=$fallback_encoding")." | ".
@@ -4687,7 +4700,8 @@ sub git_get_link_target {
46874700
my $link_target;
46884701

46894702
# read link
4690-
open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
4703+
open my $fd, "-|", git_cmd(), "cat-file", "blob", "--end-of-options",
4704+
$hash
46914705
or return;
46924706
{
46934707
local $/ = undef;
@@ -6377,7 +6391,7 @@ sub git_search_files {
63776391

63786392
local $/ = "\n";
63796393
open my $fd, "-|", git_cmd(), 'grep', '-n', '-z',
6380-
$search_use_regexp ? ('-E', '-i') : '-F',
6394+
$search_use_regexp ? ('-E', '-i') : '-F', '--end-of-options',
63816395
$searchtext, $co{'tree'}
63826396
or die_error(500, "Open git-grep failed");
63836397

@@ -6768,17 +6782,18 @@ sub git_blame_common {
67686782
my $fd;
67696783
if ($format eq 'incremental') {
67706784
# get file contents (as base)
6771-
open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
6785+
open $fd, "-|", git_cmd(), 'cat-file', 'blob',
6786+
'--end-of-options', $hash
67726787
or die_error(500, "Open git-cat-file failed");
67736788
} elsif ($format eq 'data') {
67746789
# run git-blame --incremental
67756790
open $fd, "-|", git_cmd(), "blame", "--incremental",
6776-
$hash_base, "--", $file_name
6791+
"--end-of-options", $hash_base, "--", $file_name
67776792
or die_error(500, "Open git-blame --incremental failed");
67786793
} else {
67796794
# run git-blame --porcelain
67806795
open $fd, "-|", git_cmd(), "blame", '-p',
6781-
$hash_base, '--', $file_name
6796+
"--end-of-options", $hash_base, '--', $file_name
67826797
or die_error(500, "Open git-blame --porcelain failed");
67836798
}
67846799
binmode $fd, ':utf8';
@@ -7058,7 +7073,8 @@ sub git_blob_plain {
70587073
$expires = "+1d";
70597074
}
70607075

7061-
open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
7076+
open my $fd, "-|", git_cmd(), "cat-file", "blob", "--end-of-options",
7077+
$hash
70627078
or die_error(500, "Open git-cat-file blob '$hash' failed");
70637079

70647080
# content-type (can include charset)
@@ -7121,7 +7137,8 @@ sub git_blob {
71217137
}
71227138

71237139
my $have_blame = gitweb_check_feature('blame');
7124-
open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
7140+
open my $fd, "-|", git_cmd(), "cat-file", "blob", "--end-of-options",
7141+
$hash
71257142
or die_error(500, "Couldn't cat $file_name, $hash");
71267143
my $mimetype = blob_mimetype($fd, $file_name);
71277144
# use 'blob_plain' (aka 'raw') view for files that cannot be displayed
@@ -7216,7 +7233,8 @@ sub git_tree {
72167233
{
72177234
local $/ = "\0";
72187235
open my $fd, "-|", git_cmd(), "ls-tree", '-z',
7219-
($show_sizes ? '-l' : ()), @extra_options, $hash
7236+
($show_sizes ? '-l' : ()), @extra_options,
7237+
"--end-of-options", $hash
72207238
or die_error(500, "Open git-ls-tree failed");
72217239
@entries = map { chomp; $_ } <$fd>;
72227240
close $fd
@@ -7417,7 +7435,7 @@ sub git_snapshot {
74177435
my $cmd = quote_command(
74187436
git_cmd(), 'archive',
74197437
"--format=$known_snapshot_formats{$format}{'format'}",
7420-
"--prefix=$prefix/", $hash);
7438+
"--prefix=$prefix/", "--end-of-options", $hash);
74217439
if (exists $known_snapshot_formats{$format}{'compressor'}) {
74227440
$cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
74237441
}
@@ -7569,6 +7587,7 @@ sub git_commit {
75697587
open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
75707588
@diff_opts,
75717589
(@$parents <= 1 ? $parent : '-c'),
7590+
"--end-of-options",
75727591
$hash, "--"
75737592
or die_error(500, "Open git-diff-tree failed");
75747593
@difftree = map { chomp; $_ } <$fd>;
@@ -7649,7 +7668,8 @@ sub git_object {
76497668
my $object_id = $hash || $hash_base;
76507669

76517670
open my $fd, "-|", quote_command(
7652-
git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
7671+
git_cmd(), 'cat-file', '-t', '--end-of-options',
7672+
$object_id) . ' 2> /dev/null'
76537673
or die_error(404, "Object does not exist");
76547674
$type = <$fd>;
76557675
defined $type && chomp $type;
@@ -7660,11 +7680,13 @@ sub git_object {
76607680
} elsif ($hash_base && defined $file_name) {
76617681
$file_name =~ s,/+$,,;
76627682

7663-
system(git_cmd(), "cat-file", '-e', $hash_base) == 0
7683+
system(git_cmd(), "cat-file", '-e', '--end-of-options',
7684+
$hash_base) == 0
76647685
or die_error(404, "Base object does not exist");
76657686

76667687
# here errors should not happen
7667-
open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
7688+
open my $fd, "-|", git_cmd(), "ls-tree", "--end-of-options",
7689+
$hash_base, "--", $file_name
76687690
or die_error(500, "Open git-ls-tree failed");
76697691
my $line = <$fd>;
76707692
close $fd;
@@ -7700,7 +7722,7 @@ sub git_blobdiff {
77007722
if (defined $file_name) {
77017723
# read raw output
77027724
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
7703-
$hash_parent_base, $hash_base,
7725+
"--end-of-options", $hash_parent_base, $hash_base,
77047726
"--", (defined $file_parent ? $file_parent : ()), $file_name
77057727
or die_error(500, "Open git-diff-tree failed");
77067728
@difftree = map { chomp; $_ } <$fd>;
@@ -7715,7 +7737,8 @@ sub git_blobdiff {
77157737

77167738
# read filtered raw output
77177739
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
7718-
$hash_parent_base, $hash_base, "--"
7740+
"--end-of-options", $hash_parent_base, $hash_base,
7741+
"--"
77197742
or die_error(500, "Open git-diff-tree failed");
77207743
@difftree =
77217744
# ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
@@ -7751,6 +7774,7 @@ sub git_blobdiff {
77517774
# open patch output
77527775
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
77537776
'-p', ($format eq 'html' ? "--full-index" : ()),
7777+
"--end-of-options",
77547778
$hash_parent_base, $hash_base,
77557779
"--", (defined $file_parent ? $file_parent : ()), $file_name
77567780
or die_error(500, "Open git-diff-tree failed");
@@ -7945,7 +7969,7 @@ sub git_commitdiff {
79457969
if ($format eq 'html') {
79467970
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
79477971
"--no-commit-id", "--patch-with-raw", "--full-index",
7948-
$hash_parent_param, $hash, "--"
7972+
"--end-of-options", $hash_parent_param, $hash, "--",
79497973
or die_error(500, "Open git-diff-tree failed");
79507974

79517975
while (my $line = <$fd>) {
@@ -7957,7 +7981,8 @@ sub git_commitdiff {
79577981

79587982
} elsif ($format eq 'plain') {
79597983
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
7960-
'-p', $hash_parent_param, $hash, "--"
7984+
'-p', '--end-of-options', $hash_parent_param, $hash,
7985+
"--"
79617986
or die_error(500, "Open git-diff-tree failed");
79627987
} elsif ($format eq 'patch') {
79637988
# For commit ranges, we limit the output to the number of
@@ -7982,7 +8007,8 @@ sub git_commitdiff {
79828007
push @commit_spec, '--root', $hash;
79838008
}
79848009
open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
7985-
'--encoding=utf8', '--stdout', @commit_spec
8010+
'--encoding=utf8', '--stdout', '--end-of-options',
8011+
@commit_spec
79868012
or die_error(500, "Open git-format-patch failed");
79878013
} else {
79888014
die_error(400, "Unknown commitdiff format");
@@ -8334,7 +8360,8 @@ sub git_feed {
83348360
# get list of changed files
83358361
open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
83368362
$co{'parent'} || "--root",
8337-
$co{'id'}, "--", (defined $file_name ? $file_name : ())
8363+
$co{'id'}, "--end-of-options", "--",
8364+
(defined $file_name ? $file_name : ())
83388365
or next;
83398366
my @difftree = map { chomp; $_ } <$fd>;
83408367
close $fd

0 commit comments

Comments
 (0)