Проверка целостности корня с помощью скрипта



Ниже приведен мой скрипт для проверки целостности корневого пути, чтобы убедиться, что в переменной 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 корня. много длинных идей приходит в голову. Но поиск быстрого и "не столь сложного" метода, пожалуйста.

613   4  

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, например, чтобы проверить, не является ли запись допустимым каталогом:

elif [[ ! -d ${path_ary[i]} ]]; then
    printf 'Warning: the entry %s is not a directory\n' "$i"
Теперь, чтобы проверить разрешение и право собственности, к сожалению, нет ни чистых способов Bash, ни портативных способов продолжения. Но разбор Весьма вероятно, что это не очень хорошая идея. stat может работать, но, как известно, имеет различное поведение на разных платформах. Так что вам придется поэкспериментировать с тем, что вам подходит. Вот пример, который работает с GNU stat в 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:

IFS=: eval 'PATH="${path_ary[*]}"'
Я знаю, что некоторые будут громко кричать, что это зло, но это каноническое (и безопасное!) путь к соедините элементы массива в Bash (обратите внимание на одинарные кавычки).

Наконец, соответствующая функция может выглядеть следующим образом:

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 вернет a 0 , Если $file является исполняемым, и A 1, Если это не так.

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 if is 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/sbin
Обратите внимание, что это не переносится из-за того, что stat не переносится, но он будет работать на 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

    Ничего не найдено.