Back to Articles

Extracting Source Code from Exposed .svn Directories: A Security Researcher's Guide

[ View on GitHub ]

Extracting Source Code from Exposed .svn Directories: A Security Researcher's Guide

Hook

Every month, security researchers discover production web servers accidentally serving their entire source code history through a single misconfigured directory. The .svn folder—meant to be invisible—becomes a treasure trove for attackers.

Context

In the mid-2000s, Subversion (SVN) became the dominant version control system, replacing CVS at countless organizations. Developers would check out repositories directly to web server document roots, and the .svn metadata directories would come along for the ride. Unlike Git's single .git folder at the repository root, SVN originally placed a .svn directory in every single folder of a working copy. This design decision created a massive attack surface.

While most developers have migrated to Git, legacy SVN installations persist in production environments—often forgotten and unmaintained. Web servers misconfigured to serve these .svn directories expose not just current source code, but the entire version history, commit messages, developer usernames, and even deleted files. Traditional tools like wget can download exposed files, but they can't intelligently parse SVN's metadata formats to discover hidden paths or retrieve files stored by cryptographic hash. This gap is where svn-extractor proves invaluable for penetration testers and security auditors.

Technical Insight

target URL + options

HTTP GET .svn/entries

HTTP GET .svn/wc.db

parse text entries

query SQLite DB

filename + checksum

compute SHA-1 path

compute SHA-1 path

HTTP GET

HTTP GET

optional

Command Line Interface

SVN Format Detector

Legacy Parser

Pre-1.7

Modern Parser

Post-1.7 SQLite

File Metadata

Extractor

File Downloader

.svn/text-base/

*.svn-base

.svn/pristine/

XX/hash

Extracted Source Files

HTTP Proxy

System architecture — auto-generated

The architectural brilliance of svn-extractor lies in its dual-format parsing strategy. SVN underwent a major metadata restructuring in version 1.7, shifting from plaintext entries files to SQLite databases. The tool handles both formats seamlessly, making it effective against servers running any SVN version from the last fifteen years.

For legacy SVN (pre-1.7), the tool parses .svn/entries files—simple text files with a specific structure. Each entry contains a filename, revision number, and checksum. Here's how the tool processes these files:

# Simplified extraction logic for legacy SVN
def parse_entries_file(entries_content):
    lines = entries_content.strip().split('\n')
    files = []
    i = 0
    while i < len(lines):
        if lines[i] == 'file':
            filename = lines[i+1]
            checksum = lines[i+4] if len(lines) > i+4 else None
            files.append({
                'name': filename,
                'checksum': checksum,
                'path': f'.svn/text-base/{filename}.svn-base'
            })
            i += 10  # Entry blocks are typically 10-11 lines
        else:
            i += 1
    return files

Once file metadata is extracted, svn-extractor downloads pristine copies from .svn/text-base/filename.svn-base. This approach bypasses common security misconfigurations where administrators block access to source files (like *.php or *.py) but forget to protect the .svn directory itself.

For modern SVN (1.7+), the tool takes a different approach. Metadata is stored in a SQLite database at .svn/wc.db. The tool connects to this database and executes SQL queries to enumerate all versioned files:

# Modern SVN uses SQLite - query the NODES table
import sqlite3

def extract_from_wc_db(db_path):
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    
    # The NODES table contains working copy state
    cursor.execute("""
        SELECT local_relpath, checksum, kind 
        FROM NODES 
        WHERE checksum IS NOT NULL
    """)
    
    files = []
    for row in cursor.fetchall():
        path, checksum, kind = row
        if kind == 'file' and checksum:
            # Checksums are stored as $sha1$hash_value
            sha1_hash = checksum.split('$')[2]
            # Files stored as .svn/pristine/XX/FULL_HASH.svn-base
            pristine_path = f".svn/pristine/{sha1_hash[:2]}/{sha1_hash}.svn-base"
            files.append({
                'name': path,
                'pristine_path': pristine_path
            })
    
    conn.close()
    return files

The pristine storage mechanism is particularly clever from an attacker's perspective. Rather than requesting /admin/config.php (which might be blocked), the tool requests /.svn/pristine/a4/a4b2c8d9e1f2...hash.svn-base—a path that reveals nothing about the file's purpose and is unlikely to be specifically blocked by web application firewalls.

The tool also extracts valuable reconnaissance data. From the wc.db database or entries files, it can discover developer usernames, commit timestamps, and directory structures that aren't directly web-accessible. This metadata often reveals internal network paths, developer workstation names, or staging environment URLs embedded in commit messages—information goldmines for further attacks.

Svn-extractor implements a simple HTTP client with configurable user agents and proxy support, allowing it to operate through Burp Suite or other interception proxies for manual analysis. The download logic includes basic retry mechanisms and handles HTTP authentication if the .svn directory is protected (though poorly, by basic auth rather than proper filesystem restrictions).

Gotcha

The tool's primary limitation is its single-threaded architecture. When extracting a large repository with thousands of files, each HTTP request executes sequentially. On a repository with 5,000 files and 100ms average latency, you're looking at over eight minutes of waiting. There's no threading, no connection pooling, and no concurrent downloads. For penetration testers on time-limited engagements, this can be frustrating.

More critically, svn-extractor completely fails if the initial metadata files aren't accessible. If a web server blocks .svn/entries and .svn/wc.db specifically (while leaving other .svn contents exposed), the tool has no fallback mechanism. It doesn't attempt to bruteforce common filenames or use directory listing to discover files. It's entirely dependent on parsing SVN's metadata structures. Additionally, there's zero state management—if your network connection drops halfway through extracting a 10GB repository, you start over from scratch. No resume capability, no checkpoint files, nothing. Modern alternatives like dvcs-ripper maintain download state and can resume interrupted extractions.

Verdict

Use if: You're conducting web application penetration tests or security audits and discover an exposed .svn directory on a target. This tool excels at quickly extracting complete source code from both legacy and modern SVN working copies, and it's particularly valuable when you need to bypass filename-based access controls. It's also useful for security awareness training—demonstrating to development teams exactly how much sensitive data an exposed .svn folder leaks. Skip if: You're looking for a general-purpose SVN client, need high-performance concurrent downloads, or want a maintained tool with active development. Consider dvcs-ripper for production penetration testing work—it supports multiple version control systems, includes threading, and handles edge cases better. Also skip if you're working on modern, well-configured web infrastructure where this vulnerability class has largely been mitigated through .htaccess rules, security scanners, and migration to Git.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/automation/anantshri-svn-extractor.svg)](https://starlog.is/api/badge-click/automation/anantshri-svn-extractor)