diff options
context:
space:
mode:
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Pin the advisory classes with tests and name an empty author
Each class behind cgit's published advisories now has a check against the current code, from a newline in a file name to shell syntax handed to a filter. The feed also treated an ident with an empty name and an empty address as present and wrote an empty person, which Atom forbids.
Diffstat (limited to 'tests/t0301-security.sh')
-rwxr-xr-xtests/t0301-security.sh110
1 file changed, 110 insertions, 0 deletions
diff --git a/tests/t0301-security.sh b/tests/t0301-security.sh
index f1af8e9..1a5ee44 100755
--- a/tests/t0301-security.sh
+++ b/tests/t0301-security.sh
@@ -373,4 +373,114 @@ test_expect_success 'a quote in a web url cannot break out of the href' '
grep "href=.https://example.com/x&#x27;&gt;&lt;script&gt;" tmp
'
+# The classes behind cgit's published advisories, each pinned against the
+# current code: a newline in a file name splitting the headers, a posted
+# length overflowing its buffer, a path climbing out on the dumb transport
+# and the about page, a commit with an empty author, a percent sign with no
+# digits behind it, script in a file name shown by the diff, and shell
+# syntax in a file name handed to a filter.
+test_expect_success 'set up the advisory fixtures' '
+ mkrepo repos/cve 1 &&
+ (
+ cd repos/cve &&
+ printf "x\n" >"$(printf "crlf\r\nX-Injected: 1.txt")" &&
+ printf "x\n" >"<script>alert(1)x.txt" &&
+ printf "x\n" >"\$(touch pwned).c" &&
+ git add -A &&
+ git commit -m names &&
+ tree=$(git rev-parse HEAD^{tree}) &&
+ printf "tree %s\nparent %s\n" "$tree" "$(git rev-parse HEAD)" >raw &&
+ printf "author <> 1735689600 +0000\ncommitter <> 1735689600 +0000\n\nempty author\n" >>raw &&
+ cid=$(git hash-object --literally -t commit -w raw) &&
+ git update-ref refs/heads/noname "$cid"
+ ) &&
+ mkdir -p readme &&
+ printf "readme text\n" >readme/README &&
+ cat >auth.sh <<-\EOF &&
+ #!/bin/sh
+ case "$1" in
+ authenticate-post)
+ cat >/dev/null
+ printf "Status: 302 Found\nLocation: /\n\n"
+ ;;
+ esac
+ exit 1
+ EOF
+ chmod +x auth.sh &&
+ {
+ echo "virtual-root=/" &&
+ echo "cache-size=0" &&
+ echo "auth-filter=exec:$PWD/auth.sh" &&
+ echo "repo.url=cve" &&
+ echo "repo.path=$PWD/repos/cve/.git" &&
+ echo "repo.readme=$PWD/readme/README" &&
+ echo "repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh"
+ } >cverc
+'
+
+cveq() { CGIT_CONFIG="$PWD/cverc" QUERY_STRING="$1" cgit; }
+
+test_expect_success 'a newline in a file name cannot split the headers' '
+ cveq "url=cve/plain/crlf%0d%0aX-Injected:%201.txt" >tmp &&
+ grep "^Status: 200" tmp &&
+ ! grep "^X-Injected" tmp &&
+ grep "^Content-Disposition: inline; filename=.crlf\\\\r\\\\nX-Injected: 1.txt.$" tmp
+'
+
+test_expect_success 'a posted length past any buffer is clamped' '
+ echo "username=a&password=b" |
+ CGIT_CONFIG="$PWD/cverc" REQUEST_METHOD=POST CONTENT_LENGTH=99999999999999 \
+ QUERY_STRING="p=login" cgit >tmp &&
+ grep "^Status: 302" tmp
+'
+
+test_expect_success 'the dumb transport refuses a path that climbs out' '
+ cveq "url=cve/objects/../config" >tmp &&
+ grep "^Status: 400" tmp &&
+ ! grep "repositoryformatversion" tmp &&
+ cveq "url=cve/objects/info/../../config" >tmp &&
+ grep "^Status: 400" tmp &&
+ ! grep "repositoryformatversion" tmp
+'
+
+test_expect_success 'the about page serves nothing above the readme directory' '
+ cveq "url=cve/about/../cverc" >tmp &&
+ grep "^Status: 200" tmp &&
+ ! grep "virtual-root" tmp &&
+ cveq "url=cve/about/" >tmp &&
+ grep "readme text" tmp
+'
+
+test_expect_success 'a commit with an empty author renders everywhere' '
+ cveq "url=cve/log/&h=noname" >tmp &&
+ grep ">empty author</a>" tmp &&
+ cveq "url=cve/commit/&h=noname" >tmp &&
+ grep "<div class=.commit-subject.>empty author<" tmp &&
+ cveq "url=cve/atom/&h=noname" >tmp &&
+ grep "<name>unknown</name>" tmp &&
+ ! grep "<email></email>" tmp
+'
+
+test_expect_success 'a percent sign without digits behind it is read as itself' '
+ cveq "url=cve/log/&q=%" >tmp &&
+ grep "^Status: 200" tmp &&
+ cveq "url=cve/log/&q=%zz%2" >tmp &&
+ grep "^Status: 200" tmp &&
+ grep "value=.%zz%2." tmp
+'
+
+test_expect_success 'script in a file name is escaped on the diff page' '
+ cveq "url=cve/diff/" >tmp &&
+ ! grep "<script>alert" tmp &&
+ grep "&lt;script&gt;alert(1)x.txt" tmp
+'
+
+test_expect_success 'shell syntax in a file name reaches a filter as a plain argument' '
+ cveq "url=cve/tree/%24(touch%20pwned).c" >tmp &&
+ grep "^Status: 200" tmp &&
+ grep "pwned).c" tmp &&
+ ! test -e pwned &&
+ ! test -e repos/cve/pwned
+'
+
test_done