Weakness reference
CWE-359

Exposure of Private Personal Information to an Unauthorized Actor

This weakness occurs when software fails to properly control who can access private or personally identifiable information PII — such as names, email…

01Summary

This weakness occurs when software fails to properly control who can access private or personally identifiable information (PII) — such as names, email addresses, phone numbers, social security numbers, or financial data. When access controls are missing or misconfigured, unauthorized users can view, download, or manipulate sensitive data they should never see. This is one of the most common and damaging vulnerabilities in real-world applications.

02How It Happens

PII exposure typically stems from one of three patterns: missing access checks (code that retrieves user data without verifying the requester's permission), overly permissive default settings (data exposed publicly by accident), or broken authentication/session handling (allowing one user to impersonate another). Developers often assume that if a user is logged in, they can access any data in the system — but authorization (what you're allowed to do) is separate from authentication (who you are). A logged-in user should only see their own data or data they have explicit permission to access. When this boundary is not enforced in code, any authenticated user can often view anyone else's records.

03Real-World Impact

Exposed PII can lead to identity theft, financial fraud, account takeover, regulatory fines (GDPR, CCPA, HIPAA), loss of customer trust, and reputational damage. In healthcare, education, and financial sectors, PII exposure is particularly severe. Even a single leaked record can harm an individual; exposure of thousands of records can trigger mandatory breach notifications and lawsuits. For the organization, the cost of remediation, legal liability, and lost business can be substantial.

04Vulnerable & Fixed Patterns

Vulnerable pattern
from flask import Flask, request, jsonify
import sqlite3

app = Flask(__name__)

@app.route('/user/<user_id>')
def get_user(user_id):
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute('SELECT name, email, phone, ssn FROM users WHERE id = ?', (user_id,))
    user = cursor.fetchone()
    conn.close()
    return jsonify({'name': user[0], 'email': user[1], 'phone': user[2], 'ssn': user[3]})

Why it's vulnerable:
The endpoint returns any user's full record (including SSN) to anyone who knows the user ID, with no check that the requester is authorized to view that specific user's data. An attacker can iterate through user IDs and harvest PII.

Fixed pattern
from flask import Flask, request, jsonify, session
import sqlite3

app = Flask(__name__)

@app.route('/user/<user_id>')
def get_user(user_id):
    # Verify the requester is authenticated and authorized
    if 'user_id' not in session:
        return jsonify({'error': 'Unauthorized'}), 401
    
    # Only allow users to view their own data
    if session['user_id'] != int(user_id):
        return jsonify({'error': 'Forbidden'}), 403
    
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    # Return only non-sensitive fields
    cursor.execute('SELECT name, email FROM users WHERE id = ?', (user_id,))
    user = cursor.fetchone()
    conn.close()
    return jsonify({'name': user[0], 'email': user[1]})
Vulnerable pattern
<?php
$user_id = $_GET['id'];
$conn = new mysqli('localhost', 'user', 'pass', 'mydb');
$result = $conn->query("SELECT name, email, phone, ssn FROM users WHERE id = $user_id");
$user = $result->fetch_assoc();
echo json_encode($user);
?>

Why it's vulnerable:
No authentication or authorization check; any visitor can request any user ID and receive full PII including SSN. Additionally, the query is vulnerable to SQL injection.

Fixed pattern
<?php
session_start();

// Verify authentication
if (!isset($_SESSION['user_id'])) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

$requested_id = intval($_GET['id']);

// Verify authorization: users can only view their own data
if ($_SESSION['user_id'] !== $requested_id) {
    http_response_code(403);
    echo json_encode(['error' => 'Forbidden']);
    exit;
}

$conn = new mysqli('localhost', 'user', 'pass', 'mydb');
$stmt = $conn->prepare("SELECT name, email FROM users WHERE id = ?");
$stmt->bind_param('i', $requested_id);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
echo json_encode($user);
?>

05Prevention Checklist

Implement role-based access control (RBAC).
Define what data each user role can access, and enforce it at the code level before returning any record.
Always verify authorization, not just authentication.
Check that the logged-in user has permission to access the specific resource they're requesting.
Minimize PII in responses.
Return only the fields actually needed; never expose SSNs, credit card numbers, or other highly sensitive data unless absolutely required.
Use parameterized queries.
Prevent SQL injection, which is often used to bypass access controls and extract PII.
Audit data access logs.
Monitor who accessed what data and when; unusual patterns may indicate unauthorized access.
Apply principle of least privilege.
Database accounts and application service accounts should have minimal permissions; restrict SELECT queries to only the columns and rows needed.

06Signs You May Already Be Affected

Check your application logs for unusual data access patterns — multiple requests for different user IDs from a single session, or access to sensitive fields that are rarely queried. Review your database permissions and API endpoints to confirm that authorization checks are in place before returning user records. If you find endpoints that return PII without verifying the requester's permission, treat them as active vulnerabilities.

07Related Recent Vulnerabilities