Actionable threat hunting with GTI (II) - Analyzing a massive phishing campaign

Joseliyo_Jstnk

Overview

Welcome back to our series on Actionable Threat Hunting with Google Threat Intelligence! This time, we're going to talk about a huge phishing scam that's just happened in the last few months. It's connected to Booking, which is one of the most popular websites for booking hotels.

Our main goal here is to show you how just one email we got helped us figure out how big this scam really was. We also found some interesting files that seem connected to Telegram messages the threat actors used. And don't worry, we'll share some of our code from Google Colab so you can try this yourself!

One clever thing about this scam is that the threat actors sent messages directly to victims through Booking's official website. They even used the message chats from bookings people had already made. This strongly suggests that the threat actors were somehow able to access this information through a method that is currently unknown.

‘Your reservation is at risk’

If you've recently received an email that appears to be from Booking.com, or even a message in the message thread of the Booking application for a reservation you had saying “your reservation is at risk”, don't worry, you're not alone.

For several weeks now, several users have reported a massive Booking phishing campaign where the actors behind the phishing sites are attempting to steal information such as credit cards. Pictured in Figure 1 is an example of a message sent through the Booking chat, using a real reservation thread.

image.png

Figure 1: Example of message discovered in a phishing website related to booking.com

According to our analysis, the phishing message is sent using two different ways:

  1. Through the Booking.com website or mobile application, within the conversation thread with the hotel reservation. 
  2. Through email from a legitimate Booking message.

For example, if you have a reservation at a hotel in Los Angeles made via Booking, the attackers will send you a message in that reservation's chat, stating that your booking is at risk and you must update your card details, otherwise it will be canceled.

Due to Booking.com's functionality, all messages sent via chat are also delivered to the user by email, which means the user receives the same message twice through two different channels.

In this campaign, the message the user receives contains a supposed URL that they must click to enter their card details and prevent their reservation from being canceled. Based on our observations, these URLs serve only as redirectors to the sites that actually host the fraudulent content.

Therefore, the actors registered Tier 1 domains that simply act as redirectors to the Tier 2 domains. The Tier 2 domains contain the content for stealing the victims' credit card details. Additionally, these domains were registered with names similar to "Booking" to make the user think it is the legitimate domain.

image.pngFigure 2: How this phishing is usually distributed

Discovering Tier 1 infra

The email we received had a domain setup that looked like [hotel_name].[3_letters]-[2_letters].com (for example: hostelmandarinkauxeh.eto-la[.]com). What's important here is that the first part of this domain name was actually the name of a real hotel from a current booking. 

After you clicked the link from the Booking chat, it redirects you to a different URL. This new domain name tried to look like a real Booking domain, and its name often looked like booking.confirmation-id[5_numbers].com.

Knowing how redirects work, and the patterns of both domains, we can make different searches to help us find possible URLs related to the campaign.

The first query is to find Tier 1 URLs that redirect to Tier 2 URLs. To do this, we want to avoid getting results that are Tier 2 URLs. We can use the redirects_to modifier to set the pattern found in the Tier 2 URLs, which is the typical behavior. We will also use the hostname modifier to only show results for URLs that might be Tier 1.

entity:url redirects_to:"https://booking.confirmation-*" and not
hostname:"booking.confirmation-"

The results we expect from this search are URLs related to Tier 1 domains. Some example URLs we find will match the patterns we described earlier:

image.pngFigure 3: Results obtained from the query used looking for Tier 1 URLs

With the results we have, we now want to find certain patterns that all these URLs share. This way, we can widen our search to find new possible URLs related to both Tier 1 and Tier 2 infra. Let’s focus on the HTML information from these URLs obtained in the first query.

For the following code examples we are going to use vt-py, we recommend you read the documentation and the examples that we have available in our GitHub repository to get familiar. The next code snippet is the base to build the rest of the code snippets that we have to obtain different kinds of data.

! DISCLAIMER: Please note that this code is for educational purposes only. It is not intended to be run directly in production. This is provided on a best effort basis. Please make sure the code you run does what you expect it to do.
# Base code
# Before running the code, make sure you have enough quota to do so. This query can consume a lot of quota.
import vt
cli = vt.Client(getpass.getpass('Introduce your VirusTotal API key: '))
query = "entity:url redirects_to:\"https://booking.confirmation-*\" and not hostname:\"booking.confirmation-\"" # @PARAM {type: "string"}

# Look for the samples
query_results_t1 = []

async for itemobj in cli.iterator('/intelligence/search',params={'query': "%s"%(query)},limit=0): # Set the limit you want
    query_results_t1.append(itemobj.to_dict())
