@@ -201,11 +201,11 @@ private async Task Invoke(HttpContext httpContext, bool retry)
201201
202202 // First check for a HMAC token and capture before the command is stripped out.
203203 byte [ ] secret = this . options . HMACSecretKey ;
204- bool doHMAC = false ;
204+ bool checkHMAC = false ;
205205 string token = null ;
206206 if ( secret ? . Length > 0 )
207207 {
208- doHMAC = true ;
208+ checkHMAC = true ;
209209 token = commands . GetValueOrDefault ( HMACUtilities . TokenCommand ) ;
210210 }
211211
@@ -218,19 +218,15 @@ private async Task Invoke(HttpContext httpContext, bool retry)
218218 if ( ! this . knownCommands . Contains ( command ) )
219219 {
220220 // Need to actually remove, allocates new list to allow modifications
221- this . StripUnknownCommands ( commands , startAtIndex : index ) ;
221+ this . StripUnknownCommands ( commands , index ) ;
222222 break ;
223223 }
224224
225225 index ++ ;
226226 }
227227 }
228228
229- ImageCommandContext imageCommandContext = new ( httpContext , commands , this . commandParser , this . parserCulture ) ;
230-
231- await this . options . OnParseCommandsAsync . Invoke ( imageCommandContext ) ;
232-
233- // Get the correct service for the request
229+ // Get the correct provider for the request
234230 IImageProvider provider = null ;
235231 foreach ( IImageProvider resolver in this . providers )
236232 {
@@ -241,30 +237,44 @@ private async Task Invoke(HttpContext httpContext, bool retry)
241237 }
242238 }
243239
244- if ( ( commands . Count == 0 && provider ? . ProcessingBehavior != ProcessingBehavior . All )
245- || provider ? . IsValidRequest ( httpContext ) != true )
240+ if ( provider ? . IsValidRequest ( httpContext ) != true )
246241 {
247242 // Nothing to do. call the next delegate/middleware in the pipeline
248243 await this . next ( httpContext ) ;
249244 return ;
250245 }
251246
252- // At this point we know that this is a valid image request
253- // Check for a token if required and reject if invalid.
254- if ( doHMAC )
247+ ImageCommandContext imageCommandContext = new ( httpContext , commands , this . commandParser , this . parserCulture ) ;
248+
249+ // At this point we know that this is an image request so should attempt to compute a validating HMAC..
250+ string hmac = null ;
251+ if ( checkHMAC && token != null )
255252 {
256- if ( token is null )
257- {
258- // Throw a 401. We don't log the error to avoid attempts at log poisoning.
259- SetUnauthorized ( httpContext ) ;
260- return ;
261- }
253+ // Generate and cache a HMAC to validate against based upon the current valid commands from the request.
254+ //
255+ // If the command collection differs following the stripping of invalid commands prior to this point then this will mean
256+ // the token will not match our validating HMAC, however, this would be indicative of an attack and should be treated as such.
257+ //
258+ // As a rule all image requests should contain valid commands only.
259+ hmac = await HMACTokenLru . GetOrAddAsync ( token , _ => this . options . OnComputeHMACAsync ( imageCommandContext , secret ) ) ;
260+ }
262261
263- // Compare the passed token to our generated mac.
264- string mac = await HMACTokenLru . GetOrAddAsync ( token , _ => this . options . OnComputeHMACAsync ( imageCommandContext , secret ) ) ;
265- if ( mac != token )
262+ await this . options . OnParseCommandsAsync . Invoke ( imageCommandContext ) ;
263+
264+ if ( commands . Count == 0 && provider ? . ProcessingBehavior != ProcessingBehavior . All )
265+ {
266+ // Nothing to do. call the next delegate/middleware in the pipeline
267+ await this . next ( httpContext ) ;
268+ return ;
269+ }
270+
271+ // At this point we know that this is an image request designed for processing via this middleware.
272+ // Check for a token if required and reject if invalid.
273+ if ( checkHMAC )
274+ {
275+ if ( token == null || hmac != token )
266276 {
267- SetUnauthorized ( httpContext ) ;
277+ SetBadRequest ( httpContext ) ;
268278 return ;
269279 }
270280 }
@@ -288,11 +298,12 @@ await this.ProcessRequestAsync(
288298 retry ) ;
289299 }
290300
291- private static void SetUnauthorized ( HttpContext httpContext )
301+ private static void SetBadRequest ( HttpContext httpContext )
292302 {
303+ // We return a 400 rather than a 401 as we do not want to prompt follow up requests.
304+ // We don't log the error to avoid attempts at log poisoning.
293305 httpContext . Response . Clear ( ) ;
294- httpContext . Response . Headers . Add ( "WWW-Authenticate" , "HMAC realm=\" " + httpContext . Request . Host + "\" " ) ;
295- httpContext . Response . StatusCode = StatusCodes . Status401Unauthorized ;
306+ httpContext . Response . StatusCode = StatusCodes . Status400BadRequest ;
296307 }
297308
298309 private void StripUnknownCommands ( CommandCollection commands , int startAtIndex )
0 commit comments