Tiff Server security update
Background
Tiff Server was one of Aquaforest’s first products, released in 2002.
It enabled users to view TIFF files within a web site or web-based application without requiring the use of special image viewer plugins or applets ( a requirement for the cutting edge Internet Explorer and Netscape Navigator browsers at that time).
It was designed to be used on an intranet, and was never intended to be exposed to the outside world. When TIFF Server was designed, the concept of zero-trust networking and the risk of network-internal threats had not yet entered the mainstream industry consciousness.
Over the years, additional features were added but the underlying model has remained.
Security Advisory
External partners have identified that organisations running an insufficiently configured instance of TIFF Server may allow an unauthenticated attacker to craft a request that abuses certain parameters available in TIFF Server’s exposed ASP.NET forms. This weakness can be mapped to the MITRE CWE framework as CWE-22.
Parameters in the *.aspx
forms bundled in TIFF Server are designed to leverage the capabilities of IIS to allow it to source files from arbitrary provided locations in order to maximise customisation flexibility. At the time, the implications of these original design decisions were unclear but meant that carefully crafted requests to certain insufficiently configured servers may be able to:
-
Allow an unauthenticated user to view documents that should otherwise require authentication.
-
Detect directories that the user TIFF Server is running as has access to.
-
Count files within directories the user TIFF Server is running as has access to.
-
Detect if files exist in locations TIFF Server has access to.
This architectural design pattern that could allow this flaw is present in all versions of TIFF Server.
As this functionality is intended to allow customisation of features provided by TIFF Server and may be in use by customers still using TIFF Server, there is not a “one-size-fits-all” resolution that does not require customer-side configuration.
Improving Security Configuration within TIFF Server
The best way to mitigate the risk of an unauthenticated attacker accessing contents that should not be accessible is to use the Custom Auditing and Security option supplied in Section 13 of the Tiff Server Reference Guide to implement allowlisting for limiting incoming requests to only the directories intended for use with TIFF Server.
Though the Custom Auditing and Security option and its use is described in the reference guide, we had not previously provided examples of using it.
In order to provide guidance to our customers still using TIFF Server, we have provided an example below for implementing such a configuration. The following implementation uses a file containing a list of accepted file paths:
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Web; /// <summary> /// The following AquaforestTIFFServerAudit code is designed to check /// the filepath is within the allowlisted URLs /// </summary> namespace AquaforestTIFFServerAudit { /// <summary> /// This code has a hard coded filepath for the AllowList /// </summary> public class TIFFServerAudit { /// <summary> /// This is a skeleton function, add features as required. /// </summary> /// <param name="fileName"></param> /// <param name="action"></param> /// <param name="AUTH_USER"></param> /// <returns></returns> public static bool securityCheck(string fileName, string action, string AUTH_USER) { // Add check that filepath is within the AllowList // Get the filepath from the filename string filePath = Path.GetDirectoryName(Path.GetFullPath(fileName).ToLowerInvariant()); // If it is not in the allowlist end the response. if (!TiffServerAllowlist.InAllowList(filePath)) { return false; } else { return true; } // Return False to disable access } public static void logAction(string action) { // Action is one of : // VIEW, PRINT, PDF, SAVEPDF, GETANNOTATIONS, SAVEANNOTATIONS, EDIT, SAVEEDIT try { // Get the file path from one of these supplied sources depending upon the supplied Action string fileName = HttpContext.Current.Request["FN"]; if (action == "EDIT" || action == "SAVEEDIT") { fileName = HttpContext.Current.Request["filename"]; } else if (action == "GETANNOTATIONS") { fileName = HttpContext.Current.Server.MapPath(HttpContext.Current.Request["an_url"]); } else if (action == "SAVEANNOTATIONS") { fileName = HttpContext.Current.Request["anfile"]; } // Audit operations string status = "OK"; // Default string auditFileName = (string)HttpContext.Current.Session["ts_audit_file"]; string PC = HttpContext.Current.Request["PC"]; string AUTH_USER = HttpContext.Current.Request.ServerVariables["AUTH_USER"]; string REMOTE_HOST = HttpContext.Current.Request.ServerVariables["REMOTE_HOST"]; if (auditFileName == null || auditFileName == "") { if (!securityCheck(fileName, action, AUTH_USER)) { HttpContext.Current.Response.Write($"You do not have permission to {action} this file.{Environment.NewLine}{Path.GetExtension(fileName)}{Environment.NewLine}Please contact your system administrator."); HttpContext.Current.Response.End(); } return; } /* * Any Security Checks May Go Here and Terminate Response with * HttpContext.Current.Response.End() before or after writing log entry */ if (!securityCheck(fileName,action,AUTH_USER)) { if (action == "GETANNOTATIONS") { HttpContext.Current.Response.Write("<ts_annot></ts_annot>"); HttpContext.Current.Response.End(); } if (PC == "Y") { HttpContext.Current.Response.Write("afPageCount=-5;afMessage='You do not have permission to access this file. Please contact your system administrator.';"); HttpContext.Current.Response.End(); } else { HttpContext.Current.Response.Clear(); HttpContext.Current.Response.End(); } } if (action == "VIEW" && PC != "Y") { // Only log for the initial page count request, not for each page return; } System.IO.StreamWriter auditFile = new System.IO.StreamWriter(auditFileName, true); if (auditFile != null) { auditFile.WriteLine(DateTime.Now + "," + action+","+status+","+fileName+","+AUTH_USER+","+REMOTE_HOST+","+HttpContext.Current.Request.Url); auditFile.WriteLine(); auditFile.Flush(); auditFile.Close(); } } catch (WebException wex) { } catch(Exception ex) { } } } /// <summary> /// This static class checks if a filepath is included in the AllowList /// </summary> public static class TiffServerAllowlist { /// <summary> /// Static list of AllowListed locations /// </summary> private static List<string> _AllowList; /// <summary> /// Constructor that loads up the allowlist from the filepath /// </summary> static TiffServerAllowlist() { _AllowList = new List<string>(); // This is hard coded to a specified location, it will need to be edited and pointed at a valid text file. // The file contains root paths that TifServer is allowed to access. string temp =Path.GetFullPath(@"C:\inetpub\wwwroot\tiffserver\AllowList.txt"); if (File.Exists(temp)) { string contents = File.ReadAllText(temp); foreach (string line in contents.Split('\n')) { _AllowList.Add(line.Replace("\r", "")); } } } /// <summary> /// Returns whether the supplied filepath is within the AllowList /// </summary> /// <param name="filePath"></param> /// <returns></returns> public static bool InAllowList(string filePath) { bool result; if (_AllowList.Count == 0) { result = true; } else { result = false; foreach(string rootPath in _AllowList) { if (filePath.Contains(rootPath)) { result = true; break; } } } return result; } } }
Example allowlist file:
D:\data\project_1\tiffs D:\data\project_2\tiffs C:\inetpub\wwwroot\tiffserver\samples
As the full source of the Custom Auditing and Security class is available, it’s possible to edit it in other ways if the example given above is not compatible with the way you have implemented TIFF Server.
The future…
As described in the blog post TIFF Server Sunsetting (aquaforest.com), Tiff Server will be sunsetted by 31 May 2024. We will continue to provide product support for existing customers until that date, but no updates are planned prior to sunset.
We have provided the example above to help customers hold themselves over until the sunset date while planning their migration to other solutions. Customers are not advised to remain using TIFF Server after the sunset date because no further updates, support, or guidance will be offered at that time.
We would suggest that if you are a current user of TIFF Server and have not begun to plan a replacement, this would be a good opportunity to look at our PSPDFKit Web-Standalone solution that has superseded TIFF Server.
You can explore a demo of the PSPDFKit image viewer here and get started with a trial by following this link.
As a valued customer, we’d like to provide you with a custom offer tailored to your needs. If you are not sure what products best suit your usage needs, our sales team can help work with you to determine a solution for your needs moving forward. To discuss your requirements, please contact us via the sales team.