@@ -3,7 +3,7 @@ private import codeql.util.Unit
3
3
private import semmle.code.csharp.frameworks.microsoft.AspNetCore
4
4
5
5
/** An additional flow step for cross-site scripting (XSS) vulnerabilities */
6
- abstract class XssAdditionalFlowStep extends Unit {
6
+ class XssAdditionalFlowStep extends Unit {
7
7
abstract predicate isAdditionalFlowStep ( DataFlow:: Node node1 , DataFlow:: Node node2 ) ;
8
8
}
9
9
@@ -73,9 +73,82 @@ private class ViewCallFlowStep extends XssAdditionalFlowStep {
73
73
}
74
74
75
75
private predicate viewCallRefersToPage ( ViewCall vc , RazorPage rp ) {
76
- viewCallRefersToPageAbsolute ( vc , rp )
76
+ viewCallRefersToPageAbsolute ( vc , rp ) or
77
+ viewCallRefersToPageRelative ( vc , rp )
77
78
}
78
79
79
80
private predicate viewCallRefersToPageAbsolute ( ViewCall vc , RazorPage rp ) {
80
- [ "/" , "~/" , "" ] + vc .getNameArgument ( ) = rp .getSourceFilepath ( )
81
+ [ "/" , "" ] + vc .getNameArgument ( ) = [ "" , "~" ] + rp .getSourceFilepath ( )
82
+ }
83
+
84
+ private predicate viewCallRefersToPageRelative ( ViewCall vc , RazorPage rp ) {
85
+ rp .getSourceFilepath ( ) =
86
+ min ( int i , RelativeViewCallFilepath fp |
87
+ fp .hasViewCallWithIndex ( vc , i ) and
88
+ exists ( RazorPage rp2 | rp2 .getSourceFilepath ( ) = fp .getNormalizedPath ( ) )
89
+ |
90
+ fp .getNormalizedPath ( ) order by i
91
+ )
92
+ }
93
+
94
+ private class RelativeViewCallFilepath extends NormalizableFilepath {
95
+ ViewCall vc ;
96
+ int idx ;
97
+
98
+ RelativeViewCallFilepath ( ) {
99
+ exists ( string actionName |
100
+ actionName = vc .getNameArgument ( ) and
101
+ not actionName .matches ( "%.cshtml" )
102
+ or
103
+ not exists ( vc .getNameArgument ( ) ) and
104
+ actionName = vc .getActionMethod ( ) .getName ( )
105
+ |
106
+ idx = 0 and
107
+ this = "/Views/" + vc .getControllerName ( ) + "/" + actionName + ".cshtml"
108
+ or
109
+ idx = 1 and
110
+ this = "/Views/Shared/" + actionName + ".cshtml"
111
+ )
112
+ }
113
+
114
+ predicate hasViewCallWithIndex ( ViewCall vc2 , int idx2 ) { vc = vc2 and idx = idx2 }
115
+ }
116
+
117
+ // TODO: this could be a shared library
118
+ /** A filepath that should be normalized. */
119
+ abstract private class NormalizableFilepath extends string {
120
+ bindingset [ this ]
121
+ NormalizableFilepath ( ) { any ( ) }
122
+
123
+ /** Gets the normalized filepath for this string; traversing `/../` paths. */
124
+ string getNormalizedPath ( ) {
125
+ exists ( string norm |
126
+ norm = this .getNormalizedUpTo ( 0 ) .regexpReplaceAll ( "/+$" , "" ) and
127
+ ( if this .matches ( "/%" ) then result = "/" + norm else result = norm )
128
+ )
129
+ }
130
+
131
+ private string getComponent ( int i ) { result = this .splitAt ( "/" , i ) }
132
+
133
+ private int getNumComponents ( ) { result = strictcount ( int i | exists ( this .getComponent ( i ) ) ) }
134
+
135
+ private string getNormalizedUpTo ( int i ) {
136
+ i in [ 0 .. this .getNumComponents ( ) ] and
137
+ (
138
+ i = this .getNumComponents ( ) and
139
+ result = ""
140
+ or
141
+ i < this .getNumComponents ( ) and
142
+ exists ( string comp , string sofar |
143
+ comp = this .getComponent ( i ) and sofar = this .getNormalizedUpTo ( i + 1 )
144
+ |
145
+ if comp = [ "." , "" ]
146
+ then result = sofar
147
+ else
148
+ if comp = ".." or not sofar .matches ( "../%" )
149
+ then result = comp + "/" + sofar
150
+ else exists ( string base | sofar = "../" + base | result = base )
151
+ )
152
+ )
153
+ }
81
154
}
0 commit comments