all_results_t1 = list(json.loads(json.dumps(query_results_t1, default=lambda o: getattr(o, '__dict__', str(o)))))

From the initial query that we ran, let’s get the HTML titles and group them to identify the most observed. We can achieve this by running the following snippet in our Google Colab.

# Get URL Titles
df = pd.DataFrame(columns=['title', 'url'])

for item in all_results_t1:
    # Get the 'attributes' dictionary from the current item, defaulting to an empty dictionary if not present
    attributes = item.get('attributes', {})
    # Get the 'title' and 'url' values from the attributes dictionary
    title = attributes.get('title')
    url = attributes.get('url')
    # Check if both title and url are present (not None or empty)
    if title and url:
        # If both are present, create a new DataFrame with the title and url
        # and concatenate it to the main DataFrame. ignore_index=True resets the index.
        df = pd.concat([df, pd.DataFrame({'title': [title], 'url': [url]})], ignore_index=True)

# Group by title and count the number of URLs
title_counts = df.groupby('title')['url'].count().reset_index(name='url_count')

# Sort by url_count in descending order
title_counts = title_counts.sort_values(by='url_count', ascending=False).reset_index(drop=True)

# Display the resulting DataFrame
display(title_counts)

Running the last code snippet, the TOP 5 titles we obtained were the following:

Title Count
One moment... 779
One moment... 67
AD not found (captcha2) 46
Suspected phishing site | Cloudflare 5
booking.confirmation-id83974.com | 521: Web se..  

The first two results might look the same, but they are not. The difference is the dots used after One moment. In this case, it seems the first three results are the most common, so we can save this information to build a search later. Please note that these values can be used with the title modifier for URLs.

Another interesting piece of information that could help find new URLs is the <meta> tags from the URLs we've identified. To do this, we will run a search like before, but this time, instead of titles, we will look at the values of any <meta> tag. These values can be used with the meta modifier.

# Get meta values
from collections import Counter

all_meta_values = []

# Iterate through each item in dsaaaa (assuming this contains the relevant data)
for item in all_results_t1:
    attributes = item.get('attributes', {})
    html_meta = attributes.get('html_meta', {})

    # Iterate through the values of the html_meta dictionary
    for meta_values_list in html_meta.values():
        # Extend the all_meta_values list with the items from the current list
        all_meta_values.extend(meta_values_list)

# Count the occurrences of each unique meta value
meta_value_counts = Counter(all_meta_values)

# Convert the counts to a pandas DataFrame
df_meta_counts = pd.DataFrame(list(meta_value_counts.items()), columns=['Meta Value', 'Count'])

# Sort by count in descending order
df_meta_counts = df_meta_counts.sort_values(by='Count', ascending=False).reset_index(drop=True)

# Display the resulting DataFrame
display(df_meta_counts)

The results we got from the <meta> tags were very interesting, and there were over 750 possible results. First, we found many meta values that actually were URLs storing images. Some of these images were icons used on fake websites, and others were from hotels hosted in a legitimate Booking domain.

  • URL identified in the <meta> tags 126 times: hxxps://ltdfoto[.]ru/images/2025/06/04/photo_2025-06-02_11-23-22.md.jpg
  • URL identified in the <meta> tags 14 times: hxxps://cf.bstatic[.]com/xdata/images/hotel/max300/619949340.jpg?k=971fb717a1df5b8d7ae0b077411493921f7f041a8992a9deb4d11b8aef870571&o= 

Bstatic.com is a legitimate domain owned by Booking. Multiple images hosted in that domain were discovered in the <meta> tags of the phishing websites.

We also found many strings in the <meta> tag values that started with Booking - [hotel_name]. This finding is interesting because there were up to 400 possible results that started this way. Some examples were the following:

Meta string Count
Booking - HEIMALEIGA - Your Icelandic home awa... 24
Booking - Grand Hotel Duomo 9
Booking - FOUND Hotel Carlton Nob Hill 7
Booking - La Castellana
7
Booking - Payment verification
7
...
...
Booking - Villa Preselle Country Resort
1
Booking - Hotel Hagemann
1
Booking - Tahiti Airport Motel
1
Booking - Hotel Aldea Berlin
1
Booking - Fasthôtel Clermont-Ferrand Gerzat
1

Basically, with these results, we can now add more filters to our first search or even create a new one. The following query can help us to expand the results and get new URLs that could be related to some Booking campaigns, even before this one.

