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
- 2. Uploading and Displaying Binary Images
- 3. Authorization
- 4. Preventing Cross-Site Request Forgery (CSRF)
- 5. Preventing Sensitive Information Exposure
- 6. Preventing Cross-Site Scripting (XSS) Attacks with Encoding
- 7. User Input Validation
- 8. Export to CSV
- 9. Unauthorized Operations on Server Through Client API
- 10. Downloading Files From External URLs
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
- 1.2. Avoid Uncontrolled Resource Consumption
- 1.3. Protect Temporary Files
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:
-
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); //} //}
-
Run the solution and open the UploadingFiles/UploadControl.aspx page.
-
Upload the \App_Data\TestData\Malicious.aspx file.
-
Visit the following URL to execute the uploaded file on the server: /UploadingFiles/Images/Malicious.aspx
To mitigate this risk, you must:
- Initialize the
AllowedFileExtensions
setting with a list of allowed file extensions. The server will then validate uploaded file type:
<ValidationSettings AllowedFileExtensions=".jpg, .png">
</ValidationSettings>
- 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 |
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:
- Use the
FileContent
property (a Stream) rather than theFileBytes
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);
}
}
}
- 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
andrequestLimits
>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.
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
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:
- Store temporary files in a folder unreachable via URL (for example, App_Data).
- Use a dedicated file extension for temporary files on the server (for example,
"\*.mytmp"
). - 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
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
- Disable protection: Un-comment the call to the
IsValidImage
method in the following sample project: UploadingBinaryImage/UploadControl.aspx.cs// if(!IsValidImage(stream)) return;
- In the BinaryImageViewer.aspx.cs file, un-comment
Response.ContentType = "image"
line and comment outResponse.ContentType = "image/jpeg"
. - Run the example solution and open the UploadingBinaryImage/UploadControl.aspx page.
- 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.
- 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.
- The JavaScript code from the uploaded file is executed by the browser:
To mitigate this vulnerability:
- 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;
}
}
- 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");
}
- Microsoft Edge automatically detects file type based on its content, which prevents the execution of malicious scripts described herein.
- Make certain to specify max upload file size to prevent DoS attacks via large file uploads.
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:
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:
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();
}
}
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");
}
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());
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.
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:
- Run the example application and log in via /Login.aspx.
- Open the report preview (/Authorization/Reports/ReportViewerPage.aspx) in a separate browser tab.
- Log out.
- Try switching report pages.
The following error will appear on screen:
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.
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)
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.
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>();
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:
- A threat actor implements a phishing page.
- A user inadvertently visits this phishing page, which then sends a malicious request to your web application with the user's cookie information.
- 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:
- Generates an AntiForgery token, and
- 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:
The sample project illustrates how to maximize web application security for two specific use-case scenarios:
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).
Review our sample UsingAntiForgeryToken/EditForm.aspx file to familiarize yourself with this particular vulnerability.
In this scenario, an attack attempts to modify the user account information (the email address in the example).
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
Security Risks: CWE-209
This section describes security vulnerabilities that can expose sensitive information to untrusted parties.
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:
By using secure configuration, the error message is substituted by a more generic message:
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.
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
Set the AllowReadUnexposedColumnsFromClientApi
property to false
to disable this behavior:
AllowReadUnexposedColumnsFromClientApi = "False";
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):
General recommendation: Always execute separate queries for data sources (data) displayed on-screen. These queries should never request data that should be kept secret.
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(){...}
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 <
, >
and &
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.
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.
In safe configuration, field content would be interpreted as text and correctly displayed:
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:
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:
With encoding, content would be interpreted as text and correctly displayed:
By default, DevExpress controls wrap templated contents with a HttpUtility.HtmlEncode
method call.
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)>");
}
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: </title><script>alert('XSS')</script>
</title>
</head>
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:
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.
Security Risks: CWE-20
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:
-
Specify required input restrictions for on the client.
-
Validate submitted values on the server before save operations.
-
Specifies required data integrity conditions at the database level.
-
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.
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:
Review our sample ValidateInput/General.aspx file for more information.
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) |
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.
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.
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;
}
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
Security Risks: CWE-284, CWE-285
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:
-
Comment out the following line in ClientSideApi/GridView.aspx:
<SettingsDataSecurity AllowEdit="False" AllowInsert="False" AllowDelete="False" AllowReadUnexposedColumnsFromClientApi="False" />
-
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)
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'sSettingsDataSecurity.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
andUpdateCommand
). -
Use the control's
SettingsDataSecurity
property to disable CRUD operations at the control level:
<SettingsDataSecurity AllowEdit="False" AllowInsert="False" AllowDelete="False" />
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.
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
).
<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>
<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.
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.