Skip to content
This repository was archived by the owner on Oct 14, 2024. It is now read-only.

Commit 26874e4

Browse files
security hardening
1 parent d464d36 commit 26874e4

File tree

12 files changed

+238
-46
lines changed

12 files changed

+238
-46
lines changed

AjaxControlToolkit.SampleSite/AjaxFileUpload/AjaxFileUpload.aspx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
3939
function createFileInfo(e) {
4040
var holder = document.createElement('div');
41-
holder.appendChild(document.createTextNode(e.get_fileName() + ' with size ' + e.get_fileSize() + ' bytes'));
41+
holder.appendChild(document.createTextNode(e.get_fileName() + ' with size ' + (e.get_fileSize() / 1024).toFixed(2) + ' KB'));
4242
4343
return holder;
4444
}

AjaxControlToolkit.SampleSite/Global.asax

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<%@ Application Language="C#" %>
22
<%@ Import Namespace="System.Web.Optimization" %>
33
<%@ Import Namespace="AjaxControlToolkit" %>
4+
<%@ Import Namespace="System.IO" %>
45

56
<script RunAt="server">
67
@@ -12,6 +13,13 @@
1213
"~/Scripts/WebForms/MsAjax/MicrosoftAjaxWebForms.js"));
1314
1415
BundleTable.EnableOptimizations = true;
16+
17+
var tempFolder = Server.MapPath(ToolkitConfig.TempFolder);
18+
foreach(var dir in Directory.EnumerateDirectories(tempFolder)) {
19+
try {
20+
Directory.Delete(dir, true);
21+
} catch { }
22+
}
1523
}
1624
1725
</script>

AjaxControlToolkit.SampleSite/Web.config

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212
renderStyleLinks="false"
1313
htmlSanitizer="AjaxControlToolkit.HtmlEditor.Sanitizer.DefaultHtmlSanitizer, AjaxControlToolkit.HtmlEditor.Sanitizer"
1414
tempFolder="~/Temp"/>
15+
<location path="Temp">
16+
<system.webServer>
17+
<handlers>
18+
<clear/>
19+
</handlers>
20+
<modules>
21+
<clear/>
22+
</modules>
23+
</system.webServer>
24+
</location>
1525
<system.web>
1626
<compilation debug="true" targetFramework="4.0"/>
1727
<machineKey validation="SHA1" decryptionKey="7EE421F6987EAFF4998E0F2ED5544AF1B931C82A1602BC2E" validationKey="5278D83EDD8E36C27E019D3E975D62A3FDF0E8EF50DB69F659D03EB18A4459D2B3271AA075173012EF122E2B7BFA49CDE16CC0DCC68F3E862E1EEE491D300DC9" />

