Запрещаем использование известных UserJS из песочницы

Введение


UserJS предоставляет пользователям удобный и простой механизм модификации веб-страниц, именно благодаря этому многие пользователи автоматизирую свои действия с помощью UserJS, а иногда и обходят слабые системы защиты.
Больше всего от использования UserJS пользователями страдают браузерные онлайн-игры, многие из которых уже начали войну против UserJS. Так, например, в игре Travian используются поддельные скрытые веб-формы, которые иногда появляются вместе с обычными, UserJS-скрипты, написанные без учета этой особенности, ошибаются и отправляют данные через фэйковую форму, за что игрок немедленно получает наказание.
Хотелось бы сразу отметить, что бороться можно только с известными UserJS-скриптами, показанное решение не универсально и не может защитить от любого скрипта.
Сегодня я представляю на ваш суд свой метод борьбы с пользовательскими скриптами — MD5-хэширование с последующим сравнением. Основные действующие роли играют JavaScript и PHP.

Легенда


Предположим, у нас есть веб-сайт с простой формой авторизации, но пользователи повадились не вводить свой логин, а использовать UserJS-скрипт, который добавляет в форму дополнительную кнопку, нажатие на которую автоматически вводит их логин. Этот пример взят просто для наглядности, на месте формы авторизации может быть, например, форма отправки войск в онлайн-стратегии, а UserJS-скрипт — бот, автоматически рассылающий войска.

Исходные данные


Итак, наша форма выглядит так:


Пользователи используют следующий UserJS в браузере Opera:
Copy Source | Copy HTML
  1. window.opera.addEventListener('AfterEvent.load', function (e){
  2.     if (e.event.target instanceof Document){
  3.               e.preventDefault();
  4.         document.forms[ 0].innerHTML+='<input type="button" onclick="document.forms[0].login.value=\'Login\';" value="AutoLogin">';
  5.         }
  6. },false);


Или в FireFox для расширения GreaceMonkey:
Copy Source | Copy HTML
  1. // ==UserScript==
  2. // @name           test1
  3. // @namespace      test
  4. // @include        http://localhost/anti-userjs/test1.php
  5. // ==/UserScript==
  6.  
  7. document.forms[ 0].innerHTML+='<input type="button" onclick="document.forms[0].login.value=\'Login\';" value="AutoLogin">';


После включения этого скрипта пользователи получают такой вид формы авторизации:


Нажатие на кнопку «AutoLogin», как видно из исходных текстов, приводит к заполнению поля «login».
Попробуем запретить пользователем использование этого скрипта.

Вывод формы авторизации


