Skip to content

Commit cc16527

Browse files
authored
Prevent browser refresh script from injecting into iframes (#21954)
Fixes dotnet/aspnetcore#37326
1 parent 5b5c9e0 commit cc16527

File tree

2 files changed

+121
-11
lines changed

2 files changed

+121
-11
lines changed

src/BuiltInTools/BrowserRefresh/BrowserRefreshMiddleware.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Http;
88
using Microsoft.AspNetCore.Http.Features;
99
using Microsoft.Extensions.Logging;
10+
using Microsoft.Extensions.Primitives;
1011
using Microsoft.Net.Http.Headers;
1112

1213
namespace Microsoft.AspNetCore.Watch.BrowserRefresh
@@ -23,7 +24,7 @@ public BrowserRefreshMiddleware(RequestDelegate next, ILogger<BrowserRefreshMidd
2324
public async Task InvokeAsync(HttpContext context)
2425
{
2526
// We only need to support this for requests that could be initiated by a browser.
26-
if (IsBrowserRequest(context))
27+
if (IsBrowserDocumentRequest(context))
2728
{
2829
// Use a custom StreamWrapper to rewrite output on Write/WriteAsync
2930
using var responseStreamWrapper = new ResponseStreamWrapper(context, _logger);
@@ -57,16 +58,25 @@ public async Task InvokeAsync(HttpContext context)
5758
}
5859
}
5960

60-
internal static bool IsBrowserRequest(HttpContext context)
61+
internal static bool IsBrowserDocumentRequest(HttpContext context)
6162
{
6263
var request = context.Request;
6364
if (!HttpMethods.IsGet(request.Method) && !HttpMethods.IsPost(request.Method))
6465
{
6566
return false;
6667
}
6768

69+
if (request.Headers.TryGetValue("Sec-Fetch-Dest", out var values) &&
70+
!StringValues.IsNullOrEmpty(values) &&
71+
!string.Equals(values[0], "document", StringComparison.OrdinalIgnoreCase))
72+
{
73+
// See https://github.com/dotnet/aspnetcore/issues/37326.
74+
// Only inject scripts that are destined for a browser page.
75+
return false;
76+
}
77+
6878
var typedHeaders = request.GetTypedHeaders();
69-
if (!(typedHeaders.Accept is IList<MediaTypeHeaderValue> acceptHeaders))
79+
if (typedHeaders.Accept is not IList<MediaTypeHeaderValue> acceptHeaders)
7080
{
7181
return false;
7282
}

src/Tests/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/BrowserRefreshMiddlewareTest.cs

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class BrowserRefreshMiddlewareTest
1616
[InlineData("DELETE")]
1717
[InlineData("head")]
1818
[InlineData("Put")]
19-
public void IsBrowserRequest_ReturnsFalse_ForNonGetOrPostRequests(string method)
19+
public void IsBrowserDocumentRequest_ReturnsFalse_ForNonGetOrPostRequests(string method)
2020
{
2121
// Arrange
2222
var context = new DefaultHttpContext
@@ -32,14 +32,14 @@ public void IsBrowserRequest_ReturnsFalse_ForNonGetOrPostRequests(string method)
3232
};
3333

3434
// Act
35-
var result = BrowserRefreshMiddleware.IsBrowserRequest(context);
35+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
3636

3737
// Assert
3838
Assert.False(result);
3939
}
4040

4141
[Fact]
42-
public void IsBrowserRequest_ReturnsFalse_IsRequestDoesNotAcceptHtml()
42+
public void IsBrowserDocumentRequest_ReturnsFalse_IsRequestDoesNotAcceptHtml()
4343
{
4444
// Arrange
4545
var context = new DefaultHttpContext
@@ -55,14 +55,14 @@ public void IsBrowserRequest_ReturnsFalse_IsRequestDoesNotAcceptHtml()
5555
};
5656

5757
// Act
58-
var result = BrowserRefreshMiddleware.IsBrowserRequest(context);
58+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
5959

6060
// Assert
6161
Assert.False(result);
6262
}
6363

6464
[Fact]
65-
public void IsBrowserRequest_ReturnsTrue_ForGetRequestsThatAcceptHtml()
65+
public void IsBrowserDocumentRequest_ReturnsTrue_ForGetRequestsThatAcceptHtml()
6666
{
6767
// Arrange
6868
var context = new DefaultHttpContext
@@ -78,14 +78,14 @@ public void IsBrowserRequest_ReturnsTrue_ForGetRequestsThatAcceptHtml()
7878
};
7979

8080
// Act
81-
var result = BrowserRefreshMiddleware.IsBrowserRequest(context);
81+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
8282

8383
// Assert
8484
Assert.True(result);
8585
}
8686

