@@ -3,7 +3,7 @@ private import codeql.util.Unit
33private import semmle.code.csharp.frameworks.microsoft.AspNetCore
44
55/** An additional flow step for cross-site scripting (XSS) vulnerabilities */
6- abstract class XssAdditionalFlowStep extends Unit {
6+ class XssAdditionalFlowStep extends Unit {
77 abstract predicate isAdditionalFlowStep ( DataFlow:: Node node1 , DataFlow:: Node node2 ) ;
88}
99
@@ -73,9 +73,82 @@ private class ViewCallFlowStep extends XssAdditionalFlowStep {
7373}
7474
7575private predicate viewCallRefersToPage ( ViewCall vc , RazorPage rp ) {
76- viewCallRefersToPageAbsolute ( vc , rp )
76+ viewCallRefersToPageAbsolute ( vc , rp ) or
77+ viewCallRefersToPageRelative ( vc , rp )
7778}
7879
7980private 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+ }
81154}
0 commit comments