AjaxControlToolkit.Tests/AjaxControlToolkit.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
<Folder Include="Properties\" />
8282
</ItemGroup>
8383
<ItemGroup>
84+
<Compile Include="AjaxFileUploadTests.cs" />
8485
<Compile Include="BaseValidatorTests.cs" />
8586
<Compile Include="BundleResolverTests.cs" />
8687
<Compile Include="JasmineTests.cs" />
@@ -96,6 +97,7 @@
9697
<Compile Include="LocalizationTests.cs" />
9798
<Compile Include="HtmlSanitizer\DefaultHtmlSanitizerTests.cs" />
9899
<Compile Include="MultipartFormDataParserTests.cs" />
100+
<Compile Include="WorkerRequest.cs" />
99101
</ItemGroup>
100102
<ItemGroup>
101103
<ProjectReference Include="..\AjaxControlToolkit.HtmlEditor.Sanitizer\AjaxControlToolkit.HtmlEditor.Sanitizer.csproj">
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using NUnit.Framework;
2+
using System;
3+
using System.IO;
4+
using System.Web;
5+
using System.Web.Hosting;
6+
7+
namespace AjaxControlToolkit.Tests {
8+
9+
[TestFixture]
10+
public class AjaxFileUploadTests {
11+
string _tempFolder;
12+
13+
[OneTimeSetUp]
14+
public void Init() {
15+
_tempFolder = AjaxFileUpload.GetRootTempFolder();
16+
}
17+
18+
[SetUp]
19+
public void ClearTempFoder() {
20+
if(Directory.Exists(_tempFolder))
21+
Directory.Delete(_tempFolder, true);
22+
}
23+
24+
[Test]
25+
public void NotAllowedFileExtensionIsBlocked() {
26+
var request = new WorkerRequest("", "fileName=aaa.exe", "");
27+
var context = new HttpContext(request);
28+
Assert.Throws<Exception>(() => AjaxFileUploadHelper.Process(context));
29+
}
30+
31+
[Test]
32+
public void AllowedFileExtensionIsAccepted() {
33+
var request = new WorkerRequest("------WebKitFormBoundaryCqenIHPHe1ZTCr0d\r\nContent-Disposition: form-data; name=\"act-file-data\"; filename=\"zero.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n\r\n------WebKitFormBoundaryCqenIHPHe1ZTCr0d--\r\n", "filename=aaa.jpg&fileId=E63F2078-D5C7-66FA-5CAD-02C169149BD5", "multipart/form-data; boundary=----WebKitFormBoundaryCqenIHPHe1ZTCr0d");
34+
var context = new HttpContext(request);
35+
AjaxFileUploadHelper.Process(context);
36+
Assert.True(File.Exists(Path.Combine(_tempFolder, "E63F2078-D5C7-66FA-5CAD-02C169149BD5", "aaa.jpg.tmp")));
37+
}
38+
39+
[Test, Timeout(5000)]
40+
public void EmptyBodyDoesNotHangHandler() {
41+
var request = new WorkerRequest("", "filename=aaa.jpg", "");
42+
var context = new HttpContext(request);
43+
AjaxFileUploadHelper.Process(context);
44+
}
45+
46+
[Test]
47+
public void CheckTempFileExtension() {
48+
var root = AjaxFileUpload.GetRootTempFolder();
49+
Assert.Throws<Exception>(() => AjaxFileUpload.CheckTempFilePath(Path.Combine(root, @"E63F2078-D5C7-66FA-5CAD-02C169149BD5\a.exe")));
50+
}
51+
52+
[Test]
53+
public void CheckTempFolderMask() {
54+
var root = AjaxFileUpload.GetRootTempFolder();
55+
Assert.Throws<Exception>(() => AjaxFileUpload.CheckTempFilePath(Path.Combine(root, @"b\a.tmp")));
56+
}
57+
58+
[Test]
59+
public void CheckTempFolderNesting() {
60+
var root = AjaxFileUpload.GetRootTempFolder();
61+
Assert.Throws<Exception>(() => AjaxFileUpload.CheckTempFilePath(Path.Combine(root, @"extraFolder\E63F2078-D5C7-66FA-5CAD-02C169149BD5\a.tmp")));
62+
}
63+
}
64+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Globalization;
2+
using System.IO;
3+
using System.Net;
4+
using System.Text;
5+
using System.Web.Hosting;
6+
7+
namespace AjaxControlToolkit.Tests {
8+
public class WorkerRequest : SimpleWorkerRequest {
9+
Stream _data;
10+
string _size;
11+
string _contentType;
12+
13+
public WorkerRequest(string body, string query, string contentType)
14+
: base("", "", "", query, new StringWriter()) {
15+
_data = new MemoryStream(Encoding.ASCII.GetBytes(body));
16+
_size = _data.Length.ToString(CultureInfo.InvariantCulture);
17+
_contentType = contentType;
18+
}
19+
20+
public override string GetKnownRequestHeader(int index) {
21+
switch((HttpRequestHeader)index) {
22+
case HttpRequestHeader.ContentLength:
23+
return _size;
24+
case HttpRequestHeader.ContentType:
25+
return _contentType;
26+
}
27+
return base.GetKnownRequestHeader(index);
28+
}
29+
30+
public override int ReadEntityBody(byte[] buffer, int offset, int size) {
31+
return _data.Read(buffer, offset, size);
32+
}
33+
34+
public override int ReadEntityBody(byte[] buffer, int size) {
35+
return ReadEntityBody(buffer, 0, size);
36+
}
37+
}
38+
}

AjaxControlToolkit/AjaxControlToolkitConfigSection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public string TempFolder {
3131
set { base["tempFolder"] = value; }
3232
}
3333

34+
[ConfigurationProperty("additionalUploadFileExtensions", IsRequired = false)]
35+
public string AdditionalUploadFileExtensions {
36+
get { return (string)base["additionalUploadFileExtensions"]; }
37+
set { base["additionalUploadFileExtensions"] = value; }
38+
}
39+
3440
[ConfigurationProperty("customControls", IsDefaultCollection = false)]
3541
[ConfigurationCollection(typeof(CustomControlsCollection))]
3642
public CustomControlsCollection CustomControls {

AjaxControlToolkit/AjaxFileUpload/AjaxFileUpload.cs

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Web.UI.HtmlControls;
1111
using System.Drawing;
1212
using System.Collections.ObjectModel;
13+
using System.Text.RegularExpressions;
1314

1415
namespace AjaxControlToolkit {
1516

@@ -301,34 +302,23 @@ public void SaveAs(string fileName) {
301302
}
302303

303304
public static void CleanAllTemporaryData() {
304-
var dirInfo = new DirectoryInfo(BuildRootTempFolder());
305+
var dirInfo = new DirectoryInfo(GetRootTempFolder());
305306
foreach(var dir in dirInfo.GetDirectories()) {
306307
dir.Delete(true);
307308
}
308309
}
309310

310-
public static string BuildTempFolder(string fileId) {
311-
return Path.Combine(BuildRootTempFolder(), fileId);
311+
public static string GetTempFolder(string fileId) {
312+
return Path.Combine(GetRootTempFolder(), fileId);
312313
}
313314

314-
public static string BuildRootTempFolder() {
315+
public static string GetRootTempFolder() {
315316
var userPath = ToolkitConfig.TempFolder;
316317

317-
if(!String.IsNullOrWhiteSpace(userPath)) {
318-
var physicalPath = GetPhysicalPath(userPath);
319-
320-
if(!Directory.Exists(physicalPath))
321-
throw new IOException(String.Format("Temp directory '{0}' does not exist.", physicalPath));
322-
323-
return physicalPath;
324-
}
325-
326-
var defaultPath = Path.Combine(Path.GetTempPath(), DefaultTempSubDir);
327-
328-
if(!Directory.Exists(defaultPath))
329-
Directory.CreateDirectory(defaultPath);
330-
331-
return defaultPath;
318+
if(!String.IsNullOrWhiteSpace(userPath))
319+
return GetPhysicalPath(userPath);
320+
321+
return Path.Combine(Path.GetTempPath(), DefaultTempSubDir);
332322
}
333323

334324
static string GetPhysicalPath(string path) {
@@ -504,6 +494,33 @@ static string CombineUrl(string part1, string part2) {
504494
return part1 + part2;
505495
}
506496

497+
public static void CheckTempFilePath(string tmpFilePath) {
498+
if(!IsValidExtension(tmpFilePath)
499+
|| !IsValidLastFolderName(tmpFilePath)
500+
|| !IsImmediateFolder(tmpFilePath))
501+
throw new Exception("Insecure operation prevented");
502+
}
503+
504+
static bool IsImmediateFolder(string tmpFilePath) {
505+
var rootTempFolderFromParam = Path.GetFullPath(GetGrandParentDirectoryName(tmpFilePath));
506+
var rootTempFolder = Path.GetFullPath(GetRootTempFolder());
507+
508+
return String.Compare(rootTempFolder, rootTempFolderFromParam, true) == 0;
509+
}
510+
511+
static bool IsValidLastFolderName(string tmpFilePath) {
512+
var directory = Path.GetFullPath(Path.GetDirectoryName(tmpFilePath));
513+
return Regex.IsMatch(directory, @"[\w-]{36}$", RegexOptions.IgnoreCase);
514+
}
515+
516+
static bool IsValidExtension(string tmpFilePath) {
517+
return String.Compare(Path.GetExtension(tmpFilePath), Constants.UploadTempFileExtension, true) == 0;
518+
}
519+
520+
static string GetGrandParentDirectoryName(string tmpFilePath) {
521+
return Path.GetDirectoryName(Path.GetDirectoryName(tmpFilePath));
522+
}
523+
507524
class UploadRequestProcessor {
508525
public HttpContext Context;
509526

@@ -619,7 +636,7 @@ void XhrComplete() {
619636
void XhrDone(string fileId) {
620637
AjaxFileUploadEventArgs args;
621638

622-
var tempFolder = BuildTempFolder(fileId);
639+
var tempFolder = GetTempFolder(fileId);
623640
if(!Directory.Exists(tempFolder))
624641
return;
625642

@@ -628,18 +645,24 @@ void XhrDone(string fileId) {
628645
return;
629646

630647
var fileInfo = new FileInfo(files[0]);
631-
SetUploadedFilePath(fileInfo.FullName);
632648

649+
CheckTempFilePath(fileInfo.FullName);
650+
SetUploadedFilePath(fileInfo.FullName);
651+
var originalFilename = StripTempFileExtension(fileInfo.Name);
633652
args = new AjaxFileUploadEventArgs(
634-
fileId, AjaxFileUploadState.Success, "Success", fileInfo.Name, (int)fileInfo.Length,
635-
fileInfo.Extension);
653+
fileId, AjaxFileUploadState.Success, "Success", originalFilename, (int)fileInfo.Length,
654+
Path.GetExtension(originalFilename));
636655

637656
if(UploadComplete != null)
638657
UploadComplete(this, args);
639658

640659
Response.Write(new JavaScriptSerializer().Serialize(args));
641660
}
642661

662+
static string StripTempFileExtension(string fileName) {
663+
return fileName.Substring(0, fileName.Length - Constants.UploadTempFileExtension.Length);
664+
}
665+
643666
void XhrCancel(string fileId) {
644667
AjaxFileUploadHelper.Abort(Context, fileId);
645668
}

AjaxControlToolkit/AjaxFileUpload/AjaxFileUploadEventArgs.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@ public byte[] GetContents() {
6363
}
6464

6565
public Stream GetStreamContents() {
66-
var dir = AjaxFileUpload.BuildTempFolder(this._fileId);
67-
return File.OpenRead(Path.Combine(dir, this._fileName));
66+
var dir = AjaxFileUpload.GetTempFolder(this._fileId);
67+
var filePath = Path.Combine(dir, this._fileName) + Constants.UploadTempFileExtension;
68+
AjaxFileUpload.CheckTempFilePath(filePath);
69+
return File.OpenRead(filePath);
6870
}
6971

7072
public void DeleteTemporaryData() {
71-
var dirInfo = new DirectoryInfo(AjaxFileUpload.BuildTempFolder(this._fileId));
73+
var dirInfo = new DirectoryInfo(AjaxFileUpload.GetTempFolder(this._fileId));
7274
if(dirInfo.Exists)
7375
dirInfo.Delete(true);
7476
}

0 commit comments

Comments
 (0)