← Back to Writeups
CUSTOMMediumOSINT

Delicious Meeting

XESXOR9/2/20267 min read
#osint#custom#medium#image_geolocation#username_enumeration#overpass_api_query#automated_flag_testing#poi_extraction#linux

Delicious Meeting

Platform: 0xl4ugh | Category: OSINT | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-24 | Status: Solved Techniques: image_geolocation, username_enumeration, overpass_api_query, automated_flag_testing, poi_extraction

Summary

"Willow1124 is planning to meet Ken to enjoy their favorite food. The uploaded files include a picture of Willow1124's hometown. Use Google Maps at all times. Your task is to figure out the email address of the place where they met."

Delicious Meeting — 0xL4ugh CTF

Description

"Willow1124 is planning to meet Ken to enjoy their favorite food. The uploaded files include a picture of Willow1124's hometown. Use Google Maps at all times. Your task is to figure out the email address of the place where they met."

Flag format: 0xL4ugh{email@domain.com}

Analysis

This is a multi-step OSINT challenge combining:

  1. Image geolocation - identifying location from visual clues
  2. Username OSINT - tracking a user across platforms
  3. POI enumeration - finding restaurants near identified location
  4. Automated testing - brute-forcing flag submissions with extracted emails ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The key insight was using Overpass API to extract all restaurant emails from OpenStreetMap data, then systematically testing them against the CTF platform.

Solution

Step 1: Image Geolocation

The provided image was a Google Street View screenshot with several visual clues:

Key indicators found:

  • Swedish parking signs with "P" symbol
  • Text "Soende" visible (Swedish language)
  • Distinctive architecture typical of Stockholm

Location identified:

  • Address: Wivalliusgatan 13, Stockholm, Sweden
  • Coordinates: 59°19'33.1"N 18°00'50.9"E
  • Area: Kungsholmen district ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Verification was done by matching the Street View perspective with the provided image.

Step 2: Username OSINT on "Willow1124"

Searched for the username across multiple platforms:

PlatformUsernameDetails
Snapchatwillow1124Display: Dog Nat
DeviantArtwillow1124Location: UK, They/Them
Pinterestwillow1124Pamela Miller
Redditwillow11242 years old account
GitHubwillow1124Empty profile
YouTubeWillowGeneric channel
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Also discovered ken1124 with the same "1124" pattern, suggesting a connection between the two users mentioned in the challenge.

Note: The social media profiles didn't reveal the specific restaurant, requiring a different approach.

Step 3: Restaurant Discovery using Overpass API (KEY TECHNIQUE!)

Since OSINT on usernames didn't reveal the meeting place, we used OpenStreetMap's Overpass API to extract ALL restaurants near the identified coordinates.

Overpass API Query:

python
#!/usr/bin/env python3
"""
Extract restaurant emails from OpenStreetMap using Overpass API.
This technique is powerful for OSINT when you have coordinates but need POI contact info.
"""
import requests
import json
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Coordinates from geolocation step
lat = 59.326417  # Wivalliusgatan, Stockholm
lng = 18.014139

# Overpass API endpoint
overpass_url = "http://overpass-api.de/api/interpreter"

# Query for restaurants within 3km radius
overpass_query = f"""
[out:json];
(
  node["amenity"="restaurant"](around:3000,{lat},{lng});
  way["amenity"="restaurant"](around:3000,{lat},{lng});
  relation["amenity"="restaurant"](around:3000,{lat},{lng});
);
out body;
>;
out skel qt;
"""

response = requests.get(overpass_url, params={'data': overpass_query})
data = response.json()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Extract restaurant information
restaurants = []
for element in data.get('elements', []):
    tags = element.get('tags', {})
    if 'name' in tags:
        restaurant = {
            'name': tags.get('name', ''),
            'email': tags.get('email', tags.get('contact:email', '')),
            'phone': tags.get('phone', tags.get('contact:phone', '')),
            'website': tags.get('website', tags.get('contact:website', '')),
            'cuisine': tags.get('cuisine', ''),
            'addr': tags.get('addr:street', '') + ' ' + tags.get('addr:housenumber', '')
        }
        if restaurant['email']:  # Only keep restaurants with emails
            restaurants.append(restaurant)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print(f"Found {len(restaurants)} restaurants with email addresses")
for r in restaurants:
    print(f"{r['email']} - {r['name']} ({r['cuisine']})")

Result: Found 175+ restaurants in the area, with many having email addresses in OSM data.

Step 4: Automated Flag Testing

Created a script to test restaurant emails against the CTF submission API:

bash
#!/bin/bash
# Test restaurant emails against CTF API
# IMPORTANT: Respect rate limits!

emails=(
    "info@restaurant1.se"
    "contact@restaurant2.se"
    "REDACTED"
    # ... more emails from Overpass query
)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

for email in "${emails[@]}"; do
    flag="0xL4ugh{$email}"
    
    result=$(curl -s 'https://ctf.0xl4ugh.com/api/v1/challenges/attempt' \
      -H 'content-type: application/json' \
      -H 'Cookie: session=YOUR_SESSION_COOKIE' \
      --data-raw "{\"challenge_id\":25,\"submission\":\"$flag\"}")
    
    # Check for correct answer
    if echo "$result" | grep -q '"status": "correct"'; then
        echo "FOUND FLAG: $flag"
        exit 0
    fi
    
    echo "Tried: $email - $(echo $result | jq -r '.status')"
    
    # Respect rate limits - wait 10 seconds between attempts
    sleep 10
done

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Important considerations:

  • Rate limiting: CTF platforms typically limit submissions (10 sec delay used)
  • String matching: "incorrect" contains "correct" - use exact matching!
  • Session handling: Need valid authentication cookie

