├── .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 | 
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 | 
147 |
148 | #### Scan from file
149 |
150 | 
151 |
152 | #### Scan url
153 |
154 | 
155 |
156 | #### Scan verbose
157 |
158 | 
159 |
160 | #### Headers information
161 |
162 | 
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 |
--------------------------------------------------------------------------------