mirror of
https://github.com/nvm-sh/nvm.git
synced 2026-09-22 00:00:08 +08:00
[Fix] nvm_alias, nvm_version_path: reject .. path components
`nvm_alias` concatenated the requested name onto `$NVM_DIR/alias` and read whatever that resolved to,
so a name with a `..` component escaped the alias dir
- under the default layout, `../../.npmrc` reaches `$HOME/.npmrc`.
`nvm_print_alias_file` echoes every non-comment line of what it opens,
and `nvm use` reports the first one in its "is not yet installed" error,
so an untrusted `.nvmrc` could disclose any file the invoking user can read.
The write side already rejected `..` in an alias name (9275c5ba);
apply the same check on the read side, and to `nvm_version_path`,
which composes a version into a path with no check of its own.
Slashes stay legal, since `lts/iron` and the `lts/*` alias file depend on them.
This commit is contained in:
@@ -800,6 +800,12 @@ nvm_alias_path() {
|
||||
nvm_version_path() {
|
||||
local VERSION
|
||||
VERSION="${1-}"
|
||||
case "/${VERSION}/" in
|
||||
*/../*)
|
||||
nvm_err "invalid version: ${VERSION}"
|
||||
return 3
|
||||
;;
|
||||
esac
|
||||
if [ -z "${VERSION}" ]; then
|
||||
nvm_err 'version is required'
|
||||
return 3
|
||||
@@ -1498,6 +1504,15 @@ nvm_alias() {
|
||||
return 2
|
||||
fi
|
||||
|
||||
# slashes are legal (eg `lts/iron`), but a `..` component would read outside
|
||||
# the alias dir; `nvm_make_alias` rejects the same shape on the write side
|
||||
case "/${ALIAS}/" in
|
||||
*/../*)
|
||||
nvm_err "invalid alias name: ${ALIAS}"
|
||||
return 3
|
||||
;;
|
||||
esac
|
||||
|
||||
local NVM_ALIAS_PATH
|
||||
NVM_ALIAS_PATH="$(nvm_alias_path)/${ALIAS}"
|
||||
if [ ! -f "${NVM_ALIAS_PATH}" ]; then
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
|
||||
ORIG_NVM_DIR="${NVM_DIR}"
|
||||
TMP_DIR=''
|
||||
|
||||
cleanup () {
|
||||
case "${TMP_DIR}" in
|
||||
/*) rm -rf "${TMP_DIR}" ;;
|
||||
esac
|
||||
NVM_DIR="${ORIG_NVM_DIR}"
|
||||
export NVM_DIR
|
||||
}
|
||||
|
||||
die () { echo "$@" ; cleanup ; exit 1; }
|
||||
|
||||
: nvm.sh
|
||||
\. ../../../nvm.sh
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
NVM_DIR="${TMP_DIR}/nvm"
|
||||
export NVM_DIR
|
||||
mkdir -p "${NVM_DIR}/alias/lts" || die 'could not create the scratch alias dir'
|
||||
|
||||
# a file outside $NVM_DIR that alias resolution must never reach
|
||||
printf '%s\n' 'SECRET_OUTSIDE_NVM_DIR' > "${TMP_DIR}/outside"
|
||||
|
||||
# legitimate aliases, to prove the guard does not reject the names nvm uses
|
||||
printf '%s\n' 'v1.2.3' > "${NVM_DIR}/alias/default"
|
||||
printf '%s\n' 'v4.5.6' > "${NVM_DIR}/alias/lts/iron"
|
||||
|
||||
for TRAVERSAL in '../../outside' 'lts/../../../outside' '..' 'lts/..' 'a/../../outside'; do
|
||||
OUTPUT="$(nvm_alias "${TRAVERSAL}" 2>/dev/null)"
|
||||
EXIT_CODE=$?
|
||||
[ "_${EXIT_CODE}" = '_3' ] || die "'nvm_alias ${TRAVERSAL}' exited with ${EXIT_CODE}, expected 3"
|
||||
[ -z "${OUTPUT}" ] || die "'nvm_alias ${TRAVERSAL}' produced output: ${OUTPUT}"
|
||||
# capture stderr through a file, and match rather than compare: ksh 93u+ has
|
||||
# no `local`, so it interleaves its own noise with anything nvm.sh writes
|
||||
nvm_alias "${TRAVERSAL}" >/dev/null 2>"${TMP_DIR}/stderr"
|
||||
grep -qF "invalid alias name: ${TRAVERSAL}" "${TMP_DIR}/stderr" \
|
||||
|| die "'nvm_alias ${TRAVERSAL}' produced stderr: $(cat "${TMP_DIR}/stderr")"
|
||||
done
|
||||
|
||||
# the disclosure path the advisory reports: resolution must not echo the file
|
||||
for TRAVERSAL in '../../outside' 'lts/../../../outside'; do
|
||||
OUTPUT="$(nvm_resolve_alias "${TRAVERSAL}" 2>&1)"
|
||||
case "${OUTPUT}" in
|
||||
*SECRET_OUTSIDE_NVM_DIR*) die "'nvm_resolve_alias ${TRAVERSAL}' leaked a file outside \$NVM_DIR" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
OUTPUT="$(nvm_alias default)"
|
||||
[ "_${OUTPUT}" = '_v1.2.3' ] || die "'nvm_alias default' produced \"${OUTPUT}\", expected v1.2.3"
|
||||
|
||||
OUTPUT="$(nvm_alias lts/iron)"
|
||||
[ "_${OUTPUT}" = '_v4.5.6' ] || die "'nvm_alias lts/iron' produced \"${OUTPUT}\", expected v4.5.6"
|
||||
|
||||
for BAD_VERSION in '../../outside' 'v1.2.3/../../..' '..'; do
|
||||
OUTPUT="$(nvm_version_path "${BAD_VERSION}" 2>/dev/null)"
|
||||
EXIT_CODE=$?
|
||||
[ "_${EXIT_CODE}" = '_3' ] || die "'nvm_version_path ${BAD_VERSION}' exited with ${EXIT_CODE}, expected 3"
|
||||
[ -z "${OUTPUT}" ] || die "'nvm_version_path ${BAD_VERSION}' produced output: ${OUTPUT}"
|
||||
done
|
||||
|
||||
OUTPUT="$(nvm_version_path v18.0.0)"
|
||||
[ "_${OUTPUT}" = "_${NVM_DIR}/versions/node/v18.0.0" ] || die "'nvm_version_path v18.0.0' produced \"${OUTPUT}\""
|
||||
|
||||
cleanup
|
||||
Reference in New Issue
Block a user