Об опасности использования eval в bash-скриптах.

Преамбула

В недавнем примере я допустил, что называется, «детский мат», грубую и далеко неочевидную новичку ошибку, о которой, впрочем, все знают. Но прописные истины неплохо повторять, так что не стоит кидаться на эту заметку с криками «боян!!!111пыщ».
Для кого-то может и боян, а кому-то это может быть неизвестно. Или вот я, зная в теории об ошибке, все равно ее допустил, и даже не сразу понял, что я так грубо наебался, и мой скрипт стал небезопасным от слова «совсем».

Исходная задача

Имелся следующий набор данных в виде форматированного текста (таблицы в текстовом файле):

verb666,Misha Verbitsky,+415314499922,42,11:00-16:00
ktvs421,Vasiliy Kotov,+415314499966,77a,00:00-06:00
dkldn89,Dmitry Kaledin,+415314499949,65b,22:00-00:00
vfurry1,Veniamin Furman,+415314499900,99,12:20-19:25
tpunk56,Tolik Punkoff,+415314499911,59,00:00-11:00

Исходная задача состояла в том, чтобы раскидать вывод awk в переменные bash, так, чтоб значение из соответствующей колонки таблицы попало в указанную переменную bash, и изначальный код был таким:

# ... цикл по строкам
for TMPSTRING in $(cat "testfile.txt")
do
    eval $(echo "$TMPSTRING"|awk -F "," '{print "LOGIN=" dq $1 dq; 
    print "FULLNAME=" dq $2 dq; print "PHONE=" dq $3 dq; print"ROOM=" dq $4 dq;
    print "WORKTIME=" dq $5 dq}' dq='"')

# ... делаем что-то с данными в переменных
done
# ...

Т.е. тут мы сначала получаем строчку текстового файла, передаем ее awk, awk, в свою очередь, возвращает строки:

...
LOGIN="vfurry1"
FULLNAME="Veniamin Furman"
PHONE="+415314499922"
ROOM="99"
WORKTIME="12:20-19:25"
...

Похоже на команду присваивания значений переменным. Да так оно и есть!
Далее, полученная строка передается команде eval.

Опасность eval

В чем опасность eval? Да в том, что эта команда преобразует любую переданную ей строку в команду или команды bash, и немедленно их выполняет. Любую, абсолютно любую строку.

И тут возникает ошибка безопасности. Мы получаем данные из внешнего источника. Это может быть файл с внешнего носителя, или скачанный из Интернета. В нем в худшем случае, который всегда надо держать в голове, работая с внешними источниками данных, может содержаться все, что угодно. И злоумышленнику не надо получать доступ к скрипту, достаточно изменить входные данные:

verb666,Misha Verbitsky,+415314499922,42,11:00-16:00
ktvs421,Vasiliy Kotov,+415314499966,77a,00:00-06:00
dkldn89,Dmitry Kaledin,+415314499949,65b,22:00-00:00
vfurry1,Veniamin Furman `rm -rf ./testdir`,+415314499900,99,12:20-19:25
tpunk56,Tolik Punkoff,+415314499911,59,00:00-11:00

Тут, после Veniamin Furman вписана команда удаления директории ./testdir в текущем каталоге, а могло быть вписано и $HOME или даже /.

Строки, взятые в двойные кавычки, интерпретируются bash’ем не только, как строки с чисто тестовыми данными. В двойных кавычках можно и нужно сделать подстановку, если это возможно. А символы `` (обратные кавычки) значат, что нужно выполнить команду внктри них, а потом подставить результат в то место, где указано выражение в обратных кавычках.

Таким образом, команда

FULLNAME="Veniamin Furman `rm -rf ./testdir`"
выполнится так:
1. В переменную записывается строка Veniamin Furman
2. Выполняется команда rm -rf ./testdir
3. Вывод команды дописывается в переменную

Соответственно, каталог ./testdir в процессе будет удален.

Общие рекомендации

Всегда держите в голове, что данные внутри eval$() будут исполнены bash’ем, как код (команды оболочки).

Исправление

1. Заменить двойные кавычки (") на одинарные ('). Строки в одинарных кавычках будут интерпретированы просто как текстовые последовательности, и без всякого выполнения и изменения записаны в переменные, так как есть:

eval $(echo "$TMPSTRING"|awk -F "," '{print "LOGIN=" sq $1 sq;
print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq; print"ROOM=" sq $4 sq;
print "WORKTIME=" sq $5 sq}' sq="'")

Вывод:

vfurry1 Veniamin Furman `rm -rf ./testdir` +415314499900 99 12:20-19:25

2. Но и это еще не конец, злоумышленник может обмануть и одинарные кавычки, обернув свою команду в …одинарные кавычки:

vfurry1,Veniamin Furman '`rm -rf ./testdir`',+415314499900,99,12:20-19:25

Тогда выполнение будет происходить так. Когда очередь дойдет до FULLNAME awk добросовестно выведет:

FULLNAME='Veniamin Furman '`rm -rf ./testdir`'

eval же это также добросовестно интерпретирует. В $FULLNAME будет записано Veniamin Furman, далее закроется одинарная кавычка, т.е. больше ничего в переменную писаться не будет, а далее выполнится rm -rf ./testdir.

Решение таково — удалить или заменить на что-либо безобидное, одинарные кавычки из входной строки перед передачей ее awk. Можно удалить sed‘ом:

sed -e 's~'\''~~g'

Код целиком:

eval $(echo "$TMPSTRING"|sed -e 's~'\''~~g'|awk -F "," '{print "LOGIN=" sq $1 sq;
print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq; print"ROOM=" sq $4 sq;
print "WORKTIME=" sq $5 sq}' sq="'")

Вывод зловредной сткроки будет:

vfurry1 Veniamin Furman `rm -rf ./testdir` +415314499900 99 12:20-19:25

Тестовый пример

Файлы:

maketestdir — скрипт для создания тестовых каталогов.

testfile.txt — Тестовый файл. Ошибку безопасности можно обойти, заменив двойные кавычки одинарными в коде.
testfile2.txt — Замена двойных кавычек одинарными не поможет. Необходимо дополнительно удалять одинарные кавычки во входной строке.

wrongcode — файл без исправлений кода (в коде используются двойные кавычки)
wrongcode2 — только первое исправление (в коде используются одинарные кавычки)
goodcode — окончательно исправленный код.

Вызов скриптов: <скрипт> <тестовый файл>

Например:

wrongcode testfile2.txt

Пример на GitHub

One Response to Об опасности использования eval в bash-скриптах.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *