Skip to content

Latest commit

 

History

History

SecurityBestPractices.WebForms

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

ASP.NET Web Forms Security Best Practices

This article details security-related best practices for the ASP.NET Web Forms platform.

Each section within this document includes a specific security-related use-case scenario, with possible mitigation strategies.

The Visual Studio solutions herein include fully commented code samples. You will need to have DevExpress ASP.NET controls installed to load and compile each solution. If you are new to DevExpress, you can download our ASP.NET product installer from the DevExpress website.


1. Uploading Files

Related Controls: ASPxBinaryImage, ASPxUploadControl, ASPxFileManager, ASPxHtmlEditor, ASPxRichEdit, ASPxSpreadsheet

Security Risks: CWE-400, CWE-434

This section covers secure file upload operations and explores the following usage scenarios:

1.1. Stop Malicious Files from Being Uploaded

Visit UploadingFiles\UploadControl.aspx for a full code sample.

Consider a web application that allows users to upload files. In this scenario, a user can use a URL to access the uploaded files (for example: example.com/uploaded/uploaded-filename).

A security risk can be introduced if users are allowed to upload malicious files – files that can be executed on the server side. For example, a threat actor could upload an ASPX file that contains malicious code and guess its URL. If the threat actor is correct and requests this URL, the file would be executed on the server as if it were part of the application.

