Преамбула
В недавнем примере я допустил, что называется, «детский мат», грубую и далеко неочевидную новичку ошибку, о которой, впрочем, все знают. Но прописные истины неплохо повторять, так что не стоит кидаться на эту заметку с криками «боян!!!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