├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── docs ├── gildasio.png ├── gildasio_header_info.png ├── gildasio_verbose.png ├── hackme.png ├── headers.md └── list.png ├── h2t.py ├── headers ├── bad.json └── good.json ├── requirements.txt └── src ├── __init__.py ├── banners.py ├── connection.py ├── files.py ├── list_command.py ├── output.py └── scan_command.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__ 3 | *.swp 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # h2t 2 | 3 | ## Contribute 4 | 5 | Thank you for your interest in **h2t**. If you want to contribute to **h2t** there is a lot ways: 6 | 7 | ### Type Issues 8 | 9 | Maybe I've typed. So, open an [issue][issue] or submit a [PR][pr]. 10 | 11 | ### Features 12 | 13 | Do you wanna a feature that isn't implemented? Open an [issue][issue] or a [PR][pr] for that. 14 | 15 | ### Add headers 16 | 17 | Do you know any headers that would be interesting to **h2t** but isn't here? Open an [issue][issue] or a [PR][pr]. 18 | 19 | If do you want to open a [PR][pr] make sure that the database is a JSON file located at `headers` directory. 20 | 21 | * `good.json` are headers that would be nice to be implemented 22 | * `bad.json` are headers that isn't a good idea to maintain in production 23 | 24 | ~~~ json 25 | "header-in-lower-case": { 26 | "title": "Header title", 27 | "description": "Description about the header", 28 | "refs": [ 29 | "some_tech_links", 30 | "to_know_more_about_that_header", 31 | "it's_a_good_idea_to_link_the_specification" 32 | ] 33 | } 34 | ~~~ 35 | 36 | ### Code Smells 37 | 38 | I'm not an expert pythonic so if you see something that can be better, open an [issue][issue], or submit a [PR][pr] or mailme (look me e-mail in my [website][website]). 39 | 40 | [issue]: https://github.com/gildasio/h2t/issues/new 41 | [pr]: https://github.com/gildasio/h2t/pulls 42 | [website]: https://gildasio.gitlab.io 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-alpine as build 2 | WORKDIR /wheels 3 | RUN apk update --no-cache \ 4 | && apk add --no-cache \ 5 | g++ \ 6 | gcc \ 7 | libxml2 \ 8 | libxml2-dev \ 9 | libxslt-dev \ 10 | linux-headers 11 | COPY requirements.txt /opt/h2t/ 12 | RUN pip3 wheel -r /opt/h2t/requirements.txt 13 | 14 | 15 | FROM python:3.7-alpine 16 | WORKDIR /opt/h2t 17 | ARG VCS_REF 18 | ARG VCS_URL="https://github.com/gildasio/h2t" 19 | LABEL org.label-schema.vcs-ref=$VCS_REF \ 20 | org.label-schema.vcs-url=$VCS_URL 21 | COPY --from=build /wheels /wheels 22 | COPY . /opt/h2t/ 23 | RUN pip3 install -r requirements.txt -f /wheels \ 24 | && rm -rf /wheels \ 25 | && rm -rf /root/.cache/pip/* 26 | 27 | ENTRYPOINT ["python", "h2t.py"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gildásio Júnior 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # h2t - HTTP Hardening Tool 2 | 3 | ## Description 4 | 5 | **h2t** is a simple tool to help sysadmins to hardening their websites. 6 | 7 | Until now **h2t** checks the website headers and recommends how to make it better. 8 | 9 | ## Dependences 10 | 11 | * [Python 3](https://www.python.org/download/releases/3.0/) 12 | * [colorama](https://github.com/tartley/colorama) 13 | * [requests](http://docs.python-requests.org/en/master/) 14 | 15 | ## Install 16 | 17 | ~~~ 18 | $ git clone https://github.com/gildasio/h2t 19 | $ cd h2t 20 | $ pip install -r requirements.txt 21 | $ ./h2t.py -h 22 | ~~~ 23 | 24 | ... or the Docker way: 25 | ~~~ 26 | $ git clone https://github.com/gildasio/h2t 27 | $ cd h2t 28 | $ docker build -t h2t . 29 | $ docker run --rm h2t -h 30 | ~~~ 31 | 32 | You also can put `alias h2t='docker run --rm h2t'` on a file (such as `~/.bash_aliases`) and run as follows: 33 | 34 | ~~~ 35 | $ h2t -h 36 | ~~~ 37 | 38 | ## Usage 39 | 40 | **h2t** has subcommands: *list* and *scan*. 41 | 42 | ~~~ 43 | $ ./h2t.py -h 44 | usage: h2t.py [-h] {list,l,scan,s} ... 45 | 46 | h2t - HTTP Hardening Tool 47 | 48 | positional arguments: 49 | {list,l,scan,s} sub-command help 50 | list (l) show a list of available headers in h2t catalog (that can 51 | be used in scan subcommand -H option) 52 | scan (s) scan url to hardening headers 53 | 54 | optional arguments: 55 | -h, --help show this help message and exit 56 | ~~~ 57 | 58 | ### List Subcommand 59 | 60 | The **list** subcommand lists all headers cataloged in **h2t** and can show informations about it as a description, links for more information and for how to's. 61 | 62 | ~~~ 63 | $ ./h2t.py list -h 64 | usage: h2t.py list [-h] [-p PRINT [PRINT ...]] [-B] 65 | [-a | -H HEADERS [HEADERS ...]] 66 | 67 | optional arguments: 68 | -h, --help show this help message and exit 69 | -p PRINT [PRINT ...], --print PRINT [PRINT ...] 70 | a list of additional information about the headers to 71 | print. For now there are two options: description and 72 | refs (you can use either or both) 73 | -B, --no-banner don't print the h2t banner 74 | -a, --all list all available headers [default] 75 | -H HEADERS [HEADERS ...], --headers HEADERS [HEADERS ...] 76 | a list of headers to look for in the h2t catalog 77 | ~~~ 78 | 79 | ### Scan Subcommand 80 | 81 | The **scan** subcommand perform a scan in a website looking for their headers. 82 | 83 | ~~~ 84 | $ ./h2t.py scan -h 85 | usage: h2t.py scan [-h] [-v] [-a] [-g] [-b] [-H HEADERS [HEADERS ...]] 86 | [-p PRINT [PRINT ...]] 87 | [-i IGNORE_HEADERS [IGNORE_HEADERS ...]] [-B] [-E] [-n] 88 | [-u USER_AGENT] [-r | -s] 89 | url 90 | 91 | positional arguments: 92 | url url to look for 93 | 94 | optional arguments: 95 | -h, --help show this help message and exit 96 | -v, --verbose increase output verbosity: -v print response headers, 97 | -vv print response and request headers 98 | -a, --all scan all cataloged headers [default] 99 | -g, --good scan good headers only 100 | -b, --bad scan bad headers only 101 | -H HEADERS [HEADERS ...], --headers HEADERS [HEADERS ...] 102 | scan only these headers (see available in list sub- 103 | command) 104 | -p PRINT [PRINT ...], --print PRINT [PRINT ...] 105 | a list of additional information about the headers to 106 | print. For now there are two options: description and 107 | refs (you can use either or both) 108 | -i IGNORE_HEADERS [IGNORE_HEADERS ...], --ignore-headers IGNORE_HEADERS [IGNORE_HEADERS ...] 109 | a list of headers to ignore in the results 110 | -B, --no-banner don't print the h2t banner 111 | -E, --no-explanation don't print the h2t output explanation 112 | -o {normal,csv,json}, --output {normal,csv,json} 113 | choose which output format to use (available: normal, 114 | csv, json) 115 | -n, --no-redirect don't follow http redirects 116 | -u USER_AGENT, --user-agent USER_AGENT 117 | set user agent to scan request 118 | -k, --insecure don't verify SSL certificate as valid 119 | -r, --recommendation output only recommendations [default] 120 | -s, --status output actual status (eg: existent headers only) 121 | ~~~ 122 | 123 | ### Output 124 | 125 | For now the output is only in normal mode. Understant it as follows: 126 | 127 | * [+] Red Headers are bad headers that open a breach on your website or maybe show a lots of information. We recommend fix it. 128 | * [+] Yellow Headers are good headers that is not applied on your website. We recommend apply them. 129 | * [-] Green Headers are good headers that is already used in your website. It's shown when use `-s` flag. 130 | 131 | Example: 132 | 133 | ![h2t agains hack.me](docs/hackme.png) 134 | 135 | * Cookie HTTP Only would be good to be applied 136 | * Cookie over SSL/TLS would be good to be applied 137 | * Server header would be good to be removed 138 | * Referrer-Policy would be good to be applied 139 | * X-Frame-Options is already in use, nothing to do here 140 | * X-XSS-Protection is already in use, nothing to do here 141 | 142 | ### Screenshots 143 | 144 | #### List h2t catalog 145 | 146 | ![h2t catalog](docs/list.png) 147 | 148 | #### Scan from file 149 | 150 | ![h2t against my website](docs/gildasio.png) 151 | 152 | #### Scan url 153 | 154 | ![h2t against hackme](docs/hackme.png) 155 | 156 | #### Scan verbose 157 | 158 | ![h2t against my website in verbose mode](docs/gildasio_verbose.png) 159 | 160 | #### Headers information 161 | 162 | ![h2t against my website and print headers information](docs/gildasio_header_info.png) 163 | 164 | ## Contribute 165 | 166 | For contribute guidelines look at [CONTRIBUTING](CONTRIBUTING.md) 167 | -------------------------------------------------------------------------------- /docs/gildasio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gildasio/h2t/56e1447848f5382b8a5a7dfd00e84b1a0335a589/docs/gildasio.png -------------------------------------------------------------------------------- /docs/gildasio_header_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gildasio/h2t/56e1447848f5382b8a5a7dfd00e84b1a0335a589/docs/gildasio_header_info.png -------------------------------------------------------------------------------- /docs/gildasio_verbose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gildasio/h2t/56e1447848f5382b8a5a7dfd00e84b1a0335a589/docs/gildasio_verbose.png -------------------------------------------------------------------------------- /docs/hackme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gildasio/h2t/56e1447848f5382b8a5a7dfd00e84b1a0335a589/docs/hackme.png -------------------------------------------------------------------------------- /docs/headers.md: -------------------------------------------------------------------------------- 1 | # Headers 2 | 3 | Tech information about headers 4 | 5 | ## Clear-Site-Data 6 | 7 | ### Options 8 | 9 | * `cache` 10 | * `cookies` 11 | * `storage` 12 | * `executionContexts` 13 | * `*` 14 | 15 | ### Configuration 16 | 17 | #### Apache 18 | 19 | ~~~ 20 | # Apache conf 21 | 22 | Header always set Clear-Site-Data "*" 23 | 24 | ~~~ 25 | 26 | #### Nginx 27 | 28 | ~~~ 29 | # Nginx conf 30 | location /logout/ { 31 | add_header Clear-Site-Data "*" always; 32 | } 33 | ~~~ 34 | 35 | ## Content-Security-Policy 36 | 37 | ### Options 38 | 39 | There is a lot of option, it's better see [here](https://content-security-policy.com/#directive). 40 | 41 | ### Configuration 42 | 43 | #### Apache 44 | 45 | ~~~ 46 | # Apache conf 47 | Header always set Content-Security-Policy "default-src 'self'" 48 | ~~~ 49 | 50 | #### Nginx 51 | 52 | ~~~ 53 | # Nginx conf 54 | add_header Content-Security-Data "default-src 'self'" always; 55 | ~~~ 56 | 57 | ## Cookies 58 | 59 | ### HttpOnly 60 | 61 | #### Configuration 62 | 63 | ##### Apache 64 | 65 | ~~~ 66 | # Apache Conf 67 | Header edit Set-Cookie ^(.*)$ $1;HttpOnly 68 | ~~~ 69 | 70 | ##### Nginx 71 | 72 | ~~~ 73 | # Nginx conf 74 | proxy_cookie-Path / "/; HttpOnly" 75 | ~~~ 76 | 77 | ##### PHP 78 | 79 | ~~~ 80 | # php.ini 81 | session.cookie_httponly = True 82 | ~~~ 83 | 84 | [Documentation](http://php.net/session.cookie-httponly) 85 | 86 | ### Same Site 87 | 88 | #### Options 89 | 90 | * Strict: only send cookies in same-site requests 91 | 92 | * Lax: send cookies to cross-site requests when it's a GET request 93 | 94 | #### Configuration 95 | 96 | ##### Apache 97 | 98 | ~~~ 99 | # Apache conf 100 | Header edit Set-Cookie ^(.*)$ $1;; SameSite=strict 101 | ~~~ 102 | 103 | ##### Nginx 104 | 105 | ~~~ 106 | # Nginx conf 107 | proxy_cookie-Path / "/; ; SameSite=strict" 108 | ~~~ 109 | 110 | ### Secure 111 | 112 | #### Configuration 113 | 114 | ##### Apache 115 | 116 | ~~~ 117 | # Apache Conf 118 | Header edit Set-Cookie ^(.*)$ $1;Secure 119 | ~~~ 120 | 121 | ##### Nginx 122 | 123 | ~~~ 124 | # Nginx conf: SSL or default 125 | proxy_cookie_path / "/; Secure"; 126 | ~~~ 127 | 128 | ##### PHP 129 | 130 | ~~~ 131 | # php.ini 132 | session.cookie_secure = True 133 | ~~~ 134 | 135 | [Documentation](http://php.net/session.cookie-secure) 136 | 137 | ## Except-CT 138 | 139 | ### Options 140 | 141 | * `max-age` 142 | * `enforce`: optional 143 | * `report-uri`: optional 144 | 145 | ### Configuration 146 | 147 | #### Apache 148 | 149 | ~~~ 150 | Header always set Except-CT 'max-age=86400, enforce, report-uri="http://example.com/report"'; 151 | ~~~ 152 | 153 | #### Nginx 154 | 155 | ~~~ 156 | # Nginx conf 157 | add_header Except-CT 'max-age=86400, enforce, report-uri="http://example.com/report"' always; 158 | ~~~ 159 | 160 | ## Feature-Policy 161 | 162 | ### Options 163 | 164 | #### Set feature 165 | 166 | * `autoplay` 167 | * `camera` 168 | * `document-domain` 169 | * `encrypted-data` 170 | * `fullscreen` 171 | * `geolocation` 172 | * `microphone` 173 | * `midi` 174 | * `payment` 175 | * `vr` 176 | 177 | #### Set origin 178 | 179 | * `*` 180 | * `self` 181 | * `src` 182 | * `none` 183 | 184 | ### Configuration 185 | 186 | #### Apache 187 | 188 | ~~~ 189 | # Apache conf 190 | Header always set Feature-Policy "camera: 'none'; geolocation: 'none'" 191 | ~~~ 192 | 193 | #### Nginx 194 | 195 | ~~~ 196 | # Nginx conf 197 | add_header Feature-Policy "camera: 'none'; geolocation: 'none'"; 198 | ~~~ 199 | 200 | ## HTTP-Strict-Transport-Security 201 | 202 | ### Options 203 | 204 | * `max-age`: time in seconds to browser store this configuration 205 | * `includeSubDomains`: instructs browser to act in same way with subdomains 206 | * `preload` 207 | 208 | `preload` isn't an option in the specification but is widely used. More information [here](https://hstspreload.org). 209 | 210 | ### Configuration 211 | 212 | #### Apache 213 | 214 | ~~~ 215 | # Apache conf 216 | Header always set Strict-Transport-Security "max-age=31536000 ; includeSubDomains; preload" 217 | ~~~ 218 | 219 | #### Nginx 220 | 221 | ~~~ 222 | # Nginx conf 223 | add_header Strict-Transport-Security "max-age=31536000 ; includeSubDomains; preload"; 224 | ~~~ 225 | 226 | ## Servers 227 | 228 | ### Configuration 229 | 230 | #### Apache 231 | 232 | * Core Way 233 | 234 | ~~~ 235 | # Apache Conf 236 | ServerTokens Prod 237 | 238 | # Options 239 | # Full => Apache/2.4.2 (Unix) PHP/4.2.2 MyMod/1.2 240 | # Prod => Apache 241 | # Major => Apache/2 242 | # Minor => Apache/2.4 243 | # Min => Apache/2.4.2 244 | # OS => Apache/2.4.2 (Unix) 245 | ~~~ 246 | 247 | * ModSecurity Way 248 | 249 | ~~~ 250 | 251 | ServerTokens Full 252 | SecServerSignature "Welcome to the rabbit hole" 253 | 254 | ~~~ 255 | 256 | #### Nginx 257 | 258 | ~~~ 259 | # Nginx 260 | server_tokens off; 261 | ~~~ 262 | 263 | ## Public-Key-Pins 264 | 265 | ### Options 266 | 267 | * `pin-sha256`: SPKI fingerprint encoded in BASE64 268 | * `max-age`: in seconds 269 | * `includeSubDomains`: optional 270 | * `report-uri`: optional 271 | 272 | ### Configuration 273 | 274 | #### Apache 275 | 276 | ~~~ 277 | # Apache conf 278 | Header always set Public-Key-Pins 'pin-sha256="base64=="; pin-sha256="base64=="; max-age=360000; includeSubDomains; report-uri="http://examplo.com/report"' 279 | ~~~ 280 | 281 | #### Nginx 282 | 283 | ~~~ 284 | # Nginx conf 285 | add_header Public-Key-Pins 'pin-sha256="base64=="; pin-sha256="base64=="; max-age=360000; includeSubDomains; report-uri="http://example.com/report"' always; 286 | ~~~ 287 | 288 | ## Referrer-Policy 289 | 290 | ### Options 291 | 292 | * `""`: Tells to browser use a referrer policy provided in another place (eg meta-tag), or use the default (no-referrer-when-downgrade) 293 | * `no-referrer`: Never sends Referer 294 | * `no-referrer-when-downgrade`: Send entire URL unless change the traffic from HTTPS to HTTP 295 | * `origin`: Send only origin from URL in Referer 296 | * `origin-when-cross-origin`: Send only origin from URL in Referer when the destination origin is different from source origin 297 | * `same-origin`: Send entire URL to same origin but nothing to other origins 298 | * `strict-origin`: Send only origin to any origin but nothin when change HTTPS to HTTP 299 | * `strict-origin-when-cross-origin`: Send the entire URL when destination is same origin, only origin when it's another one and nothing when change HTTPS to HTTP 300 | * `unsafe-url`: Send entire URL in Referer 301 | 302 | ### Configuration 303 | 304 | #### Apache 305 | 306 | ~~~ 307 | # Apache Conf 308 | Header always set Referrer-Policy strict-origin-when-cross-origin 309 | ~~~ 310 | 311 | #### Nginx 312 | 313 | ~~~ 314 | # Nginx Conf 315 | add_header Referrer-Policy "strict-origin-when-cross-origin" always; 316 | ~~~ 317 | 318 | ## X-Content-Type-Options 319 | 320 | ### Options 321 | 322 | * nosniff: prevents browsers from mime-sniff the content and uses the informed by server 323 | 324 | ### Configuration 325 | 326 | #### Apache 327 | 328 | ~~~ 329 | # Apache conf 330 | Header always set X-Content-Type-Options "nosniff" 331 | ~~~ 332 | 333 | #### Nginx 334 | 335 | ~~~ 336 | # Nginx conf 337 | add_header X-Content-Type-Options "nosniff" always; 338 | ~~~ 339 | 340 | #### IIS 341 | 342 | 1. IIS Manager 343 | 2. Select the website 344 | 3. Double click in HTTP Response Headers 345 | 4. Click in Add 346 | 5. Add "X-Content-Type-Options" in Name and "nosniff" in Value 347 | 6. Click to save 348 | 349 | ## X-Download-Options 350 | 351 | ### Options 352 | 353 | * noopen: prevents IE directly open a dowloaded file forcing user to save it them open in any application 354 | 355 | ### Configuration 356 | 357 | #### Apache 358 | 359 | ~~~ 360 | # Apache conf 361 | Header always set X-Download-Options "noopen" 362 | ~~~ 363 | 364 | #### Nginx 365 | 366 | ~~~ 367 | # Nginx conf 368 | add_header X-Download-Options "noopen" always; 369 | ~~~ 370 | 371 | ### IIS 372 | 373 | 1. IIS Manager 374 | 2. Select the website 375 | 3. Double click in HTTP Response Headers 376 | 4. Click in ADd 377 | 5. Add "X-Download-Options" in Name and "noopen" in Value 378 | 6. Click to save 379 | 380 | ## X-Frame-Options 381 | 382 | ### Options 383 | 384 | * DENY: Deny any frame 385 | * SAMEORIGIN: Allow frame from the same website 386 | * ALLOW-FROM: Allow from a specific website 387 | 388 | ### Configuration 389 | 390 | #### Apache 391 | 392 | ~~~ 393 | # Apache Conf 394 | Header always append X-Frame-Options SAMEORIGIN 395 | 396 | # htaccess 397 | Header append X-FRAME-OPTIONS "SAMEORIGIN" 398 | ~~~ 399 | 400 | #### Nginx 401 | 402 | ~~~ 403 | # nginx.conf 404 | add_header X-Frame-Options "SAMEORIGIN"; 405 | ~~~ 406 | 407 | #### IIS 408 | 409 | 1. IIS Manager 410 | 2. Select the website 411 | 3. Double click in HTTP Response Headers 412 | 4. Click in Add 413 | 5. Add "X-Frame-Option" in Name and the option (eg SAMEORIGIN) in Value 414 | 6. Click to save 415 | 416 | [Documentation](https://support.office.com/en-us/article/mitigating-framesniffing-with-the-x-frame-options-header-1911411b-b51e-49fd-9441-e8301dcdcd79) 417 | 418 | #### ASP.Net 419 | 420 | Use [NWebsec](https://docs.nwebsec.com/en/latest/nwebsec/Configuring-xfo.html). 421 | 422 | ## X-Permitted-Cross-Domain-Policies 423 | 424 | ### Options 425 | 426 | * none No policy files are allowed anywhere on the target server, including this master policy file. 427 | master-only Only this master policy file is allowed. 428 | by-content-type [HTTP/HTTPS only] Only policy files served with Content-Type: text/x-cross-domain-policy are allowed. 429 | by-ftp-filename [FTP only] Only policy files whose file names are crossdomain.xml (i.e. URLs ending in /crossdomain.xml) are allowed. 430 | all All policy files on this target domain are allowed. 431 | 432 | ### Configuration 433 | 434 | #### Apache 435 | 436 | ~~~ 437 | # Apache Conf 438 | Header always append X-Permitted-Cross-Domain-Policies none 439 | ~~~ 440 | 441 | #### Nginx 442 | 443 | ~~~ 444 | # Nginx conf 445 | add_header X-Permitted-Cross-Domain-Policies "none" always; 446 | ~~~ 447 | 448 | #### IIS 449 | 450 | 1. IIS Manager 451 | 2. Select the website 452 | 3. Double click in HTTP Response Headers 453 | 4. Click in ADd 454 | 5. Add "X-Permitted-Cross-Domain-Policies" in Name and the option (eg none) in Value 455 | 6. Click to save 456 | ## X-XSS-Protection 457 | 458 | ### Options 459 | 460 | * `0`: disable the protection 461 | * `1`: enable the protection to sanitize the script 462 | * `1; mode=block`: enable the protection to block the response when detect an attack 463 | * `1; report=URI`: enable the protection to sanitize the script and report the attack to URI 464 | * `1; mode=block; report=URI`: enable the protection to block the request and report to URI 465 | 466 | ### Configuration 467 | 468 | #### Apache 469 | 470 | ~~~ 471 | # Apache conf 472 | Header always append X-Xss-Protection "1; mode=block" 473 | 474 | # htaccess 475 | 476 | Header set X-Xss-Protection "1; mode=block" 477 | 478 | ~~~ 479 | #### Nginx 480 | 481 | ~~~ 482 | # Nginx conf 483 | add_header X-Xss-Protection "1; mode=block" always; 484 | ~~~ 485 | #### IIS 486 | 487 | 1. IIS Manager 488 | 2. Select the website 489 | 3. Double click in HTTP Response Headers 490 | 4. Click in Add 491 | 5. Add "X-Xss-Protection" in Name and the option (eg `1; mode=block`) in Value 492 | 6. Click to sabe 493 | -------------------------------------------------------------------------------- /docs/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gildasio/h2t/56e1447848f5382b8a5a7dfd00e84b1a0335a589/docs/list.png -------------------------------------------------------------------------------- /h2t.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | 5 | from src import output 6 | from src import banners 7 | from src import connection 8 | from src import files 9 | from src import scan_command 10 | from src import list_command 11 | 12 | 13 | def show(result, content, category, url='', status=True): 14 | if len(result) > 0: 15 | if hasattr(args, "output"): 16 | output.results(result, content, category, fields=args.print, 17 | status=status, output=args.output, url=url) 18 | else: 19 | output.results(result, content, category, fields=args.print, 20 | status=status) 21 | 22 | 23 | def check(response, url, category): 24 | if category == "good": 25 | content = files.read_json("headers/good.json") 26 | elif category == "bad": 27 | content = files.read_json("headers/bad.json") 28 | 29 | result = scan_command.check(response, content, category=category, 30 | status=args.status, 31 | headers_to_analyze=args.headers) 32 | result = scan_command.ignore_headers(result, args.ignore_headers) 33 | 34 | show(result, content, category, url, status=args.status) 35 | 36 | 37 | def list_headers(args): 38 | bad = files.read_json("headers/bad.json") 39 | good = files.read_json("headers/good.json") 40 | result = dict() 41 | 42 | if args.headers: 43 | result["bad"] = list_command.list_headers(bad, headers=args.headers) 44 | result["good"] = list_command.list_headers(good, headers=args.headers) 45 | else: 46 | result["bad"] = list_command.list_headers(bad) 47 | result["good"] = list_command.list_headers(good) 48 | 49 | show(result["bad"], bad, "bad") 50 | show(result["good"], good, "good") 51 | 52 | 53 | def scan_headers_url(url, args, index=0, urls_qtd=1): 54 | if "://" not in url: 55 | url = "http://" + url 56 | 57 | response = connection.get(url, redirects=args.no_redirect, 58 | user_agent=args.user_agent, 59 | insecure=args.insecure) 60 | 61 | if index == 0: 62 | output.print_header(url, args.print, args.output) 63 | 64 | if args.verbose: 65 | output.verbose(response, args.verbose) 66 | 67 | response = {header.lower(): value.lower() for header, 68 | value in response.headers.items()} 69 | 70 | if args.bad: 71 | check(response, url, category="bad") 72 | elif args.good: 73 | check(response, url, category="good") 74 | elif args.all: 75 | check(response, url, category="bad") 76 | check(response, url, category="good") 77 | 78 | if index + 1 == urls_qtd: 79 | output.print_footer(args.output) 80 | 81 | 82 | def scan_headers(args): 83 | if args.no_explanation: 84 | output.help() 85 | 86 | if os.path.isfile(args.url): 87 | urls = files.read_lines(args.url) 88 | urls_qtd = len(urls) 89 | for i, url in enumerate(urls): 90 | scan_headers_url(url, args, index=i, urls_qtd=urls_qtd) 91 | else: 92 | scan_headers_url(args.url, args) 93 | 94 | 95 | if __name__ == '__main__': 96 | parser = argparse.ArgumentParser(description="h2t - HTTP Hardening Tool") 97 | 98 | sub_parsers = parser.add_subparsers(help="sub-command help") 99 | 100 | list_parser = sub_parsers.add_parser("list", aliases=['l'], 101 | help="show a list of available" 102 | " headers in h2t catalog (that" 103 | " can be used in scan subcommand" 104 | "-H option)") 105 | list_parser.add_argument("-p", "--print", default=False, nargs="+", 106 | help="a list of additional information about the" 107 | " headers to print. For now there are two" 108 | " options: description and refs (you can use" 109 | " either or both)") 110 | list_parser.add_argument("-B", "--no-banner", action="store_false", 111 | default=True, help="don't print the h2t banner") 112 | 113 | group_headers_list = list_parser.add_mutually_exclusive_group() 114 | group_headers_list.add_argument("-a", "--all", action="store_true", 115 | default=True, help="list all available " 116 | "headers [default]") 117 | group_headers_list.add_argument("-H", "--headers", nargs="+", 118 | help="a list of headers to look for in" 119 | " the h2t catalog") 120 | 121 | list_parser.set_defaults(command=list_headers) 122 | 123 | scan_parser = sub_parsers.add_parser("scan", aliases=['s'], 124 | help="scan url to hardening headers") 125 | scan_parser.add_argument("-v", "--verbose", action="count", default=0, 126 | help="increase output verbosity: -v print" 127 | " response headers, -vv print response and" 128 | " request headers") 129 | scan_parser.add_argument("-a", "--all", action="store_true", default=True, 130 | help="scan all cataloged headers [default]") 131 | scan_parser.add_argument("-g", "--good", action="store_true", 132 | help="scan good headers only") 133 | scan_parser.add_argument("-b", "--bad", action="store_true", 134 | help="scan bad headers only") 135 | scan_parser.add_argument("-H", "--headers", default=False, nargs="+", 136 | help="scan only these headers (see available in" 137 | " list sub-command)") 138 | scan_parser.add_argument("-p", "--print", default=False, nargs="+", 139 | help="a list of additional information about the" 140 | " headers to print. For now there are two" 141 | " options: description and refs (you can use" 142 | " either or both)") 143 | scan_parser.add_argument("-i", "--ignore-headers", default=False, 144 | nargs="+", 145 | help="a list of headers to ignore in the results") 146 | scan_parser.add_argument("-B", "--no-banner", action="store_false", 147 | default=True, help="don't print the h2t banner") 148 | scan_parser.add_argument("-E", "--no-explanation", action="store_false", 149 | default=True, 150 | help="don't print the h2t output explanation") 151 | scan_parser.add_argument("-o", "--output", 152 | choices=["normal", "csv", "json"], 153 | default="normal", 154 | help="choose which output format to use " 155 | "(available: normal, csv, json)") 156 | 157 | scan_parser.add_argument("-n", "--no-redirect", action="store_false", 158 | help="don't follow http redirects") 159 | scan_parser.add_argument("-u", "--user-agent", 160 | help="set user agent to scan request") 161 | scan_parser.add_argument("-k", "--insecure", action="store_false", 162 | help="don't verify SSL certificate as valid") 163 | 164 | groupOutput = scan_parser.add_mutually_exclusive_group() 165 | groupOutput.add_argument("-r", "--recommendation", action="store_true", 166 | default=True, 167 | help="output only recommendations [default]") 168 | groupOutput.add_argument("-s", "--status", action="store_true", 169 | help="output actual status (eg: existent" 170 | " headers only)") 171 | 172 | scan_parser.add_argument("url", help="url to look for") 173 | scan_parser.set_defaults(command=scan_headers) 174 | 175 | args = parser.parse_args() 176 | 177 | if hasattr(args, 'no_banner') and args.no_banner: 178 | output.banner(banners.get_banner()) 179 | 180 | if isinstance(args.headers, list): 181 | args.headers = {header.lower() for header in args.headers} 182 | 183 | if isinstance(args.ignore_headers, list): 184 | args.ignore_headers = [header.lower() for header in args.ignore_headers] 185 | 186 | if 'command' in args: 187 | args.command(args) 188 | else: 189 | parser.print_usage() 190 | -------------------------------------------------------------------------------- /headers/bad.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "title": "Server", 4 | "description": "Server headers show information about the software used to provide the website. Show software's version it's a lot information to give to attackers.", 5 | "condition": true, 6 | "refs": [ 7 | "https://tools.ietf.org/html/rfc2616#section-14.38", 8 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#servers" 9 | ] 10 | }, 11 | "set-cookie": [ 12 | { 13 | "title": "Cookie not HTTP Only", 14 | "description": "Using the flag 'HttpOnly' the cookie won't be read from JavaScript only sent by HTTP", 15 | "condition": "^((?!httponly).)*$", 16 | "refs": [ 17 | "https://tools.ietf.org/html/rfc6265#section-5.2.6", 18 | "https://github.com/gildasio/h2t/blob/docs/headers.md#httponly", 19 | "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Session_Management_Cheat_Sheet.md#httponly-attribute", 20 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies", 21 | "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md#bonus-rule-1-use-httponly-cookie-flag", 22 | "https://www.owasp.org/index.php/HttpOnly" 23 | ] 24 | }, 25 | { 26 | "title": "Cookie not over SSL/TLS", 27 | "description": "Using the flag 'Secure' the cookie will be sent only in HTTPS traffic avoiding MITM read its content", 28 | "condition": "^((?!secure).)*$", 29 | "refs": [ 30 | "https://tools.ietf.org/html/rfc6265#section-5.2.5", 31 | "https://github.com/gildasio/h2t/blob/docs/headers.md#secure", 32 | "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Session_Management_Cheat_Sheet.md#secure-attribute", 33 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies", 34 | "https://www.owasp.org/index.php/SecureFlag" 35 | ] 36 | }, 37 | { 38 | "title": "Cookie not only from SameSite", 39 | "description": "Samesite cookies tells to browser send the cookie only in requests initiated from the same domain. It avoid CSRF attacks", 40 | "condition": "^((?!samesite).)*$", 41 | "refs": [ 42 | "https://tools.ietf.org/html/draft-west-first-party-cookies-07", 43 | "https://caniuse.com/#feat=same-site-cookie-attribute", 44 | "https://www.owasp.org/index.php/SameSite" 45 | ] 46 | } 47 | ], 48 | "referrer-policy": { 49 | "title": "Referrer-Policy with bad practices", 50 | "description": "This site use unsafe-url in Referrer-Policy header. Because of this browsers will always send the full URL in any request to any origin", 51 | "condition": "unsafe-url", 52 | "refs": [ 53 | "https://w3c.github.io/webappsec-referrer-policy/", 54 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#referrer-policy", 55 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy", 56 | "https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#rp", 57 | "https://scotthelme.co.uk/a-new-security-header-referrer-policy/" 58 | ] 59 | }, 60 | "x-permitted-cross-domain-policies": { 61 | "title": "X-Permitted-Cross-Domain-Policies too open", 62 | "description": "The most secure option to X-Permitted-Cross-Domain-Policies is none, make sure you really need other option", 63 | "condition": "^((?!all).)*$", 64 | "refs": [ 65 | "https://www.adobe.com/devnet-docs/acrobatetk/tools/AppSec/xdomain.html", 66 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#x-permitted-cross-domain-policies", 67 | "https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xpcdp" 68 | ] 69 | }, 70 | "strict-transport-security": [ 71 | { 72 | "title": "HSTS without includeSubDomains", 73 | "description": "It's recommended protect all the subdomains using HTTPS as well", 74 | "condition": "^((?!includesubdomains).)*$", 75 | "refs": [ 76 | "https://tools.ietf.org/html/rfc6797", 77 | "https://caniuse.com/#feat=stricttransportsecurity", 78 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#http-strict-transport-security", 79 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security", 80 | "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md" 81 | ] 82 | }, 83 | { 84 | "title": "HSTS without preload", 85 | "description": "It's much better secure configure the domain under HSTS preload list in the browser", 86 | "condition": "^((?!preload).)*$", 87 | "refs": [ 88 | "https://hstspreload.org", 89 | "https://tools.ietf.org/html/rfc6797", 90 | "https://caniuse.com/#feat=stricttransportsecurity", 91 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#http-strict-transport-security", 92 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security", 93 | "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md" 94 | ] 95 | } 96 | ], 97 | "access-control-allow-origin": { 98 | "title": "Access-Control-Allow-Origin too open", 99 | "description": "Using Access-Control-Allow-Origin with option '*' are too open because any website can request the content", 100 | "condition": "\\*", 101 | "refs": [ 102 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#access-control-allow-origin", 103 | "https://caniuse.com/#feat=cors", 104 | "https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Headers/Access-Control-Allow-Origin", 105 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" 106 | ] 107 | }, 108 | "public-key-pins": { 109 | "title": "Public-Key-Pins without a backup key", 110 | "description": "Using Public-Key-Pins with only one key is dangerous because if you lost your key (CA hacked, renew...) the user can't access the site with the new key once the browser will not accept that", 111 | "condition": "^(?!.*pin-sha256.*pin-sha256)", 112 | "refs": [ 113 | "https://tools.ietf.org/html/rfc7469", 114 | "https://caniuse.com/#feat=publickeypinning", 115 | "https://github.com/gildasio/h2t/blob/naster/docs/headers.md#public-keys-pins", 116 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning", 117 | "https://scotthelme.co.uk/hpkp-http-public-key-pinning/" 118 | ] 119 | }, 120 | "content-security-policy": { 121 | "title": "Content-Security-Policy too permisive", 122 | "description": "Using 'unsafe-inline' reduces the effectiveness of CSP, prefer using Nonce or Hash", 123 | "condition": "unsafe-inline", 124 | "refs": [ 125 | "https://www.w3.org/TR/CSP/", 126 | "https://content-security-policy.com", 127 | "https://caniuse.com/#search=csp", 128 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP", 129 | "https://www.owasp.org/index.php/Content_Security_Policy", 130 | "https://scotthelme.co.uk/content-security-policy-an-introduction/" 131 | ] 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /headers/good.json: -------------------------------------------------------------------------------- 1 | { 2 | "x-frame-options": { 3 | "title": "X-Frame-Options", 4 | "description": "X-Frame-Options indicates to browser if the website can be framed or not. It protect against clickjacking attacks", 5 | "refs": [ 6 | "https://tools.ietf.org/html/rfc7034", 7 | "https://caniuse.com/#feat=x-frame-options", 8 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#x-frame-options", 9 | "https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xfo", 10 | "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Clickjacking_Defense_Cheat_Sheet.md", 11 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options", 12 | "https://www.troyhunt.com/clickjack-attack-hidden-threat-right-in/" 13 | ] 14 | }, 15 | "x-content-type-options": { 16 | "title": "X-Content-Type-Options", 17 | "description": "Indicates to browser to doens't mime-sniff the content-type of the response and uses wich the server sent", 18 | "refs": [ 19 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#x-content-type-options", 20 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options", 21 | "https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xcto" 22 | ] 23 | }, 24 | "x-xss-protection": { 25 | "title": "X-XSS-Protection", 26 | "description": "This header enable the XSS Reflected filter in the browsers. It can block, sanitize or interprete the response", 27 | "refs": [ 28 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#x-xss-protection", 29 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection", 30 | "https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xxxsp" 31 | ] 32 | }, 33 | "referrer-policy": { 34 | "title": "Referrer-Policy", 35 | "description": "Sets which information will be sent in Referer header", 36 | "refs": [ 37 | "https://w3c.github.io/webappsec-referrer-policy/", 38 | "https://caniuse.com/#feat=referrer-policy", 39 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#referrer-policy", 40 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy", 41 | "https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#rp", 42 | "https://scotthelme.co.uk/a-new-security-header-referrer-policy/" 43 | ] 44 | }, 45 | "x-download-options": { 46 | "title": "X-Download-Options", 47 | "description": "In IE it prevents downloaded files to be directly opened in the browser forcing user to save it and them open in any application.", 48 | "refs": [ 49 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#x-download-options", 50 | "http://www.nwebsec.com/HttpHeaders/SecurityHeaders/XDownloadOptions", 51 | "https://blog.giantgeek.com/?p=788" 52 | ] 53 | }, 54 | "x-permitted-cross-domain-policies": { 55 | "title": "X-Permitted-Cross-Domain-Policies", 56 | "description": "Apply a policy to some clients (mainly Adobe products, as flash player) to access or not a content", 57 | "refs": [ 58 | "https://www.adobe.com/devnet-docs/acrobatetk/tools/AppSec/xdomain.html", 59 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#x-permitted-cross-domain-policies", 60 | "https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#xpcdp" 61 | ] 62 | }, 63 | "clear-site-data": { 64 | "title": "Clear-Site-Data", 65 | "description": "Instruct browser to clear the site's local storage data. It's very usefull to a page like '/logout'", 66 | "refs": [ 67 | "https://www.w3.org/TR/clear-site-data/", 68 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#clear-site-data", 69 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data", 70 | "https://scotthelme.co.uk/a-new-security-header-clear-site-data/" 71 | ] 72 | }, 73 | "strict-transport-security": { 74 | "title": "HTTP Strict Transport Security [HSTS]", 75 | "description": "Instruct browser to navigate only via HTTPS and save it for future requests", 76 | "refs": [ 77 | "https://tools.ietf.org/html/rfc6797", 78 | "https://caniuse.com/#feat=stricttransportsecurity", 79 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#strict-transport-security", 80 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security", 81 | "https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md", 82 | "https://hstspreload.org" 83 | ] 84 | }, 85 | "public-key-pins": { 86 | "title": "Public-Key-Pins", 87 | "description": "Treat website as valid only if that HTTPS certificate match a hash in this list", 88 | "refs": [ 89 | "https://tools.ietf.org/html/rfc7469", 90 | "https://caniuse.com/#feat=publickeypinning", 91 | "https://github.com/gildasio/h2t/blob/naster/docs/headers.md#public-keys-pins", 92 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning", 93 | "https://scotthelme.co.uk/hpkp-http-public-key-pinning/" 94 | ] 95 | }, 96 | "feature-policy": { 97 | "title": "Feature-Policy", 98 | "description": "Request to browser which APIs to enable or disable", 99 | "refs": [ 100 | "https://w3c.github.io/webappsec-feature-policy/", 101 | "https://caniuse.com/#feat=feature-policy", 102 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#feature-policy", 103 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy", 104 | "https://scotthelme.co.uk/a-new-security-header-feature-policy/" 105 | ] 106 | }, 107 | "except-ct": { 108 | "title": "Except-CT", 109 | "description": "Determine if your site expect and use Certificate Transparency feature", 110 | "refs": [ 111 | "https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-08", 112 | "https://tools.ietf.org/html/rfc6962", 113 | "https://www.certificate-transparency.org", 114 | "https://github.com/gildasio/h2t/blob/master/docs/headers.md#except-ct", 115 | "https://httpwg.org/http-extensions/expect-ct.html", 116 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT", 117 | "https://scotthelme.co.uk/certificate-transparency-an-introduction/", 118 | "https://scotthelme.co.uk/a-new-security-header-expect-ct/" 119 | ] 120 | }, 121 | "content-security-policy": { 122 | "title": "Content-Security-Policy", 123 | "description": "It defines from where browser can load content such as JavaScript", 124 | "refs": [ 125 | "https://www.w3.org/TR/CSP/", 126 | "https://content-security-policy.com", 127 | "https://caniuse.com/#search=csp", 128 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP", 129 | "https://www.owasp.org/index.php/Content_Security_Policy", 130 | "https://scotthelme.co.uk/content-security-policy-an-introduction/" 131 | ] 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2018.11.29 2 | chardet==3.0.4 3 | colorama==0.4.1 4 | idna==2.8 5 | requests==2.21.0 6 | urllib3==1.24.2 7 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gildasio/h2t/56e1447848f5382b8a5a7dfd00e84b1a0335a589/src/__init__.py -------------------------------------------------------------------------------- /src/banners.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | 3 | 4 | BANNERS = [ 5 | ''' _ ___ _ 6 | | | |__ \| | 7 | | |__ ) | |_ 8 | | '_ \ / /| __| 9 | | | | |/ /_| |_ 10 | |_| |_|____|\__| 11 | ''', 12 | ''' ____ ____ ____ 13 | ||h |||2 |||t || 14 | ||__|||__|||__|| 15 | |/__\|/__\|/__\| 16 | ''', 17 | ''' 18 | .----------------. .----------------. .----------------. 19 | | .--------------. || .--------------. || .--------------. | 20 | | | ____ ____ | || | _____ | || | _________ | | 21 | | | |_ || _| | || | / ___ `. | || | | _ _ | | | 22 | | | | |__| | | || | |_/___) | | || | |_/ | | \_| | | 23 | | | | __ | | || | .'____.' | || | | | | | 24 | | | _| | | |_ | || | / /____ | || | _| |_ | | 25 | | | |____||____| | || | |_______| | || | |_____| | | 26 | | | | || | | || | | | 27 | | '--------------' || '--------------' || '--------------' | 28 | '----------------' '----------------' '----------------' 29 | ''', 30 | ''' 31 | ########################## 32 | # # # ## ## # # 33 | # ## # # ## # # # # # 34 | # ## # ##### # ### ### 35 | # # #### ## ### ### 36 | # ## # ## #### ### ### 37 | # ## # # ##### ### ### 38 | # # # # ## ## 39 | ######## ######## ######## 40 | ''', 41 | ''' ___ ___ _____ __ 42 | \ | | / / _ \ (__ __) 43 | | \_/ | (_/ ) ) | | 44 | | _ | / / | | 45 | | / \ | / /__ | | 46 | / |___| \__( )____| |____ 47 | ''', 48 | '''| /~\ | 49 | |/~\ ./~|~ 50 | | |/__ | 51 | ''', 52 | '''hhhhhhh 222222222222222 tttt 53 | h:::::h 2:::::::::::::::22 ttt:::t 54 | h:::::h 2::::::222222:::::2 t:::::t 55 | h:::::h 2222222 2:::::2 t:::::t 56 | h::::h hhhhh 2:::::2ttttttt:::::ttttttt 57 | h::::hh:::::hhh 2:::::2t:::::::::::::::::t 58 | h::::::::::::::hh 2222::::2 t:::::::::::::::::t 59 | h:::::::hhh::::::h 22222::::::22 tttttt:::::::tttttt 60 | h::::::h h::::::h 22::::::::222 t:::::t 61 | h:::::h h:::::h 2:::::22222 t:::::t 62 | h:::::h h:::::h2:::::2 t:::::t 63 | h:::::h h:::::h2:::::2 t:::::t tttttt 64 | h:::::h h:::::h2:::::2 222222 t::::::tttt:::::t 65 | h:::::h h:::::h2::::::2222222:::::2 tt::::::::::::::t 66 | h:::::h h:::::h2::::::::::::::::::2 tt:::::::::::tt 67 | hhhhhhh hhhhhhh22222222222222222222 ttttttttttt 68 | ''', 69 | ''' _/ _/_/ _/ 70 | _/_/_/ _/ _/ _/_/_/_/ 71 | _/ _/ _/ _/ 72 | _/ _/ _/ _/ 73 | _/ _/ _/_/_/_/ _/_/ 74 | ''', 75 | '''o -- o 76 | | o o | 77 | O--o / -o- 78 | | | / | 79 | o o o--o o ''', 80 | '''| |_| ||____ | _| |_ 81 | | _ || --||_ _| 82 | |__| |_||______| |__| ''', 83 | '''.__ ________ __ 84 | | |__ \_____ \ _/ |_ 85 | | | \ / ____/ \ __\\ 86 | | Y \ / \ | | 87 | |___| / \_______ \ |__| 88 | \/ \/ ''', 89 | ''' eeee 90 | e e 8 eeeee 91 | 8 8 8 8 92 | 8eee8 eee8 8e 93 | 88 8 8 88 94 | 88 8 8eee 88 95 | ''' 96 | ] 97 | 98 | def get_banner(): 99 | return choice(BANNERS) 100 | -------------------------------------------------------------------------------- /src/connection.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | 4 | 5 | def get(url, redirects=True, user_agent='h2t', insecure=True): 6 | if not insecure: 7 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 8 | 9 | headers = {'user-agent': user_agent} 10 | return requests.get(url, allow_redirects=redirects, headers=headers, verify=insecure) 11 | -------------------------------------------------------------------------------- /src/files.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def read_lines(f): 5 | with open(f) as content: 6 | lines = [line.rstrip('\n') for line in content] 7 | return lines 8 | 9 | 10 | def read_json(f): 11 | with open(f) as content: 12 | data = json.load(content) 13 | return data 14 | -------------------------------------------------------------------------------- /src/list_command.py: -------------------------------------------------------------------------------- 1 | def list_headers(catalog, headers=True): 2 | result = [] 3 | if isinstance(headers, set): 4 | for header in catalog: 5 | if header in headers: 6 | result.append(header) 7 | else: 8 | result = catalog.keys() 9 | 10 | return set(result) 11 | -------------------------------------------------------------------------------- /src/output.py: -------------------------------------------------------------------------------- 1 | import json 2 | from urllib.parse import urlparse 3 | from colorama import init, Fore, Style 4 | 5 | init() 6 | 7 | 8 | def help(): 9 | print('''Output explanation: 10 | {b}{g}[+]{reset} Good headers. Already used in your website. Good job! 11 | {b}{y}[+]{reset} Good headers. We recommend applying it 12 | {b}{r}[-]{reset} Bad headers. We recommend remove it\n'''.format(b=Style.BRIGHT, g=Fore.GREEN, y=Fore.YELLOW, r=Fore.RED, reset=Style.RESET_ALL)) 13 | 14 | 15 | def banner(b): 16 | print(Style.BRIGHT, end='') 17 | print(b) 18 | print('https://github.com/gildasio/h2t') 19 | print(Style.RESET_ALL) 20 | 21 | 22 | def print_bright(message, end='\n'): 23 | print(Style.BRIGHT + message + Style.RESET_ALL, end=end) 24 | 25 | 26 | def print_color(message, color): 27 | if color == 'red': 28 | color = Fore.RED 29 | print(color + message + Fore.RESET) 30 | 31 | 32 | def get_status(category, status): 33 | if category == 'good' and status: 34 | return 'applied' 35 | elif category == 'good' and not status: 36 | return 'touse' 37 | elif category == 'bad': 38 | return 'toremove' 39 | 40 | 41 | def print_line(message, level=1, category = None, title = None, status=False): 42 | sts = get_status(category, status) 43 | 44 | if sts == 'applied': 45 | color = Fore.GREEN 46 | pre = '[+] ' 47 | elif sts == 'touse': 48 | color = Fore.YELLOW 49 | pre = '[+] ' 50 | elif sts == 'toremove': 51 | color = Fore.RED 52 | pre = '[-] ' 53 | else: 54 | color = '' 55 | pre = '' 56 | 57 | if title: 58 | print(' '*4*level + Style.BRIGHT + title + ': ' + Style.RESET_ALL + message) 59 | else: 60 | print(' '*4*level + color + Style.BRIGHT + pre + Fore.RESET + message) 61 | 62 | 63 | def print_header(url, fields, output): 64 | if output == 'normal': 65 | print_bright(url) 66 | elif output == 'csv': 67 | csv_header(fields) 68 | elif output =='json': 69 | print('[\n\t{\'url\': '' + url + '',') 70 | print('\t\'headers\': [') 71 | 72 | 73 | def print_footer(output): 74 | if output == 'json': 75 | print('\t]}\n]') 76 | 77 | 78 | def csv_header(fields, delimiter=';'): 79 | print('url' + delimiter + 'status' + delimiter + 'title', end='') 80 | 81 | if isinstance(fields, list): 82 | for field in fields: 83 | print(delimiter + field, end='') 84 | 85 | print() 86 | 87 | 88 | def show(content, fields, category, status=False, output='normal', url=''): 89 | if output == 'normal': 90 | show_normal(content, fields, category, status) 91 | elif output == 'csv': 92 | show_csv(content, fields, category, status, url) 93 | elif output == 'json': 94 | show_json(content, fields, category, status) 95 | 96 | 97 | def show_json(content, fields, category, status): 98 | sts = get_status(category, status) 99 | 100 | result = { 101 | 'title': content['title'], 102 | 'status': sts 103 | } 104 | 105 | if isinstance(fields, list): 106 | if 'description' in fields: 107 | result['description'] = content['description'] 108 | if 'refs' in fields: 109 | result['refs'] = content['refs'] 110 | 111 | print('\t\t' + json.dumps(result) + ',') 112 | 113 | 114 | def show_csv(content, fields, category, status, url, delimiter=';'): 115 | sts = get_status(category, status) 116 | 117 | if sts == 'applied': 118 | status = 'applied' 119 | elif sts == 'touse': 120 | status = 'recommended to use' 121 | elif sts == 'toremove': 122 | status = 'recommended to remove' 123 | 124 | print(url, end=delimiter) 125 | print(status, end=delimiter) 126 | print(content['title'], end='') 127 | 128 | if isinstance(fields, list): 129 | if 'description' in fields: 130 | print(delimiter + content['description'], end='') 131 | if 'refs' in fields: 132 | print(delimiter, end='') 133 | for item in content['refs']: 134 | print(item + ' ', end='') 135 | print() 136 | 137 | 138 | def show_normal(content, fields, category, status): 139 | print_line(content['title'], category=category, status=status) 140 | 141 | if isinstance(fields, list): 142 | if 'description' in fields: 143 | print_line(content['description'], level=2, title='Description') 144 | if 'refs' in fields: 145 | print_line('', level=2, title='Refs') 146 | for item in content['refs']: 147 | print_line(Style.RESET_ALL + item, level=3) 148 | print(Style.RESET_ALL, end='') 149 | 150 | 151 | def results(result, catalog, category, fields=0, status=False, output='normal', url=''): 152 | for header in result: 153 | if header not in catalog: 154 | print_bright(header, end='') 155 | print_color(" isn't an option. See available option to -H using 'list -a'", 'red') 156 | exit() 157 | 158 | if isinstance(result, dict) and isinstance(result[header], list): 159 | for i in result[header]: 160 | show(catalog[header][i], fields, category, status=status, output=output, url=url) 161 | elif isinstance(catalog[header], list): 162 | for item in catalog[header]: 163 | show(item, fields, category, status=status, output=output) 164 | else: 165 | show(catalog[header], fields, category, status=status, output=output, url=url) 166 | 167 | 168 | def verbose(response, verbose): 169 | req = response.request 170 | if verbose >= 2: 171 | print_line(req.method + ' ' + req.path_url + ' HTTP/1.1') 172 | print_line('Host: ' + urlparse(response.url).netloc) 173 | for header in req.headers: 174 | print_line(req.headers[header], title=header) 175 | print() 176 | 177 | if verbose >= 1: 178 | print_line('HTTP/1.1 ' + str(response.status_code) + ' ' + response.reason) 179 | for header in response.headers: 180 | print_line(response.headers[header], title=header) 181 | print() 182 | -------------------------------------------------------------------------------- /src/scan_command.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def check_condition(header, rule): 5 | if isinstance(rule['condition'], str) and re.search(rule['condition'], header): 6 | return True 7 | elif isinstance(rule['condition'], bool) and rule['condition']: 8 | return True 9 | else: 10 | return False 11 | 12 | 13 | def clear_bad_headers(intersect, headers, catalog): 14 | result = dict() 15 | for header in intersect: 16 | if header in catalog: 17 | if isinstance(catalog[header], list): 18 | for index, rule in enumerate(catalog[header]): 19 | if check_condition(headers[header], rule): 20 | if header in result: 21 | result[header].append(index) 22 | else: 23 | result[header] = [index] 24 | else: 25 | if check_condition(headers[header], catalog[header]): 26 | result[header] = True 27 | 28 | return result 29 | 30 | 31 | def check(headers, catalog, category='good', status=False, headers_to_analyze=False): 32 | if isinstance(headers_to_analyze, set): 33 | lookFor = headers_to_analyze 34 | else: 35 | lookFor = catalog.keys() 36 | 37 | if category == 'good': 38 | if status: 39 | result = set(lookFor) & set(headers.keys()) 40 | else: 41 | result = set(lookFor) - set(headers.keys()) 42 | elif category == 'bad': 43 | result = set(lookFor) & set(headers.keys()) 44 | result = clear_bad_headers(result, headers, catalog) 45 | 46 | return result 47 | 48 | 49 | def ignore_headers(result, ignore): 50 | if not isinstance(ignore, list): 51 | return result 52 | elif isinstance(result, set): 53 | return result - set(ignore) 54 | elif isinstance(result, dict): 55 | for i in list(result): 56 | if i in ignore: 57 | del result[i] 58 | return result 59 | --------------------------------------------------------------------------------