The EC-Council CASE .NET exam (312-95) is 50 multiple-choice questions in 2 hours, and you need 70% to pass. Half of its ten modules are about secure coding in .NET, so the fastest way to study them is to write the bad code, then fix it. This post does that for each of the five coding modules, with C# you can paste into an ASP.NET Core app.
What does the 312-95 exam look like?
These are the numbers EC-Council publishes for the exam. Everything below them is code.
| Item | What EC-Council lists |
|---|---|
| Exam code | 312-95 (CASE .NET) |
| Questions | 50 |
| Duration | 2 hours |
| Format | Multiple choice |
| Passing score | 70%, so 35 of 50 |
| Delivery | EC-Council Exam Portal |
| Application fee | USD 100 for the experience route (2 years in InfoSec or software) |
You can also qualify by taking the official training through an accredited partner, where the fee is often part of the course price. The eligibility rules and the full module list are on the EC-Council CASE .NET certification page.
Which modules of CASE .NET are about writing code?
The course outline has ten modules. Modules 01 to 03 cover threats, security requirements and secure design. Modules 09 and 10 cover testing (SAST and DAST) and secure deployment. Modules 04 to 08 sit in the middle, and each one is a secure coding practice:
- Module 04: input validation
- Module 05: authentication and authorization
- Module 06: cryptography
- Module 07: session management
- Module 08: error handling
The map below pairs each of the ten modules with the .NET API this post uses for it.
| Module | The bug in the "before" code | The .NET fix |
|---|---|---|
| 04 Input validation | User input glued into SQL |
FromSql with an interpolated string, plus data annotations |
| 05 Authentication and authorization | Unsalted SHA-256 password hash |
Rfc2898DeriveBytes.Pbkdf2 and an authorization policy |
| 06 Cryptography | AES in ECB mode with a key in source |
AesGcm with a random 12-byte nonce |
| 07 Session management | Default cookie, no CSRF check | Cookie flags and AutoValidateAntiforgeryToken
|
| 08 Error handling | Stack trace sent to the browser |
UseExceptionHandler with problem details |
Module 04: How do you stop SQL injection in EF Core?
Here is the classic mistake. The user's e-mail address goes straight into the SQL text, so an input like x' OR '1'='1 changes the query.
// Before: user input concatenated into the SQL string
var orders = await db.Orders
.FromSqlRaw("SELECT * FROM Orders WHERE CustomerEmail = '" + email + "'")
.ToListAsync();
The fix is one method name. FromSql looks like string interpolation, but EF Core turns every {value} into a real database parameter.
// After: {email} is sent as a DbParameter, never as SQL text
var orders = await db.Orders
.FromSql($"SELECT * FROM Orders WHERE CustomerEmail = {email}")
.ToListAsync();
Microsoft's guide on raw SQL queries in EF Core says FromSql and FromSqlInterpolated are safe against SQL injection, while FromSqlRaw can be vulnerable if you build the string yourself. FromSql arrived in EF Core 7.0; on older versions, use FromSqlInterpolated.
Parameters stop injection, but the module also covers checking input before it reaches the query. Data annotations do that in a few lines:
public class OrderLookup
{
[Required]
[EmailAddress]
[StringLength(254)]
public string Email { get; set; } = "";
}
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
[HttpPost("lookup")]
public IActionResult Lookup(OrderLookup input)
{
// With [ApiController], an invalid OrderLookup never gets here:
// the framework answers HTTP 400 with the validation errors.
return Ok();
}
}
Without [ApiController], as in Razor Pages or MVC views, you have to check ModelState.IsValid yourself. The built-in attributes are listed in the ASP.NET Core model validation docs.
Module 05: How should a .NET app store and check passwords?
A fast hash is the wrong tool for passwords. SHA-256 is built to be quick, so an attacker with a stolen table can try billions of guesses, and with no salt, two users with the same password get the same hash.
// Before: fast and unsalted
string hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(password)));
A slow key-derivation function with a random salt per user fixes both problems. The figure of 600,000 iterations for PBKDF2 with HMAC-SHA256 comes from the OWASP Password Storage Cheat Sheet, which also lists Argon2id, scrypt and bcrypt as options.
public static class Pbkdf2Passwords
{
private const int SaltSize = 16; // 128-bit random salt per user
private const int KeySize = 32; // 256-bit derived key
private const int Iterations = 600_000; // OWASP figure for PBKDF2-HMAC-SHA256
public static string Hash(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] key = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, HashAlgorithmName.SHA256, KeySize);
return $"{Iterations}.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(key)}";
}
public static bool Verify(string password, string stored)
{
string[] parts = stored.Split('.');
int iterations = int.Parse(parts[0]);
byte[] salt = Convert.FromBase64String(parts[1]);
byte[] expected = Convert.FromBase64String(parts[2]);
byte[] actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, expected.Length);
return CryptographicOperations.FixedTimeEquals(actual, expected); // no early exit, no timing leak
}
}
Storing the iteration count next to the hash lets you raise it later without breaking old accounts. The overloads of the one-shot method are listed on the Rfc2898DeriveBytes.Pbkdf2 API page.
The second half of the module is authorization: deciding what a signed-in user may do. Two habits cover most questions. Name your rules as policies instead of scattering role checks, and check that the record belongs to the caller.
builder.Services.AddAuthorization(options =>
options.AddPolicy("CanRefund", p => p.RequireRole("Finance").RequireClaim("mfa", "true")));
[Authorize(Policy = "CanRefund")]
[HttpPost("{id}/refund")]
public async Task<IActionResult> Refund(int id)
{
var order = await db.Orders.FindAsync(id);
if (order is null) return NotFound();
if (order.OwnerId != User.FindFirst(ClaimTypes.NameIdentifier)?.Value) return Forbid(); // no IDOR
// ...
return NoContent();
}
Module 06: What does safe encryption look like in .NET?
This snippet makes two mistakes the cryptography module asks about. The key is a string in the source code, and ECB mode encrypts equal blocks of plaintext to equal blocks of ciphertext, so patterns in the data show through.
// Before: hard-coded key, ECB mode, no integrity check
using var aes = Aes.Create();
aes.Key = Encoding.UTF8.GetBytes("0123456789abcdef");
aes.Mode = CipherMode.ECB;
AES-GCM encrypts and authenticates in one step: if anyone changes a byte of the ciphertext, decryption fails instead of returning garbage.
public static (byte[] Nonce, byte[] Cipher, byte[] Tag) Seal(byte[] key, byte[] plain, byte[] context)
{
byte[] nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize); // 12 bytes, new every call
byte[] cipher = new byte[plain.Length];
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize]; // 16 bytes
using var gcm = new AesGcm(key, tag.Length);
gcm.Encrypt(nonce, plain, cipher, tag, context);
return (nonce, cipher, tag);
}
Three details are worth remembering. Never reuse a nonce with the same key. Pass the tag size to the constructor, because the AesGcm class reference marks the key-only constructor as obsolete. And load the 32-byte key from a secret store, not from source code or appsettings.json.
Module 07: Which cookie and CSRF settings protect a session?
The session cookie is the user's identity after login, so its flags matter. Every option below is described in the cookie authentication docs. This setup keeps scripts away from the cookie, sends it over HTTPS only, keeps it off cross-site requests, and gives it a fixed 20-minute life.
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true; // not readable from document.cookie
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // HTTPS only
options.Cookie.SameSite = SameSiteMode.Strict; // not sent on cross-site requests
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = false; // a stolen cookie cannot be kept alive
options.LoginPath = "/Account/Login";
});
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
Microsoft's own sample on that page turns sliding expiration on. That keeps users signed in while they work; turning it off trades some convenience for a shorter window if a cookie leaks. Know both choices and why you would pick each.
AutoValidateAntiforgeryToken checks the antiforgery token on every POST, PUT and DELETE, but skips GET, HEAD, OPTIONS and TRACE. Note that AddControllers alone does not turn antiforgery on; AddControllersWithViews does. Both points come from the ASP.NET Core antiforgery guide.
Module 08: How do you handle errors without leaking details?
An exception message can hold SQL text, file paths and library versions. Sending it to the browser hands an attacker a map of your app.
// Before: the full exception goes to the client
catch (Exception ex)
{
return Content(ex.ToString());
}
The fix has two parts: a generic answer for the client, and the full detail in the server log.
builder.Services.AddProblemDetails();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(); // a problem-details body, no stack trace
app.UseHsts();
}
// inside an action:
catch (DbUpdateException ex)
{
logger.LogError(ex, "Saving order {OrderId} failed", orderId); // detail stays on the server
return Problem(detail: "The order could not be saved.", statusCode: 500);
}
With the current templates, the Developer Exception Page is on only in the Development environment. It shows the stack trace, query string, cookies and headers, which is why Microsoft says never to turn it on in production. The ASP.NET Core error handling docs cover both pages and the status code pages middleware.
How can a build catch these bugs before review?
Module 09 is about testing: SAST reads the code, DAST attacks the running app. You can get a small SAST step for free from the .NET analyzers. The security rules are not on by default in .NET 10, so switch them on in the project file:
<PropertyGroup>
<AnalysisModeSecurity>All</AnalysisModeSecurity>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
</PropertyGroup>
Now dotnet build fails on rules such as CA2100 (a SQL command string built from a method argument), CA5351 (broken algorithms such as MD5 and DES) and CA5350 (weak ones such as SHA1 and TripleDES). It will not catch everything above, so pair it with a checklist. The OWASP .NET Security Cheat Sheet works well as one, and it covers the cookie and AES-GCM points from this post too.
How should you practise for the 312-95 exam?
Take each "before" snippet above, run it in a test app, attack it, then apply the fix and attack it again. Seeing a payload stop working is easier to remember than a rule. Then spend the rest of your time on the non-coding modules (requirements, design, testing and deployment), because they are half the outline.
If you want a plain overview of the exam before the code, our earlier write-up on how to crack the CASE .NET exam walks through the syllabus and study plan.
Our channel has no 312-95 video yet. The closest one is this short video on preparing for another EC-Council engineer exam, 312-40, in one month.
When you are ready to test yourself, the 312-95 syllabus and practice exam page lists the ten topics, with links to sample questions and a practice exam.
Frequently asked questions
1. How many questions are on the CASE .NET 312-95 exam?
The exam has 50 multiple-choice questions and a time limit of 2 hours. You need a score of 70%, which is 35 correct answers out of 50.
2. Do I need to take EC-Council training before the 312-95 exam?
No. You can qualify through official training at an accredited partner, or by showing at least 2 years of InfoSec or software experience and paying a USD 100 non-refundable application fee. ECSP members in good standing and holders of an equivalent certification such as GSSP .NET or Java can also apply.
3. Which modules of CASE .NET are about writing secure code?
Modules 04 to 08 are the secure coding modules: input validation, authentication and authorization, cryptography, session management, and error handling. The other five modules cover threats, requirements, design, SAST and DAST testing, and secure deployment.
4. Is FromSqlRaw always unsafe in EF Core?
No. FromSqlRaw is safe when you pass values as separate parameters, but it is vulnerable when you build the SQL string from user input yourself. FromSql and FromSqlInterpolated turn interpolated values into database parameters for you.
5. How many PBKDF2 iterations should a .NET app use for passwords?
The OWASP Password Storage Cheat Sheet recommends 600,000 iterations for PBKDF2 with HMAC-SHA256, or 220,000 with HMAC-SHA512. Use a random salt for each user and compare hashes with CryptographicOperations.FixedTimeEquals.
Written by the team behind EduSum.

Top comments (0)