Выводить форму авторизации мы будем из PHP-файла, обрамим форму тегом div, а также захешируем все данные внутри этого тега средствами PHP. Хеш сохраним в сессии для последующего контроля:
Copy Source | Copy HTML
  1. <div id="main">
  2. <?
  3. ob_start();
  4. ?>
  5. <form method="POST">
  6. Логин: <input name="login" type="text"><br>
  7. Пароль: <input name="password" type="password" ><br>
  8. <input name="md5" type="hidden"><br>
  9. <input value="Войти" type="submit">
  10. </form>
  11. <?
  12. $data=ob_get_contents();
  13. ob_end_flush();
  14. $data=strtr($data,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz');
  15. $data=preg_replace("/[ \t\n\r]/",'',$data);
  16. $md5=md5($data);
  17. $_SESSION['md5']=$md5;
  18. ?></div>


Пара слов про обработку содержимого — браузеры Opera и FireFox по-своему изгаляются над HTML-кодом, поэтому обработка его через JavaScript приводит к разным результатам. Так, Opera приводит все HTML-теги к заглавным буквам, а FireFox меняет параметры в тегах по стандарту. Поэтому важно валидно писать контролируемый HTML-код.
Для устранения различной обработки регистра тегов и переноса строк из содержимого удаляются все пробелы, табуляции и переносы строк, а также все английские буквы приводятся в нижний регистр.
Еще одно важное замечание — кодировка документа должна быть UTF-8, с CP1251 JavaScript в браузерах и PHP работаю по-разному.

Контроль через JavaScript


Итак, форма выведена, её контрольная сумма сосчитана, теперь нужно посчитать её со стороны пользователя и убедится, что вмешательства UserJS не было. Для этого мы добавляем событие на onLoad и вызываем JS-функцию:
Copy Source | Copy HTML
  1. >
  2. function checkuserjs(){
  3.     var data=document.getElementsByTagName('div')[ 0].innerHTML;
  4.     data=strtr(data,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz');
  5.     data=data.replace(/[ \t\n\r]/g,'');
  6.     document.forms[ 0].md5.value=md5(data);
  7. }

 


В ней я использовал несколько готовых функций — md5 и strtr, я не буду выкладывать их в статье, вы сможете найти их в приложенном файле.
Java-Script на стороне пользователя выполняет те же самые действия, что и PHP-скрипт выше, результат — хэш содержимого — заносится в форму.

Обработка на стороне сервера


Дело остается за малым — проверить полученные данные:
Copy Source | Copy HTML
  1. if ($_POST){
  2.     if ($_POST['md5']!=$_SESSION['md5'])
  3.         die ('Использование UserJS запрещено!');
  4.     echo 'Форма отправлена';
  5. }


Таким образом мы отлавливаем недобросовестных пользователей (обратите внимание на индикатор GreaseMonkey):


Заключение


Указанный мной пример демонстрирует сам подход к запрещению использования UserJS, при разработке решений на его основе нужно понять основной момент — запретить использование любого UserJS не возможно, поэтому нужно отслеживать выкладываемые скрипты, затрагивающие наши сайты, и вырабатывать защиту от них. Так как делаются такие скрипты на энтузиазме, велик шанс, что новая версия скрипта, обходящая нашу защиту, если и выйдет, то не скоро. Тот же Травиан это демонстрирует — простейшая защита с фэйковыми формами введена пол-года назад, до сих пор нет новой версии UserJS-скриптов.

Приложение: anti-userjs.tar.gz 3708 bytes
–1
2 ноября 2010, 10:51
2
Magir 2,0

комментарии (9)

+1
aavezel #
Сколько способов обойти
1. Простейший: document.getElementById(«main»).parentNode.innerHTML+='';
2. Замена реального события: document.getElementByName(«login»)[0].onclick=«document.forms[0].login.value=\'Login\'»;
3. Fx only, и по мне самый правильный: GM_registerMenuCommand
4. Javascript Bookmarklets
5. ???
0
Magir #
Речь не идет об универсальном решении, это просто один из способов контроля неизменности содержимого страницы. И в первую очередь это годится для борьбы с известными и публичными UserJS-ами, однако более универсально, чем, например, изменение названия элементов форм и т.д.
+1
akzhan #
Просто энтузиасты в вашей игре не программируют )

Простейший способ, это — вызвать расчет до модификации и убрать функции проверки при сабмите.
0
akzhan #
Или модифицировать данные после загрузки, если уже повесили на load )
0
Magir #
Это естественно, я и не претендовал на универсальное и взломостойкое решение. Просто одна из идей.
+1
lugansk #
+ просто добавить кнопку вне формы или же добавить на onsubmit вычистку всего сгенерированного скриптом кода.
0
lugansk #
это в ответ на
0
Magir #
Это действительно так, но многие готовые скрипты значительно не изменяются из-за лени их авторов, а ловить их по точному признаку сложно, так как изменить названий управляющих элементов может любой.
0
lugansk #
>> не изменяются из-за лени их авторов

Скрипты могут писаться за копейки на заказ, лень, возможно, не авторская, а заказчиков :)

А вообще, правильно написанный скрипт малореально отследить, разве что по косвенным признакам (к примеру, если это бот, выполняющий какие-то рутинные действия, то действия могут выполняться или слишком быстро, или через равные промежутки времени, или же слишком долго).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.