Browse Source

Tightening security in processing module.

Former-commit-id: ec9c732224e2d631e3eb38c10302ee8f4974dd65
af/merge-core
James South 12 years ago
parent
commit
cda70144e5
  1. 2
      src/ImageProcessor.Web/Helpers/ImageHelpers.cs
  2. 283
      src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs
  3. 0
      src/ImageProcessorConsole/Program.cs
  4. 2
      src/Images/Penguins-8.png.REMOVED.git-id

2
src/ImageProcessor.Web/Helpers/ImageHelpers.cs

@ -43,7 +43,7 @@ namespace ImageProcessor.Web.Helpers
/// <returns>True the value contains a valid image extension, otherwise false.</returns> /// <returns>True the value contains a valid image extension, otherwise false.</returns>
public static bool IsValidImageExtension(string fileName) public static bool IsValidImageExtension(string fileName)
{ {
return EndFormatRegex.IsMatch(fileName) || string.IsNullOrWhiteSpace(Path.GetExtension(fileName)); return EndFormatRegex.IsMatch(fileName);
} }
/// <summary> /// <summary>

283
src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs

@ -270,183 +270,181 @@ namespace ImageProcessor.Web.HttpModules
HttpRequest request = context.Request; HttpRequest request = context.Request;
IImageService currentService = this.GetImageServiceForRequest(request); IImageService currentService = this.GetImageServiceForRequest(request);
if (currentService == null) if (currentService != null)
{ {
throw new HttpException(500, "No ImageService found for current request."); bool isFileLocal = currentService.IsFileLocalService;
} string requestPath = string.Empty;
string queryString = string.Empty;
bool isFileLocal = currentService.IsFileLocalService; string urlParameters = string.Empty;
string requestPath = string.Empty;
string queryString = string.Empty;
string urlParameters = string.Empty;
if (!isFileLocal)
{
// We need to split the querystring to get the actual values we want.
string urlDecode = HttpUtility.UrlDecode(request.QueryString.ToString());
if (!string.IsNullOrWhiteSpace(urlDecode)) if (!isFileLocal)
{ {
// UrlDecode seems to mess up in some circumstance. // We need to split the querystring to get the actual values we want.
if (urlDecode.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1) string urlDecode = HttpUtility.UrlDecode(request.QueryString.ToString());
if (!string.IsNullOrWhiteSpace(urlDecode))
{ {
urlDecode = urlDecode.Replace(":/", "://"); // UrlDecode seems to mess up in some circumstance.
} if (urlDecode.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
{
urlDecode = urlDecode.Replace(":/", "://");
}
string[] paths = urlDecode.Split('?'); string[] paths = urlDecode.Split('?');
requestPath = paths[0]; requestPath = paths[0];
// Handle extension-less urls. // Handle extension-less urls.
if (paths.Length > 2) if (paths.Length > 2)
{ {
queryString = paths[2]; queryString = paths[2];
urlParameters = paths[1]; urlParameters = paths[1];
} }
else if (paths.Length > 1) else if (paths.Length > 1)
{ {
queryString = paths[1]; queryString = paths[1];
}
} }
} }
} else
else {
{ requestPath = HostingEnvironment.MapPath(request.Path);
requestPath = HostingEnvironment.MapPath(request.Path); queryString = HttpUtility.UrlDecode(request.QueryString.ToString());
queryString = HttpUtility.UrlDecode(request.QueryString.ToString()); }
}
// Only process requests that pass our sanitizing filter.
if (ImageHelpers.IsValidImageExtension(requestPath) && !string.IsNullOrWhiteSpace(queryString))
{
// Replace any presets in the querystring with the actual value.
queryString = this.ReplacePresetsInQueryString(queryString);
string parts = !string.IsNullOrWhiteSpace(urlParameters) ? "?" + urlParameters : string.Empty; // Only process requests that pass our sanitizing filter.
string fullPath = string.Format("{0}{1}?{2}", requestPath, parts, queryString); if (!string.IsNullOrWhiteSpace(queryString))
{
// Replace any presets in the querystring with the actual value.
queryString = this.ReplacePresetsInQueryString(queryString);
// Create a new cache to help process and cache the request. string parts = !string.IsNullOrWhiteSpace(urlParameters) ? "?" + urlParameters : string.Empty;
DiskCache cache = new DiskCache(requestPath, fullPath); string fullPath = string.Format("{0}{1}?{2}", requestPath, parts, queryString);
string cachedPath = cache.CachedPath;
// Since we are now rewriting the path we need to check again that the current user has access // Create a new cache to help process and cache the request.
// to the rewritten path. DiskCache cache = new DiskCache(requestPath, fullPath);
// Get the user for the current request string cachedPath = cache.CachedPath;
// If the user is anonymous or authentication doesn't work for this suffix avoid a NullReferenceException
// in the UrlAuthorizationModule by creating a generic identity.
string virtualCachedPath = cache.VirtualCachedPath;
IPrincipal user = context.User ?? new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]); // Since we are now rewriting the path we need to check again that the current user has access
// to the rewritten path.
// Get the user for the current request
// If the user is anonymous or authentication doesn't work for this suffix avoid a NullReferenceException
// in the UrlAuthorizationModule by creating a generic identity.
string virtualCachedPath = cache.VirtualCachedPath;
// Do we have permission to call UrlAuthorizationModule.CheckUrlAccessForPrincipal? IPrincipal user = context.User ?? new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]);
PermissionSet permission = new PermissionSet(PermissionState.None);
permission.AddPermission(new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted));
bool hasPermission = permission.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet);
bool isAllowed = true; // Do we have permission to call UrlAuthorizationModule.CheckUrlAccessForPrincipal?
PermissionSet permission = new PermissionSet(PermissionState.None);
permission.AddPermission(new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted));
bool hasPermission = permission.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet);
// Run the rewritten path past the authorization system again. bool isAllowed = true;
// We can then use the result as the default "AllowAccess" value
if (hasPermission && !context.SkipAuthorization)
{
isAllowed = UrlAuthorizationModule.CheckUrlAccessForPrincipal(virtualCachedPath, user, "GET");
}
if (isAllowed) // Run the rewritten path past the authorization system again.
{ // We can then use the result as the default "AllowAccess" value
// Is the file new or updated? if (hasPermission && !context.SkipAuthorization)
bool isNewOrUpdated = cache.IsNewOrUpdatedFile(cachedPath); {
isAllowed = UrlAuthorizationModule.CheckUrlAccessForPrincipal(virtualCachedPath, user, "GET");
}
// Only process if the file has been updated. if (isAllowed)
if (isNewOrUpdated)
{ {
// Process the image. // Is the file new or updated?
using (ImageFactory imageFactory = new ImageFactory(preserveExifMetaData != null && preserveExifMetaData.Value)) bool isNewOrUpdated = cache.IsNewOrUpdatedFile(cachedPath);
// Only process if the file has been updated.
if (isNewOrUpdated)
{ {
using (await Locker.LockAsync(cachedPath)) // Process the image.
using (
ImageFactory imageFactory =
new ImageFactory(preserveExifMetaData != null && preserveExifMetaData.Value))
{ {
byte[] imageBuffer; using (await Locker.LockAsync(cachedPath))
if (!isFileLocal)
{
Uri uri = new Uri(requestPath + "?" + urlParameters);
imageBuffer = await currentService.GetImage(uri);
}
else
{ {
imageBuffer = await currentService.GetImage(requestPath); byte[] imageBuffer;
if (!isFileLocal)
{
Uri uri = new Uri(requestPath + "?" + urlParameters);
imageBuffer = await currentService.GetImage(uri);
}
else
{
imageBuffer = await currentService.GetImage(requestPath);
}
using (MemoryStream memoryStream = new MemoryStream(imageBuffer))
{
// Reset the position of the stream to ensure we're reading the correct part.
memoryStream.Position = 0;
// Process the Image
imageFactory.Load(memoryStream).AutoProcess(queryString).Save(cachedPath);
// Add to the cache.
cache.AddImageToCache(cachedPath);
// Store the cached path, response type, and cache dependency in the context for later retrieval.
context.Items[CachedPathKey] = cachedPath;
context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType;
context.Items[CachedResponseFileDependency] = new List<string> { cachedPath };
}
} }
}
}
using (MemoryStream memoryStream = new MemoryStream(imageBuffer)) // Image is from the cache so the mime-type will need to be set.
{ if (context.Items[CachedResponseTypeKey] == null)
// Reset the position of the stream to ensure we're reading the correct part. {
memoryStream.Position = 0; string mimetype = ImageHelpers.GetMimeType(cachedPath);
// Process the Image
imageFactory.Load(memoryStream)
.AutoProcess(queryString)
.Save(cachedPath);
// Add to the cache.
cache.AddImageToCache(cachedPath);
// Store the cached path, response type, and cache dependency in the context for later retrieval. if (!string.IsNullOrEmpty(mimetype))
context.Items[CachedPathKey] = cachedPath; {
context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType; context.Items[CachedResponseTypeKey] = mimetype;
context.Items[CachedResponseFileDependency] = new List<string> { cachedPath };
}
} }
} }
}
// Image is from the cache so the mime-type will need to be set.
if (context.Items[CachedResponseTypeKey] == null)
{
string mimetype = ImageHelpers.GetMimeType(cachedPath);
if (!string.IsNullOrEmpty(mimetype)) if (context.Items[CachedResponseFileDependency] == null)
{ {
context.Items[CachedResponseTypeKey] = mimetype; context.Items[CachedResponseFileDependency] = new List<string> { cachedPath };
} }
}
if (context.Items[CachedResponseFileDependency] == null) string incomingEtag = context.Request.Headers["If" + "-None-Match"];
{
context.Items[CachedResponseFileDependency] = new List<string> { cachedPath };
}
string incomingEtag = context.Request.Headers["If" + "-None-Match"]; if (incomingEtag != null && !isNewOrUpdated)
{
// Set the Content-Length header so the client doesn't wait for
// content but keeps the connection open for other requests.
context.Response.AddHeader("Content-Length", "0");
context.Response.StatusCode = (int)HttpStatusCode.NotModified;
context.Response.SuppressContent = true;
if (incomingEtag != null && !isNewOrUpdated) if (isFileLocal)
{ {
// Set the Content-Length header so the client doesn't wait for // Set the headers and quit.
// content but keeps the connection open for other requests. this.SetHeaders(context, (string)context.Items[CachedResponseTypeKey], new List<string> { requestPath, cachedPath });
context.Response.AddHeader("Content-Length", "0"); return;
context.Response.StatusCode = (int)HttpStatusCode.NotModified; }
context.Response.SuppressContent = true;
if (!isFileLocal) this.SetHeaders(context, (string)context.Items[CachedResponseTypeKey], new List<string> { cachedPath });
{
// Set the headers and quit.
this.SetHeaders(context, (string)context.Items[CachedResponseTypeKey], new List<string> { requestPath, cachedPath });
return;
} }
this.SetHeaders(context, (string)context.Items[CachedResponseTypeKey], new List<string> { cachedPath }); // The cached file is valid so just rewrite the path.
context.RewritePath(virtualCachedPath, false);
}
else
{
throw new HttpException(403, "Access denied");
} }
// The cached file is valid so just rewrite the path.
context.RewritePath(virtualCachedPath, false);
} }
else else if (!isFileLocal)
{ {
throw new HttpException(403, "Access denied"); // Just re-point to the external url.
HttpContext.Current.Response.Redirect(requestPath);
} }
} }
else if (!isFileLocal)
{
// Just re-point to the external url.
HttpContext.Current.Response.Redirect(requestPath);
}
} }
/// <summary> /// <summary>
@ -539,7 +537,18 @@ namespace ImageProcessor.Web.HttpModules
} }
} }
return imageService ?? services.FirstOrDefault(s => string.IsNullOrWhiteSpace(s.Key)); if (imageService != null)
{
return imageService;
}
// Return the file based service
if (ImageHelpers.IsValidImageExtension(path))
{
return services.FirstOrDefault(s => string.IsNullOrWhiteSpace(s.Key));
}
return null;
} }
#endregion #endregion
} }

0
src/ImageProcessorConsole/Program.cs

2
src/Images/Penguins-8.png.REMOVED.git-id

@ -1 +1 @@
c433c806c6aba9b23e24ce55b26382e117c7a5d9 1672192a14349a4c93fe9ae885d57a3f34e6cc7c
Loading…
Cancel
Save