New product - Brun-water
Writeup by: briatlon for 0-Day Aarhus · Event: BrunnerCTF 2026 · Solves: 15 (2 solves on Denmark leaderboard - without AI) · Challenge Author: The Mikkel
Challenge description

This challenge featured an image of a remote lake, a forest and a few houses. The challenge hinted Norway, since the flag format was using the Norwegian language. I did a few initial reverse image search attempts, but as the labeled difficulty correctly indicated, this was going to be very hard.
Taking note of the challenge description it was immediately clear that this was an OSM (OpenStreetMap) challenge.
If you’ve never heard of OSM it’s a global map database maintained by a community of volunteers that map objects and features around the world. In many geolocation challenges we can use features like roads, houses, signs and in this case powerlines to find locations we are looking for.
The solve
Since the voltage of powerlines crossing the lake was listed specifically as 132 kV, it was a clear sign that the author had looked up this data as part of the challenge's intended solve.
The substation being <5 km from the lake would normally be something I would just tell an AI and ask for a list of candidates, to then manually look through and deny or verify. Now since our team was competing for the national prizes, we were limited by strict no AI restrictions. This meant that a somewhat difficult challenge now became even harder.
However this also turned into a great lesson on querying, data manipulation and a better understanding of how things are tagged in OpenStreetMap.

A few more notes regarding the image. We also see some shadows on the left side of the lake and since I assumed the image to be taken sometime in the middle of the day judging by the angle, I assumed we were looking north with the sun in the south.

The vegetation seemed to indicate southern Norway according to Plonk it. I also did a brief lookup on the author, and through his website I found this 12 year old video of a car trip to Norway. I didn’t see the lake there, but it was still a vague indication that the lake was within driving distance from Denmark and likely not too far north.

Lastly, but perhaps most important, I took note of the houses on the right. I could use the location of these houses relative to the powerline crossing the lake to verify. Also the roof of the leftmost house had to be angled and the porch had to be at the gable end of the house.

I did not come unprepared for this CTF. I’ve been working on a node based query builder for a while, and this seemed like an obvious challenge to put it to the test. It builds custom queries for Overpass Turbo, a site that allows queries for objects tagged with certain features and maps relations between them. However here is where I made my first mistake that cost a bit of time.
As I was setting up different queries I kept getting empty results back. Only after a few hours of frustration I realized to my dismay that I had mistakenly input 132 volts and not 132 kV as the challenge had mentioned.

What came to my rescue and became probably my biggest lesson was to go directly to OpenStreetMap and check the actual data of objects similar to what I was looking for in the area. This image from a powerline in Norway clearly shows it tagged as voltage=132000. Learning from my mistake I verified my tag assumptions on OpenStreetMap’s wiki and checked actual usage on TagInfo.

I used a custom query builder, but for these simple queries, you could freely use Overpass Turbo’s Wizard and export the results as GeoJSON. I kept trying to make more advanced queries, but servers kept timing out. The solution became to query the data in smaller chunks and then compare and analyze afterwards.

[out:json][timeout:600];
// gather results
{{geocodeArea:norway}}->.searchArea;
// print results
nwr["voltage"="132000"](area.searchArea);
out geom;
Using the GeoJSON you can view and analyze it on GeoJSON.io or Mapshaper or check Street View coverage using Map Making App.

[out:json][timeout:600];
{{geocodeArea:norway}}->.searchArea;
// gather results
nwr["water"="lake"](area.searchArea);
// print results
out geom;
With another query for water=lake in Norway, an OSM tag strongly hinted by the challenge description, I was able to put together this map that shows lakes and powerline candidates. Only problem is that there are thousands of lakes to check in Norway.
My simple approach for finding the challenge location was to only look at intersections between the 132 kV powerlines and polygons of lakes. This is something an AI would have been able to do in a single one-line prompt, but because we were competing for the no AI leaderboard I had to find a different solution.
After some late night Googling I found my options to be Geopandas for python and Turf.js for javascript. I hadn’t used either before, so it ended up taking a lot of troubleshooting, but I managed to get the following script to work after an even longer deep dive into the GeoPandas documentation, old videos and forums.
from pathlib import Path
import geopandas as gpd
import pyogrio
path = Path(__file__).parent / "data"
# Set GDAL configuration option to allow large GeoJSON objects
# I'm unsure if this was necessary, but an old forum post suggested it on larger files
pyogrio.set_gdal_config_options({"OGR_GEOJSON_MAX_OBJ_SIZE": 0})
# Read the GeoJSON files - all water = lakes and voltage = 132000 in Norway
lakes = gpd.read_file(path / "lakes_in_norway.geojson")
_132kv = gpd.read_file(path / "132kv_export.geojson")
# CRS transformation to ensure both GeoDataFrames have the same CRS
# Again not sure if fully necessary, followed forums
lakes = lakes.to_crs(_132kv.crs)
# Filter to only keep the wanted geometry types
# Both files contained points, lines and polygons so this filters them out
lakes = lakes[lakes.geometry.type == "Polygon"]
_132kv = _132kv[_132kv.geometry.type == "LineString"]
# Kept this logging for troubleshooting
# log the CRS of both GeoDataFrames // for testing
print(f"Lakes CRS: {lakes.crs}")
print(f"132kv CRS: {_132kv.crs}")
# log the number of geometries in each GeoDataFrame // for testing
print(f"Number of lakes: {len(lakes)}")
print(f"Number of 132kv lines: {len(_132kv)}")
# log the geometry types in each GeoDataFrame // for testing
print(f"Lakes geometry types: {lakes.geom_type.unique()}")
print(f"132kv geometry types: {_132kv.geom_type.unique()}")
# This is the magic that finds intersections between the two files - struggled a lot with this.
intersection_gdf = gpd.sjoin(lakes, _132kv, how="inner", predicate="intersects")
# Save the result to a new GeoJSON file, that can then be used to locate the image
intersection_gdf.to_file(path / "intersection.geojson", driver="GeoJSON")

This script brought the total amount of candidates down from thousands to only 76 lakes with powerlines going across. From that I started looking for lakes going north to south and with houses southeast of the intersecting powerline.

As part of my toolkit I also have a satellite previewer that shows node satellite imagery at a given zoom level. That makes it easy to scan through many candidates and quickly verify from a list. Skimming through the nodes I noticed this lake.

Looking at Google Maps I was further convinced about this location. The lake had no good Street View coverage, so I went back to the houses that I had initially suspected could be used for confirmation.

Using Apple Maps I got even better satellite imagery of the house, where we can see the roof and porch, as seen on the challenge image.

As mentioned I didn’t find any good Google Street View coverage of the place, but Apple Lookaround came in clutch to verify the location with another view here.
For GEOINT challenges it’s important to always have multiple tools in the belt in case one doesn’t have the desired coverage.

Finally we can confirm the location by following the powerline west and checking the name of the substation Hurdal trafo on OSM. I know other teams had the issue of finding the correct flag and I will admit that I had to try a few variations before I got the flag. However since I was so confident about the location I knew I had to keep trying until the flag was correct.

The main thing that confused me was that at most zoom levels on Google Maps the lake would be shown as Vålsjøen, but it’s a linked lake. I double checked and realized my mistake and when I changed it to Tisjøen the flag was accepted.
brunner{tisjøen_hurdal_trafo}