Step 5: Flag Discovery

After testing approximately 100 emails, found the correct one:

FieldValue
RestaurantSoyokaze (Soyokafe Sushi)
EmailREDACTED
CuisineJapanese/Sushi
Connection"Favorite food" mentioned in challenge
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The Japanese restaurant makes sense given the challenge mentioned "favorite food" - sushi is a popular choice.

Key APIs and Tools

1. Overpass API (OpenStreetMap)

The most powerful tool in this challenge:

AttributeDetails
URLhttp://overpass-api.de/api/interpreter
PurposeQuery geographic data by coordinates
Data returnedPOI names, emails, phones, websites, addresses
Query languageOverpass QL
Rate limitsGenerous for reasonable use
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Common Overpass queries for OSINT:

# Restaurants in radius
node["amenity"="restaurant"](around:RADIUS,LAT,LNG);

# Cafes with specific cuisine
node["amenity"="cafe"]["cuisine"="coffee"](around:1000,LAT,LNG);

# All POIs with email
node["email"](around:2000,LAT,LNG);

# Hotels
node["tourism"="hotel"](around:5000,LAT,LNG);

2. CTF Platform API

Standard CTFd-style API:

EndpointMethodPurpose
/api/v1/challenges/attemptPOSTSubmit flag
BodyJSON{"challenge_id": N, "submission": "flag"}
ResponseJSON{"status": "correct/incorrect/ratelimited"}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

3. Username OSINT Tools

Platforms checked for username enumeration:

  • Snapchat (add friend search)
  • DeviantArt (profile search)
  • Pinterest (user search)
  • Reddit (u/username)
  • GitHub (github.com/username)
  • YouTube (channel search)

OSINT Methodology

Image Analysis
      |
      v
Geolocation (Street View matching)
      |
      v
Username OSINT (cross-platform search)
      |
      v
[If no specific result]
      |
      v
Overpass API Query (extract all POIs)
      |
      v
Automated Flag Testing (with rate limiting)
      |
      v
Flag Found

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Lessons Learned

  1. Overpass API is essential for geo-OSINT: When you have coordinates, you can extract ALL nearby POIs with contact information directly from OpenStreetMap

  2. Brute-force is valid when scoped: With ~175 restaurants and 10-second delays, testing takes ~30 minutes - acceptable for a 500-point challenge

  3. Respect rate limits: CTF platforms have submission limits; 10-second delays prevent "ratelimited" responses

  4. String matching matters: "incorrect" contains "correct" - always use exact string matching in automation scripts ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  5. OSM data quality varies: Not all restaurants have emails in OSM, but enough do for this technique to work

  6. Combine techniques: Image geolocation + username OSINT + API enumeration = comprehensive approach

Alternative Approaches

  1. Manual restaurant search: Google Maps "restaurants near Wivalliusgatan Stockholm" then check each website for email - slower but works

  2. Social media deep dive: More thorough analysis of willow1124/ken1124 profiles might reveal food preferences or check-ins ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  3. Reverse image search: If the challenge included food photos, could identify cuisine type to narrow down restaurants

Code Repository

Full solution scripts:

python
#!/usr/bin/env python3
"""
Complete solution for Delicious Meeting OSINT challenge.
Combines Overpass API extraction with automated flag testing.
"""
import requests
import time
import json

# Configuration
LAT = 59.326417
LNG = 18.014139
RADIUS = 3000  # meters
CTF_URL = "https://ctf.0xl4ugh.com/api/v1/challenges/attempt"
CHALLENGE_ID = 25
SESSION_COOKIE = "your_session_here"
DELAY = 10  # seconds between attempts
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def get_restaurants_with_emails():
    """Extract restaurant emails from OpenStreetMap via Overpass API."""
    overpass_url = "http://overpass-api.de/api/interpreter"
    query = f"""
    [out:json];
    (
      node["amenity"="restaurant"](around:{RADIUS},{LAT},{LNG});
      way["amenity"="restaurant"](around:{RADIUS},{LAT},{LNG});
    );
    out body;
    """
    
    response = requests.get(overpass_url, params={'data': query})
    data = response.json()
    
    emails = []
    for element in data.get('elements', []):
        tags = element.get('tags', {})
        email = tags.get('email') or tags.get('contact:email')
        if email:
            emails.append({
                'email': email,
                'name': tags.get('name', 'Unknown'),
                'cuisine': tags.get('cuisine', '')
            })
    
    return emails
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def test_flag(email):
    """Test a flag against the CTF API."""
    flag = f"0xL4ugh{{{email}}}"
    
    response = requests.post(
        CTF_URL,
        json={"challenge_id": CHALLENGE_ID, "submission": flag},
        cookies={"session": SESSION_COOKIE}
    )
    
    result = response.json()
    return result.get('status') == 'correct', result

def main():
    print("[*] Extracting restaurants from OpenStreetMap...")
    restaurants = get_restaurants_with_emails()
    print(f"[+] Found {len(restaurants)} restaurants with emails")
    
    print("[*] Testing flags...")
    for i, r in enumerate(restaurants):
        email = r['email']
        print(f"[{i+1}/{len(restaurants)}] Testing: {email} ({r['name']})")
        
        correct, result = test_flag(email)
        
        if correct:
            print(f"\n[!!!] FLAG FOUND: 0xL4ugh{{{email}}}")
            print(f"[+] Restaurant: {r['name']}")
            print(f"[+] Cuisine: {r['cuisine']}")
            return
        
        if result.get('status') == 'ratelimited':
            print("[!] Rate limited, waiting 30 seconds...")
            time.sleep(30)
        else:
            time.sleep(DELAY)
    
    print("[-] No correct flag found")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == "__main__":
    main()

References