8787
[Fact]
88-
public void IsBrowserRequest_ReturnsTrue_ForRequestsThatAcceptAnyHtml()
88+
public void IsBrowserDocumentRequest_ReturnsTrue_ForRequestsThatAcceptAnyHtml()
8989
{
9090
// Arrange
9191
var context = new DefaultHttpContext
@@ -101,12 +101,112 @@ public void IsBrowserRequest_ReturnsTrue_ForRequestsThatAcceptAnyHtml()
101101
};
102102

103103
// Act
104-
var result = BrowserRefreshMiddleware.IsBrowserRequest(context);
104+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
105105

106106
// Assert
107107
Assert.True(result);
108108
}
109109

110+
[Fact]
111+
public void IsBrowserDocumentRequest_ReturnsTrue_IfRequestDoesNotHaveFetchMetadataRequestHeader()
112+
{
113+
// Arrange
114+
var context = new DefaultHttpContext
115+
{
116+
Request =
117+
{
118+
Method = "GET",
119+
Headers =
120+
{
121+
["Accept"] = "text/html",
122+
},
123+
},
124+
};
125+
126+
// Act
127+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
128+
129+
// Assert
130+
Assert.True(result);
131+
}
132+
133+
[Fact]
134+
public void IsBrowserDocumentRequest_ReturnsTrue_IfRequestFetchMetadataRequestHeaderIsEmpty()
135+
{
136+
// Arrange
137+
var context = new DefaultHttpContext
138+
{
139+
Request =
140+
{
141+
Method = "Post",
142+
Headers =
143+
{
144+
["Accept"] = "text/html",
145+
["Sec-Fetch-Dest"] = string.Empty,
146+
},
147+
},
148+
};
149+
150+
// Act
151+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
152+
153+
// Assert
154+
Assert.True(result);
155+
}
156+
157+
[Theory]
158+
[InlineData("document")]
159+
[InlineData("Document")]
160+
public void IsBrowserDocumentRequest_ReturnsTrue_IfRequestFetchMetadataRequestHeaderIsDocument(string headerValue)
161+
{
162+
// Arrange
163+
var context = new DefaultHttpContext
164+
{
165+
Request =
166+
{
167+
Method = "Post",
168+
Headers =
169+
{
170+
["Accept"] = "text/html",
171+
["Sec-Fetch-Dest"] = headerValue,
172+
},
173+
},
174+
};
175+
176+
// Act
177+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
178+
179+
// Assert
180+
Assert.True(result);
181+
}
182+
183+
[Theory]
184+
[InlineData("frame")]
185+
[InlineData("iframe")]
186+
[InlineData("serviceworker")]
187+
public void IsBrowserDocumentRequest_ReturnsFalse_IfRequestFetchMetadataRequestHeaderIsNotDocument(string headerValue)
188+
{
189+
// Arrange
190+
var context = new DefaultHttpContext
191+
{
192+
Request =
193+
{
194+
Method = "Post",
195+
Headers =
196+
{
197+
["Accept"] = "text/html",
198+
["Sec-Fetch-Dest"] = headerValue,
199+
},
200+
},
201+
};
202+
203+
// Act
204+
var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context);
205+
206+
// Assert
207+
Assert.False(result);
208+
}
209+
110210
[Fact]
111211
public async Task InvokeAsync_AddsScriptToThePage()
112212
{

0 commit comments

Comments
 (0)