Andre's Blog
Perfection is when there is nothing left to take away
Server-side connector for FCK editor

FCK editor comes with a neat file browser that comes in very handy when adding images or links to other files located on the server. The editor ships with source files in all poplar scripting languages making it possible to use the feature with a minimal effort. However, ASP code was written in VBScript and didn't integrate well into my security model, so I decided to write my own one weekend.

Many paths

The file browser in the FCK editor communicates with the server through the Server Side Integration XML protocol. The protocol supports four commands: GetFolders, GetFoldersAndFiles, CreateFolder and FileUpload. Each command is accompanied by two paths - the current URL path and the current logical path. The latter often stirs some confusion, but, really, is a good way to present user-specific path for uploaded content. That is, when a user activates the file browser, the browser starts in the user's root directory, so the current logical path displayed in the file browser is the root (/), but the URL path might be something like (/users/1234/), where 1234 would be a user identifier.

Of course, one can use other identifier to create multiple logical paths. For example, if some website keeps movies and images in separate directories or even different servers, logical path would be shown as (/) for each of these types, but the URL would point to the actual location (e.g. /movies/ and /images/).

The connector

FCK editor ships with connectors in a variety of languages, such as PHP, ASP, ASP.Net, etc. By default the connector is configured as follows:

FCKConfig.ImageBrowserURL = FCKConfig.BasePath + 
   'filemanager/browser/default/browser.html?Type=Image&' + 
   'Connector=' + encodeURIComponent( FCKConfig.BasePath + 
   'filemanager/connectors/' + _FileBrowserLanguage + 
   '/connector.' + _FileBrowserExtension );

Link and Flash URLs are similar, except that they have different resource type. Since I wanted to write my own connector, I overrode these settings for all supported resource types in the custom configuration file to point to my own connector:

FCKConfig.ImageBrowserURL = FCKConfig.BasePath + 
   "filemanager/browser/default/browser.html?Type=Image&" +
   "Connector=/filemgr.asp";

Similarly, I changed the quick upload URL for all supported types:

FCKConfig.ImageUploadURL = "/upload.aspx?Type=Image";

Security

Access to the file system must be air-tight, as a single bug in this area will allow spammers to use the website as a launch pad for their activities. Things to look out for:

  • Make sure all input is expressed in well-defined character encoding to avoid attacks based on malformed multibyte character sequences
  • Make sure access to file browsing and upload is restricted to authenticated users
  • Make sure each user is rooted at their own directory and cannot see any files outside of that directory and its sub-directories
  • Make sure parent paths (i.e. anything containing the .. sequence) are not allowed
  • Make sure new file and directory names contain only characters from a small supported subset, such as Latin characters, brackets, parenthesis, underscore and a dot. Those who want Unicode file names must make sure that only valid ranges are allowed.
  • Restrict the maximum number of characters in a file or directory name to a reasonable number, which is usually less than or equal to 255.
  • Make sure to check for a path separator character when concatenating paths. Simple string splicing may produce exploitable arbitrary paths.
  • When generating errors, avoid disclosing too much information. For example, there is no need to report the actual physical path if a directory or a file could not be created.

File system

Now that I had my ASP scripts plugged into the FCK file browser, it was time to implement access to the file system. I chose a simple prefix-based approach, so all I needed to do was to take the path value sent by the editor, validate it as described above and concatenate with the user-specific base path. That is, given this request,

/filemgr.asp?Command=GetFolders&CurrentFolder=/images/

, the complete URL path could be derived by concatenating path components, which then can be mapped to the corresponding physical path using Server.MapPath:

var path = Server.MapPath("/users/" + user_id + CurrentFolder);

Note that this is a simplified example and proper concatenation of path components must ensure that there is a path separator character at the beginning or the end of each component to avoid creating malformed folder names.

The resulting path may be passed to FileSystemObject methods to traverse directories and locate files. For example, this code will produce a list of directories:

if((folder = fso.GetFolder(path)) == null)
   throw new Error(-7, "Cannot get a directory handle");

fsenum = new Enumerator(folder.SubFolders);

xmlfolders = "<Folders>"

while(!fsenum.atEnd()) { 
   xmlfolders += "<Folder name=\"" + 
         Server.HTMLEncode(fsenum.item().Name) + "\"/>";
   fsenum.moveNext();
}

xmlfolders += "<\/Folders>";

File upload

Instead of using good old ADO stream for quick file upload, I decided to try ASP.Net. Not having much experience with .Net, I searched the web for examples and found a few quite long ones, extensively using run-at-server forms and inputs. All of these examples look too VB/C# to me and, consequently, not something I would use. I did some more reading and I came up with code similar to this:

if((file = Request.Files.Get("NewFile")) == null)
   throw new MyError(-1, "Nothing to upload");

file.SaveAs(Server.MapPath(path + file.FileName));

Again, note that this is a simplified example and production code must validate the name of the file, properly concatenate file path and name and ensure the file size does not exceed the allowed maximum.

Conclusion

After a few hours I had a working file browser with file upload. Now I can finally shut down my FTP server, which is constantly being probed by spammers. Neat.

Comments:
Name:

Comment: