Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ tasks.register('GenerateSampleVulnerability'){
dependencies {
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web'

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail'

// https://mvnrepository.com/artifact/org.mockito/mockito-core
testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.5.13'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package org.sasanlabs.service.vulnerability.emailHeaderInjection;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.sasanlabs.internal.utility.LevelConstants;
import org.sasanlabs.internal.utility.Variant;
import org.sasanlabs.internal.utility.annotations.AttackVector;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRequestMapping;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRestController;
import org.sasanlabs.service.vulnerability.bean.GenericVulnerabilityResponseBean;
import org.sasanlabs.vulnerability.types.VulnerabilityType;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestParam;

/**
* This class contains vulnerabilities related to <strong>Email Header Injection</strong> (also
* known as SMTP Header Injection).
*
* <p>Email Header Injection occurs when user-controlled data is inserted into SMTP message headers
* (such as {@code To}, {@code Subject}, {@code CC}, or {@code BCC}) without stripping CRLF ({@code
* \r\n}) sequences. Because SMTP uses CRLF as a header delimiter, an attacker can inject additional
* headers to:
*
* <ul>
* <li>Add unintended recipients via injected {@code CC:} / {@code BCC:} headers
* <li>Relay spam through a trusted mail server
* <li>Perform phishing attacks using the application's mail infrastructure
* </ul>
*
* <p>Three levels are provided:
*
* <ul>
* <li><strong>Level 1 (Fully Vulnerable):</strong> User-supplied {@code toEmail} and {@code
* subject} are used directly — no sanitization at all.
* <li><strong>Level 2 (Partially Mitigated):</strong> Only {@code \n} (LF) is checked and
* rejected; {@code \r} (CR) alone or the sequence {@code \r\n} encoded differently can still
* bypass this check.
* <li><strong>Level 3 (Secure):</strong> All CRLF characters ({@code \r} and {@code \n}) are
* stripped from both {@code toEmail} and {@code subject} before the email is sent.
* </ul>
*
* @see <a href="https://owasp.org/www-community/attacks/Email_Header_Injection">OWASP – Email
* Header Injection</a>
* @see <a href="https://cwe.mitre.org/data/definitions/93.html">CWE-93 – Improper Neutralization of
* CRLF Sequences</a>
* @author VulnerableApp contributors
*/
@Profile("unsafe")
@VulnerableAppRestController(
descriptionLabel = "EMAIL_HEADER_INJECTION_VULNERABILITY",
value = "EmailHeaderInjection")
public class EmailHeaderInjection {

private static final transient Logger LOGGER = LogManager.getLogger(EmailHeaderInjection.class);

private static final String TO_EMAIL_PARAM = "toEmail";
private static final String SUBJECT_PARAM = "subject";
private static final String BODY_PARAM = "body";
private static final String DEFAULT_BODY = "This is a test email from VulnerableApp.";

private final EmailService emailService;

public EmailHeaderInjection(EmailService emailService) {
this.emailService = emailService;
}

// -------------------------------------------------------------------------
// LEVEL 1 – No validation whatsoever
// -------------------------------------------------------------------------

/**
* <strong>Level 1 – Fully Vulnerable (No Sanitization)</strong>
*
* <p>The {@code toEmail} and {@code subject} request parameters are passed directly to the mail
* sender without any validation or encoding. An attacker can inject a CRLF sequence into either
* parameter to add arbitrary SMTP headers.
*
* <p><strong>Example exploit (subject injection):</strong>
*
* <pre>
* subject=Hello%0D%0ACC:%20attacker@evil.com
* </pre>
*
* <p>The resulting SMTP message will contain an injected {@code CC} header, causing a copy of
* the email to be delivered to {@code attacker@evil.com}.
*/
@AttackVector(
vulnerabilityExposed = VulnerabilityType.EMAIL_HEADER_INJECTION,
description = "EMAIL_HEADER_INJECTION_NO_VALIDATION",
payload = "EMAIL_HEADER_INJECTION_PAYLOAD_LEVEL_1")
@VulnerableAppRequestMapping(
value = LevelConstants.LEVEL_1,
htmlTemplate = "LEVEL_1/EmailHeaderInjection")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel1(
@RequestParam(TO_EMAIL_PARAM) String toEmail,
@RequestParam(value = SUBJECT_PARAM, defaultValue = "Test Email") String subject,
@RequestParam(value = BODY_PARAM, defaultValue = DEFAULT_BODY) String body) {
try {
emailService.sendEmail(toEmail, subject, body);
return successResponse("Email sent to: " + toEmail + " with subject: " + subject);
} catch (Exception e) {
LOGGER.error("Failed to send email at Level 1", e);
return errorResponse("Failed to send email: " + e.getMessage());
}
}

// -------------------------------------------------------------------------
// LEVEL 2 – Partial mitigation: checks \n but misses \r
// -------------------------------------------------------------------------

/**
* <strong>Level 2 – Partially Mitigated (Incomplete CRLF Check)</strong>
*
* <p>This level attempts to prevent header injection by rejecting values that contain a
* linefeed ({@code \n}) character. However, it fails to check for a bare carriage return
* ({@code \r}). Some SMTP implementations treat {@code \r} alone as a line terminator, making
* this validation bypassable.
*
* <p><strong>Bypass technique:</strong> Use {@code %0D} (URL-encoded {@code \r}) instead of the
* more commonly blocked {@code %0A} ({@code \n}).
*
* <pre>
* subject=Hello%0DCC:%20attacker@evil.com
* </pre>
*/
@AttackVector(
vulnerabilityExposed = VulnerabilityType.EMAIL_HEADER_INJECTION,
description = "EMAIL_HEADER_INJECTION_NEWLINE_CHECK_ONLY",
payload = "EMAIL_HEADER_INJECTION_PAYLOAD_LEVEL_2")
@VulnerableAppRequestMapping(
value = LevelConstants.LEVEL_2,
htmlTemplate = "LEVEL_1/EmailHeaderInjection")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel2(
@RequestParam(TO_EMAIL_PARAM) String toEmail,
@RequestParam(value = SUBJECT_PARAM, defaultValue = "Test Email") String subject,
@RequestParam(value = BODY_PARAM, defaultValue = DEFAULT_BODY) String body) {
// Incomplete check: only \n is blocked; \r alone still bypasses this guard
if (toEmail.contains("\n") || subject.contains("\n")) {
return errorResponse(
"Invalid input: newline characters are not allowed in email headers.");
}
try {
emailService.sendEmail(toEmail, subject, body);
return successResponse("Email sent to: " + toEmail + " with subject: " + subject);
} catch (Exception e) {
LOGGER.error("Failed to send email at Level 2", e);
return errorResponse("Failed to send email: " + e.getMessage());
}
}

// -------------------------------------------------------------------------
// LEVEL 3 – Secure: strip all CRLF characters
// -------------------------------------------------------------------------

/**
* <strong>Level 3 – Secure (Complete CRLF Sanitization)</strong>
*
* <p>All carriage-return ({@code \r}) and newline ({@code \n}) characters are stripped from
* both {@code toEmail} and {@code subject} before constructing the SMTP message. This prevents
* header injection regardless of how the attacker encodes the CRLF sequence.
*
* <p>This is the recommended approach for preventing email header injection in Java
* applications.
*/
@AttackVector(
vulnerabilityExposed = VulnerabilityType.EMAIL_HEADER_INJECTION,
description = "EMAIL_HEADER_INJECTION_CRLF_SANITIZATION")
@VulnerableAppRequestMapping(
value = LevelConstants.LEVEL_3,
htmlTemplate = "LEVEL_1/EmailHeaderInjection",
variant = Variant.SECURE)
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel3(
@RequestParam(TO_EMAIL_PARAM) String toEmail,
@RequestParam(value = SUBJECT_PARAM, defaultValue = "Test Email") String subject,
@RequestParam(value = BODY_PARAM, defaultValue = DEFAULT_BODY) String body) {
try {
emailService.sendEmailSanitized(toEmail, subject, body);
return successResponse("Email sent using sanitized headers.");
} catch (Exception e) {
LOGGER.error("Failed to send email at Level 3", e);
return errorResponse("Failed to send email: " + e.getMessage());
}
}

// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------

private ResponseEntity<GenericVulnerabilityResponseBean<String>> successResponse(
String message) {
return new ResponseEntity<>(
new GenericVulnerabilityResponseBean<>(message, true), HttpStatus.OK);
}

private ResponseEntity<GenericVulnerabilityResponseBean<String>> errorResponse(String message) {
return new ResponseEntity<>(
new GenericVulnerabilityResponseBean<>(message, false), HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.sasanlabs.service.vulnerability.emailHeaderInjection;

/**
* Service interface for sending emails within the EmailHeaderInjection vulnerability module.
*
* <p>Provides two variants:
*
* <ul>
* <li>{@link #sendEmail(String, String, String)} – sends the email without any sanitization of
* headers, intentionally leaving the caller responsible for header safety (or lack thereof).
* <li>{@link #sendEmailSanitized(String, String, String)} – strips CRLF characters from the
* {@code to} and {@code subject} fields before constructing the SMTP message, preventing
* header injection.
* </ul>
*
* @author VulnerableApp contributors
*/
public interface EmailService {

/**
* Sends a plain-text email <strong>without</strong> sanitizing the {@code to} or {@code
* subject} parameters. This method is intentionally unsafe and is used by the vulnerable levels
* of {@link EmailHeaderInjection} to demonstrate email header injection attacks.
*
* @param to recipient address (may contain injected CRLF sequences)
* @param subject email subject (may contain injected CRLF sequences)
* @param body email body text
*/
void sendEmail(String to, String subject, String body);

/**
* Sends a plain-text email after stripping {@code \r} and {@code \n} characters from the {@code
* to} and {@code subject} parameters. This is the secure variant used by the safe level of
* {@link EmailHeaderInjection}.
*
* @param to recipient address (sanitized)
* @param subject email subject (sanitized)
* @param body email body text
*/
void sendEmailSanitized(String to, String subject, String body);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.sasanlabs.service.vulnerability.emailHeaderInjection;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

/**
* Implementation of {@link EmailService} that delegates to Spring's {@link JavaMailSender}.
*
* <p>Two variants are provided:
*
* <ul>
* <li>{@link #sendEmail} – no header sanitization (intentionally unsafe, used by vulnerable
* levels)
* <li>{@link #sendEmailSanitized} – strips CRLF characters before setting headers (secure, used
* by the safe level)
* </ul>
*
* @author VulnerableApp contributors
*/
@Service
public class EmailServiceImpl implements EmailService {

private static final transient Logger LOGGER = LogManager.getLogger(EmailServiceImpl.class);

/** CRLF characters that an attacker can inject to add arbitrary SMTP headers. */
private static final String CRLF_REGEX = "[\r\n]";

private final JavaMailSender mailSender;
private final String fromAddress;

public EmailServiceImpl(
JavaMailSender mailSender, @Value("${vulnerableapp.email.from}") String fromAddress) {
this.mailSender = mailSender;
this.fromAddress = fromAddress;
}

/**
* {@inheritDoc}
*
* <p><strong>Intentionally unsafe:</strong> the {@code to} and {@code subject} values are
* passed directly to {@link SimpleMailMessage} without stripping CRLF sequences. An attacker
* who controls these fields can inject additional SMTP headers (e.g., {@code CC:}, {@code
* BCC:}) enabling spam relaying or phishing campaigns.
*/
@Override
public void sendEmail(String to, String subject, String body) {
LOGGER.debug("Sending email (unsanitized) to: {}, subject: {}", to, subject);
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromAddress);
message.setTo(to);
message.setSubject(subject);
message.setText(body);
mailSender.send(message);
}

/**
* {@inheritDoc}
*
* <p><strong>Secure variant:</strong> strips all {@code \r} and {@code \n} characters from
* {@code to} and {@code subject} before constructing the SMTP message. This prevents an
* attacker from injecting additional SMTP headers through these fields.
*/
@Override
public void sendEmailSanitized(String to, String subject, String body) {
String sanitizedTo = sanitizeHeader(to);
String sanitizedSubject = sanitizeHeader(subject);
LOGGER.debug(
"Sending email (sanitized) to: {}, subject: {}", sanitizedTo, sanitizedSubject);
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromAddress);
message.setTo(sanitizedTo);
message.setSubject(sanitizedSubject);
message.setText(body);
mailSender.send(message);
}

/**
* Removes all carriage-return ({@code \r}) and newline ({@code \n}) characters from the
* supplied header value to prevent CRLF / email header injection.
*
* @param headerValue raw header value supplied by the caller
* @return sanitized header value with no CRLF characters
*/
String sanitizeHeader(String headerValue) {
if (headerValue == null) {
return null;
}
return headerValue.replaceAll(CRLF_REGEX, "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public enum VulnerabilityType {
// LDAP Injection Vulnerability
LDAP_INJECTION(90, 29),

// Email Header Injection Vulnerability (CWE-93: Improper Neutralization of CRLF Sequences)
EMAIL_HEADER_INJECTION(93, null),

WEB_CACHE_POISONING(null, null);

private Integer cweID;
Expand Down
20 changes: 20 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,23 @@ benchmark.dast.ground-truth.read-timeout-ms=10000
##Details for KeyPair generation in application
#keytool -genkeypair -alias SasanLabs -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore sasanlabs.p12 -validity 3650
#password for pkcs12, sasanlabs.p12 is "changeIt"

# ============================================================
# Email / SMTP configuration (used by EmailHeaderInjection vulnerability)
# Override via environment variables for different environments.
# Defaults point to a local dev mail catcher (e.g., MailHog / MailDev).
# ============================================================
spring.mail.host=${MAIL_HOST:localhost}
spring.mail.port=${MAIL_PORT:1025}
spring.mail.username=${MAIL_USERNAME:}
spring.mail.password=${MAIL_PASSWORD:}
spring.mail.properties.mail.smtp.connectiontimeout=${MAIL_SMTP_CONNECTION_TIMEOUT_MS:5000}
spring.mail.properties.mail.smtp.timeout=${MAIL_SMTP_READ_TIMEOUT_MS:10000}
spring.mail.properties.mail.smtp.writetimeout=${MAIL_SMTP_WRITE_TIMEOUT_MS:10000}
spring.mail.properties.mail.smtp.auth=${MAIL_SMTP_AUTH:false}
spring.mail.properties.mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS:false}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
# VulnerableApp-specific email defaults used by the EmailHeaderInjection module
vulnerableapp.email.from=${EMAIL_FROM:vulnerable@example.com}
vulnerableapp.email.recipient.default=${EMAIL_RECIPIENT:victim@example.com}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
EMAIL_HEADER_INJECTION_PAYLOAD_LEVEL_1=In an Email Header Injection attack, an attacker can inject new headers into the email by introducing carriage return (\\r) and linefeed (\\n) sequences into header parameters.<br/>\\
<b>How to exploit this level?</b><br/>\\
There are no validations present at this level. You can inject additional recipients by appending CRLF and a CC or BCC header to the subject parameter, for example:<br/>\\
<code>subject=Test%0D%0ACC:attacker@evil.com</code>
EMAIL_HEADER_INJECTION_PAYLOAD_LEVEL_2=In an Email Header Injection attack, an attacker can inject new headers into the email by introducing carriage return (\\r) and linefeed (\\n) sequences into header parameters.<br/>\\
<b>How to exploit this level?</b><br/>\\
This level rejects input containing a linefeed (\\n / %0A) character. However, it fails to check for carriage returns (\\r / %0D). Some SMTP implementations treat a carriage return alone as a line separator. You can bypass the validation by injecting %0D alone, for example:<br/>\\
<code>subject=Test%0DCC:attacker@evil.com</code>
Loading
Loading