To familiarize yourself with the issue:

  1. Comment out the following lines to disable protection.

    \UploadingFiles\UploadControl.aspx:

    <%-- <ValidationSettings AllowedFileExtensions =".jpg,.png">
        </ValidationSettings> --%>
    

    \UploadingFiles\UploadControl.aspx.cs:

    using (var stream = file.FileContent) {
    // If additional checks are needed, execute them here before saving the file
    //if (!IsValidImage(stream)) {
    //    file.IsValid = false;
    //    e.ErrorText = "Validation failed!";
    //}
    //else {
        string fileName = string.Format("{0}{1}", MapPath("~/UploadingFiles/Images/"), file.FileName);
        file.SaveAs(fileName, true);
    //}
    //}
  2. Run the solution and open the UploadingFiles/UploadControl.aspx page.

  3. Upload the \App_Data\TestData\Malicious.aspx file.

  4. Visit the following URL to execute the uploaded file on the server: /UploadingFiles/Images/Malicious.aspx

To mitigate this risk, you must:

  1. Initialize the AllowedFileExtensions setting with a list of allowed file extensions. The server will then validate uploaded file type:
<ValidationSettings AllowedFileExtensions=".jpg, .png">
</ValidationSettings>
  1. Disable file execution within the upload folder (see relevant StackOverflow question):
  <location path="UploadingFiles/Images">
    <system.webServer>
      <handlers>
        <clear />
        <add
          name="StaticFile"
          path="*" verb="*"
          modules="StaticFileModule"
          resourceType="Either"
          requireAccess="Read" />
      </handlers>
    </system.webServer>
  </location>

The table below lists default file extensions allowed by various UI controls that support file upload operations:

Control Allowed Extensions
ASPxUploadControl any
ASPxBinaryImage any
ASPxFileManager any
ASPxHtmlEditor .jpe, .jpeg, .jpg, .gif, .png
.mp3, .ogg
.swf
.mp4
ASPxRichEdit .doc, .docx, .epub, .html, .htm, .mht, .mhtml, .odt, .txt, .rtf, .xml
ASPxSpreadsheet .xlsx, .xlsm, .xls, .xltx, .xltm, .xlt, .txt, .csv

1.2. Prevent Uncontrolled Resource Consumption

1.2.1 Prevent Uncontrolled Memory Consumption

Visit UploadingFiles/UploadControlMemory.aspx for a full code sample.

Consider a web application that allows files - of any size - to be uploaded to a server.

In this scenario, a threat actor can upload very large files to consume server memory and disk space - a form of Denial of Service (DoS) attack.

To mitigate this vulnerability:

  1. Use the FileContent property (a Stream) rather than the FileBytes property (a byte array) to access file contents. This prevents memory overflows and other issues when the server processes large files.
protected void uploadControl_FilesUploadComplete(object sender, DevExpress.Web.FilesUploadCompleteEventArgs e) {
    for(int i = 0; i < uploadControl.UploadedFiles.Length; i++) {
        UploadedFile file = uploadControl.UploadedFiles[i];

        // Recommended approach - use stream for large files
        using (var stream = file.FileContent) {
            DoProcessing(stream);
        }
    }
}
  1. Use the UploadControlValidationSettings.MaxFileSize property to specify the maximum size for uploaded files.

Note: In the Advanced uploading mode, files are loaded in small fragments (200KB by default). As such, setting the httpRuntime>maxRequestLength and requestLimits>maxAllowedContentLength options in web.config is not sufficient to prevent attacks.

See the Uploading Large Files documentation topic for more information in this regard.

The following controls limit the maximum size for upload operations by default:

  • The ASPxHtmlEditor defines a 31,457,280 byte limit for uploaded file size.
  • The ASPxSpreadsheet and ASPxRichEdit do not set a maximum size for an uploaded file, however there is a 31,457,280 byte limit for images to be inserted into a document. Note: Both controls must be explicitly configured to accept file uploads.

The File Manager control automatically allows files to be uploaded and does not impose any limitations on file size and extension. You can disable file upload operations with this code:

<ASPxFileManager ... >
    ...
    <SettingsUpload Enabled="false">
</ASPxFileManager>

Other file-related operations managed by the File Manager (copy, delete, download, etc.) are configured through the SettingsEditing property. All such operations are disabled by default.

1.2.2 Prevent Uncontrolled Disk Space Consumption

Security Risks: CWE-400

You should always monitor the total file size uploaded by end-users, otherwise a threat actor can perform a DoS attack by uploading too many files and consuming available disk space. You should always set a limit on the total size of uploaded files.

To restrict upload file size, check the upload directory's size in the Upload Control's FilesUploadComplete event handler:

protected void uploadControl_FilesUploadComplete(object sender, DevExpress.Web.FilesUploadCompleteEventArgs e) {
    if(uploadControl.UploadedFiles != null && uploadControl.UploadedFiles.Length > 0) {
        for(int i = 0; i < uploadControl.UploadedFiles.Length; i++) {
            UploadedFile file = uploadControl.UploadedFiles[i];
            if(file.IsValid && file.FileName != "") {
                // Check the upload folder's size taking into account the new files.
                const long DirectoryFileSizesLimit = 10000000; // bytes
                long totalFilesSize = GetDirectoryFileSizes(MapPath("~/UploadingFiles/Images/"));
                if(file.ContentLength + totalFilesSize > DirectoryFileSizesLimit) {
                    file.IsValid = false;
                    e.ErrorText = "Total files size exceeded!";
                } else {
    ...

For more information, refer to the following project: UploadingFiles/LimitDirectorySize.aspx.cs

1.3. Protect Temporary Files

Visit UploadingFiles/UploadControlTempFileName.aspx for a full code sample.

Consider an app that saves data to temporary files on the server before processing or writing results to a database.

To avoid security issues, you need to ensure that these files are inaccessible to third parties.

To mitigate this vulnerability:

  1. Store temporary files in a folder unreachable via URL (for example, App_Data).
  2. Use a dedicated file extension for temporary files on the server (for example, "\*.mytmp").
  3. Consider using the GetRandomFileName method to assign random file names.
protected void uploadControl_FilesUploadComplete(object sender, DevExpress.Web.FilesUploadCompleteEventArgs e) {
    if (uploadControl.UploadedFiles != null && uploadControl.UploadedFiles.Length > 0) {
        for (int i = 0; i < uploadControl.UploadedFiles.Length; i++) {
            UploadedFile file = uploadControl.UploadedFiles[i];
            if (file.FileName != "") {
                string fileName = string.Format("{0}{1}", MapPath("~/UploadingFiles/Processing/"),
                    Path.GetRandomFileName() + ".tmp");
                file.SaveAs(fileName, true);
                // DoFileProcessing(fileName);
                ...
            }
        }
    }
}

You can also define security permissions for folders and files accessible through the File Manager control. For more information, refer to the following help topic: Access Rules


2. Uploading and Displaying Binary Images

Related Controls: ASPxBinaryImage, ASPxUploadControl

Security Risks: CWE-79

Consider the situation where an image is uploaded to the server. The server generates a page that contains the image, and a user opens that page in their browser.

The possible security risk is as follows: a threat actor creates a file containing a malicious script. This file includes an image file extension. The file is added to the generated page by the server, and the page is downloaded by the user's browser. In this scenario, the malicious script is executed in the browser. Essentially, this is an example of XSS (Cross-site Scripting) via content-sniffing or CWE-79.

Note that newer versions of Google Chrome include embedded mechanisms to counteract this vulnerability. As of the original publication of this article, you could reproduce this vulnerability within the Mozilla Firefox browser.

To familiarize yourself with the issue

  1. Disable protection: Un-comment the call to the IsValidImage method in the following sample project: UploadingBinaryImage/UploadControl.aspx.cs
    // if(!IsValidImage(stream)) return;
  2. In the BinaryImageViewer.aspx.cs file, un-comment Response.ContentType = "image" line and comment out Response.ContentType = "image/jpeg".
  3. Run the example solution and open the UploadingBinaryImage/UploadControl.aspx page.
  4. Upload the \App_Data\TestData\Content-Sniffing-XSS.jpg file. Though labeled a JPEG image, this is a JavaScript file designed to emulate a malicious script.
  5. Open the UploadingBinaryImage/BinaryImageViewer.aspx page. Like any ASPX request, the markup is generated by the server on request, and the uploaded file is added to code behind.
  6. The JavaScript code from the uploaded file is executed by the browser:

malicious-image

To mitigate this vulnerability:

  1. Programmatically check whether the uploaded file is actually an image before you save it to server-side storage (see the IsValidImage method implementation).
protected void ASPxUploadControl1_FileUploadComplete(object sender,
  DevExpress.Web.FileUploadCompleteEventArgs e) {
    // Save contentBytes to a database here
    using(var stream = e.UploadedFile.FileContent) {
        if(!IsValidImage(stream)) return;

        // For demonstration purposes, content is saved to a file
        string fileName = Server.MapPath("~/App_Data/TestData/avatar.jpg");
        e.UploadedFile.SaveAs(fileName, true);
    }
}

static bool IsValidImage(Stream stream) {
    try {
        using(var image = Image.FromStream(stream)) {
            return true;
        }
    }
    catch(Exception) {
        return false;
    }
}
  1. Use the ASPxBinaryImage control to upload images. This control automatically checks image file type.

[Aspx]

<dx:ASPxBinaryImage ID="ASPxBinaryImage1" runat="server">
    <EditingSettings Enabled="True">
    </EditingSettings>
</dx:ASPxBinaryImage>
<dx:ASPxButton ID="ASPxButton1" runat="server" OnClick="ASPxButton1_Click" Text="Save">
</dx:ASPxButton>

[C#]

protected void ASPxButton1_Click(object sender, EventArgs e) {
    byte[] contentBytes = ASPxBinaryImage1.ContentBytes;
    // Save contentBytes to a database here

    // For demonstration purposes, content is saved to a file
    string fileName = Server.MapPath("~/App_Data/UploadedData/avatar.jpg");
    File.WriteAllBytes(fileName, contentBytes);
}

We highly recommend to always specify exact content type when you add binary data to the response:

Correct: Response.ContentType = "image/jpeg";

Potential security breach: Response.ContentType = "image".

Additionally, it is a good practice to add the X-CONTENT-TYPE-OPTIONS="nosniff" response header:

protected void ASPxButton1_Click(object sender, EventArgs e) {
    Response.Headers.Add("X-Content-Type-Options", "nosniff");
}

Notes:

  1. Microsoft Edge automatically detects file type based on its content, which prevents the execution of malicious scripts described herein.
  2. Make certain to specify max upload file size to prevent DoS attacks via large file uploads.

3. Authorization

This section includes information on the use of DevExpress UI controls within web applications that include authorization and access control. The following products/features are considered:

3.1. Reporting

To implement authorization logic for the Document Viewer, add a custom report storage derived from the ReportStorageWebExtension class. As a starting point, you can copy the reference implementation of such a storage class from ReportStorageWithAccessRules.cs to your project and fine-tune it as needs dictate. The following customizations must be considered:

A. Viewing Reports

In our sample project, the GetViewableReportDisplayNamesForCurrentUser method returns a list of reports that can be viewed by the current user (logged-in user):

// Logic used to obtain reports
public static IEnumerable<string> GetViewableReportDisplayNamesForCurrentUser() {
    var identityName = GetIdentityName();

    var result = new List<string> { reports[typeof(PublicReport)] }; // For unauthenticated users (i.e., public)

    if (identityName == "Admin") {
        result.AddRange(new[] { reports[typeof(AdminReport)], reports[typeof(JohnReport)] });
    } else if (identityName == "John") {
        result.Add(reports[typeof(JohnReport)]);
    }
    return result;
}

This method is then called from the overridden GetData method and other methods that need to interact with the report storage:

public override byte[] GetData(string url) {
    var reportNames = GetViewableReportDisplayNamesForCurrentUser();
    if (!reportNames.Contains(url))
        throw new UnauthorizedAccessException();

    XtraReport publicReport = CreateReportByDisplayName(url);
    using (MemoryStream ms = new MemoryStream()) {
        publicReport.SaveLayoutToXml(ms);
        return ms.GetBuffer();
    }
}

B. Editing Reports

In our sample project, the GetEditableReportNamesForCurrentUser method returns a list of reports that can be edited by the current user (logged-in user):

// Logic for getting reports available for editing
public static IEnumerable<string> GetEditableReportNamesForCurrentUser() {
    var identityName = GetIdentityName();
    if (identityName == "Admin") {
        return new[] { reports[typeof(AdminReport)], reports[typeof(JohnReport)] };
    }
    if (identityName == "John") {
        return new[] { reports[typeof(JohnReport)] };
    }

    return Array.Empty<string>();
}

This method is then called from the overridden IsValidUrl method and other methods that need to write report data.

public override bool IsValidUrl(string url) {
    var reportNames = GetEditableReportNamesForCurrentUser();
    return reportNames.Contains(url);
}

To prevent errors in the browser when you handle unauthorized access attempts, check access rights on the page's PageLoad event. If the user is not authorized to open the report, redirect them to a public page.

protected void Page_Load(object sender, EventArgs e) {
    var name = Request.QueryString["name"];
    var reportNames = ReportStorageWithAccessRules.GetEditableReportNamesForCurrentUser();
    if(reportNames.Contains(name))
        ASPxReportDesigner1.OpenReport(name);
    else
        Response.Redirect("~/Authorization/Reports/ReportViewerPage.aspx");
}

Register Custom Report Storage

Once you implement your custom report storage with required access rules, you must register it in the Global.asax.cs file:

DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension.RegisterExtensionGlobal(new ReportStorageWithAccessRules());

Make Certain that Authentication Rules are Applied

In our example project, follow the steps below to determine whether the customization has had any effect:

  • Open the PublicReportPage.aspx page without logging in. This page contains a Report Viewer.
  • Try to use the client API in the browser console to open a report. The example report has restricted access:
>documentViewer.OpenReport("Admin Report");

The browser console will respond with the following error.

console-output

Implement an Operation Logger

The Web Document Viewer control maintains communication with the server side to obtain any additional document data when required (for example, when a user switches pages or exports the document). Because of this feature, a user could navigate through an individual report pages even after logging out.

Security issue: A threat actor can forge a request that emulates page switching to obtain access to protected information.

To mitigate this vulnerability, implement a custom Operation Logger to enforce control over which operations are available to a user. Extend the WebDocumentViewerOperationLogger class and override class methods to implement necessary access control rules (review OperationLogger.cs in the example project).

Register the operation logger in Global.asax.cs:

DefaultWebDocumentViewerContainer.Register<WebDocumentViewerOperationLogger, OperationLogger>();

Note: To simplify the example project, our logger implementation obtains necessary user account data from a static property. This is not a recommended solution: such an implementation could fail in a cloud environment or on a web farm. Instead, we recommend that you store authentication information in an appropriate data storage.

To familiarize yourself with the solution:

  1. Run the example application and log in via /Login.aspx.
  2. Open the report preview (/Authorization/Reports/ReportViewerPage.aspx) in a separate browser tab.
  3. Log out.
  4. Try switching report pages.

The following error will appear on screen:

Operation Logger Error

Restrict Access to Data Connections and Data Tables

Our Report Designer component allows a user to browse available data connections and data tables within the Designer’s integrated Query Builder UI. Refer to the Query Builder subsection of this document to learn how you can restrict access to this information based on internal authorization rules.

3.2. Dashboard

The DevExpress Web Dashboard can be used in one of the following two modes:

1) Callbacks are processed by the ASPx page

The ASPx page includes the ASPxDashboard control and the UseDashboardConfigurator property is set to false (default mode). You can use standard ASP.NET access restriction mechanisms within your app:

<location path="Authorization/Dashboards">
    <system.web>
        <authorization>
           <deny users="?" />
        </authorization>
    </system.web>
</location>

2) Callbacks are processed by the Dashboard Configurator

In this mode, callbacks are processed by the DevExpress HTTP handler, and the UseDashboardConfigurator property is set to true.

When the Dashboard is used in this manner, access restriction rules enforced by default mechanisms have no impact. Instead, access control must be performed by a custom dashboard storage class that implements the IEditableDashboardStorage interface.

As a starting point, you can copy the reference implementation of such a storage class from the DashboardStorageWithAccessRules.cs file to your application and fine-tune it to address your business needs.

The DashboardStorageWithAccessRules class implementation defines appropriate access restrictions:

// Register dashboard layouts
var adminId = AddDashboardCore(XDocument.Load(HttpContext.Current.Server.MapPath(@"/App_Data/AdminDashboard.xml")), "Admin Dashboard");
var johnId = AddDashboardCore(XDocument.Load(HttpContext.Current.Server.MapPath(@"/App_Data/JohnDashboard.xml")), "John Dashboard");
this.publicDashboardId = AddDashboardCore(XDocument.Load(HttpContext.Current.Server.MapPath(publicDashboardPath)), "Public Dashboard");

The following code defines which user should have access to which dashboards:

// Authorization logic
authDictionary.Add("Admin", new HashSet<string>(new [] { adminId, johnId, publicDashboardId })); // Admin can view/edit all dashboards.
authDictionary.Add("John", new HashSet<string>(new[] { johnId })); // John can view/edit only his dashboard.

public bool IsAuthorized(string dashboardId) {
    var identityName = GetIdentityName();
    if(!string.IsNullOrEmpty(identityName)) {
        return authDictionary.ContainsKey(identityName) && authDictionary[identityName].Contains(dashboardId);
    }

    return false;
}

static string GetIdentityName() {
    return HttpContext.Current.User?.Identity?.Name;
}

Register the custom dashboard storage class in the Global.asax.cs file as shown below.

DashboardConfigurator.Default.SetDashboardStorage(new DashboardStorageWithAccessRules());
DashboardConfigurator.Default.CustomParameters += (o, args) => {
if (!new DashboardStorageWithAccessRules().IsAuthorized(args.DashboardId))
    throw new UnauthorizedAccessException();
};

With this custom dashboard storage implementation, if a user named 'John' tries to use the client API to open a dashboard with restricted access (e.g., a dashboard with id='1'), the handler returns error 404, File Not Found:

dashboard.LoadDashboard("1"); // Load a dashboard available only to Admin.
GET http://localhost:65252/Authorization/Dashboards/DXDD.axd?action=DashboardAction/1&_=1525787741461 404 (Not Found)

console-output

Restrict Access to Data Connections and Data Tables

Our Web Dashboard control allows a user to browse available data connections and data tables within the control’s integrated Query Builder UI. Refer to the Query Builder subsection of this document to learn how to restrict access to this information based on internal authorization rules.

3.3. Query Builder

Our standalone Query Builder and the Query Builder integrated into DevExpress Report and Dashboard designers allows end-users to browse web application data connections and the data tables available through those connections. If your web application uses access control, you must implement custom logic to restrict user access to all available connections and data tables.

To restrict access to connection strings, implement a custom connection string provider:

public class DataSourceWizardConnectionStringsProvider : IDataSourceWizardConnectionStringsProvider {

    public Dictionary<string, string> GetConnectionDescriptions() {
        Dictionary<string, string> connections =
            new Dictionary<string, string> { { "nwindConnection", "NWind database" } };

        // Customize loaded connections list.

        // Access restriction logic.
        //if(GetIdentityName() == "Admin")
        //    connections.Add("secretConnection", "Admin only database");

        return connections;
    }

    public DataConnectionParametersBase GetDataConnectionParameters(string name) {
        return AppConfigHelper.LoadConnectionParameters(name);
    }
}

To restrict access to data tables, implement a custom database schema provider:

public class DBSchemaProviderEx : IDBSchemaProviderEx {
    public DBTable[] GetTables(SqlDataConnection connection, params string[] tableList) {
        // Check permissions.

        var dbTables = connection.GetDBSchema().Tables;
        return dbTables.Where(t => t.Name == "Categories" || t.Name == "Products").ToArray();
    }

    public DBTable[] GetViews(SqlDataConnection connection, params string[] viewList) {
        return Array.Empty<DBTable>();
    }

    public DBStoredProcedure[] GetProcedures(SqlDataConnection connection, params string[] procedureList) {
        return Array.Empty<DBStoredProcedure>();
    }

    public void LoadColumns(SqlDataConnection connection, params DBTable[] tables) {
    }
}

The Dashboard Designer requires the database schema provider to be created by a factory object:

public class DataSourceWizardDBSchemaProviderExFactory : DevExpress.DataAccess.Web.IDataSourceWizardDBSchemaProviderExFactory {
    public IDBSchemaProviderEx Create() {
        return new DBSchemaProviderEx();
    }
}

As a starting point, you can copy the reference implementation from DataSourceWizardConnectionStringsProvider.cs and DataSourceWizardDBSchemaProviderExFactory.cs to your application and fine-tune it to address your business needs.

Register the implemented classes for the Report Designer, Dashboard Designer, or standalone Query Builder in the Global.asax.cs file, as shown below:

Report Designer:

DefaultReportDesignerContainer.RegisterDataSourceWizardConnectionStringsProvider<DataSourceWizardConnectionStringsProvider>();
DefaultReportDesignerContainer.RegisterDataSourceWizardDBSchemaProviderExFactory<DataSourceWizardDBSchemaProviderExFactory>();

Dashboard Designer:

DashboardConfigurator.Default.SetConnectionStringsProvider(new DataSourceWizardConnectionStringsProvider());
DashboardConfigurator.Default.SetDBSchemaProvider(new DBSchemaProviderEx());

Query Builder:

DefaultQueryBuilderContainer.Register<IDataSourceWizardConnectionStringsProvider, DataSourceWizardConnectionStringsProvider>();
DefaultQueryBuilderContainer.RegisterDataSourceWizardDBSchemaProviderExFactory<DataSourceWizardDBSchemaProviderExFactory>();

4. Preventing Cross-Site Request Forgery (CSRF)

Related Controls: Controls with data editing available by default (e.g., ASPxGridView, ASPxCardView, ASPxVerticalGrid, ASPxTreeList, ASPxDiagram, etc.)

Security Risks: CWE-352

This section contains information on how to prevent cross-site request forgery (CSRF) attacks against your web application. The vulnerability affects applications that execute POST requests (which includes requests made by DevExpress AJAX-enabled controls such as the Grid View). Although authorization mechanisms allow you to deny access by Insecure Direct Object References (for example: example.com/app/SecureReport.aspx?id=1), they do not protect you from CSRF attacks.

The possible attacks could occur if:

  1. A threat actor implements a phishing page.
  2. A user inadvertently visits this phishing page, which then sends a malicious request to your web application with the user's cookie information.
  3. As a result, malicious action is executed on the user's behalf and the threat actor can access or modify the user's private data or account information.

For more information on this vulnerability, refer to the following article: CWE-352 - Cross-Site Request Forgery (CSRF).

To protect against this vulnerability, use the AntiForgeryToken pattern. Refer to the following MSDN article to learn more: AntiForgery.Validate.

One option designed to prevent CSRF is to create a MasterPage which:

  1. Generates an AntiForgery token, and
  2. Checks this token within the Pre_Load event.
<form id="form1" runat="server">
    <%= System.Web.Helpers.AntiForgery.GetHtml() %>

On the client, generate a token and a cookie signed with a machine key:

<input
  name="__RequestVerificationToken"
  type="hidden"
  value="SKZi1uvLbg_G1P-KoK2AJdmeorX1fBgdCbVhLUDim9sk6AFwReVEY6XsuPrvsXJLq5MWOVaGXMnpx09srXkLM_Yjtcfg_4tpc1747jOgo941"
/>

On the server, check the cookie and token within the Validate method:

protected override void OnInit(EventArgs e) {
    base.OnInit(e);
    Page.PreLoad += OnPreLoad;
}
protected void OnPreLoad(object sender, EventArgs e) {
    if (IsPostBack)
        System.Web.Helpers.AntiForgery.Validate();
}

If validation fails, the MasterPage generates an error:

AntiForgeryError

The sample project illustrates how to maximize web application security for two specific use-case scenarios:

Preventing Unauthorized CRUD Operations

In this scenario, an attack attempts to perform a CRUD operation on the server by emulating a request from a data aware control (an ASPxGridView in the example).

AntiForgeryGrid

Review our sample UsingAntiForgeryToken/EditForm.aspx file to familiarize yourself with this particular vulnerability.

Preventing Unauthorized Changes to User Account Information

In this scenario, an attack attempts to modify the user account information (the email address in the example).

AntiForgeryEmail

See the example project's UsingAntiForgeryToken/EditForm.aspx file to familiarize yourself with the vulnerability.

See Also: Stack Overflow - preventing cross-site request forgery (csrf) attacks in asp.net web forms


5. Preventing Exposure of Sensitive Information

Security Risks: CWE-209

This section describes security vulnerabilities that can expose sensitive information to untrusted parties.

5.1 Information Exposure Through Error Messages

A breach or exposure can occur when the server generates an exception. If an application is configured incorrectly, detailed error information will be displayed to an end-user. This information can include information a threat actor can use to gain insight on an app’s infrastructure, file names, etc.

This behavior is controlled by the customErrors web.config option. This option accepts the following values:

  • RemoteOnly (default) - In this mode, detailed errors are displayed only for connections from the local machine.
  • On - Ensures that private messages are never displayed.
  • Off - Forces private messages for all connections.

In the following image, sensitive information is displayed within an error message:

Unsafe Error Message

By using secure configuration, the error message is substituted by a more generic message:

Safe Error Message

Manually Displaying Error Messages

You should never display exception messages (Exception.Message) in your application's UI because this text may contain sensitive information. For example, the following code sample is considered unsafe:

protected void UpdateButton_Click(object sender, EventArgs e) {
    try {
        // DoSomething()
        throw new InvalidOperationException("Some sensitive information");
    } catch(Exception ex) {
        UpdateStatusLabel.Visible = true;
        UpdateStatusLabel.Text = ex.Message;
    }
}

Consider displaying custom error messages if you want to inform end-users about occurred errors:

protected void UpdateButton_Click(object sender, EventArgs e) {
    try {
        // DoSomething()
        throw new InvalidOperationException("Some sensitive information");
    } catch(Exception ex) {
        if(ex is InvalidOperationException)
            UpdateStatusLabel.Text = "Some error occured...";
        else
            UpdateStatusLabel.Text = "General error occured...";
    }
}

Refer to the sample InformationExposure/ErrorMessage.aspx.cs file to familiarize yourself with this issue.

5.2 Availability of Invisible Column Values Through the Client-Side APIs

Prevent Access to Hidden Column Data

This vulnerability is associated with grid-based controls. Consider an app wherein a control includes hidden columns bound to sensitive data (hidden data is not displayed on-screen and is only used on the server). A threat actor can still use the control's client API to request the value of such a column:

gridView.GetRowValues(0, "UnitPrice", function (Value) {
  alert(Value);
});

Review our sample ClientSideApi/GridView.aspx page for more information.

In the following image, the browser's console is used to access hidden column values

Unprotected Column Values

Set the AllowReadUnexposedColumnsFromClientApi property to false to disable this behavior:

AllowReadUnexposedColumnsFromClientApi = "False";

Prevent Access by Field Name

Another risk involves threat actors who attempt to obtain a row value for a data field for which there is no column in the control:

gridView.GetRowValues(0, "GuidField", function (Value) {
  alert(Value);
});

This is controlled by the AllowReadUnlistedFieldsFromClientApi property and is disabled by default (safe configuration):

Protected Column Values

General recommendation: Always execute separate queries for data sources (data) displayed on-screen. These queries should never request data that should be kept secret.

5.3 Information Exposure Through Source Code

Security Risks: CWE-540, CWE-615

The DevExpress default HTTP handler (DXR.axd) serves static files including images, scripts and styles. We assume that these static files are intended for public access and do not expose sensitive information or server-side code. However, there are additional recommendations on custom scripts and styles:

  • Do not hardcode any credentials in scripts.
  • Consider obfuscating these files in the following instances:
    • The file contains code that should be protected as intellectual property.
    • File content can provide a threat actor information about the backend system, its architecture, and security vulnerabilities.

For example, consider the following code:

function GetSystemState() {
...
}

The minified version provides considerably less information about the backend system:

function s1(){...}

6. Preventing Cross-Site Scripting (XSS) Attacks with Encoding

Security Risks: CWE-80, CWE-20

Occurs when a web page is rendered based on content specified by an end user. If user input is not properly sanitized, the resulting page can be injected with a malicious script.

It is strongly suggested that you always sanitize page content specified by a user. Note: You should be aware of what type of sanitization to use so it is both compatible with displayed content and offers the desired level of safety. For example, you can remove HTML tags throughout content but it can corrupt text that is intended to contain code samples. A more generic approach would be to substitute all '<', '>' and '&' symbols with &lt;, &gt; and &amp; character codes.

Microsoft supplies a standard HttpUtility class you can use to encode data in various use-case scenarios. It exposes the following methods:

Method Usage
HtmlEncode Sanitizes untrusted input inserted into HTML output
HtmlAttributeEncode Sanitizes untrusted input assigned to a tag attribute
JavaScriptEncode Sanitizes untrusted input used within a script
UrlEncode Sanitizes untrusted input used to generate a URL

To safely insert user input value into markup, wrap it with a HttpUtility.HtmlEncode method call:

SearchResultLiteral.Text =
  string.Format("Your search - {0} - did not match any documents.", HttpUtility.HtmlEncode(SearchBox.Text));

Review our sample HtmlEncoding/General.aspx.cs file for more information.

Before you insert user input into a JavaScript block, you need to use the HttpUtility.JavaScriptStringEncode method to prepare the string:

<script>
  var s = "<%= HttpUtility.JavaScriptStringEncode(SearchBox.Text) %>";
  // DoSomething(s);
</script>

Input text that contains unsafe symbols will be converted to a safe form. In this example, if a user specifies the following unsafe string...

"<b>'test'</b>

... the script will be rendered in the following manner:

var s = "\"\u003cb\u003e'test'\u003c/b\u003e";

Review our sample HtmlEncoding/General.aspx file for more information.

6.1 Encoding in DevExpress Controls

By default, DevExpress controls encode displayed values obtained from a data source. Refer to the HTML-Encoding document for more information.

This behavior is specified by a control's EncodeHtml property. If a control displays a value that can be modified by an untrusted party, we recommend that you never disable this setting or sanitize displayed content manually.

To learn more about this vulnerability, review the sample EncodeHtml.aspx page and uncomment the following line in code behind:

((GridViewDataTextColumn)GridView.Columns["ProductName"]).PropertiesEdit.EncodeHtml = false;

Launch the project and open the page in the browser. Data field content will be interpreted as a script and you will see an alert popup.

Devexpress Controls - No Encoding

In safe configuration, field content would be interpreted as text and correctly displayed:

Devexpress Controls - Use Encoding

6.1.1 Encoding Header Filter Items

Related Controls: ASPxGridView, ASPxCardView, ASPxVerticalGrid, ASPxTreeList

You should always encode (wrap with a HttpUtility.HtmlEncode method call) filter items obtained from an unsafe data source or specified by an end user.

ASPxGridViewHeaderFilterEventArgs e) {
    if(e.Column.FieldName == "ProductName") {
        e.Values.Clear();
        // Adding custom values from an unsafe data source

        // Safe approach - Display Text is encoded
        e.AddValue(HttpUtility.HtmlEncode("<b>T</b>est <img src=1 onerror=alert('XSS') />"), "1");

        // Unsafe approach - Display Text is not encoded
        //e.AddValue("<b>T</b>est <img src=1 onerror=alert('XSS') />", "1");
    }
}

Review our sample HtmlEncoding/EncodeHtml.aspx.cs file for more information.

If filter items are not encoded, an XSS can be executed when a user opens a header filter dropdown:

Templates - Unsanitized Content

6.2 Encoding in Templates

If you inject data field values in templates, we recommend that you always encode the data field values:

<asp:Label ID="ProductNameLabel" runat="server"
   Text='<%# System.Web.HttpUtility.HtmlEncode(Eval("ProductName")) %>' />

Inserting unsanitized content can expose your application to XSS attacks:

Templates - Unsanitized Content

With encoding, content would be interpreted as text and correctly displayed:

Templates - Sanitized Content

By default, DevExpress controls wrap templated contents with a HttpUtility.HtmlEncode method call.

6.3 Encoding Callback Data

When a client displays data received from the server via a callback, a breach may occur if this data has not been properly encoded. For example, in the code below, such content is assigned to an element's innerHTML:

<dx:ASPxCallback runat="server" ID="CallbackControl" OnCallback="Callback_Callback" ClientInstanceName="callbackControl" >
    <ClientSideEvents CallbackComplete="function(s, e) {
        document.getElementById('namePlaceHodler').innerHTML = e.result;
            if(callbackControl.cpSomeInfo)
                document.getElementById('someInfo').innerHTML = callbackControl.cpSomeInfo;
        }" />
</dx:ASPxCallback>

The safe approach is to use HtmlEncode in server-side code:

protected void Callback_Callback(object source, DevExpress.Web.CallbackEventArgs e) {
    // Not secure
    // e.Result = "<img src=1 onerror=alert('XSS') /> ";
    // CallbackControl.JSProperties["cpSomeInfo"] = "<video src=1 onerror=alert(document.cookie)>";

    e.Result = HttpUtility.HtmlEncode("<img src=1 onerror=alert('XSS') /> ");
    CallbackControl.JSProperties["cpSomeInfo"] = HttpUtility.HtmlEncode("<video src=1 onerror=alert(document.cookie)>");
}

6.4 Encoding Page Title

When you assign a value from a database or user input to a page title, you need to make certain that the value is properly encoded to prevent possible script injections.

For example, the code below assigns a value from a database to the page title.

protected void Page_Load(object sender, EventArgs e) {
    var ds = SqlDataSource1.Select(new System.Web.UI.DataSourceSelectArguments()) as DataView;
    var value = ds[0]["ProductName"]; // value from DB = "</title><script>alert('XSS')</script>";

    //Title = "Product: " + value.ToString(); // Not secure
    Title = "Product: " + HttpUtility.HtmlEncode(value).ToString(); // Secure
}

If encoding was not used, the resulting markup would contain a malicious script:

<head><title>
    Product: </title><script>alert('XSS')</script>
</title></head>

With encoding, the markup is rendered as follows:

<head>
  <title>
    Product: &lt;/title&gt;&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;
  </title>
</head>

6.5 Dangerous Links

It is potentially dangerous to render a hyperlink's HREF attribute based on a value from a database or user input.

DevExpress grid based controls remove all potentially dangerous contents (for example, javascript:) from HREF values when they render hyperlink columns:

Unsafe Link

This behavior is controlled by a column's RemovePotentiallyDangerousNavigateUrl option (true by default):

<dx:GridViewDataHyperLinkColumn FieldName="Description" VisibleIndex="1">
   <PropertiesHyperLinkEdit RemovePotentiallyDangerousNavigateUrl="True” />
</dx:GridViewDataHyperLinkColumn>

We recommend that you never set this option to false if the URL value in the database can be modified by untrusted parties.

Review our sample HtmlEncoding/DangerousNavigateUrl.aspx page for more information.


7. User Input Validation

Security Risks: CWE-20

7.1 General Recommendations

You should always validate values obtained from an end user before you save them to a database or use in any other manner. Values should be validated on multiple levels:

  1. Specify required input restrictions for on the client.

  2. Validate submitted values on the server before save operations.

  3. Specifies required data integrity conditions at the database level.

  4. Validate values in the code that directly uses the value(s).

Note that client validation is simply server load optimization. To ensure safety, always use client validation in conjunction with server validation.

Validation Diagram

You can use control properties such as MaxLength, MinValue, MaxValue, and Required to specify input restrictions. Server side validation does not allow a user to submit an invalid value even if a malefactor manages to send an invalid value bypassing the client validation. If the value is invalid, the editor's value is set to the previous value assigned on the editor's init.

Note: starting with our v19.2 release, the value set on init is also validated and cannot be saved if validation fails. In earlier versions, this value would be saved without validation if it was not modified on the client.

The image below demonstrates how validation errors are indicated by DevExpress controls:

Validation Errors

Review our sample ValidateInput/General.aspx file for more information.

7.2 Built-in Validation in DevExpress Controls

Some DevExpress web controls have built-in validation mechanisms. These mechanisms apply restrictions to input values when certain settings are specified. The table below lists controls with built-in validation support along with properties that control associated validation logic.

Control Validation-Related Properties
ASPxTextBox MaxLength
MaskSettings.Mask
ASPxSpinEdit MinValue
MaxValue
MaxLength
MaskSettings.Mask
ASPxCalendar MinDate
MaxDate
ASPxDateEdit DateRangeSettings
MinDate
MaxDate
ASPxListBox DataSecurityMode (if set to Strict)
ASPxComboBox DataSecurityMode (if set to Strict)
MaxLength
MaskSettings.Mask
ASPxTokenBox DataSecurityMode (if set to Strict)

7.3 Validation in List Editors

List-based UI controls within the DevExpress ASP.NET suite expose a DataSecurity property. This property specifies how a control handles input values that do not exist in the list. By default, this property is set to Default. With this setting, an editor accepts values that aren't in the list. Set the DataSecurity property to Strict to prohibit such values.

If you use the Strict DataSecurity mode, ViewState is disabled. If data binding is handled at runtime, you should execute data binding on Page_Init. Binding on Page_load is too late because editor validation triggers earlier than when its items are populated.

Review our sample ValidateInput/ListEditors.aspx page for more information.

7.4 Disable Built-in ASP.NET Request Validation

ASP.NET checks input values for potentially dangerous content. For example, when an end-user enters <b> into a text field (and submits the form), they are redirected to an error page with the following message:

System.Web.HttpRequestValidationException: A potentially dangerous Request value was detected from the client (Property="<b>")

In many instances, such checks can have undesired side effects:

  • End users cannot enter text containing elements that are common in technical texts, for example, "Use the <b> tag to apply a bold text effect."

  • On a validation error, ASP.NET raises an exception and responds with the default error page. Because of this, you cannot handle the error and display a user-friendly error message.

For these reasons, built-in request value checks are commonly disabled. If you wish to remove value checks, disable the validateRequest option available in the /system.web/pages section of web.config:

<system.web>
  <httpRuntime requestValidationMode="2.0" />
  <pages validateRequest="false">

Whether checks are enabled or not, you should use encoding to protect your application from XSS attacks. Refer to the section 6 of this document to learn more.

7.5 Inlining SVG Images

SVG markup can include scripts that will be executed if the SVG is inlined into a page. For example, the code below executes a script embedded into SVG markup:

<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <p>Inline Svg images are not secure! You must trust the source!</p>
    <p>For example: Svg bytes uploaded by end user, stored in the data source and embedded inline. JavaScript is excecuted:</p>
    <div id="svgInlineImageContainer" runat="server"></div>
</asp:Content>
protected void Page_Load(object sender, EventArgs e) {
    // Init Value from DataSource
    var svgImageWithJavaScriptCode = "<svg height=100 width=100><circle cx=50 cy=50 r=40 stroke=black stroke-width=2 fill=red /><script>alert('XXS')</script></svg>";
    svgInlineImageContainer.InnerHtml = svgImageWithJavaScriptCode;
}

8. Export to CSV

Security Risks: CWE-74

Control data exported to CSV can include content that spreadsheet software such as Microsoft Excel interprets as a formula. Such formulas can potentially execute shell commands. For example, the following formula runs the Windows Calculator when evaluated:

=cmd|' /C calc'!'!A1'

This may generate a risk if an end-user opens the exported file in a spreadsheet program and allows the program to run executable content. Microsoft Excel displays a message explicitly asking if a user wishes to run executable content.

To prevent possible vulnerabilities, set the EncodeCsvExecutableContent property to true. In this mode, exported content is encoded so it cannot be interpreted as executable.

This behavior is controlled by the following properties:

  • At the control level - DevExpress.XtraPrinting.CsvExportOptions.EncodeExecutableContent:

    // Standard Approach
    protected void Button_Click(object sender, EventArgs e) {
      var options = new CsvExportOptionsEx();
      options.EncodeExecutableContent = DefaultBoolean.True;
      ASPxGridView1.ExportCsvToResponse(options);
    }
    
    // For a Toolbar Button
    protected void ASPxGridView1_BeforeExport(object sender, DevExpress.Web.ASPxGridBeforeExportEventArgs e) {
      (e.ExportOptions as CsvExportOptionsEx).EncodeExecutableContent = DefaultBoolean.True;
    }
  • At the application level (a Global.asax setting) - DevExpress.Export.ExportSettings.EncodeCsvExecutableContent:

    DevExpress.Export.ExportSettings.EncodeCsvExecutableContent = DevExpress.Utils.DefaultBoolean.True;

IMPORTANT NOTE: This setting is not enabled by default because escaped content (everything that starts with "=", "-", "+", "@", "") within a CSV file may be unacceptable in many use-case scenarios. For example, escaping brakes text values that begin with "=" or negative numbers:

Product Name,Quantity Per Unit,Unit Price,Units In Stock, Status
"""=Chai""",10 boxes x 20 bags,$18.00,39,$702.00, """-10%"""
Chang,24 - 12 oz bottles,$19.00,17,$323.00, 5%

Because Excel requires a user's permission to run executable content, we do not enable this setting by default and allow a user to enable this settings if it addresses a specific use-case.

Review the following article to learn more about CSV injections: https://www.owasp.org/index.php/CSV_Injection


9. Unauthorized Operations on the Server Through Client API

Security Risks: CWE-284, CWE-285

9.1. Unauthorized CRUD Operations in View Mode

Review our sample ClientSideApi/GridView.aspx page for more information.

Grid-based controls (ASPxGridView, ASPxCardView, etc.) expose client methods that trigger CRUD operations on the server. For example, you can call the ASPxClientGridView.DeleteRow method on the client to delete a grid row. If a control is configured incorrectly, these methods can be used to alter data even if the control is set to display data in view-only mode (data editting-related UI elements are hidden).

To familiarize yourself with the issue:

  1. Comment out the following line in ClientSideApi/GridView.aspx:

    <SettingsDataSecurity AllowEdit="False" AllowInsert="False" AllowDelete="False" AllowReadUnexposedColumnsFromClientApi="False" />
  2. Open this page in the browser and click the DeleteRow(0) button or enter the following code in the browser's console:

    >gridView.DeleteRow(0)

    Templates - Sanitized Content

    This will delete a data row with index 0. This is possible because the Grid View's data source still has the Delete statement and the grid's SettingsDataSecurity.AllowDelete property is set to the default value (True).

To address this vulnerability:

  • If you intend to use a grid-based control in view-only mode, make certain that its data source does not allow data editing (for example a SqlDataSource does not have a DeleteCommand, InsertCommand and UpdateCommand).

  • Use the control's SettingsDataSecurity property to disable CRUD operations at the control level:

<SettingsDataSecurity AllowEdit="False" AllowInsert="False" AllowDelete="False" />

9.2. Using Spreadsheet in Read-Only Mode

If you wish to use our Spreadsheet control in read-only mode (the SettingsView.Mode option is set to "Reading"), you must prevent users from switching to edit mode:

<Settings>
    <Behavior SwitchViewModes="Hidden" />
</Settings>

You should also set the ReadOnly property to true:

spreadsheet.ReadOnly="true"

Review our sample ClientSideApi/SpreadsheetReadingModeOnly.aspx page for more inforamtion.

9.3 File Selector Commands in the RichEdit and Spreadsheet

Related Controls: ASPxRichEdit, ASPxSpreadsheet

In a popular use case scenario, the RichEdit or Spreadsheet control's File tab is hidden to prevent an end-user from accessing FileSelector commands (New, Open, Save, etc.) In such instances, documents are opened and saved programmatically.

IMPORTANT: To prevent access to FileSelector commands, you must take additional steps as these commands may still be executed with JavaScript or keyboard shortcuts (for example, Ctrl+O can invoke the Open dialog).

When you want to disable file-related commands, you must disable file operations by disabling corresponding Behavior options (CreateNew, Open, Save, SaveAs, SwitchViewModes).

ASPxSpreadsheet:
<dx:ASPxSpreadsheet ID="spreadsheet" runat="server" WorkDirectory="~/App_Data/WorkDirectory">
    <Settings>
        <Behavior CreateNew="Hidden" Open="Hidden" Save="Hidden" SaveAs="Hidden" SwitchViewModes="Hidden"/>
    </Settings>
</dx:ASPxSpreadsheet>
ASPxRichEdit:
<dx:ASPxRichEdit ID="ASPxRichEdit1" runat="server" WorkDirectory="~\App_Data\WorkDirectory">
    <Settings>
        <Behavior CreateNew="Hidden" Open="Hidden" Save="Hidden" SaveAs="Hidden" ></Behavior>
    </Settings>
</dx:ASPxRichEdit>

To familiarize yourself with the issue, open our sample project's ClientSideApi/FileSelector.aspx file and comment out the following line:

<Behavior CreateNew="Hidden" Open="Hidden" Save="Hidden" SaveAs="Hidden" SwitchViewModes="Hidden"/>

Run the application and open the page. If you press Ctrl+O, the Open dialog will be invoked, even though the File tab is hidden.

Templates - Sanitized Content


10. Downloading Files From External URLs

Consider a web application that receives a URL from an end-user, downloads an image file from this URL and saves the file to a database. This file is then displayed on the application page using the ASPxBinaryImage control.

Many suggest the use of the WebClient class to download a file in this particular usage scenario:

using(var webClient = new WebClient())
    BinaryImage.ContentBytes = webClient.DownloadData(url);

Unfortunately, this is an unsafe strategy as the WebClient can accept a path to a local resource on the server (for example, c:\...\App_Data\ConfidentialImages\...). This allows a threat actor to obtain access to confidential files (such as web.config, the App_Data folder or other files/folders with private content).

To mitigate this vulnerability, use HttpWebRequest:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
using(HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
    ...
}

In this instance, an attempt to download a local file generates an exception.

Review our sample DownloadingFiles/DownloadFileFromUrl.aspx.cs file for more information.


Analytics