MongoDB · RetrieveMultiple
Catch an injection attempt
A search endpoint attacked three ways: an operator as the field to search, a regular expression as the text to find, and a sort field built from user input.
Set up for this page
The scenarios build on the sample application: SampleContext.cs (the dbContext every sample is given); SampleOutput.cs (Step and Show, which print); SampleContext.Application.cs (dbContext.Service, the application's ARX service); SampleCatalog.cs (the tables' ids, keys, lookups and display names); SampleContext.Direct.cs (dbContext.Direct); MongoDirectHelper.cs (the checks behind dbContext.Direct).
dbContext.Direct is the samples' own checking code on the plain driver, not part of ARX: leave it out of your application.
Before scenario 1 the sample runs this, so the service and the rows the scenarios use are there:
var contact = new ArxEntity("contact");
contact["first_name"] = "Private";
contact["segment"] = "M90";
await service.CreateAsync(contact);1 An operator as the field
Search field "$where": ARX1017.
Already in scope: service (ArxMongoDbService)
var query = new ArxQueryExpression("contact");
query.Criteria.AddCondition("$where", ArxConditionOperator.Equal, "this.first_name != ''");
try
{
await service.RetrieveMultipleAsync(query);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.SuspectedInjection)
{
Console.WriteLine(exception.Message);
}Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.contact.countDocuments({ segment: "M90" })Expect: 1: still there, and nothing was read.
2 A regular expression as the text
Search text ".*": escaped, it matches no name.
Already in scope: service (ArxMongoDbService)
var query = new ArxQueryExpression("contact");
query.Criteria.AddCondition("segment", ArxConditionOperator.Equal, "M90");
query.Criteria.AddCondition("first_name", ArxConditionOperator.Contains, ".*");
Console.WriteLine($"{(await service.RetrieveMultipleAsync(query)).Entities.Count} contacts found");Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.contact.countDocuments({ segment: "M90", first_name: { $regex: "\\.\\*", $options: "i" } })Expect: 0.
3 A sort field from the user
A sort field from the query string, with SQL in it: ARX1017.
Already in scope: service (ArxMongoDbService)
var query = new ArxQueryExpression("contact");
query.AddOrder("first_name; DROP TABLE contact", ArxOrderType.Ascending);
try
{
await service.RetrieveMultipleAsync(query);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.SuspectedInjection)
{
Console.WriteLine(exception.Message);
}Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.getCollectionNames().includes("contact")Expect: true.
Good to know
Names with $ or SQL syntax are ARX1017 before anything is sent. Search text is escaped, so .* finds the two characters ".*", not everything.
See also
Run the complete sample
The code above comes from one program, built in a fresh project and run against the database. To run it yourself, here is every file and step.
The program, its files and how to run it
Run it locally
- .NET SDK: .NET SDK 10.0.100 or later 10.0.x (target
net10.0). - Create the project:
Terminal dotnet new console -n ArxSample -f net10.0 cd ArxSample - Install the packages:
Terminal dotnet add package ARX.SDK.MongoDb --version 1.0.0-rc.1 - Database: a MongoDB 7.0, a replica set (tested with 7.0.45, one node) database with the sample schema.
- Configuration: set
ARX_SAMPLES_MONGODBto its connection string. Nothing is hard-coded. - Source code: add the files of the sample application this sample uses (
SampleContext.cs,SampleOutput.cs,SampleContext.Application.cs,SampleCatalog.cs,SampleContext.Direct.cs,MongoDirectHelper.cs), then replaceProgram.csand add the other file below, with the file names shown. - Run it:
Terminal dotnet run - Expected output: below.
The files
Runs the sample. Generated for this page; it reads the connection string from the environment and first remakes the sample database from sample-schema.json.
using ArxSamples.MongoDb;
using ArxSamples.MongoDb.Operations.RetrieveMultiple.RealCases;
// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_MONGODB")
?? throw new InvalidOperationException("Set the ARX_SAMPLES_MONGODB environment variable to your MongoDB connection string.");
var dbContext = new SampleContext(connectionString);
// Every run starts from the sample data: this deletes and makes again what sample-schema.json lists
// (save the file in the project folder, see Get started).
await dbContext.Direct.ResetAsync();
await new RealCase3_CatchAnInjectionAttempt().RunAsync(dbContext);
The sample, as it is in the repository.
using ARX.SDK;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.MongoDb.SampleOutput;
namespace ArxSamples.MongoDb.Operations.RetrieveMultiple.RealCases;
/// <summary>
/// A search endpoint attacked three ways: an operator as the field to search, a regular expression as the text to
/// find, and a sort field built from user input.
/// </summary>
/// <remarks>
/// Names with <c>$</c> or SQL syntax are ARX1017 before anything is sent. Search text is escaped, so <c>.*</c> finds the
/// two characters ".*", not everything.
/// </remarks>
public sealed class RealCase3_CatchAnInjectionAttempt : ISample
{
public string Name => "retrieve-multiple-real-3-catch-an-injection-attempt";
public string Title => "Catch an injection attempt";
public async Task RunAsync(SampleContext dbContext)
{
var contact = new ArxEntity("contact");
contact["first_name"] = "Private";
contact["segment"] = "M90";
await dbContext.Service.CreateAsync(contact);
await Scenario1_AnOperatorAsTheFieldAsync(dbContext.Service);
await Scenario2_ARegularExpressionAsTheTextAsync(dbContext.Service);
await Scenario3_ASortFieldFromTheUserAsync(dbContext.Service);
}
/// <summary>1. Search field "$where": ARX1017.</summary>
private static async Task Scenario1_AnOperatorAsTheFieldAsync(ArxMongoDbService service)
{
Step("1. An operator as the field");
var query = new ArxQueryExpression("contact");
query.Criteria.AddCondition("$where", ArxConditionOperator.Equal, "this.first_name != ''");
try
{
await service.RetrieveMultipleAsync(query);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.SuspectedInjection)
{
ShowError(exception);
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.contact.countDocuments({ segment: "M90" })
* -- Expect: 1: still there, and nothing was read.
* ===========================================================
*/
}
/// <summary>2. Search text ".*": escaped, it matches no name.</summary>
private static async Task Scenario2_ARegularExpressionAsTheTextAsync(ArxMongoDbService service)
{
Step("2. A regular expression as the text");
var query = new ArxQueryExpression("contact");
query.Criteria.AddCondition("segment", ArxConditionOperator.Equal, "M90");
query.Criteria.AddCondition("first_name", ArxConditionOperator.Contains, ".*");
Show($"{(await service.RetrieveMultipleAsync(query)).Entities.Count} contacts found");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.contact.countDocuments({ segment: "M90", first_name: { $regex: "\\.\\*", $options: "i" } })
* -- Expect: 0.
* ===========================================================
*/
}
/// <summary>3. A sort field from the query string, with SQL in it: ARX1017.</summary>
private static async Task Scenario3_ASortFieldFromTheUserAsync(ArxMongoDbService service)
{
Step("3. A sort field from the user");
var query = new ArxQueryExpression("contact");
query.AddOrder("first_name; DROP TABLE contact", ArxOrderType.Ascending);
try
{
await service.RetrieveMultipleAsync(query);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.SuspectedInjection)
{
ShowError(exception);
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.getCollectionNames().includes("contact")
* -- Expect: true.
* ===========================================================
*/
}
}
Expected output
What dotnet run printed when the sample was verified, on a freshly created sample schema. Ids differ when the tables already hold rows.
> 1. An operator as the field
caught ArxValidationException: ARX1017 SuspectedInjection: Attribute name '$where' looks like an SQL injection attempt (contains '$' (a MongoDB operator)). ARX refused it before sending anything to the database. Names must come from your code or a fixed list, never from user input.
> 2. A regular expression as the text
0 contacts found
> 3. A sort field from the user
caught ArxValidationException: ARX1017 SuspectedInjection: Attribute name 'first_name; DROP TABLE contact' looks like an SQL injection attempt (contains ';'). ARX refused it before sending anything to the database. Names must come from your code or a fixed list, never from user input.