Skip to content

Commit 4de19b3

Browse files
Merge pull request #15039 from joefarebrother/csharp-razor-flow-page-models
C#: Add flow steps from a PageModel to cshtml page.
2 parents 24855dd + e8c0fce commit 4de19b3

File tree

8 files changed

+205
-0
lines changed

8 files changed

+205
-0
lines changed

csharp/ql/lib/semmle/code/csharp/frameworks/Razor.qll

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ private import csharp
44
private import codeql.util.Unit
55
private import codeql.util.FilePath
66
private import semmle.code.csharp.frameworks.microsoft.AspNetCore
7+
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
78

89
/** A call to the `View` method */
910
private class ViewCall extends MethodCall {
@@ -215,3 +216,62 @@ private class RelativeViewCallFilepath extends NormalizableFilepath {
215216
/** Holds if this string is the `idx`th path that will be searched for the `vc` call. */
216217
predicate hasViewCallWithIndex(ViewCall vc, int idx) { vc = vc_ and idx = idx_ }
217218
}
219+
220+
/** A subclass of `Microsoft.AspNetCore.Mvc.RazorPages.PageModel` */
221+
class PageModelClass extends Class {
222+
PageModelClass() {
223+
this.getABaseType+().hasFullyQualifiedName("Microsoft.AspNetCore.Mvc.RazorPages", "PageModel")
224+
}
225+
226+
/** Gets a handler method such as `OnGetAsync` */
227+
Method getAHandlerMethod() {
228+
result = this.getAMethod() and
229+
result.getName().matches("On%") and
230+
not exists(Attribute attr |
231+
attr = result.getAnAttribute() and
232+
attr.getType()
233+
.hasFullyQualifiedName("Microsoft.AspNetCore.Mvc.RazorPages", "NonHandlerAttribute")
234+
)
235+
}
236+
237+
/** Gets the Razor Page that has this PageModel. */
238+
RazorViewClass getPage() {
239+
exists(Property modelProp |
240+
modelProp.hasName("Model") and
241+
modelProp.getType() = this and
242+
modelProp.getDeclaringType() = result
243+
)
244+
}
245+
}
246+
247+
private MethodCall getAPageCall(PageModelClass pm) {
248+
result.getEnclosingCallable() = pm.getAHandlerMethod() and
249+
result
250+
.getTarget()
251+
.hasFullyQualifiedName("Microsoft.AspNetCore.Mvc.RazorPages", "PageModel",
252+
["Page", "RedirectToPage"])
253+
}
254+
255+
private ThisAccess getThisCallInVoidHandler(PageModelClass pm) {
256+
result.getEnclosingCallable() = pm.getAHandlerMethod() and
257+
result.getEnclosingCallable().getReturnType() instanceof VoidType
258+
}
259+
260+
private class PageModelJumpNode extends DataFlow::NonLocalJumpNode {
261+
PageModelClass pm;
262+
263+
PageModelJumpNode() {
264+
this.asExpr() = getAPageCall(pm).getQualifier()
265+
or
266+
this.(PostUpdateNode).getPreUpdateNode().asExpr() = getThisCallInVoidHandler(pm)
267+
}
268+
269+
override DataFlow::Node getAJumpSuccessor(boolean preservesValue) {
270+
preservesValue = true and
271+
exists(PropertyAccess modelProp |
272+
result.asExpr() = modelProp and
273+
modelProp.getTarget().hasName("Model") and
274+
modelProp.getEnclosingCallable().getDeclaringType() = pm.getPage()
275+
)
276+
}
277+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Modelled additional flow steps to track flow from handler methods of a `PageModel` class to the corresponding Razor Page (`.cshtml`) file, which may result in additional results for queries such as `cs/web/xss`.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.AspNetCore.Mvc.RazorPages;
2+
using Microsoft.AspNetCore.Mvc;
3+
using System.Net;
4+
using System.Threading.Tasks;
5+
namespace test;
6+
7+
class TestModel : PageModel {
8+
public string Name { get; set; } = "abc";
9+
10+
private string source() { return "x"; }
11+
12+
public async Task<IActionResult> OnPostAsync() {
13+
this.Name = source();
14+
return Page();
15+
}
16+
17+
public void OnGet() {
18+
Name = source();
19+
}
20+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@page
2+
@model TestModel
3+
@namespace test
4+
5+
<h3>Hello "@Html.Raw(Model.Name)"</h3>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// A hand-written test file that mimics the output of compiling a `.cshtml` file
2+
3+
// <auto-generated/>
4+
#pragma warning disable 1591
5+
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(test.Pages.Pages_TestPage), @"mvc.1.0.razor-page", @"TestPage.cshrml")]
6+
namespace test.Pages
7+
{
8+
#line hidden
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
using System.Threading.Tasks;
13+
using Microsoft.AspNetCore.Mvc;
14+
using Microsoft.AspNetCore.Mvc.Rendering;
15+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
16+
#nullable restore
17+
using test;
18+
19+
#line default
20+
#line hidden
21+
#nullable disable
22+
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"c4ae76542f1958092cebd8f57beef899d20fc548", @"TestPage.cshtml")]
23+
// [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"c13da96c2597d5ddb7d415fb4892c644a268f50b", @"/Pages/_ViewImports.cshtml")]
24+
internal sealed class Pages_TestPage : global::Microsoft.AspNetCore.Mvc.RazorPages.Page
25+
{
26+
#pragma warning disable 1998
27+
public async override global::System.Threading.Tasks.Task ExecuteAsync()
28+
{
29+
#nullable disable
30+
WriteLiteral("<h3>Hello \"");
31+
#nullable restore
32+
#line 5 "TestPage.cshtml"
33+
Write(Html.Raw(Model.Name));
34+
35+
#line default
36+
#line hidden
37+
#nullable disable
38+
WriteLiteral("\"</h3>\n");
39+
#nullable restore
40+
}
41+
#pragma warning restore 1998
42+
#nullable restore
43+
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
44+
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
45+
#nullable disable
46+
#nullable restore
47+
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
48+
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
49+
#nullable disable
50+
#nullable restore
51+
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
52+
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
53+
#nullable disable
54+
#nullable restore
55+
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
56+
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
57+
#nullable disable
58+
#nullable restore
59+
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
60+
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TestModel> Html { get; private set; } = default!;
61+
#nullable disable
62+
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestModel> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestModel>)PageContext?.ViewData;
63+
public TestModel Model => ViewData.Model; }
64+
}
65+
#pragma warning restore 1591
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
semmle-extractor-options: /nostdlib /noconfig
2+
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
3+
semmle-extractor-options: --load-sources-from-project:../../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
edges
2+
| TestModel.cs:13:9:13:12 | [post] this access : TestModel [property Name] : String | TestModel.cs:14:16:14:21 | this access : TestModel [property Name] : String |
3+
| TestModel.cs:13:21:13:28 | call to method source : String | TestModel.cs:13:9:13:12 | [post] this access : TestModel [property Name] : String |
4+
| TestModel.cs:14:16:14:21 | this access : TestModel [property Name] : String | TestPage.cshtml.g.cs:63:35:63:48 | access to property Model : TestModel [property Name] : String |
5+
| TestModel.cs:14:16:14:21 | this access : TestModel [property Name] : String | TestPage.cshtml:5:16:5:20 | access to property Model : TestModel [property Name] : String |
6+
| TestModel.cs:18:9:18:12 | [post] this access : TestModel [property Name] : String | TestPage.cshtml.g.cs:63:35:63:48 | access to property Model : TestModel [property Name] : String |
7+
| TestModel.cs:18:9:18:12 | [post] this access : TestModel [property Name] : String | TestPage.cshtml:5:16:5:20 | access to property Model : TestModel [property Name] : String |
8+
| TestModel.cs:18:16:18:23 | call to method source : String | TestModel.cs:18:9:18:12 | [post] this access : TestModel [property Name] : String |
9+
| TestPage.cshtml.g.cs:63:35:63:48 | access to property Model : TestModel [property Name] : String | TestPage.cshtml:5:16:5:20 | access to property Model : TestModel [property Name] : String |
10+
| TestPage.cshtml:5:16:5:20 | access to property Model : TestModel [property Name] : String | TestPage.cshtml:5:16:5:25 | access to property Name |
11+
nodes
12+
| TestModel.cs:13:9:13:12 | [post] this access : TestModel [property Name] : String | semmle.label | [post] this access : TestModel [property Name] : String |
13+
| TestModel.cs:13:21:13:28 | call to method source : String | semmle.label | call to method source : String |
14+
| TestModel.cs:14:16:14:21 | this access : TestModel [property Name] : String | semmle.label | this access : TestModel [property Name] : String |
15+
| TestModel.cs:18:9:18:12 | [post] this access : TestModel [property Name] : String | semmle.label | [post] this access : TestModel [property Name] : String |
16+
| TestModel.cs:18:16:18:23 | call to method source : String | semmle.label | call to method source : String |
17+
| TestPage.cshtml.g.cs:63:35:63:48 | access to property Model : TestModel [property Name] : String | semmle.label | access to property Model : TestModel [property Name] : String |
18+
| TestPage.cshtml:5:16:5:20 | access to property Model : TestModel [property Name] : String | semmle.label | access to property Model : TestModel [property Name] : String |
19+
| TestPage.cshtml:5:16:5:25 | access to property Name | semmle.label | access to property Name |
20+
subpaths
21+
#select
22+
| TestPage.cshtml:5:16:5:25 | access to property Name | TestModel.cs:13:21:13:28 | call to method source : String | TestPage.cshtml:5:16:5:25 | access to property Name | Xss |
23+
| TestPage.cshtml:5:16:5:25 | access to property Name | TestModel.cs:18:16:18:23 | call to method source : String | TestPage.cshtml:5:16:5:25 | access to property Name | Xss |
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @kind path-problem
3+
*/
4+
5+
import csharp
6+
import semmle.code.csharp.security.dataflow.XSSQuery
7+
import semmle.code.csharp.security.dataflow.XSSSinks
8+
9+
module TestXssTrackingConfig implements DataFlow::ConfigSig {
10+
predicate isSource(DataFlow::Node source) {
11+
source.asExpr().(MethodCall).getTarget().getName() = "source"
12+
}
13+
14+
predicate isSink = XssTrackingConfig::isSink/1;
15+
16+
predicate isBarrier = XssTrackingConfig::isBarrier/1;
17+
}
18+
19+
module TestXss = TaintTracking::Global<TestXssTrackingConfig>;
20+
21+
import TestXss::PathGraph
22+
23+
from TestXss::PathNode source, TestXss::PathNode sink
24+
where TestXss::flowPath(source, sink)
25+
select sink, source, sink, "Xss"

0 commit comments

Comments
 (0)