Проверка целостности корня с помощью скрипта
Ниже приведен мой скрипт для проверки целостности корневого пути, чтобы убедиться, что в переменной PATH нет уязвимости.
#! /bin/bash
if [ ""`echo $PATH | /bin/grep :: `"" != """" ]; then
echo "Empty Directory in PATH (::)"
fi
if [ ""`echo $PATH | /bin/grep :$`"" != """" ]; then echo ""Trailing : in PATH""
fi
p=`echo $PATH | /bin/sed -e 's/::/:/' -e 's/:$//' -e 's/:/ /g'`
set -- $p
while [ ""$1"" != """" ]; do
if [ ""$1"" = ""."" ]; then
echo ""PATH contains ."" shift
continue
fi
if [ -d $1 ]; then
dirperm=`/bin/ls -ldH $1 | /bin/cut -f1 -d"" ""`
if [ `echo $dirperm | /bin/cut -c6 ` != ""-"" ]; then
echo ""Group Write permission set on directory $1""
fi
if [ `echo $dirperm | /bin/cut -c9 ` != ""-"" ]; then
echo ""Other Write permission set on directory $1""
fi
dirown=`ls -ldH $1 | awk '{print $3}'`
if [ ""$dirown"" != ""root"" ] ; then
echo $1 is not owned by root
fi
else
echo $1 is not a directory
fi
shift
done
Скрипт отлично работает для меня и показывает все уязвимые пути, определенные в переменной PATH. Я также хочу автоматизировать процесс правильной установки переменной PATH на основе приведенного выше результата. Любой быстрый способ сделать это.
Например, в моем окне Linux скрипт выдает вывод в виде:
/usr/bin/X11 is not a directory
/root/bin is not a directory
В то время как моя переменная пути имеет эти определенные, и поэтому я хочу чтобы иметь механизм удаления, удалите их из переменной PATH корня. много длинных идей приходит в голову. Но поиск быстрого и "не столь сложного" метода, пожалуйста.
4 ответов:
Не обижайтесь, но ваш код полностью нарушен. Вы используете цитаты ... творчески, но совершенно неправильно. К сожалению, Ваш код подвержен расширению пути и разделению слов. И это действительно позор, чтобы иметь небезопасный код для "защиты" вашего
PATH.Одна стратегия заключается в том, чтобы (безопасно!) разбейте переменную
PATHна массив и сканируйте каждую запись. Расщепление производится следующим образом:IFS=: read -r -d '' -a path_ary < <(printf '%s:\0' "$PATH")Смотрите мой макет
whichи Как разделить строку на разделитель отвечает.С помощью этой команды вы получите хороший массив
path_ary, содержащий все поляPATH.Затем вы можете проверить, есть ли там пустое поле, или поле
., или относительный путь:for ((i=0;i<${#path_ary[@]};++i)); do if [[ ${path_ary[i]} = ?(.) ]]; then printf 'Warning: the entry %d contains the current dir\n' "$i" elif [[ ${path_ary[i]} != /* ]]; then printf 'Warning: the entry %s is not an absolute path\n' "$i" fi doneВы можете добавить еще
elif, например, чтобы проверить, не является ли запись допустимым каталогом:Теперь, чтобы проверить разрешение и право собственности, к сожалению, нет ни чистых способов Bash, ни портативных способов продолжения. Но разбор Весьма вероятно, что это не очень хорошая идея.elif [[ ! -d ${path_ary[i]} ]]; then printf 'Warning: the entry %s is not a directory\n' "$i"statможет работать, но, как известно, имеет различное поведение на разных платформах. Так что вам придется поэкспериментировать с тем, что вам подходит. Вот пример, который работает с GNUstatв Linux:read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}")Вы захотите проверить, что
owner_idявляется0(обратите внимание, что это нормально иметь путь dir, который не принадлежит root; например, у меня есть/home/gniourf/bin, и это нормально!).permsнаходится в восьмеричном, и вы можете легко проверить дляg+wилиo+wс битом тесты:Обратите внимание на использованиеelif [[ $owner_id != 0 ]]; then printf 'Warning: the entry %s is not owned by root\n' "$i" elif ((0022&8#$perms)); then printf 'Warning: the entry %s has group or other write permission\n' "$i"8#$perms, чтобы заставить Баша пониматьpermsкак восьмеричное число. Теперь, чтобы удалить их, вы можетеunset path_ary[i]при срабатывании одного из этих тестов, а затем поместить все оставшиеся обратно вPATH:else # In the else statement, the corresponding entry is good unset_it=false fi if $unset_it; then printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}" unset path_ary[i] fiКонечно, у вас будет
unset_it=trueв качестве первой инструкции цикла.И вернуть все обратно в
PATH:Я знаю, что некоторые будут громко кричать, что это зло, но это каноническое (и безопасное!) путь к соедините элементы массива в Bash (обратите внимание на одинарные кавычки).IFS=: eval 'PATH="${path_ary[*]}"'Наконец, соответствующая функция может выглядеть следующим образом:
clean_path() { local path_ary perms owner_id unset_it IFS=: read -r -d '' -a path_ary < <(printf '%s:\0' "$PATH") for ((i=0;i<${#path_ary[@]};++i)); do unset_it=true read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}" 2>/dev/null) if [[ ${path_ary[i]} = ?(.) ]]; then printf 'Warning: the entry %d contains the current dir\n' "$i" elif [[ ${path_ary[i]} != /* ]]; then printf 'Warning: the entry %s is not an absolute path\n' "$i" elif [[ ! -d ${path_ary[i]} ]]; then printf 'Warning: the entry %s is not a directory\n' "$i" elif [[ $owner_id != 0 ]]; then printf 'Warning: the entry %s is not owned by root\n' "$i" elif ((0022 & 8#$perms)); then printf 'Warning: the entry %s has group or other write permission\n' "$i" else # In the else statement, the corresponding entry is good unset_it=false fi if $unset_it; then printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}" unset path_ary[i] fi done IFS=: eval 'PATH="${path_ary[*]}"' }Эта конструкция с
if/elif/.../else/fiхороша для этой простой задачи, но может стать неудобной для использования для более сложных тестов. Например, обратите внимание, что мы должны были вызватьstatзаранее перед тестами, чтобы информация была доступна позже в тестах, прежде чем мы даже проверили, что имеем дело с каталогом.Конструкция может быть изменена с помощью своего рода спагетти ужасны следующим образом:
for ((oneblock=1;oneblock--;)); do # This block is only executed once # You can exit this block with break at any moment doneОбычно гораздо лучше использовать функцию вместо этого и
returnиз функции. Но поскольку в следующем я также собираюсь проверить наличие нескольких записей, мне понадобится таблица подстановки (ассоциативный массив), и странно иметь независимую функцию, которая использует ассоциативный массив, определенный где-то еще...clean_path() { local path_ary perms owner_id unset_it oneblock local -A lookup IFS=: read -r -d '' -a path_ary < <(printf '%s:\0' "$PATH") for ((i=0;i<${#path_ary[@]};++i)); do unset_it=true for ((oneblock=1;oneblock--;)); do if [[ ${path_ary[i]} = ?(.) ]]; then printf 'Warning: the entry %d contains the current dir\n' "$i" break elif [[ ${path_ary[i]} != /* ]]; then printf 'Warning: the entry %s is not an absolute path\n' "$i" break elif [[ ! -d ${path_ary[i]} ]]; then printf 'Warning: the entry %s is not a directory\n' "$i" break elif [[ ${lookup[${path_ary[i]}]} ]]; then printf 'Warning: the entry %s appears multiple times\n' "$i" break fi # Here I'm sure I'm dealing with a directory read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}") if [[ $owner_id != 0 ]]; then printf 'Warning: the entry %s is not owned by root\n' "$i" break elif ((0022 & 8#$perms)); then printf 'Warning: the entry %s has group or other write permission\n' "$i" break fi # All tests passed, will keep it lookup[${path_ary[i]}]=1 unset_it=false done if $unset_it; then printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}" unset path_ary[i] fi done IFS=: eval 'PATH="${path_ary[*]}"' }Все это действительно безопасно в отношении пробелов и символов Глоб и новых строк внутри
PATH; только что мне действительно не нравится, так это использование внешней (и непереносимой) командыstat.
Я бы порекомендовал вам получить хорошую книгу о скриптах bash shell. Похоже, что вы научились Bash, глядя на 30-летние системные сценарии оболочки и взламывая их. В этом нет ничего страшного. На самом деле, он проявляет инициативу и большие логические способности. К сожалению, это приводит вас к очень плохому коду.
Если утверждения
В оригинальной оболочке Борна
[была команда. Фактически,/bin/[было жесткой связью с/bin/test. Командаtestбыла способом проверить некоторые аспекты файл. Например,test -e $fileвернет a0, Если$fileявляется исполняемым, и A1, Если это не так.
ifпросто берет команду после нее и запускает предложениеthen, если эта команда возвращает нулевой код выхода, или предложениеelse(если оно существует), если код выхода не равен нулю.Это одно и то же:
Важная идея состоит в том, чтоif test -e $file then echo "$file is executable" fi if [ -e $file ] then echo "$file is executable" fi[- это просто системная команда. Они вам не нужны сif:if grep -q "foo" $file then echo "Found 'foo' in $file" fiОбратите внимание, что я просто запустите
grep, и еслиgrepбудет успешным,я повторяю свое утверждение. Никакие[ ... ]не нужны.A shortcut to the
ifis to use the list operators&&и||. Например:Grep-q " foo "$file & & echo "я нашел 'foo' в $file "
- это то же самое, что и приведенное выше утверждение
if.Никогда не разбирайте
lsНикогда не следует анализировать команду
ls. Вместо этого следует использоватьstat.statполучает вас всех информация в команде, но в легко разбираемом виде.Как я уже упоминал ранее, в оригинальной оболочке Bourne,
[ ... ]против[[ ... ]][была системная команда. В Корншелле это была внутренняя команда, и Баш тоже ее выполнял.Проблема с
[ ... ]заключается в том, что оболочка сначала интерполирует команду до выполнения теста. Таким образом, он был уязвим для всех видов проблем оболочки. Корншелл ввел[[ ... ]]в качестве альтернативы И Баш тоже этим пользуется.
[[ ... ]]позволяет Kornshell и Bash оценить аргументыдо того, как оболочка интерполирует команду. Например:foo="this is a test" bar="test this is" [ $foo = $bar ] && echo "'$foo' and '$bar' are equal." [[ $foo = $bar ]] && echo "'$foo' and '$bar' are equal."В тесте
[ ... ]оболочка интерполируется первой, что означает, что она становится[ this is a test = test this is ], и это недопустимо. В[[ ... ]]аргументы оцениваются первыми, таким образом, оболочка понимает, что это тест между$fooи$bar. Затем интерполируются значения$fooи$bar. Эта работа.Для петли и
$IFSЕсть переменная оболочки под названием
$IFS, которая задает, какreadиforциклы разбирают свои аргументы. Обычно он имеет значение space / tab/NL,но вы можете изменить его. Поскольку каждый аргумент пути разделен:, вы можете установитьIFS=:"и использовать циклforдля разбора вашего$PATH.Перенаправление
<<<
<<<позволяет взять переменную оболочки и передать ее в качестве STDIN команде. Они оба более или менее делают то же самое вещь:statement="This contains the word 'foo'" echo "$statement" | sed 's/foo/bar/' statement="This contains the word 'foo'" sed 's/foo/bar/'<<<$statementМатематика в оболочке
Использование
((...))позволяет использовать математику, и одна из математических функций-маскировка. Я использую маски, чтобы определить, установлены ли определенные биты в разрешении.Например, если мое разрешение каталога
0755и я и против0022, я могу видеть, установлены ли права пользователя на чтение и запись. Обратите внимание на ведущие нули. Это важно, чтобы они интерпретировались как восьмеричные значения.Вот ваша программа переписано с использованием вышеприведенного:
#! /bin/bash grep -q "::" <<<"$PATH" && echo "Empty directory in PATH ('::')." grep -q ":$" <<<$PATH && "PATH has trailing ':'" # # Fix Path Issues # path=$(sed -e 's/::/:/g' -e 's/:$//'<<<$PATH); OLDIFS="$IFS" IFS=":" for directory in $PATH do [[ $directory == "." ]] && echo "Path contains '.'." [[ ! -d "$directory" ]] && echo "'$directory' isn't a directory in path." mode=$(stat -L -f %04Lp "$directory") # Differs from system to system [[ $(stat -L -f %u "$directory") -eq 0 ]] && echo "Directory '$directory' owned by root" ((mode & 0022)) && echo "Group or Other write permission is set on '$directory'." doneЯ не на 100% уверен, что вы хотите сделать или подразумеваете об уязвимостях пути. Я не знаю, почему вас волнует, принадлежит ли каталог root, и если запись в
$PATHне является каталогом, это не повлияет на$PATH. Тем не менее, одна вещь, которую я хотел бы проверить, - это убедиться, что все каталоги в вашем$PATHявляются абсолютными путями.[[ $directory != /* ]] && echo "Directory '$directory' is a relative path"
Следующее может выполнить всю работу, а также удалить дубликаты записей
export PATH="$(perl -e 'print join(q{:}, grep{ -d && !((stat(_))[2]&022) && !$seen{$_}++ } split/:/, $ENV{PATH})')"
Мне нравится ответ @kobame, но если вам не нравится
perl-зависимость, вы можете сделать что-то подобное:$ cat path.sh #!/bin/bash PATH="/root/bin:/tmp/groupwrite:/tmp/otherwrite:/usr/bin:/usr/sbin" echo "${PATH}" OIFS=$IFS IFS=: for path in ${PATH}; do [ -d "${path}" ] || continue paths=( "${paths[@]}" "${path}" ) done while read -r stat path; do [ "${stat:5:1}${stat:8:1}" = '--' ] || continue newpath="${newpath}:${path}" done < <(stat -c "%A:%n" "${paths[@]}" 2>/dev/null) IFS=${OIFS} PATH=${newpath#:} echo "${PATH}"
Обратите внимание, что это не переносится из-за того, что$ ./path.sh /root/bin:/tmp/groupwrite:/tmp/otherwrite:/usr/bin:/usr/sbin /usr/bin:/usr/sbinstatне переносится, но он будет работать на Linux (и Cygwin). Для того, чтобы это работало на системах BSD, вам придется адаптировать строку формата, другие Unices не поставляются сstatна всех OOTB (Solaris, например).Он не удаляет дубликаты или каталоги, не принадлежащие
root, но которые можно легко добавить. Последнее требует лишь незначительной адаптации цикла, так чтоstatтакже возвращает имя пользователя владельца:while read -r stat owner path; do [ "${owner}${stat:5:1}${stat:8:1}" = 'root--' ] || continue newpath="${newpath}:${path}" done < <(stat -c "%A:%U:%n" "${paths[@]}" 2>/dev/null)
Comments