Толик Панков
hex_laden
............ .................. ................

November 2020
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30

Толик Панков [userpic]
Об опасности использования 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

Это репост с сайта http://tolik-punkoff.com
Оригинал: http://tolik-punkoff.com/2020/01/25/ob-opasnosti-ispolzovaniya-eval-v-bash-skriptah/

Tags: ,
Comments

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

Ругнулся на "command not found", вывод норм не дал, но каталог прилежно удалил.

понял почему, но пока не понял, как поправить, хоть Кетмара зови... Пойду пока за водкой :)

Исправил. Топором, но лучше как сделать хз.

Да сомневаюсь, что лучше есть как-то. Штатной функции "escape control characters" в awk я не нашел (что удивительно), хотя ее в теории можно написать регулярками (а потом долго отлаживать на предмет "не забыли ли чего").

Сходу -- я бы вообще избегал eval путем вычисления по одной переменной, в стиле
var=`echo "$TMPSTRING"| awk -F "," '{print $1;}'`, но это тоже крипи, плюс гораздо дольше.

Но по некотором раздумии и поиске, чище всего, наверное, будет сделать bash array какой-нибудь: https://stackoverflow.com/questions/15105135/bash-capturing-output-of-awk-into-array/15105237

А еще чище -- вообще по возможности избегать переброски awk->bash и делать все необходимое прямо в awk -- если это возможно, конечно.

>Штатной функции "escape control characters" в awk я не нашел
Во-во. Тоже удивительно.

>можно написать регулярками (а потом долго отлаживать на предмет "не забыли ли чего")
Именно, икра не стоит рыбы.

>Сходу -- я бы вообще избегал eval путем вычисления по одной переменной
Изначально, оно так и було. Только 200 записей обрабатывались секунд 15.

>делать все необходимое прямо в awk -- если это возможно, конечно.
В реальном проекте так оно в итоге и вышло (Спасибо Карамзину из Телеги), но, ради истины, захотелось заморочиться с eval. А так да, зело страшен eval и лучше его по возможности избегать.