Однако опасайтесь двойных подстановок!
Эта небольшая статья посвящена "подводным камням" тикля и способам их обхода. Первоначально TCL кажется достаточно прямолинейным и простым в использовании. Однако попытка решения нетривиальных задач может поставить перед вами некоторые проблемы, например: чрезмерное обилие скобок, "нежелательные" вычисления и т.д. Эти проблемы, с которыми сталкивались многие очень умные люди, указывают на то, что представление программиста о работе интерпретатора TCL неверно. Задача данной статьи - в общих чертах описать базовую модель, "подводные камни" и способ мышления (программирования).
Базовая модель.
Практически все проблемы подпадают под три простых правила:
- За каждый проход интерпретатора TCL выполняется один, и только дин, уровень подстановок и/или вычислений.
- Интерпретатор TCL за один свой проход сканирует каждый символ только один раз.
- Любой правильно оформленный список является также правильно оформленной командой; при вычислении, каждый элемент списка рассматривается как одно слово команды, без каких-либо дальнейших подстановок.
В первом скрипте команда set рассматривается интерпретатором единожды. Она разбивается на три слова: "set", "a" и значение переменной "b". Со значением переменной b не выполняется никаких дальнейших подстановок: пробелы в b не рассматриваются как разделители слов команды "set", знак доллара в значении переменной b не станет причиной подстановки переменной и тд.
Во втором скрипте команда "set" будет рассмотрена интерпретатором дважды: первый раз - при разборе команды "eval" и ещё раз - когда "eval" передаст свой аргумент интерпретатору для вычисления. Однако фигурные скобки вокруг выражения предотвратят подстановки значения переменной b: аргумент команды eval - "set a $b". Таким образом, результат вычисления данной команды идентичен результату первого скрипта.
В третьем скрипте вместо фигурных скобок используются кавычки, таким образом, у аргумента команды eval происходит подстановка переменной, и это может привести к нежелательным эффектам, когда eval, в свою очередь, вычислит свой аргумент. Например, если b содержит строку "x y z", то аргументом eval будет "set a x y z"; при вычислении такого скрипта (команда set из пяти слов) возникнет ошибка. Проблема возникла из-за того, что сначала была выполнена подстановка $b, а затем скрипт был "перевычислен". Это двойное вычисление иногда может использоваться для достижения интересных эффектов. Например, если $b содержит строку "$c", то наш скрипт присвоит переменной a значение переменной c (т.е. косвенность).
Четвертый скрипт, как и второй, безопасен. При разборе команды "eval" выполняется подстановка команды, при которой результат команды "list" становится аргументом команды "eval". Результом команды list будет правильный TCL-список из трёх элементов: "set", "a" и содержимого переменной b (всё в одном элементе). Например, если $b равно "x y z", то результатом команды "list" станет строка "set a {x y z}". Эта строка передаётся eval в качестве аргумента, и затем eval "перевычисляет" правильно оформленную команду "set", согласно правилу #3: каждый элемент списка рассматривается как одна часть команды. Таким образом, четвёртый скрипт приводит к тому же результату, что первый и второй.
Подводные камни.
Основная идея проблемы заключается в том, что мы имеем случайную строку и хотим предотвратить её вычисление в скрипте и, возможно, в вашем С коде. Самое простое решение - использование команду list для защиты от выполнения строки, если она сгенерирована TCL-скриптом, или использование библиотечной процедуры Tcl_Merge, если строка порождена вашим С кодом. Также, старайтесь избегать кавычек и использовать вместо них списки.
Что это значит для eggdrop'а? Многие скрипты используют команды [timer] и [utimer], которые передают свои аргументы для вычисления интерпретатору (пусть и отложенного) точно так же, как и eval. Так что, если вы настолько беспечны чтобы написать что-нибудь вроде этого:
и таймер сработает на кого-нибудь с ником [die], то ваш бот "сдохнет" не успев даже кикнуть негодяя. Поэтому всегда используйте [list] для защиты от двойной подстановки.
А также никогда не допускайте вычисления/выполнения данных пришедших от непроверенных пользователей. Наивный скрипт для выполнения shell-команд может выглядеть так:
proc doexec {nick uhost hand chan text} {
puthelp "privmsg $chan :Shell command results:"
foreach line [split [eval exec $text] n] {
puthelp "privmsg $chan :$line"
}
return 1
}
puthelp "privmsg $chan :Shell command results:"
foreach line [split [eval exec $text] n] {
puthelp "privmsg $chan :$line"
}
return 1
}
Таким образом, некий подонок оп Johny пишет '!exec echo "mypublickeydatastring" >>../.ssh/authorized_keys', затем просто открывает ssh-соединение с вашим шеллом и коннектится под вашим аккаунтом даже без пароля.
---
Оригинал поста: http://forum.egghelp.org/viewtopic.php?t=9945
Перевод на русский язык: v0id.
Взято с: http://kreon.net.ru/index.php?name=Pages&op=page&pid=2