entity:url fs:2024-09-01+ (title:"One moment" or title:"AD not found (captcha2)") and
(meta:"Booking -" or meta:"https://ltdfoto.ru/images/2025/06/04/photo_2025-06-02_11-
3-22.md.jpg" or meta:"https://cf.bstatic.com/xdata/images/hotel/") and not
(hostname:"booking.confirmation-" or hostname:"booking.com")

image.pngFigure 4: Ways to do hunting in the Tier 1 infra (download SVG format)

Discovering Tier 2 infra

We have enough information to also find Tier 2 infrastructure set up by the actors. The first thing we will do is look for domains that match the patterns we found earlier, with the following search. The next search gives us over 100 recently registered domain results.

entity:domain domain_regex:"^booking\.confirmation-id[0-9]{5}\.com$"

image.png
Figure 5: Tier 2 domains matching the query regex

The next step is to find URLs that have that pattern in their hostname. One of the best queries that we can run in this case is the following.

entity:url hostname:"booking.confirmation-id*"

Around 1,500 URLs match the hostname criteria, and all of them are related to Booking campaigns. In fact, the oldest URL was uploaded to the platform in November 2023.

To follow the same idea we used with the Tier 1 URLs, it would now be good to look for titles and patterns in the <meta> tags for all these new URLs discovered with the last query. This could help us find new possible phishing sites. For the titles we obtained the following results.

Title count
AD not found (captcha2) 492
One moment... 300
Booking.com | Official website | Best hotels and accommodation 43
One moment… 33
403 Forbidden 23
Booking.com | Official site | The best hotels & accommodation 17
Suspected phishing site | Cloudflare 16
404 Not Found 16
Just a moment... 14
Booking.com | Official site | The best hotels, flights, car rentals & accommodations 8

Strings like One moment or AD not found (captcha2) were seen before in the Tier 1 infrastructure. However, in this case, we also see another title pattern that could be interesting to find other URLs, like Booking.com | Official.

For patterns found in the <meta> tags, we saw similar results. However, there are also values that start with Booking.com followed by the hotel's name, similar to the pattern identified in infra Tier 1 Booking - followed by the hotel's name.

Meta String Count
https[:]//ltdfoto[.]ru/images/2025/06/04/photo_2025-06-02_11-23-22.md.jpg 68
light dark 40
Booking.com 34
company
34
https[:]//www.booking[.]com 26
Booking.com Hotel Reservations
16
com.booking
16
Booking.com: The largest selection of hotels, homes, and vacation rentals
16
@bookingcom
16
Booking - Wind Hotel
3
Booking: Barcelona City North Hostal
2

Based on the results, if we want to create a new query and make sure we only get URLs related to the Tier 2 infrastructure, the next search will definitely give us that information.

entity:url (title:"One moment" or title:"AD not found (captcha2)" or
title:"Booking.com | Official") and (meta:"Booking -" or
meta:"https://ltdfoto.ru/images/2025/06/04/photo_2025-06-02_11-23-22.md.jpg" or
meta:"https://cf.bstatic.com/xdata/images/hotel/") and
hostname:"booking.confirmation-*"

image.png
Figure 6: Tier 2 URLs matching the query

With this, we'll now have access to URLs that have been distributed by the threat actors for Tier 2. Next, we will put together all the URLs from both Tiers to better understand this campaign and get more details.

image.pngFigure 7: Ways to do hunting in the Tier 2 infra (download SVG format)

Analyzing the whole campaign

The different queries we've made can help us create YARA rules to monitor for new activity from this campaign. In fact, at the end of this blog we have created a section for YARA rules. 

But to better understand everything about the campaign, it's also good to know other related information. This includes when this activity started, how many URLs Google Threat Intelligence’s scanning tools identify the URLs as malicious, interesting keywords to monitor, and so on. We will go step by step to get this information.

Obtaining Tier 1 and Tier 2 URLs

First, we need to find all possible URLs related to this campaign. To do this, the following search was made to help us.

entity:url fs:2022-01-01+ (title:"One moment" or title:"AD not found (captcha2)" or
title:"Booking.com | Official") and (meta:"Booking -" or
meta:"https://ltdfoto.ru/images/2025/06/04/photo_2025-06-02_11-23-22.md.jpg" or
meta:"https://cf.bstatic.com/xdata/images/hotel/") and not (hostname:"booking.com" or 
edirects_to:booking.com or redirects_to:placetobe.homes) and not tag:trackers

In this search, it's important to use and not tag:trackers. This helps reduce wrong results from real ad campaigns related to Booking. Another important filter is not to get URLs that redirect to the legitimate Booking website. To avoid outdated results, we also incorporated a time filter.

# Before running the code, make sure you have enough quota to do so. This query can consume a lot of quota.
import vt
cli = vt.Client(getpass.getpass('Introduce your VirusTotal API key: '))
query = "entity:url fs:2022-01-01+ (title:\"One moment\" or title:\"AD not found (captcha2)\" or title:\"Booking.com | Official\") and (meta:\"Booking -\" or meta:\"https://ltdfoto.ru/images/2025/06/04/photo_2025-06-02_11-23-22.md.jpg\" or meta:\"https://cf.bstatic.com/xdata/images/hotel/\") and not (hostname:\"booking.com\" or redirects_to:booking.com or redirects_to:placetobe.homes) and not tag:trackers" # @PARAM {type: "string"}

# Look for the samples
query_results = []

async for itemobj in cli.iterator('/intelligence/search',params={'query': "%s"%(query)},limit=0): # Set the limit you want
    query_results.append(itemobj.to_dict())
all_results = list(json.loads(json.dumps(query_results, default=lambda o: getattr(o, '__dict__', str(o)))))

The previous code snippet stores all the URLs in the all_results variable. Let's now get the details we want.

Timeline

Understanding how big the campaign is helps us know roughly when the actors might have started. We can't know the exact date, but the times URLs obtained from the initial query of this section were uploaded to Google Threat Intelligence give us a very good idea. There were periods when many URLs were uploaded at once, which suggests the actors were very busy then. The next code snippet can give us a Daily Submission Timeline.

from datetime import datetime
import pandas as pd
import plotly.express as px

# Data processing for daily submissions
daily_submissions = []
for item in all_results:
    attributes = item.get('attributes', {})
    timestamp = attributes.get('first_submission_date')
    if timestamp:
        dt_object = datetime.fromtimestamp(timestamp)
        # Extract only the date
        daily_submissions.append(dt_object.date())

# Count submissions per day
daily_counts = pd.Series(daily_submissions).value_counts().reset_index()
daily_counts.columns = ['date', 'submission_count']

# Sort by date
daily_counts = daily_counts.sort_values(by='date').reset_index(drop=True)

# Data visualization of daily submissions
fig = px.area(daily_counts, x='date', y='submission_count',
                 labels={'date': 'Date', 'submission_count': 'Number of Submissions'},
                 title='Daily Submission Timeline')

# Update layout for better date formatting on x-axis
fig.update_layout(xaxis_title="Date", yaxis_title="Number of Submissions")

# Display the plot
fig.show()

image.pngFigure 8: Daily submission timeline suggest huge activity since January 2025

Looking at all the URLs from both Tiers, it seems the patterns we found have been around for a few years. But in January 2025, more spikes of activity began. Specifically, May and June were the busiest months.

image.pngFigure 9: Monthly submissions during 2025

Redirections

We wanted to know how many URLs were redirected to a different URL. We could guess from our past results that more URLs were redirected (since there was more Tier 1 infrastructure than Tier  2), but we still wanted to know a percentage to understand it better.

import plotly.express as px
import pandas as pd

# Count URLs with and without redirection
redirected_urls = 0
not_redirected_urls = 0

# Iterate through each item in the results
for item in all_results:
    attributes = item.get('attributes', {})
    original_url = attributes.get('url')
    last_final_url = attributes.get('last_final_url')

    # Check if both original_url and last_final_url exist and are different
    if original_url and last_final_url and original_url != last_final_url:
        redirected_urls += 1
    else:
        not_redirected_urls += 1


# Calculate the total number of URLs
total_urls = redirected_urls + not_redirected_urls

# Calculate percentages, handling the case where total_urls is 0 to avoid division by zero
percentage_redirected = (redirected_urls / total_urls) * 100 if total_urls > 0 else 0
percentage_not_redirected = (not_redirected_urls / total_urls) * 100 if total_urls > 0 else 0

# Print the results
print(f"URLs with redirection: {redirected_urls} ({percentage_redirected:.2f}%)")
print(f"URLs without redirection: {not_redirected_urls} ({percentage_not_redirected:.2f}%)")

# Create a DataFrame for the pie chart
data = {'Category': ['Redirected URLs', 'Not Redirected URLs'],
        'Count': [redirected_urls, not_redirected_urls]}
df_redirection_status = pd.DataFrame(data)

# Create the pie chart
fig = px.pie(df_redirection_status, values='Count', names='Category',
             title='Percentage of URLs with and without Redirection')

# Update traces to show text inside the pie chart
fig.update_traces(textinfo='percent+label', insidetextorientation='radial')

# Display the plot
fig.show()

image.pngFigure 10: Percentage of URLs with and without Redirection

52.6% of the URLs redirect, which is interesting. This is because the other 47.4% seem to be phishing sites where users most likely have to enter their credit card details.

When we look at which domains have the most redirects, it's clear that the domains with the initial pattern booking.confirmation-id[5_numbers].com are seen the most. But, there are also other domains in the Top 10 redirections that don't match this pattern. These are also useful for possible YARA rules to help us watch this activity.

from urllib.parse import urlparse

last_final_urls = []
for item in all_results:
    attributes = item.get('attributes', {})
    original_url = attributes.get('url')
    last_final_url = attributes.get('last_final_url')
    # Only consider last_final_url if it exists and is different from the original_url
    if last_final_url and last_final_url != original_url:
        last_final_urls.append(last_final_url)

# Extract the domain from each last_final_url
domains = []
for url in last_final_urls:
    try:
        parsed_url = urlparse(url)
        domains.append(parsed_url.netloc)
    except Exception as e:
        print(f"Error parsing URL {url}: {e}")
        domains.append("Error parsing URL")

# Create a pandas Series from the list of domains and get the value counts
domain_counts = pd.Series(domains).value_counts().reset_index()
domain_counts.columns = ['Domain', 'Count']

# Display the domain counts
display(domain_counts)
Top 10 domains with the most redirects
Domain Count
booking.id5225211246[.]world 62
booking.confirmation-id9918[.]com 25
booking.confirmation-id901823[.]com 25
booking.confirmation-id542[.]com 15
booking.confirmation-id089172[.]com 15
booking.id455512201[.]world 15
booking.confirmation-id190238[.]com 14
booking.confirmation-id987933[.]com 14
booking.confirmation-id4321[.]com 14
booking.confirmation-id89712[.]com 13

These are the final Top 10 domains where most redirects happen from Tier 1 to Tier 2. The first domain booking.id5225211246[.]world appears because many URLs on that same domain redirect, but to different parts of the site (different paths) as you can see in the next example.

URL Final URL
https://booking.id5225211246[.]world/EC669QWO2 https://booking.id5225211246[.]world/YZJMYDLNV
https://booking.id5225211246[.]world/EC669QWO2 https://booking.id5225211246[.]world/UT4DOPJPB
https://booking.id5225211246[.]world/EC669QWO2 https://booking.id5225211246[.]world/ZPOCL8FBK
https://booking.id5225211246[.]world/EC669QWO2 https://booking.id5225211246[.]world/6WJCSCMOX
https://booking.id5225211246[.]world/EC669QWO2 https://booking.id5225211246[.]world/Y72WFFHD7
https://booking.id5225211246[.]world/EC669QWO2 https://booking.id5225211246[.]world/58H3YTAOO
https://booking.id5225211246[.]world/EC669QWO2 https://booking.id5225211246[.]world/QHKXP8VB2

For other domains, redirects from Tier 1 infrastructure led to different paths for the same domains, indicating that a single domain stored multiple phishing attempts.

URL Final URL
https://rsvnmwww.stayiceland[.]com/ https://booking.confirmation-id9918[.]com/4106029014
http://rsvnenom.stayiceland[.]com/ https://booking.confirmation-id9918[.]com/4831933247
http://rsvnuitr.icestayland[.]com/ https://booking.confirmation-id9918[.]com/4718128210
http://rsvnokwc.icestayland[.]com/ https://booking.confirmation-id9918[.]com/4009187168
http://rsvnfbsz.icestayland[.]com/ https://booking.confirmation-id9918[.]com/4747003708
https://rsvnxgnz.icestayland[.]com/ https://booking.confirmation-id9918[.]com/4548282193
https://rsvnjzjp.icestayland[.]com/ https://booking.confirmation-id9918[.]com/4555634971
https://rsvndobm.stayiceland[.]com/ https://booking.confirmation-id9918[.]com/4562368486

Interesting keywords

We used Gemini to look at all the domain names from the URLs we had access to. Our goal was to find keywords that repeat in these domain names. This helps us find patterns to make new detections.

The main keywords identified by Gemini were the following:

keywords = ["booking", "reservation", "reserv", "id", "guest", "hotel", "confirm", "confrim", "confirmation"]

The fun part is that Gemini gave different options for some words. For example, the keyword reservation was seen as widely used, but it also offered reserv as an alternative. The reason was that some domains included the misspelling reservetion. In the same way, we found domains with confirmed, which explains the choices between confirm, confirmation and confrim.

from urllib.parse import urlparse
import pandas as pd
import plotly.express as px

keywords = ["booking", "reservation", "reserv", "id", "guest", "hotel", "confirm", "confrim", "confirmation"] 
keyword_counts_original_url = {keyword: 0 for keyword in keywords}

for item in all_results:
    attributes = item.get('attributes', {})
    url = attributes.get('url')
    if url:
        try:
            parsed_url = urlparse(url)
            netloc = parsed_url.netloc.lower()
            for keyword in keywords:
                if keyword in netloc:
                    keyword_counts_original_url[keyword] += 1
                    #break  # Count each URL only once even if it contains multiple keywords
        except Exception as e:
            print(f"Error parsing URL {url}: {e}")

# Create a DataFrame for plotting
df_keyword_counts_original_url = pd.DataFrame(list(keyword_counts_original_url.items()), columns=['Keyword', 'Count'])

# Create a bar chart using Plotly Express
fig = px.bar(df_keyword_counts_original_url, x='Keyword', y='Count',
             title='Count of Original URLs Containing Specific Keywords in Domain Names')

# Display the plot
fig.show()

image.png

Figure 11: (image above) keywords identified in the initial URLs. 
(image below) keywords identified in the final URLs (redirections).

For the final URLs redirections (figure below), there are 4 main keywords used: booking, id, confirmation, and confirm (which is part of confirmation). These words are used intentionally, as they are on the final domains where victims will enter their information.

On the other hand, the keywords for the initial URLs (redirectors) domains are more varied. For example, hotel, guest, and reserv are also widely used, along with booking.

Detections

When analyzing the overall campaign, it's important to consider the detection rates of the identified URLs by various security vendors. Across all the URLs gathered from both Tier 1 and Tier 2 infrastructure, a significant portion has been flagged with 0 and 1 detections by the security vendors. It underscores a potential gap in current URL detections and highlights the need to improve it.

import pandas as pd
import plotly.express as px
from collections import Counter

malicious_counts = []
for item in all_results:
    attributes = item.get('attributes', {})
    last_analysis_stats = attributes.get('last_analysis_stats', {})
    malicious = last_analysis_stats.get('malicious', 0)  # Default to 0 if not present
    malicious_counts.append(malicious)

# Count the occurrences of each malicious count
malicious_count_distribution = Counter(malicious_counts)

# Convert to a DataFrame for plotting
df_malicious_counts = pd.DataFrame(list(malicious_count_distribution.items()), columns=['Malicious Count', 'Number of URLs'])

# Sort by the malicious count for better visualization
df_malicious_counts = df_malicious_counts.sort_values(by='Malicious Count')

# Create a bar chart
fig = px.bar(df_malicious_counts, x='Malicious Count', y='Number of URLs',
             title='Distribution of Malicious Detections per URL')

# Display the plot
fig.show()

image.pngFigure 12: Detections per URL identified.

Interesting file identified

So far, we've been able to understand the campaign's scope, various ways to conduct threat hunting, and how to identify new infrastructure. But the next question we asked ourselves was: Are there files that interact with any of the URLs we've identified? So, we started running different queries in Google Threat Intelligence, and one of them gave an interesting result.

embedded_url:"https://booking.confirmation-"

As a result of the previous query, we found a RAR file that includes the URL hxxps://booking-confirmation.id6151961[.]date/p/360580105 as embedded. It's not exactly the domain pattern we've been discussing, but it's very close and seemed interesting enough to analyze. The RAR file was first uploaded to GTI 2024-09-17, which indicates it's likely related to an older campaign.

The RAR file contains a folder inside it named ChatExport_2024-09-12. This folder, holds HTML files named messages[sequential_numbers].html (where sequential_numbers represents a series of numbers, e.g: messages113.html), as well as the following folders:

  • css
  • files
  • images
  • js
  • photos
  • round_video_messages
  • stickers
  • video_files

The most interesting information is found in the files folder and the HTML files in the main folder. The files folder contains XLS files, which hold hotel information and guest reservation check-in details from hotels. Among the information identified, we've been able to verify that there are details like booking number, guest name, check-in, check-out, rooms, persons, price, payment method, payment status, and remarks.

image.png
Figure 13: Sample of information found in the XLS files

Most XLS files follow a naming pattern like Check-in_ 2023-10-06 to 2025-05-12.xls (with the dates changing from one file to another). On the other hand, there are also files named хуетаполная.xls or хуетAU.xls, which translate to "complete bullshit" and "Fuck AU" respectively, but in both cases, they include the same type of information mentioned previously.

image.pngFigure 14: Example of XLS files found

In addition to these XLS files, there are also other TXT files in the files folder that follow the pattern of bookings_1714070330.txt (with the numbers changing, which appear to be a timestamp). The content of these TXT files usually has the following format:

ID|Potential Phishing URL|Victim Name and Surname

After analyzing all the TXT files, a total of 4727 entries were found. As an example, with the surname removed from one of the identified files, the structure would be as follows:

4288978392|https://booking-acceptance.id7025952.date/p/469721259|Zdeňka 
1002158967|https://booking-acceptance.id7025952.date/p/252952452|Klaudia 
1765220922|https://booking-acceptance.id7025952.date/p/524802246|Tim 
1765229583|https://booking-acceptance.id7025952.date/p/110390723|Christian 
2286734163|https://booking-acceptance.id7025952.date/p/972626209|Bohdana 
2711402539|https://booking-acceptance.id7025952.date/p/651852046|Beata 
3041886535|https://booking-acceptance.id7025952.date/p/128521414|John 
3189067073|https://booking-acceptance.id7025952.date/p/559744710|Mavroudis

Changing the vision to the HTMLs mentioned earlier, these files contain an extensive log of interactions, seemingly from a system related to booking and transactions. It details various transactions with information such as transaction IDs, services rendered, property names, prices, and critical payment details including card numbers, issuing banks, payment systems (MC, VISA), status, and countries of origin.

Notably, the files also include Telegram links associated with specific worker IDs (e.g., "@dept_sales"), suggesting that these usernames might be related to the transaction in the phishing website. In this way, they ensure they have a worker for each operation to obtain their victims' financial data.

image.pngFigure 15: Example of log identified with information about transactions

In our analysis of the HTML files, we have been able to identify a total of 118 unique Telegram accounts that are mentioned throughout these HTML files. Many of these accounts belong to operators who have some involvement in the phishing campaign, as many appear as "Воркер: #klgerkgoergko (@onlycashvvs)", where "Воркер" translates to "worker". We confirmed too that some of these accounts are still available. Next table contains the Telegram accounts discovered

razikgikk chiefkeef095 pizdec_nahuy_pro wwwaaaxxx
En0t777 piyupivo Mallusck ya_soliii
HollyHellsing darks1de777 hfgujisdg goodkidsmaadcity
KrakenTomm Tandjiiroo Elchapoel wtcrimson
RevolutLimited rocarlos1337 stalnoekolesiko1 lownormalhigh
VildRise2 anqez1 og_tomik sanders_wt
zxcFAKEGEN wwwaaaxxxx POLKO9 OG_sisik_dedok
redakciaForbes molodoimalchikdengi PH_MTR scmluxe
fooooxxx_1 pab1o777 sglilyy Elliot_420
findmexdq trytofriendfortuna8 B3rl1in lovelly121
Zzona445 pnzo78ud ashkav2 Oppkop2281
gabapentium buda_iy vbiver_ebi black6jack
cashcali astrit_gold WT_fooxxx Elliot_044
lovlybadtrip dead_pooII wsapmel m00ree_psychokids
xARSENIY Bumblebeeeeeee wtapap YKTFL_dabb
clepoy_kf favorit_91 dept_sales DefaultBorov
ascnuumm Cartieronmee JuicerTON kev1nwt
jbsvavonme1 POXMisterX wtmoneko qwertyweqq
hkobooking PIVOED228 plsdme nfuknf
Genshitss scamsecurityesitgrc yMiratvorenie username101239
Stillworkee2 manager_official7 S_H_R_E_D_E_ yako_j
majorkawt m00re1 og_Nimb yolis444
harrychelm haharekrisna subowt taxixst
TOM_3211 djery_wt os5d1 sssukkkkaaa
xRAMPAGA anxtosha UzrTarxunchik_O ewqyy
MAMOHTCVV wtworkmachine TT_CziaoDzi luc1fer111
onlycashvvs WT_KasT matvey2006brawl JustFloyd
tyhmatyosa MadaraMadara666 slimecsum visacarduser
yoshikss Lexx_sus228 Vsedenigivmiremo monkey_di_flufi
qqqqqqqqq13 wtleko    

YARA Rules

The following YARA rules can be used in Google Threat Intelligence to monitor activity related to this campaign.

This rule identifies Tier 1 URLs that redirect to the hostname pattern we have identified on Tier 2 domains:

import "vt"

rule Booking_Campaign_Tier1_Redirecting_PhishingTier2_June25 {
  meta:
    target_entity = "url"
    author = "GTI"
    sample_url = "http://hostelmandarinkauxeh.eto-la.com/"
    date = "2025/06/23"
    description = "This rule identifies Tier 1 URLs that redirect to the hostname pattern we have identified on Tier 2 domains."
    //query_gti = entity:url redirects_to:"https://booking.confirmation-*" and not hostname:"booking.confirmation-"
  condition:
    for any vt_net_url_redirects in vt.net.url.redirects: (
      vt_net_url_redirects contains "https://booking.confirmation-id"
    ) and not
    vt.net.url.hostname startswith "booking.confirmation-id"
}

This rule identifies Tier 1 URLs that meet a set of metadata identified in the titles and meta tags of the HTML:

import "vt"

rule Booking_Campaign_Tier1_Matching_HTML_Metadata_June25 {
  meta:
    target_entity = "url"
    author = "GTI"
    sample_url = "https://booking.2020000542.world/yzidotc"
    date = "2025/06/23"
    description = "This rule identifies Tier 1 URLs that meet a set of metadata identified in the titles and meta tags of the HTML."
    //query_gti = entity:url redirects_to:"https://booking.confirmation-*" and not hostname:"booking.confirmation-"
  condition:
    (
        vt.net.url.html_title istartswith "One moment" or // we have observed different types of dots '.'. With this we ensure to catch all of them"
        vt.net.url.html_title == "AD not found (captcha2)"
    ) and
    (
        for any vt_net_url_html_meta_tags in vt.net.url.html_meta_tags: (
            for any vt_net_url_html_meta_tags_values in vt_net_url_html_meta_tags.values: (
                vt_net_url_html_meta_tags_values istartswith "Booking -" or
                vt_net_url_html_meta_tags_values == "https://ltdfoto.ru/images/2025/06/04/photo_2025-06-02_11-23-22.md.jpg" or
                vt_net_url_html_meta_tags_values startswith "https://cf.bstatic.com/xdata/images/hotel/" 
            )
        )
    ) and not
    (
        vt.net.url.hostname startswith "booking.confirmation-id" or
        vt.net.url.hostname == "booking.com" 
    )

}

This rule identifies domains that match a pattern identified in the campaign:

import "vt"

rule Booking_Campaign_Domains_Tier2_June25  {
  meta:
    target_entity = "domain"
    author = "GTI"
    sample_url = "booking.confirmation-id79508.com"
    date = "2025/06/23"
    description = "This rule identifies domains that match a pattern identified in the campaign."
    //query_gti = entity:domain domain_regex:"^booking\.confirmation-id[0-9]{5}\.com$"
  condition:
    vt.net.domain.raw matches /^booking\.confirmation-id[0-9]{5}\.com$/
}

This rule identifies URLs that match the pattern identified in the Tier 2 infrastructure:

import "vt"

rule Booking_Campaign_Tier2_URLs_June25 {
  meta:
    target_entity = "url"
    author = "GTI"
    sample_url = "https://booking.confirmation-id77351.com/reservation/"
    date = "2025/06/23"
    description = "This rule identifies URLs that match the pattern identified in the Tier 2 infrastructure."
    //query_gti = entity:url hostname:"booking.confirmation-id*"
  condition:
    vt.net.url.new_url and
    vt.net.url.hostname startswith "booking.confirmation-id"
}

Wrapping up

This deep dive into a recent phishing campaign targeting Booking.com users highlights the importance of understanding threat actor tactics for proactive defense. We’ve demonstrated how a single malicious email can unravel a much larger infrastructure, identifying both Tier 1 (redirector) and Tier 2 (phishing content host) domains.

Key takeaways from our analysis include:

  • Phishing Distribution: The campaign ingeniously leveraged Booking.com's official messaging system and legitimate email channels, making it highly effective.
  • Infrastructure Discovery: By analyzing HTML titles and meta tags, and using patterns in domain names, we were able to identify and categorize a significant number of malicious URLs.
  • Campaign Timeline: Activity spiked significantly from January 2025, with May and June being particularly busy, suggesting a recent surge in this specific campaign.
  • Redirection Analysis: Over half of the identified URLs acted as redirectors, emphasizing the multi-stage nature of the attack. We also pinpointed key domains used for these redirects.

By applying these threat hunting methodologies and utilizing tools like Google Threat Intelligence and Google Colab, organizations and individuals can enhance their ability to detect and research similar campaigns. This detailed investigation provides actionable insights that can be translated into more robust security measures and YARA rules to counter evolving attack techniques.

We have created a collection in Google Threat Intelligence to share the IOCs that we have discovered during the research.

As always, we are happy to hear your feedback.

Other entries in the series Actionable threat hunting with Google Threat Intelligence

Actionable threat hunting with Google Threat Intelligence (I) - Hunting malicious desktop files