├── thresholds.txt
├── dangerous_headers.txt
├── error1.png
├── gear_2.png
├── UI_theme_dark.txt
├── UI_theme_light.txt
├── template
├── template.docx
├── template.py
└── info.txt
├── cookie_flags.txt
├── output
└── .gitignore
├── __pycache__
├── docx.cpython-38.pyc
├── docx.cpython-39.pyc
└── template.cpython-39.pyc
├── potentially_dangerous_headers.txt
├── config.txt
├── security_headers.txt
├── LICENSE
├── .github
└── workflows
│ └── codeql.yml
├── request_headers.txt
├── response_headers.txt
├── README.md
└── headers_window.py
/thresholds.txt:
--------------------------------------------------------------------------------
1 | 1 3
2 | 0 3
3 | 0 2
--------------------------------------------------------------------------------
/dangerous_headers.txt:
--------------------------------------------------------------------------------
1 | 1 x-powered-by
2 | 1 server
--------------------------------------------------------------------------------
/error1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antlarac/Headers/HEAD/error1.png
--------------------------------------------------------------------------------
/gear_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antlarac/Headers/HEAD/gear_2.png
--------------------------------------------------------------------------------
/UI_theme_dark.txt:
--------------------------------------------------------------------------------
1 | #F07338
2 | #00FF00
3 | #FF0000
4 | #4FC3F7
5 | #707070
6 | #FFC745
--------------------------------------------------------------------------------
/UI_theme_light.txt:
--------------------------------------------------------------------------------
1 | #5B4FFF
2 | #00FF00
3 | #FF0000
4 | #4FC3F7
5 | #a0a0a0
6 | #447EAA
--------------------------------------------------------------------------------
/template/template.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antlarac/Headers/HEAD/template/template.docx
--------------------------------------------------------------------------------
/cookie_flags.txt:
--------------------------------------------------------------------------------
1 | 1 httponly
2 | 1 secure
3 | 0 samesite
4 | 0 expire
5 | 0 max-age
6 | 0 path
7 | 0 domain
--------------------------------------------------------------------------------
/output/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/__pycache__/docx.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antlarac/Headers/HEAD/__pycache__/docx.cpython-38.pyc
--------------------------------------------------------------------------------
/__pycache__/docx.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antlarac/Headers/HEAD/__pycache__/docx.cpython-39.pyc
--------------------------------------------------------------------------------
/potentially_dangerous_headers.txt:
--------------------------------------------------------------------------------
1 | 1 access-control-allow-origin
2 | 1 access-control-allow-credentials
3 | 0 set-cookie
--------------------------------------------------------------------------------
/__pycache__/template.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antlarac/Headers/HEAD/__pycache__/template.cpython-39.pyc
--------------------------------------------------------------------------------
/config.txt:
--------------------------------------------------------------------------------
1 | UI_theme -- dark
2 | last_save_type -- JSON: Header -> Host
3 | last_filter_type -- Request + Response +
4 | last_output_file -- Enter your output file path
5 |
--------------------------------------------------------------------------------
/security_headers.txt:
--------------------------------------------------------------------------------
1 | 1 Cache-Control
2 | 1 Content-Security-Policy
3 | 1 Strict-Transport-Security
4 | 0 Public-Key-Pins
5 | 1 X-XSS-Protection
6 | 1 X-Frame-Options
7 | 1 X-Content-Type-Options
8 | 0 Feature-Policy
9 | 1 Referrer-Policy
10 | 0 Permissions-Policy
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 dh0ck
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 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '20 13 * * 0'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Use only 'java' to analyze code written in Java, Kotlin or both
38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
40 |
41 | steps:
42 | - name: Checkout repository
43 | uses: actions/checkout@v3
44 |
45 | # Initializes the CodeQL tools for scanning.
46 | - name: Initialize CodeQL
47 | uses: github/codeql-action/init@v2
48 | with:
49 | languages: ${{ matrix.language }}
50 | # If you wish to specify custom queries, you can do so here or in a config file.
51 | # By default, queries listed here will override any specified in a config file.
52 | # Prefix the list here with "+" to use these queries and those in the config file.
53 |
54 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
55 | # queries: security-extended,security-and-quality
56 |
57 |
58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
59 | # If this step fails, then you should remove it and run the build manually (see below)
60 | - name: Autobuild
61 | uses: github/codeql-action/autobuild@v2
62 |
63 | # ℹ️ Command-line programs to run using the OS shell.
64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
65 |
66 | # If the Autobuild fails above, remove it and uncomment the following three lines.
67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
68 |
69 | # - run: |
70 | # echo "Run, Build Application using script"
71 | # ./location_of_script_within_repo/buildscript.sh
72 |
73 | - name: Perform CodeQL Analysis
74 | uses: github/codeql-action/analyze@v2
75 | with:
76 | category: "/language:${{matrix.language}}"
77 |
--------------------------------------------------------------------------------
/template/template.py:
--------------------------------------------------------------------------------
1 | from docx.oxml.ns import nsdecls
2 | from docx.oxml import parse_xml
3 | import os, sys
4 | from docxtpl import DocxTemplate
5 | from docxtpl import InlineImage
6 | from docx.shared import Mm
7 |
8 | f = open('info.txt')
9 | infos = f.readlines()
10 | f.close()
11 | descriptions = {}
12 | solutions = []
13 | # info.txt file cannot contain empty lines!!! all lines must be of the format:
14 | # header ::: description --- solution
15 | for info in infos:
16 | header = info.split(' ::: ')[0]
17 | contents = info.split(' ::: ')[1]
18 | descriptions[header] = {}
19 | descriptions[header]["description"] = contents.split('---')[0]
20 | descriptions[header]["solution"] = contents.split('---')[1]
21 |
22 | #breakpoint()
23 | vulns = [
24 | ('Missing HTTP-Strict-Transport-Security header','Missing Security Header','MEDIUM','6.5','CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N'),
25 | ('Missing Cache-Control header','Missing Security Header', 'LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
26 | ('Missing X-Frame-Options header','Missing Security Header','LOW','3.1','CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:N'),
27 | ('Missing Content-Security-Policy header','Missing Security Header','LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
28 | ('Missing X-Content-Type-Options header','Missing Security Header','LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
29 | ('Missing X-XSS-Protection header','Missing Security Header','MEDIUM','5.4','CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N'),
30 | ('Missing Referrer-Policy header','Missing Security Header','MEDIUM','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
31 | ('Missing Cookie Secure flag','Cookies without flags','LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
32 | ('Missing Cookie HttpOnly flag','Cookies without flags','LOW','3.1','CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
33 | ('Dangerous Server header','Dangerous header','LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
34 | ('Dangerous X-Powered-By header','Dangerous header','LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
35 | ('Potentially Dangerous Access-Control-Allow-Origin header','Potentially Dangerous Header','LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N'),
36 | ('Potentially Dangerous Access-Control-Allow-Credentials header','Potentially Dangerous Header','LOW','3.1','CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N')
37 | ]
38 |
39 |
40 | cvss_images={'3.1':'3,1.png','6.5':'6,5.png','0':'0.png','5.4':'5,4.png'}
41 |
42 | def get_issues():
43 | """ Generates output dictionary form output file generated by burp extension
44 | in the order Host > Issue > Issue details > URL"""
45 | dic = {'Host':{}}
46 | def fill_dic(title, variable):
47 | if title not in dic['Host'][host]['Issue'].keys():
48 | dic['Host'][host]['Issue'][issue] = {title:{variable:[url]}}
49 | else:
50 | if variable not in dic['Host'][host]['Issue'][issue][title].keys():
51 | dic['Host'][host]['Issue'][issue][title][variable] = []
52 | dic['Host'][host]['Issue'][issue][title][variable].append(url)
53 |
54 | f = open('../output/selected_output.txt','r')
55 | for line in f.readlines():
56 | issue = line.split(';')[0].split(': ')[1]
57 | host = line.split(';')[1].split(': ')[1]
58 | url = line.split(';')[2].split('- URL: ')[1].split('- Port')[0]
59 | port = line.split(';')[2].split('- Port: ')[1].rstrip('\n')
60 |
61 | host = host + ' [' + port + ']'
62 |
63 | if host not in dic['Host'].keys():
64 | dic['Host'][host] = {'Issue':{}}
65 |
66 | if issue == "Missing Security Header":
67 | """for missing securit headers use the structure of the dictionary:
68 | host -> issue -> missing header -> url"""
69 | missing_header = line.split(';')[2].split('Missing "')[1].split('" header')[0]
70 | fill_dic("Missing Security Header", missing_header)
71 |
72 | if issue == "Dangerous header":
73 | dangerous_header = line.split(';')[2].split('" header')[0].split('Detail: ')[1].lstrip('"')
74 | fill_dic("Dangerous header", dangerous_header)
75 |
76 | if issue == "Potentially Dangerous Header":
77 | potentially_dangerous_header = line.split(';')[2].split('" header')[0].split('Detail: ')[1].lstrip('"')
78 | fill_dic("Potentially Dangerous Header", potentially_dangerous_header)
79 |
80 | if issue == "Cookies without flags":
81 | missing_flag = line.split('Missing "')[1].split('" flag -')[0]
82 | fill_dic("Cookies without flags", missing_flag)
83 |
84 | f.close()
85 | return dic
86 |
87 | fill_dic = get_issues()
88 | doc = DocxTemplate("template.docx")
89 |
90 | def build_item(IP,host,port,vuln,cvss,urls):
91 | dic = {
92 | "Name":vuln[0],
93 | "Port":port,
94 | "Protocol":"TCP",
95 | "Description":descriptions[vuln[0]]["description"],
96 | "Severity":vuln[2],
97 | "Code":'',
98 | "Host":host,
99 | "IP":IP,
100 | "State":"OPEN",
101 | "Solution":descriptions[vuln[0]]["solution"],
102 | "CVSS":vuln[3],
103 | "CVSS_image":InlineImage(doc, cvss_images[vuln[3]], width=Mm(160)),
104 | "URLs":urls
105 | }
106 | return dic
107 |
108 | headers = []
109 | headers1 = []
110 | for host in fill_dic['Host'].keys():
111 | for issue in fill_dic['Host'][host]['Issue'].keys():
112 | for detail in fill_dic['Host'][host]['Issue'][issue][issue].keys():
113 | urls = fill_dic['Host'][host]['Issue'][issue][issue][detail]
114 | IP = '-'
115 | # loop to retrieve the appropriate description and solution of a certain issue type
116 | for vuln in vulns:
117 | if detail.lower() in vuln[0].lower():
118 | break
119 | cvss = vuln[3]
120 | if IP != '-':
121 | IP = ' - (' + IP + ')'
122 | else:
123 | IP = ''
124 | to_append = build_item(IP,host.split(' [')[0],host.split(' [')[1].split(']')[0],vuln,cvss,urls)
125 | headers.append(to_append)
126 | headers1.append({"headers":to_append})#, "urls":urls})
127 |
128 | '''las access-control-allow-origin mirar si las ponian en el informe anterior
129 | si no estaban. si no las ponen, quitarlas de aqui, solo habria que ponerla
130 | si esta mal configurada, no si no esta (en principio), puedo crearla pero poniendo
131 | un aviso de revisarla a mano'''
132 |
133 | context1 = {
134 | "headers" : headers1
135 | }
136 | doc.render(context1)
137 | # Add colors to severity cell
138 | colors = {"CRITICAL":"C857C9","HIGH":"FF0000","MEDIUM":"ffff00","LOW":"00B050"}
139 | for table in doc.tables:
140 | severity = table.cell(1,3).text
141 | shading_elm_1 = parse_xml(r''.format(nsdecls('w'),colors[severity]))
142 |
143 |
144 | table.rows[1].cells[3]._tc.get_or_add_tcPr().append(shading_elm_1)
145 |
146 | if len(sys.argv) != 2:
147 | filename = "output.docx"
148 | else:
149 | filename = sys.argv[1]
150 | try:
151 | os.remove(filename)
152 | except:
153 | pass
154 | doc.save(filename)
155 |
156 |
--------------------------------------------------------------------------------
/template/info.txt:
--------------------------------------------------------------------------------
1 | Missing HTTP-Strict-Transport-Security header ::: The HTTP protocol by itself is clear text, meaning that any data that is transmitted via HTTP can be captured and the contents viewed. To keep data private and prevent it from being intercepted, HTTP is often tunnelled through either Secure Sockets Layer (SSL) or Transport Layer Security (TLS). When either of these encryption standards are used, it is referred to as HTTPS.HTTP Strict Transport Security (HSTS) is an optional response header that can be configured on the server to instruct the browser to only communicate via HTTPS. This will be enforced by the browser even if the user requests a HTTP resource on the same server.Cyber-criminals will often attempt to compromise sensitive information passed from the client to the server using HTTP. This can be conducted via various Man-in-The-Middle (MiTM) attacks or through network packet captures.---Depending on the framework being used the implementation methods will vary, however it is advised that the `Strict-Transport-Security` header be configured on the server.One of the options for this header is `max-age`, which is a representation (in milliseconds) determining the time in which the client's browser will adhere to the header policy.Depending on the environment and the application this time period could be from as low as minutes to as long as days.
2 | Missing Cache-Control header ::: The HTTP 'Cache-Control' header is used to specify directives for caching mechanisms.The server did not return or returned an invalid 'Cache-Control' header which means page containing sensitive information (password, credit card, personal data, social security number, etc) could be stored on client side disk and then be exposed to unauthorised persons.---Configure your web server to include a 'Cache-Control' header with appropriate directives. If page contains sensitive information 'Cache-Control' value should be 'no-store' and 'Pragma' header value should be 'no-cache'.
3 | Missing X-Frame-Options header ::: Clickjacking (User Interface redress attack, UI redress attack, UI redressing) is a malicious technique of tricking a Web user into clicking on something different from what the user perceives they are clicking on, thus potentially revealing confidential information or taking control of their computer while clicking on seemingly innocuous web pages.The server didn't return an `X-Frame-Options` header which means that this website could be at risk of a clickjacking attack.The `X-Frame-Options` HTTP response header can be used to indicate whether or not a browser should be allowed to render a page inside a frame or iframe. Sites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites.---Configure your web server to include an `X-Frame-Options` header.
4 | Missing Content-Security-Policy header ::: Content Security Policy (CSP) is a web security standard that helps to mitigate attacks like cross-site scripting (XSS), clickjacking or mixed content issues. CSP provides mechanisms to websites to restrict content that browsers will be allowed to load.---Configure Content Security Policy on your website by adding 'Content-Security-Policy' HTTP header or meta tag http-equiv='Content-Security-Policy'.
5 | Missing X-Content-Type-Options header ::: The HTTP 'X-Content-Type-Options' response header prevents the browser from MIME-sniffing a response away from the declared content-type.The server did not return a correct 'X-Content-Type-Options' header, which means that this website could be at risk of a Cross-Site Scripting (XSS) attack.---Configure your web server to include an 'X-Content-Type-Options' header with a value of 'nosniff'.
6 | Missing X-XSS-Protection header ::: The HTTP 'X-XSS-Protection' response header is a feature of modern browsers that allows websites to control their XSS auditors.The server is not configured to return a 'X-XSS-Protection' header which means that any pages on this website could be at risk of a Cross-Site Scripting (XSS) attack. This URL is flagged as a specific example.If legacy browsers support is not needed, it is recommended to use Content-Security-Policy without allowing unsafe-inline scripts instead.---Configure your web server to include an 'X-XSS-Protection' header with a value of '1; mode=block' on all pages.:web
7 | Missing Referrer-Policy header ::: Referrer Policy provides mechanisms to websites to restrict referrer information (sent in the referer header) that browsers will be allowed to add.No Referrer Policy header or metatag configuration has been detected.---Configure Referrer Policy on your website by adding Referrer-Policy HTTP header or meta tag referrer in HTML.
8 | Missing Cookie Secure flag ::: When the Secure flag is set on a cookie, the browser will prevent it from being sent over a clear text channel (HTTP) and only allow it to be sent when an encrypted channel is used (HTTPS)It was detected that a cookie is being set without the Secure flag. Note that if the cookie does not contain sensitive information, the risk of this vulnerability is mitigated.---If the cookie contains sensitive information, then the server should ensure that the cookie has the `secure` flag set.
9 | Missing Cookie HttpOnly flag ::: The HttpOnly flag assists in the prevention of client side-scripts (such as JavaScript) from accessing and using the cookie.This can help prevent XSS attacks from targeting the cookies holding the client's session token (setting the HttpOnly flag does not prevent, nor safeguard against XSS vulnerabilities themselves).---The initial step to remedy this would be to determine whether any client-side scripts (such as JavaScript) need to access the cookie and if not, set the HttpOnly flag.It should be noted that some older browsers are not compatible with the HttpOnly flag; therefore, setting this flag will not protect those clients against this form of attack.
10 | Dangerous Server header ::: The Server header sent by the remote web server disclose information that can aid an attacker, such as the server type of version.---It is advised to remove the Server header of the web server to not disclose detailed information about the underlying web server.
11 | Dangerous X-Powered-By header ::: The X-Powered-By header sent by the remote web server disclose information that can aid an attacker, such as languages and/or frameworks or technologies used by the web server.---It is advised to remove the X-Powered-By headers of the web server to not disclose detailed information about the underlying technologies.
12 | Potentially Dangerous Access-Control-Allow-Origin header ::: The CORS policy allows the application to specify exceptions to the protections implemented by the browser, and enables the developer to specify allowlisted domains for which external JavaScript is permitted to execute and interact with the page.The 'Access-Control-Allow-Origin' header is insecure when set to '*' or null, as it allows any domain to perform cross-domain requests and read responses. An attacker could abuse this configuration to retrieve private content from an application which does not use standard authentication mechanisms (for example, an Intranet allowing access from the internal network only).---Unless the target application is specifically designed to serve public content to any domain, the 'Access-Control-Allow-Origin' should be configured with an allowlist including only known and trusted domains to perform cross-domain requests if needed, or should be disabled.
13 | Potentially Dangerous Access-Control-Allow-Credentials header ::: The default behaviour of cross-origin resource requests is for requests to be passed without credentials like cookies and the Authorization header. However, the cross-domain server can permit reading of the response when credentials are passed to it by setting the CORS Access-Control-Allow-Credentials header to true.If the value is set to truethen the browser will send credentials (cookies, authorization headers or TLS client certificates). A wrong Access-Control-Allow-Origin configuration can allow external sites to steal credentials.---Set the Access-Control-Allow-Credentials header to true only when required and after having validated that the Access-Control-Allow-Origin header is properly set.
--------------------------------------------------------------------------------
/request_headers.txt:
--------------------------------------------------------------------------------
1 | A-IM&&Acceptable instance-manipulations for the request.&&A-IM: feed&& &&
2 | Accept&&Media type(s) that is/are acceptable for the response. See Content negotiation.&&Accept: text/html&& &&
3 | Accept-Charset&&Character sets that are acceptable.&&Accept-Charset: utf-8&& &&
4 | Accept-Datetime&&Acceptable version in time.&&Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT&& &&
5 | Accept-Encoding&&List of acceptable encodings. See HTTP compression.&&Accept-Encoding: gzip, deflate&& &&
6 | Accept-Language&&List of acceptable human languages for response. See Content negotiation.&&Accept-Language: en-US&& &&
7 | Access-Control-Request-Method&& && && &&
8 | Access-Control-Request-Headers&&Initiates a request for cross-origin resource sharing with Origin (below).&&Access-Control-Request-Method: GET&& &&
9 | Authorization&&Authentication credentials for HTTP authentication.&&Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==&& &&
10 | Cache-Control&&Used to specify directives that must be obeyed by all caching mechanisms along the request-response chain.&&Cache-Control: no-cache&& &&
11 | Connection&&Control options for the current connection and list of hop-by-hop request fields. Must not be used with HTTP/2.&&Connection: keep-alive&& &&
12 | Content-Encoding&&The type of encoding used on the data. See HTTP compression.&&Content-Encoding: gzip&& &&
13 | Content-Length&&The length of the request body in octets (8-bit bytes).&&Content-Length: 348&& &&
14 | Content-MD5&&A Base64-encoded binary MD5 sum of the content of the request body.&&Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==&& &&
15 | Content-Type&&The Media type of the body of the request (used with POST and PUT requests).&&Content-Type: application/x-www-form-urlencoded&& &&
16 | Cookie&&An HTTP cookie previously sent by the server with Set-Cookie (below).&&Cookie: $Version=1; Skin=new;&& &&
17 | Date&&The date and time at which the message was originated (in "HTTP-date" format as defined by RFC 7231 Date/Time Formats).&&Date: Tue, 15 Nov 1994 08:12:31 GMT&& &&
18 | Expect&&Indicates that particular server behaviors are required by the client.&&Expect: 100-continue&& &&
19 | Forwarded&&Disclose original information of a client connecting to a web server through an HTTP proxy.&&Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43 Forwarded: for=192.0.2.43, for=198.51.100.17&& &&
20 | From&&The email address of the user making the request.&&From: user@example.com&& &&
21 | Host&&The domain name of the server (for virtual hosting), and the TCP port number on which the server is listening. The port number may be omitted if the port is the standard port for the service requested. Mandatory since HTTP/1.1. If the request is generated directly in HTTP/2, it should not be used.&&Host: en.wikipedia.org:8080&& &&
22 | HTTP2-Settings&&A request that upgrades from HTTP/1.1 to HTTP/2 MUST include exactly one HTTP2-Setting header field. The HTTP2-Settings header field is a connection-specific header field that includes parameters that govern the HTTP/2 connection, provided in anticipation of the server accepting the request to upgrade.&&HTTP2-Settings: token64&& &&
23 | If-Match&&Only perform the action if the client supplied entity matches the same entity on the server. This is mainly for methods like PUT to only update a resource if it has not been modified since the user last updated it.&&If-Match: "737060cd8c284d8af7ad3082f209582d"&& &&
24 | If-Modified-Since&&Allows a 304 Not Modified to be returned if content is unchanged.&&If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT&& &&
25 | If-None-Match&&Allows a 304 Not Modified to be returned if content is unchanged, see HTTP ETag.&&If-None-Match: "737060cd8c284d8af7ad3082f209582d"&& &&
26 | If-Range&&If the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity.&&If-Range: "737060cd8c284d8af7ad3082f209582d"&& &&
27 | If-Unmodified-Since&&Only send the response if the entity has not been modified since a specific time.&&If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT&& &&
28 | Max-Forwards&&Limit the number of times the message can be forwarded through proxies or gateways.&&Max-Forwards: 10&& &&
29 | Origin&&Initiates a request for cross-origin resource sharing (asks server for Access-Control-* response fields).&&Origin: http://www.example-social-network.com&& &&
30 | Pragma&&Implementation-specific fields that may have various effects anywhere along the request-response chain.&&Pragma: no-cache&& &&
31 | Prefer&&Allows client to request that certain behaviors be employed by a server while processing a request.&&Prefer: return=representation&& &&
32 | Proxy-Authorization&&Authorization credentials for connecting to a proxy.&&Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==&& &&
33 | Range&&Request only part of an entity. Bytes are numbered from 0. See Byte serving.&&Range: bytes=500-999&& &&
34 | TE&&The transfer encodings the user agent is willing to accept: the same values as for the response header field Transfer-Encoding can be used, plus the "trailers" value (related to the "chunked" transfer method) to notify the server it expects to receive additional fields in the trailer after the last, zero-sized, chunk.Only trailers is supported in HTTP/2.&&TE: trailers, deflate&& &&
35 | Trailer&&The Trailer general field value indicates that the given set of header fields is present in the trailer of a message encoded with chunked transfer coding.&&Trailer: Max-Forwards&& &&
36 | Transfer-Encoding&&The form of encoding used to safely transfer the entity to the user. Currently defined methods are: chunked, compress, deflate, gzip, identity. Must not be used with HTTP/2.&&Transfer-Encoding: chunked&& &&
37 | User-Agent&&The user agent string of the user agent.&&User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0&& &&
38 | Upgrade&&Ask the server to upgrade to another protocol. Must not be used in HTTP/2.&& Upgrade: h2c, HTTPS/1.3, IRC/6.9, RTA/x11, websocket&& &&
39 | Via&&Informs the server of proxies through which the request was sent.&&Via: 1.0 fred, 1.1 example.com (Apache/1.1)&& &&
40 | Warning&&A general warning about possible problems with the entity body.&&Warning: 199 Miscellaneous warning && &&
41 | Upgrade-Insecure-Requests&&Tells a server which (presumably in the middle of a HTTP -> HTTPS migration) hosts mixed content that the client would prefer redirection to HTTPS and can handle Content-Security-Policy: upgrade-insecure-requests&&Upgrade-Insecure-Requests: 1 && &&
42 | X-Requested-With&&Mainly used to identify Ajax requests (most JavaScript frameworks send this field with value of XMLHttpRequest); also identifies Android apps using WebView&&X-Requested-With: XMLHttpRequest && &&
43 | DNT&&Requests a web application to disable their tracking of a user. This is Mozilla's version of the X-Do-Not-Track header field (since Firefox 4.0 Beta 11). Safari and IE9 also have support for this field. &&DNT: 1 (Do Not Track Enabled), DNT: 0 (Do Not Track Disabled) && &&
44 | X-Forwarded-For&&A de facto standard for identifying the originating IP address of a client connecting to a web server through an HTTP proxy or load balancer. Superseded by Forwarded header.&&X-Forwarded-For: client1, proxy1, proxy2; X-Forwarded-For: 129.78.138.66, 129.78.64.103 && &&
45 | X-Forwarded-Host&&A de facto standard for identifying the original host requested by the client in the Host HTTP request header, since the host name and/or port of the reverse proxy (load balancer) may differ from the origin server handling the request. Superseded by Forwarded header.&&X-Forwarded-Host: en.wikipedia.org:8080; X-Forwarded-Host: en.wikipedia.org && &&
46 | X-Forwarded-Proto&&A de facto standard for identifying the originating protocol of an HTTP request, since a reverse proxy (or a load balancer) may communicate with a web server using HTTP even if the request to the reverse proxy is HTTPS. An alternative form of the header (X-ProxyUser-Ip) is used by Google clients talking to Google servers. Superseded by Forwarded header.&&X-Forwarded-Proto: https && &&
47 | Front-End-Https&&Non-standard header field used by Microsoft applications and load-balancers&&Front-End-Https: on && &&
48 | X-Http-Method-Override&&Requests a web application to override the method specified in the request (typically POST) with the method given in the header field (typically PUT or DELETE). This can be used when a user agent or firewall prevents PUT or DELETE methods from being sent directly.&&X-HTTP-Method-Override: DELETE && &&
49 | X-ATT-DeviceId&&Allows easier parsing of the MakeModel/Firmware that is usually found in the User-Agent String of AT&T Devices&&X-Att-Deviceid: GT-P7320/P7320XXLPG && &&
50 | X-Wap-Profile&&Links to an XML file on the Internet with a full description and details about the device currently connecting. In the example to the right is an XML file for an AT&T Samsung Galaxy S2.&&x-wap-profile: http://wap.samsungmobile.com/uaprof/SGH-I777.xml && &&
51 | Proxy-Connection&&Implemented as a misunderstanding of the HTTP specifications. Common because of mistakes in implementations of early HTTP versions. Has exactly the same functionality as standard Connection field.&&Proxy-Connection: keep-alive && &&
52 | X-UIDH&&Server-side deep packet insertion of a unique ID identifying customers of Verizon Wireless; also known as "perma-cookie" or "supercookie"&&X-UIDH: ... && &&
53 | X-Csrf-Token&&Used to prevent cross-site request forgery. Alternative header names are: X-CSRFToken and X-XSRF-TOKEN&&X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql && &&
54 | X-Request-ID&& && && &&
55 | X-Correlation-ID&&Correlates HTTP requests between a client and server.&&X-Request-ID: f058ebd6-02f7-4d3f-942e-904344e8cde5 && &&
--------------------------------------------------------------------------------
/response_headers.txt:
--------------------------------------------------------------------------------
1 | Accept-CH&&Requests HTTP Client Hints&&Accept-CH: UA, Platform&& &&
2 | Access-Control-Allow-Origin&&Specifies which web sites can participate in cross-origin resource sharing&& && &&
3 | Access-Control-Allow-Credentials&&Specifies which web sites can participate in cross-origin resource sharing&& && &&
4 | Access-Control-Expose-Headers&&Specifies which web sites can participate in cross-origin resource sharing&& && &&
5 | Access-Control-Max-Age&&Specifies which web sites can participate in cross-origin resource sharing&& && &&
6 | Access-Control-Allow-Methods&&Specifies which web sites can participate in cross-origin resource sharing&& && &&
7 | Access-Control-Allow-Headers&&Specifies which web sites can participate in cross-origin resource sharing&& && &&
8 | Accept-Patch&&Specifies which patch document formats this server supports&&Accept-Patch: text/example;charset=utf-8&& &&
9 | Accept-Ranges&&What partial content range types this server supports via byte serving&&Accept-Ranges: bytes&& &&
10 | Age&&The age the object has been in a proxy cache in seconds&&Age: 12&& &&
11 | Allow&&Valid methods for a specified resource. To be used for a 405 Method not allowed&&Allow: GET, HEAD&& &&
12 | Alt-Svc&&A server uses "Alt-Svc" header (meaning Alternative Services) to indicate that its resources can also be accessed at a different network location (host or port) or using a different protocol When using HTTP/2, servers should instead send an ALTSVC frame.&&Alt-Svc: http/1.1="http2.example.com:8001"; ma=7200&& &&
13 | Cache-Control&&Tells all caching mechanisms from server to client whether they may cache this object. It is measured in seconds&&Cache-Control: max-age=3600&& &&
14 | Connection&&Control options for the current connection and list of hop-by-hop response fields.&&Connection: close&& &&
15 | Content-Disposition&&An opportunity to raise a "File Download" dialogue box for a known MIME type with binary format or suggest a filename for dynamic content. Quotes are necessary with special characters.&&Content-Disposition: attachment; filename="fname.ext"&& &&
16 | Content-Encoding&&The type of encoding used on the data. See HTTP compression.&&Content-Encoding: gzip&& &&
17 | Content-Language&&The natural language or languages of the intended audience for the enclosed content&&Content-Language: da&& &&
18 | Content-Length&&The length of the response body in octets (8-bit bytes)&&Content-Length: 348&& &&
19 | Content-Location&&An alternate location for the returned data&&Content-Location: /index.htm&& &&
20 | Content-MD5&&A Base64-encoded binary MD5 sum of the content of the response&&Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==&& &&
21 | Content-Range&&Where in a full body message this partial message belongs&&Content-Range: bytes 21010-47021/47022&& &&
22 | Content-Type&&The MIME type of this content&&Content-Type: text/html; charset=utf-8&& &&
23 | Date&&The date and time that the message was sent (in "HTTP-date" format as defined by RFC 7231)&&Date: Tue, 15 Nov 1994 08:12:31 GMT&& &&
24 | Delta-Base&&Specifies the delta-encoding entity tag of the response.&&Delta-Base: "abc"&& &&
25 | ETag&&An identifier for a specific version of a resource, often a message digest&&ETag: "737060cd8c284d8af7ad3082f209582d"&& &&
26 | Expires&&Gives the date/time after which the response is considered stale (in "HTTP-date" format as defined by RFC 7231)&&Expires: Thu, 01 Dec 1994 16:00:00 GMT&& &&
27 | IM&&Instance-manipulations applied to the response.&&IM: feed&& &&
28 | Last-Modified&&The last modified date for the requested object (in "HTTP-date" format as defined by RFC 7231)&&Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT&& &&
29 | Link&&Used to express a typed relationship with another resource, where the relation type is defined by RFC 5988&&Link: ; rel="alternate"&& &&
30 | Location&&Used in redirection, or when a new resource has been created.&&Location: http://www.w3.org/pub/WWW/People.html&& &&
31 | P3P&&This field is supposed to set P3P policy, in the form of P3P:CP="your_compact_policy". However, P3P did not take off, most browsers have never fully implemented it, a lot of websites set this field with fake policy text, that was enough to fool browsers the existence of P3P policy and grant permissions for third party cookies.&&P3P: CP="This is not a P3P policy! See https://en.wikipedia.org/wiki/Special:CentralAutoLogin/P3P for more info."&& &&
32 | Pragma&&Implementation-specific fields that may have various effects anywhere along the request-response chain.&&Pragma: no-cache&& &&
33 | Preference-Applied&&Indicates which Prefer tokens were honored by the server and applied to the processing of the request.&&Preference-Applied: return=representation&& &&RFC 7240
34 | Proxy-Authenticate&&Request authentication to access the proxy.&&Proxy-Authenticate: Basic&& &&
35 | Public-Key-Pins&&HTTP Public Key Pinning, announces hash of website's authentic TLS certificate&&Public-Key-Pins: max-age=2592000; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";&& &&
36 | Retry-After&&If an entity is temporarily unavailable, this instructs the client to try again later. Value could be a specified period of time (in seconds) or a HTTP-date.&&Retry-After: 120&& &&
37 | Server&&A name for the server&&Server: Apache/2.4.1 (Unix)&& &&
38 | Set-Cookie&&An HTTP cookie&&Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1&& &&
39 | Strict-Transport-Security&&A HSTS Policy informing the HTTP client how long to cache the HTTPS only policy and whether this applies to subdomains.&&Strict-Transport-Security: max-age=16070400; includeSubDomains&& &&
40 | Trailer&&The Trailer general field value indicates that the given set of header fields is present in the trailer of a message encoded with chunked transfer coding.&&Trailer: Max-Forwards&& &&
41 | Transfer-Encoding&&The form of encoding used to safely transfer the entity to the user. Currently defined methods are: chunked, compress, deflate, gzip, identity.&&Transfer-Encoding: chunked&& &&
42 | Tk&&Tracking Status header, value suggested to be sent in response to a DNT(do-not-track), possible values: "!" — under construction, "?" — dynamic, "G" — gateway to multiple parties, "N" — not tracking, "T" — tracking, "C" — tracking with consent, "P" — tracking only if consented, "D" — disregarding DNT, "U" — updated&&Tk: ?Upgrade: Ask the client to upgrade to another protocol. Upgrade: h2c, HTTPS/1.3, IRC/6.9, RTA/x11, websocket&& &&
43 | Vary&&Tells downstream proxies how to match future request headers to decide whether the cached response can be used rather than requesting a fresh one from the origin server.&&Example Vary: *, Example 2: Vary: Accept-Language&& &&
44 | Via&&Informs the client of proxies through which the response was sent.&&Via: 1.0 fred, 1.1 example.com (Apache/1.1)&& &&
45 | Warning&&A general warning about possible problems with the entity body.&&Warning: 199 Miscellaneous warning&& &&
46 | WWW-Authenticate&&Indicates the authentication scheme that should be used to access the requested entity.&&WWW-Authenticate: Basic&& &&
47 | X-Frame-Options&&Clickjacking protection: deny - no rendering within a frame, sameorigin - no rendering if origin mismatch, allow-from - allow from specified location, allowall - non-standard, allow from any location&&X-Frame-Options: deny&& &&
48 | Content-Security-Policy&&Content Security Policy definition.&& && &&
49 | X-Content-Security-Policy&&Content Security Policy definition.&& && &&
50 | X-WebKit-CSP&&Content Security Policy definition.&&X-WebKit-CSP: default-src 'self'&& &&
51 | Expect-CT&&Notify to prefer to enforce Certificate Transparency.&&Expect-CT: max-age=604800, enforce, report-uri="https://example.example/report"&& &&
52 | NEL&&Used to configure network request logging.&&NEL: { "report_to": "name_of_reporting_group", "max_age": 12345, "include_subdomains": false, "success_fraction": 0.0, "failure_fraction": 1.0 }&& &&
53 | Permissions-Policy&&To allow or disable different features or APIs of the browser.&&Permissions-Policy: fullscreen=(), camera=(), microphone=(), geolocation=(), interest-cohort=()&& &&
54 | Refresh&&Used in redirection, or when a new resource has been created. This refresh redirects after 5 seconds. Header extension introduced by Netscape and supported by most web browsers. Defined by HTML Standard&&Refresh: 5; url=http://www.w3.org/pub/WWW/People.html&& &&
55 | Report-To&&Instructs the user agent to store reporting endpoints for an origin.&&Report-To: { "group": "csp-endpoint", "max_age": 10886400, "endpoints": [ { "url": "https-url-of-site-which-collects-reports" } ] }&& &&
56 | Status&&CGI header field specifying the status of the HTTP response. Normal HTTP responses use a separate "Status-Line" instead, defined by RFC 7230.&&Status: 200 OK&& &&
57 | Timing-Allow-Origin&&The Timing-Allow-Origin response header specifies origins that are allowed to see values of attributes retrieved via features of the Resource Timing API, which would otherwise be reported as zero due to cross-origin restrictions.&&Timing-Allow-Origin: *&& &&
58 | X-Content-Duration&&Provide the duration of the audio or video in seconds; only supported by Gecko browsers&&X-Content-Duration: 42.666&& &&
59 | X-Content-Type-Options&&The only defined value, "nosniff", prevents Internet Explorer from MIME-sniffing a response away from the declared content-type. This also applies to Google Chrome, when downloading extensions.&&X-Content-Type-Options: nosniff&& &&
60 | X-Powered-By&&Specifies the technology (e.g. ASP.NET, PHP, JBoss) supporting the web application (version details are often in X-Runtime, X-Version, or X-AspNet-Version)&&X-Powered-By: PHP/5.4.0&& &&
61 | X-Redirect-By&&Specifies the component that is responsible for a particular redirect.&&X-Redirect-By: WordPress&& &&
62 | X-Request-ID&&Correlates HTTP requests between a client and server.&& && &&
63 | X-Correlation-ID&&Correlates HTTP requests between a client and server.&&X-Request-ID: f058ebd6-02f7-4d3f-942e-904344e8cde5&& &&
64 | X-UA-Compatible&&Recommends the preferred rendering engine (often a backward-compatibility mode) to use to display the content. Also used to activate Chrome Frame in Internet Explorer. In HTML Standard, only the IE=edge value is defined.&&X-UA-Compatible: IE=edge&& &&
65 | X-XSS-Protection&&Cross-site scripting (XSS) filter&&X-XSS-Protection: 1; mode=block&& &&
66 | X-Azure-OriginStatusCode&&This header contains the HTTP status code returned by the backend&&X-Azure-OriginStatusCode: 503&& &&
67 | X-Azure-ExternalError&&This header shows the error code that Front Door servers come across while establishing connectivity to the backend server to process a request.&&X-Azure-ExternalError: 0x830c1011, The certificate authority is unfamiliar&& &&
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Headers Burp Extension
2 |
3 | ## Introduction
4 | Let me ask you two questions:
5 |
6 | 1- Are you a pentester or bug bounty hunter?
7 |
8 | 2- Do you hate reporting?
9 |
10 | If you answered yes to both (and I assume that if you answered "yes" to the first one, you necessarily answered "yes" to the second one), check out this extension. It removes the hassle of reporting missing security headers in your pentest reports. Here is what the extension does (the last part is the best, so read until the end!):
11 |
12 | - It gets all your Burp Proxy History, element by element.
13 | - It identifies all headers, both for the requests and the responses.
14 | - From all these URLs and Hosts you have visited, it retains only the unique ones, discarding duplicates.
15 | - It gives you a list of all the headers available, and if you click on them it will tell you at which Hosts and URLs you can find them.
16 | - It also identifies the meta headers and shows you their contents in an unified way, so you don't need to scrape websites or do other dirty and annoying tricks.
17 | - You can select headers you are interested in, or define new ones, from different categories (missing security headers, dangerous or potentially dangerous headers). A large preset of headers is included, but you can add your own if you miss any (feel free to send pull requests with headers you would like to include)
18 | - It gives you some nice formatting of headers information for every request, as well as color effects to let you know which requests contain more of the headers you are looking for.
19 | - You can output to text files the headers detected in CSV or JSON format. More options will be included in the future, if they make sense.
20 | - But, best of all, you can export to MS Word custom templates the results, so that you can autofill your own reports with a couple of mouse clicks, or generate a Word file from which you can just copy and paste to your own report individual tables, graphs, and use the included vulnerable headers or cookie flags definitions, or replace them with your own.
21 |
22 | No more hours wasted reporting missing security headers, server headers, etc etc etc :)
23 | A couple of clicks will do that for you!!!
24 |
25 | ## Disclaimer
26 | As far as I have used and tested this extension, both while developing it and for my personal and professional use, it works as it should, but there may be unexpected cases where it doesn't find some header, doesn't properly find unique endpoints or some other minor issues. In any case I don't think you will find many of these outliers, but complicated URLs could cause some problems. If you detect any, feel free to let me know (check the How to contribute section further down below). Also, I advise that you check manually the results provided by this extension, at least a couple of times to get used to how this extension works, and to make sure that you don't report to your client anything that is not actually there. By now I don't check it anymore for my own pentests because I know it works, but use it at your own risk!
27 |
28 |
29 |
30 |
31 | ## Requirements
32 | This extension is written in Python, therefore you will need the Jython .jar file set on your Burp Suite configuration. If you don't know how to do that, check my course on Burp Extensions creation (https://medium.com/system-weakness/burp-extensions-creation-1-7ddeb61efb33) or google it, it shouldn't be difficult :)
33 |
34 | Also, one of the main components of this extension requires that you install Python 3 on your system (on most linux distros should be included by default, on Windows use the installer from https://www.python.org/downloads/). If everything happens with no problems, this extension should detect your installation of Python and install the docxtpl library, to be able to create the .docx reports. In any case, you can install the library yourself with ``pip install docxtpl``. If the extension cannot find your Python binary path you can set it manually, read item 5 on the Instructions or, create an Issue if it doesn't work for you.
35 |
36 | In any case, the extension generates an auxiliary file that you can copy to another machine, and generate it there.
37 |
38 | ## Instructions
39 | 1- After you have captured some proxy traffic, switch to the Headers tab and click the Update button. That will fill the left panel with the headers that appear in the requests from your captured traffic, sorted by alphabetical order. If you change to the Response tab, in that same panel, you will see the sorted headers from the captured responses. In both cases, under each header you can see the hosts where that header is present.
40 |
41 | 
42 |
43 | 2- If there are many results, you can use the two filters at the top to filter hosts and endpoints. In the case of endpoints filter, you can use different words separated by a comma. The filter will show all results matching any of these words. Press enter after writing filters, or click again the Update button to filter results.
44 |
45 | 
46 |
47 |
48 | 3- On the left panel as well, you can click on the Meta tab, to get the ```` HTML tags for each host that has any.
49 |
50 | 
51 |
52 | 4- If you click in any row of the left panel, either for Requests, Responses or Meta, the central panel will fill with the URLs (endpoint) where those headers or meta tags can be found. The central panel has two tabs, "Unique endpoints" and "All endpoints". If you have browsed the same endpoints during your tests, you will have different requests to and responses from the same endpoint. Or you may have requests to an endpoint, with slight variations in parameters or routes in the URLs. That can be confusing, and seem bulkier than it really is. In the "All endpoints" tab you will see all requests where a certain header, in a certain Host, appears. In the "Unique endpoints" tab, some preprocessing is done, to show you only unique endpoints. Also, query string parameters and things like numbers in the URL have been grepped and replaced by a star symbol. This will remove duplicated URLs, and only show you unique endpoints, which is specially useful when your history has grown pretty big. Addittionaly, the Unique endpoints tab shows you the number of detected headers belonging to three categories: "Security headers", "Dangerous or verbose headers" and "Potentially dangerous headers". Along with the number, you will see a color which becomes brighter, the more headers you want to be taken into account in these categories. You can select these headers from the configuration window (click on the gear icon to open it, and don't forget to click on the "Apply changes" button when you are done!!!). The brightest color will be when all headers for that category are present.
53 |
54 | 5- Both for "Unique endpoints" and "All endpoints", when you click them (in the case of unique endpoints it will show the first occurrence), the panel on the right will show you a summary of the request and response headers for that endpoint, highlighting in orange and bold font the currently selected header on the left panel, in bold the Host header, for easier visibility, and color symbols for any detected Security header (green [+]), Dangerous or verbose header (red [-]), or potentially dangerous header (blue [?]). This summary also includes a list of missing security headers, compared to all the security headers you have chosen in the configuration window.
55 |
56 | 
57 |
58 | 6- Click on the blue Summary button on the lower right corner to open an additional window. This is where the magic happens, and you can export a Word report based on a custom template all the headers findings. First, click on the "Update Hosts" button on the upper left corner to populate the left panel with all hosts detected from the Proxy History. Then, check the checkboxes next to every host that you want to include in the report. Take into account that there will probably be a lot of undesired hosts in this list, from analytic sites and other third parties. Also, make sure that you select all subdomains or desired hosts. Next, make sure that the categories of vulnerability types that you desire to report are checked (by default, all): "Missing security headers", "Potentially dangerous headers", "dangerous or verbose headers" and "Cookies without flags". Lastly, click the "Update for selected hosts" button to populate the main panel with all the headers that will be reported. Again, uncheck all the checkboxes of the entries that you don't want to include in the report.
59 |
60 | I have noticed during my tests that on many occasions a host uses different headers for different endpoints. This makes it very easy to miss problematic headers when you test manually, but not anymore thanks to this extension. Change the value of "Depth" on the upper right corner in the Summary window to an integer value, to scan for different depths on the URL structure (for example, depth=1 means the endpoint "/x", depth=2 means "/.../x", depth=3 means "/.../.../x" and so on (where "x" would be the last portion of the endpoint), and depth=0 will give you the full URL). Of course, the lower the depth (which, by default is 1, the lowest possible) the less "granular" the results will be, and the fewer entries you will have. So if you want to report more headers, or "dig" deeper, increase the depth and click again on the "Update for selected hosts" button.
61 |
62 | Finally, click on the "Choose output file" on the lower left corner to choose the output .docx file to export the results, or write the full path directly on the textbox next to the button. And click on the ".docx report" button on the lower right corner to generate the report. To the left of this button there is a gear icon that you can click to open the configuration for generating Word reports. As mentioned earlier, you need to have Python3 installed on your computer (Jython is not enough, since the module to generate word reports doesn't work in Jython). If you install it using standard methods (the Python installer for Windows, or apt install python on linux) everything should work. However, you can provide the path to the Python executable on the upper textbox in this window. Then, click the button to make sure you are running the "docxtpl" Python library, which is used to create the reports. You can install it yourself, but this extension will install it for you (at least I didn't have problems installing it, but you never know...).
63 |
64 | 
65 |
66 | 
67 |
68 | WARNING!I have noticed that some strange urls, with a lot of symbols can cause problems. If the report is not appearing in the target path, uncheck the longest and weirdest looking URLs, to see if the report appears now. Then, decide if you can skip that URL in your report, or add it manually. It's hard to predict every type of URLs out there, so some cannot be easily parsed, sorry! As an example, this URL caused me problems when generating the report, so if you see something similar, please uncheck it from the Summary window and try again: ``/rum.js;rAraT1IazsN9CNL_S+tTtSCsOVg=``. In this case the problem was the semicolon between "js" and what comes next, that messed up some string splitting that the code does
69 |
70 | 6- If you use the example template, or modify it, don't forget to update the figure numbers for the evidences that you can paste later into the report. For that, you can select all text on Word (Ctrl-A, or some other key combination depending on your keyboard language) and then right click on the selection and click on "Update field" (for some keyboard languages, the F9 key also works).
71 |
72 | 
73 |
74 | NOTE: You don't need to create the report on the same machine where you have Burp installed. The extension generates the file called output/selected_output.txt file when you click the generate report button. You can copy this file over to another machine that has Python and Docxtpl, and call the script in /template/template.py. To do that, copy the selected_output.txt file to another machine where you have downloaded the repo, and place it in the output/ directory. Then go to the template directory and run: ``python template.py
75 |
76 | 7- Enjoy the many hours of manual work that you just saved yourself. Preferably, use these hours to go for a walk or do some exercise instead of gaming, so that the electricity that you save won't pollute the planet, I guess that typing in Word requires less electricity than games to run :)
77 |
78 | ## Word template and other customization
79 | 1- This extension recalls the configurations that you use, but make sure to "save changes" or "apply changes" in the different config windows, whenever you see such buttons. Doing so, next time you load the extension it will have the same settings you last used. There are also buttons to revert to factory settings.
80 |
81 | 2- You will probably want to use a custom template for the word reports. First, look for the provided template. It is in the /template/template.docx file. If you installed this extension via the Portswigger BApp store, you will find the directory of this extension under ``C:\Users\\AppData\Roaming\BurpSuite\bapps``. Then to find it (folders are given weird names, look for some file belonging to this extension, for example, "headers_window.py". Then look for the template).
82 |
83 | The template contains Jinja2 instructions to replace in each generated table the appropriate values. Notice that the provided template contains a for loop instruction surrounding the table. This means that a different table will be generated for every issue that will be reported. You can skip some of this information if you don't need it. or you can place it in other arrangements. Next is a description of the different fields you can use (include the "{ " before and " }}" after, they are Jinja2 syntax and are necessary.):
84 |
85 | - ``{{ host[‘headers’]["Name"] }}``: Vulnerability name
86 | - ``{{ host[‘headers’]["Host"] }}``: Host name
87 | - ``{{ host[‘headers’]["IP"] }}``: Host IP, in case it's available
88 | - ``{{ host[‘headers’]["Port"] }}``: Port where the endpoint is
89 | - ``{{ host[‘headers’]["Protocol"] }}``: Protocol
90 | - ``{{ host[‘headers’]["Severity"] }}``: Severity (Critical, High, Medium or Low)
91 | - ``{{ host[‘headers’]["Code"] }}``: In case you use some code to identify vulnerabilities. Not implemented right now.
92 | - ``{{ host[‘headers’][“CVSS”] }}``: CVSS value (from 0 to 10)
93 | - ``{{ host[‘headers’][“CVSS_image”] }}``: Any image that you want to include to graphically represent the CVSS score
94 | - ``{{ host[‘headers’][“Description”] }}``: Vulnerability description
95 | - ``{{ host[‘headers’][“Solution”] }}``: Remediation instructions for the vulnerability
96 | - in case you want to include a list of endpoints where the vulnerability is found
97 | ```
98 | {% for url in host[‘headers’][“URLs”] %}
99 | - {{ url }}
100 | {%- endfor %}```
101 |
102 |
103 | Feel free to modify the template.py file to include any new functionality that is currently not implemented. This script already contains CVSS scores and vectors for the different vulnerabilities, but keep in mind that they could vary for specific conditions. However I think they are a valid approximation in most cases.
104 |
105 | Also, the info.txt file, also in the template directory, contains the description and remediation for every vulnerability. Feel free to modify them, to adapt them to your needs or style of reporting. Every line is in the following format:
106 | `` ::: --- ``
107 | So if you add new, or modify the current ones, make sure they are in this format. Also, avoid empty lines, just in case.
108 |
109 | ## How to contribute
110 | If you enjoy this extension, please give a star to the repo: https://github.com/dh0ck/Headers
111 | I have dedicated many hours to create it, not just to save myself time in the long run, but especially to free pentesters around the world from something that I know we all hate, so that we can dedicate more time to actually doing what we like, and finding more vulns.
112 |
113 | If you want to contribute, feel free to add pull requests, or if you have ideas that you would like this extension to include, feel free to create an Issue on this repo, write to me on Telegram (@dh0ck) or via LinkedIn (https://www.linkedin.com/in/dh0ck). If I find your idea is feasible I will add it to my to-do list! Let's make this tool save us even more hours of stupid reporting!
114 |
115 | Thanks again for taking the time to try this extension, hope you find it useful ;)
116 |
--------------------------------------------------------------------------------
/headers_window.py:
--------------------------------------------------------------------------------
1 | from burp import IBurpExtender, ITab
2 | from burp import IContextMenuFactory
3 |
4 | import threading
5 | #import java
6 | import shutil, glob, re, sys, os, subprocess
7 | #from time import sleep
8 | from javax.swing import JFrame, JProgressBar, JSplitPane, JTable, JScrollPane, JPanel, BoxLayout, WindowConstants, JLabel, JMenuItem, JTabbedPane, JButton, JTextField, JTextArea, SwingConstants, JEditorPane, JComboBox, DefaultComboBoxModel, JFileChooser, ImageIcon, JCheckBox, JRadioButton, ButtonGroup, KeyStroke
9 | from javax.swing.table import DefaultTableModel, DefaultTableCellRenderer, TableCellRenderer
10 | from java.awt import BorderLayout, Dimension, FlowLayout, GridLayout, GridBagLayout, GridBagConstraints, Point, Component, Color # quitar los layout que no utilice
11 | from java.util import List, ArrayList
12 | from java.lang import Boolean, String, Integer
13 | #from java.awt.event.MouseEvent import getPoint
14 | from java.awt.event import MouseListener, FocusListener
15 |
16 |
17 | burp_extender_instance = "" # variable global que sera el instance de bupr extender, para acceder a los valores de la instancia de IBurpExtender que burp crea, pero desde fuera, sobre todo para cambiar con clicks la tabla de endpoints
18 | history1 = []
19 | host_endpoint = [] #se rellena al darle a filter en la tab, pero habra que arreglar que no haya duplicados cuando cambian los valores de los query string rameters (o puedo dejar que se repitan y ponerlos todos.) lo bueno seria poner tambien el index del history y en el text area poner los headers de la req los de la resp, separados por una =========, etc
20 | endpoint_table = []
21 | endpoint_table_meta = []
22 | selected_header_name = ""
23 |
24 |
25 | class RawHtmlRenderer(DefaultTableCellRenderer):
26 | def __init__(self):
27 | self.result = JLabel()
28 | self.DTCR = DefaultTableCellRenderer()
29 |
30 | def getTableCellRendererComponent(
31 | self,
32 | table, # JTable - table containing value
33 | value, # Object - value being rendered
34 | isSelected, # boolean - Is value selected?
35 | hasFocus, # boolean - Does this cell have focus?
36 | row, # int - Row # (0..N)
37 | col # int - Col # (0..N)
38 | ) :
39 | comp = self.DTCR.getTableCellRendererComponent(
40 | table, value, isSelected, hasFocus, row, col
41 | )
42 |
43 |
44 |
45 | result = self.result
46 |
47 | ############################################################################
48 | ###### something is not right, clicking on cells doesn't change selected background
49 | result.setBorder( comp.getBorder() )
50 | if (isSelected):
51 | result.setBackground(table.getSelectionBackground())
52 | #result.setBackground(Color.blue))
53 | result.setForeground(table.getSelectionForeground())
54 |
55 | else:
56 | result.setBackground(table.getBackground())
57 | result.setForeground(table.getForeground())
58 | ################################################################################
59 | result.setText(value)
60 | result.putClientProperty("html.disable", None)
61 |
62 | return result
63 |
64 | class ConfigTableModel(DefaultTableModel):
65 | def __init__(self, data, headings):
66 | DefaultTableModel.__init__(self, data, headings)
67 |
68 | def getColumnClass(self, col):
69 | return [Boolean, String][col]
70 |
71 | class IssueTableModel(DefaultTableModel):
72 | """Extends the DefaultTableModel to make it readonly."""
73 | def __init__(self, data, headings):
74 | # call the DefaultTableModel constructor to populate the table
75 | DefaultTableModel.__init__(self, data, headings)
76 |
77 | def isCellEditable(self, row, column):
78 | """Returns True if cells are editable."""
79 | canEdit = [False, False, False]
80 | return canEdit[column]
81 |
82 | class IssueTableMouseListener(MouseListener):
83 | """Some necessary entries that must be present on all mouse listeners, so this is a parent class that is inherited by the other specific mouse listener clases below."""
84 |
85 | def getClickedIndex(self, event):
86 | """Returns the value of the first column of the table row that was
87 | clicked. This is not the same as the row index because the table
88 | can be sorted."""
89 | # get the event source, the table in this case.
90 | tbl = event.getSource()
91 | # get the clicked row
92 | row = tbl.getSelectedRow()
93 | # get the first value of clicked row
94 | return tbl.getValueAt(row, 0)
95 | # return event.getSource.getValueAt(event.getSource().getSelectedRow(), 0)
96 |
97 | def getClickedRow(self, event):
98 | """Returns the complete clicked row."""
99 | tbl = event.getSource()
100 | return [tbl.getModel().getDataVector().elementAt(tbl.getSelectedRow()), tbl.getSelectedRow()]
101 |
102 | def mousePressed(self, event):
103 | pass
104 |
105 | def mouseReleased(self, event):
106 | pass
107 |
108 | # the following two are necessary, although they are empty, otherwise the extension crashes when the mouse cursor enters or exits the table
109 | def mouseEntered(self, event):
110 | pass
111 |
112 | def mouseExited(self, event):
113 | pass
114 |
115 | class IssueTableMouseListener_Window(IssueTableMouseListener):
116 | """Adds values to the extra information panel when an entry in the floating window is double clicked. The extra info panel is always there, double clicking elements of the floating window only makes it visible."""
117 | def mouseClicked(self, event):
118 | if event.getClickCount() == 1: # single click on table elements
119 | header = self.getClickedRow(event)[0]
120 |
121 | header = header[0].split(''.format(burp_extender_instance.color1))[1].split('')[0] #debe haber mas abajo al reves el orden de las closing tabs b y font
122 | burp_extender_instance.extra_info_textarea1.setText(header)
123 | if header in list(burp_extender_instance.dict_req_headers.keys()) and header not in list(burp_extender_instance.dict_resp_headers.keys()):
124 | burp_extender_instance.extra_info_textarea2.setText(burp_extender_instance.dict_req_headers[header][0])
125 | burp_extender_instance.extra_info_textarea3.setText(burp_extender_instance.dict_req_headers[header][1])
126 | burp_extender_instance.extra_info_textarea4.setText(burp_extender_instance.dict_req_headers[header][2])
127 | burp_extender_instance.extra_info_textarea5.setText(burp_extender_instance.dict_req_headers[header][3])
128 | if header not in (list(burp_extender_instance.dict_req_headers.keys())) and header not in list(burp_extender_instance.dict_resp_headers.keys()):
129 | burp_extender_instance.extra_info_textarea2.setText('Description unavailable for header: ' + header)
130 | burp_extender_instance.extra_info_textarea3.setText('Example unavailable for header: ' + header)
131 | burp_extender_instance.extra_info_textarea4.setText('URL unavailable for header: ' + header)
132 | burp_extender_instance.extra_info_textarea5.setText('Potential risks unavailable for header: ' + header)
133 | if header in list(burp_extender_instance.dict_resp_headers.keys()):
134 | burp_extender_instance.extra_info_textarea2.setText(burp_extender_instance.dict_resp_headers[header][0])
135 | burp_extender_instance.extra_info_textarea3.setText(burp_extender_instance.dict_resp_headers[header][1])
136 | burp_extender_instance.extra_info_textarea4.setText(burp_extender_instance.dict_resp_headers[header][2])
137 | burp_extender_instance.extra_info_textarea5.setText(burp_extender_instance.dict_resp_headers[header][3])
138 | if event.getClickCount() == 2: # double click to make extra info panel visible
139 | burp_extender_instance.extra_info.setVisible(True)
140 |
141 | class IssueTableMouseListener_Meta(IssueTableMouseListener):
142 | """Adds functionality to the Header-host table (to the tab) when its elements are clicked."""
143 | def mouseClicked(self, event):
144 | burp_extender_instance.is_meta = True
145 | if event.getClickCount() == 1:
146 | tbl = event.getSource()
147 | val = tbl.getModel().getDataVector().elementAt(tbl.getSelectedRow())
148 |
149 | identifier = val[0]
150 | clicked_host = val[1]
151 |
152 | k = tbl.getSelectedRow()
153 | if identifier == '':
154 | while identifier == '':
155 | k -= 1
156 | identifier = tbl.getModel().getDataVector().elementAt(k)[0]
157 |
158 | global endpoint_table_meta # igual no tiene que ser gobal
159 | endpoint_table_meta = []
160 |
161 | # meta_table tiene columnas: host | url | meta tag, una para cada tag, repitiendo host y url si hay mas de una tag en una url
162 | for (host, endpoint, meta) in burp_extender_instance.meta_table:
163 | #if identifier not in meta and clicked_host != host:
164 | if clicked_host == host:
165 | spl = endpoint.split(' ')
166 | line = spl[0] + " :: " + host + " :: " + " ".join(spl[1:])
167 | endpoint_table_meta.append([endpoint]) #poner el host antes de la url pero despues del method
168 | #endpoint_table_meta.append([line]) #poner el host antes de la url pero despues del method
169 |
170 | #ESTA COGIENDO ENPOINTS QUE NO CORRESPONDEN, VER SI ES QUE COINCIDEN CON UN HOST DIFERENTE. TAMBIEN TENGO QUE APLICAR REGEX EN LOS UNIQUE ENDPOINTS
171 |
172 | burp_extender_instance.selected_meta_header = identifier#header # este lo settea ok para la de endpoints
173 | burp_extender_instance.selected_host = clicked_host
174 | burp_extender_instance.update_meta_endpoints(endpoint_table_meta)
175 |
176 | class IssueTableMouseListener_Tab(IssueTableMouseListener):
177 | """Adds functionality to the Header-host table when its elements are clicked."""
178 | def mouseClicked(self, event):
179 | burp_extender_instance.is_meta = False
180 | if event.getClickCount() == 1:
181 |
182 | tbl = event.getSource()
183 | val = tbl.getModel().getDataVector().elementAt(tbl.getSelectedRow())
184 |
185 | header = val[0]
186 | clicked_host = val[1]
187 |
188 | k = tbl.getSelectedRow()
189 | if header == '':
190 | while header == '':
191 | k -= 1
192 | header = tbl.getModel().getDataVector().elementAt(k)[0]
193 | header_value = header.split(''.format(burp_extender_instance.color1))[1].split('')[0]
194 | #hasta aqui ok, header_value es el header que se ha marcado (primera columna), creo que este solo lo uso para subrayado en el textarea
195 |
196 | global host_endpoint
197 | global endpoint_table
198 | endpoint_table = []
199 | for (host, endpoint) in host_endpoint:
200 |
201 | if clicked_host == host:# and endpoint not in endpoint_table:
202 | endpoint_table.append([endpoint])
203 |
204 |
205 | ###global burp_extender_instance #variable global que representa la instancia de IBurpExtender que se crea al cargar la extension. se usa para acceder desde fuera (especialmente desde el mouse event handler para actualizar la endpoint_table) a propiedades y metodos de la instancia "principal" de la extension. el valor se lo doy dentro de la intancia, igualando esta variable a self
206 | global selected_header_name
207 | selected_header_name = header_value
208 |
209 | burp_extender_instance.selected_host = clicked_host
210 | burp_extender_instance.selected_header = header_value#header # este lo settea ok para la de endpoints
211 | burp_extender_instance.endpoint_table1 = endpoint_table
212 | burp_extender_instance.update_endpoints(endpoint_table)
213 |
214 | class IssueTableMouseListener_Endpoints(IssueTableMouseListener):
215 | """Adds functionality to the click actions on rows of the "Unique endpoints" and "All endpoints" tables."""
216 | def extra_symbol(self, head):
217 |
218 | if head.split(": ")[0].lower() in self.security_headers:
219 | extra_symbol = ' [ + ] '
220 | elif head.split(": ")[0].lower() in self.dangerous_headers:
221 | extra_symbol = ' [ X ] '
222 | elif head.split(": ")[0].lower() in self.potentially_dangerous_headers:
223 | extra_symbol = ' [ ? ] '
224 | else:
225 | extra_symbol = ""
226 | return extra_symbol
227 |
228 |
229 | def mouseClicked(self, event):
230 | if event.getClickCount() == 1:
231 | tbl = event.getSource()
232 |
233 | burp_extender_instance.clicked_endpoint(tbl, True)
234 |
235 | class summary_unique_mouse_listener(IssueTableMouseListener):
236 | def mouseClicked(self, event):
237 | print('summary table clicked')
238 |
239 | class summary_all_mouse_listener(IssueTableMouseListener):
240 | def mouseClicked(self, event):
241 | print('summary all clicked, go to history and show request/response')
242 |
243 | class SummaryTableModel_left(DefaultTableModel):
244 | def __init__(self, data, headings):
245 | DefaultTableModel.__init__(self, data, headings)
246 |
247 | def getColumnClass(self, col):
248 | # columnas: add to report?, Host
249 | return [Boolean, String][col]
250 |
251 | def isCellEditable(self, row, column):
252 | """Returns True if cells are editable."""
253 | canEdit = [True, False]
254 | return canEdit[column]
255 |
256 | class SummaryTableModel_right(DefaultTableModel):
257 | def __init__(self, data, headings):
258 | DefaultTableModel.__init__(self, data, headings)
259 |
260 | def getColumnClass(self, col):
261 | # columnas: history index, add to report?, issue type, host, unique endpoint
262 | # issue types:
263 | # - missing security headers
264 | # - dangerous
265 | # - potentially dangerous
266 | # - http verbs
267 | # - cookies without flags
268 |
269 | return [Boolean, String, String, String][col]
270 | #return [Integer, Boolean, String, String, String][col]
271 |
272 | def isCellEditable(self, row, column):
273 | """Returns True if cells are editable."""
274 | canEdit = [True, False, False, False, False]
275 | return canEdit[column]
276 |
277 | class IssueTable(JTable):
278 | """Table class for the tables used in the extension. Needed to give the capacity to tables to perform actions when their rows are clicked."""
279 | def __init__(self, model, table_type):
280 | self.setModel(model)
281 | self.getTableHeader().setReorderingAllowed(False)
282 | if table_type == "tab":
283 | self.addMouseListener(IssueTableMouseListener_Tab())
284 | elif table_type == "meta":
285 | self.addMouseListener(IssueTableMouseListener_Meta())
286 | elif table_type == "window":
287 | self.addMouseListener(IssueTableMouseListener_Window())
288 | elif table_type == "endpoints":
289 | self.addMouseListener(IssueTableMouseListener_Endpoints())
290 | elif table_type == "config_headers":
291 | pass
292 | elif table_type == "summary_unique_endpoints":
293 | self.addMouseListener(summary_unique_mouse_listener())
294 | elif table_type == "summary_all_endpoints":
295 | self.addMouseListener(summary_all_mouse_listener())
296 |
297 | '''def getTableCellRendererComponent(
298 | self,
299 | table, # JTable - table containing value
300 | value, # Object - value being rendered
301 | isSelected, # boolean - Is value selected?
302 | hasFocus, # boolean - Does this cell have focus?
303 | row, # int - Row # (0..N)
304 | col # int - Col # (0..N)
305 | ) :
306 | comp = self.DTCR.getTableCellRendererComponent(
307 | table, value, isSelected, hasFocus, row, col
308 | )
309 | result = self.result
310 | result.setText(value)
311 | result.putClientProperty("html.disable", None)
312 |
313 | return result'''
314 |
315 |
316 |
317 |
318 |
319 | class BurpExtender(IBurpExtender, IContextMenuFactory, ITab):
320 | """Main class of the Headers extension, instantiated by Burp."""
321 |
322 | def apply_config(self):
323 | """Read the configuration file and load the configurations. It is run when the extension loads."""
324 | print("Applying config...")
325 | f = open("config.txt","r")
326 | for line in f.readlines():
327 | feature = line.split(' -- ')[0]
328 | value = line.split(' -- ')[1].strip('\n')
329 | if feature == "last_save_type":
330 | self.save_format.setSelectedItem(value)
331 | self.config_dict[feature] = value
332 | elif feature == "last_output_file":
333 | self.save_path.setText(value)
334 | self.config_dict[feature] = value
335 | elif feature == "last_filter_type":
336 | self.preset_filters.setSelectedItem(value)
337 | self.config_dict[feature] = value
338 | elif feature == "UI_theme":
339 | self.UI_theme = value
340 | self.config_dict[feature] = value
341 | f.close()
342 | f = open('security_headers.txt','r')
343 | self.total_security_headers = len(f.readlines())
344 | f.close()
345 | f = open('potentially_dangerous_headers.txt','r')
346 | self.total_potential_headers = len(f.readlines())
347 | f.close()
348 | f = open('dangerous_headers.txt','r')
349 | self.total_dangerous_headers = len(f.readlines())
350 | f.close()
351 | f = open('cookie_flags.txt','r')
352 | self.total_cookie_flags = len(f.readlines())
353 | f.close()
354 |
355 | # UI colors
356 | if self.UI_theme == "dark":
357 | f = open('UI_theme_dark.txt', 'r')
358 | elif self.UI_theme == "light":
359 | f = open('UI_theme_light.txt', 'r')
360 | for k, line in enumerate(f.readlines()):
361 | if k == 0:
362 | self.color1 = line.strip('\n') # for titles and dashed lines
363 | elif k == 1:
364 | self.color2 = line.strip('\n') # for security headers
365 | elif k == 2:
366 | self.color3 = line.strip('\n') # for potentially dangerous headers
367 | elif k == 3:
368 | self.color4 = line.strip('\n') # for dangerous or verbose headers
369 | elif k == 4:
370 | self.color5 = line.strip('\n') # for the rest of headers
371 | elif k == 5:
372 | self.color6 = line.strip('\n') # for the wildcards in the unique endpoints URL
373 | f.close()
374 |
375 | def update_config(self):#, feature, value):
376 | """Update the configuration file with values supplied in the configuration panel of the extension"""
377 | f = open("config.txt", "w")
378 | for key in list(self.config_dict.keys()):
379 | f.write(key + " -- " + self.config_dict[key] + "\n") #comprobar
380 | f.close()
381 |
382 | def read_headers(self):
383 | """ Read the values currently checked in the advanced config tables and use them in the future """
384 |
385 | self.table_config_security = []
386 | self.table_config_dangerous = []
387 | self.table_config_potentially_dangerous = []
388 | self.table_config_cookie_flags = []
389 |
390 |
391 | for i in range(self.initial_count_security_headers):
392 | if self.model_tab_config_security.getValueAt(i,0):
393 | self.table_config_security.append([True, self.model_tab_config_security.getValueAt(i,1)])
394 |
395 | for i in range(self.initial_count_dangerous_headers):
396 | if self.model_tab_config_dangerous.getValueAt(i,0):
397 | self.table_config_dangerous.append([True, self.model_tab_config_dangerous.getValueAt(i,1)])
398 |
399 | for i in range(self.initial_count_potentially_dangerous_headers):
400 | if self.model_tab_config_potentially_dangerous.getValueAt(i,0):
401 | self.table_config_potentially_dangerous.append([True, self.model_tab_config_potentially_dangerous.getValueAt(i,1)])
402 |
403 | for i in range(self.initial_count_cookie_flags):
404 | if self.model_tab_config_cookie_flags.getValueAt(i,0):
405 | self.table_config_cookie_flags.append([True, self.model_tab_config_cookie_flags.getValueAt(i,1)])
406 |
407 | self.dangerous_headers = []
408 | self.security_headers = []
409 | self.potentially_dangerous_headers = []
410 | self.cookie_flags = []
411 |
412 | for line in self.table_config_security:
413 | self.security_headers.append(line[1].strip('\n').lower())
414 |
415 | for line in self.table_config_dangerous:
416 | self.dangerous_headers.append(line[1].strip('\n'))
417 |
418 | for line in self.table_config_potentially_dangerous:
419 | self.potentially_dangerous_headers.append(line[1].strip('\n'))
420 |
421 | for line in self.table_config_cookie_flags:
422 | self.cookie_flags.append(line[1].strip('\n'))
423 |
424 | def make_chosen_headers_permanent(self, event):
425 | self.security_headers = []
426 | f = open("security_headers.txt","w")
427 | for i in range(self.initial_count_security_headers):
428 | #print(str(i) + '/' + str(self.initial_count_security_headers))
429 | if i < self.initial_count_security_headers - 1:
430 | if self.model_tab_config_security.getValueAt(i,0):
431 | f.write("1 " + self.model_tab_config_security.getValueAt(i,1) + '\n')
432 | self.security_headers.append(self.model_tab_config_security.getValueAt(i,1))
433 | else:
434 | f.write("0 " + self.model_tab_config_security.getValueAt(i,1) + '\n')
435 | else:
436 | if self.model_tab_config_security.getValueAt(i,0):
437 | f.write("1 " + self.model_tab_config_security.getValueAt(i,1))
438 | self.security_headers.append(self.model_tab_config_security.getValueAt(i,1))
439 | else:
440 | f.write("0 " + self.model_tab_config_security.getValueAt(i,1))
441 |
442 | f.close()
443 |
444 | f = open("dangerous_headers.txt","w")
445 | self.dangerous_headers = []
446 | for i in range(self.initial_count_dangerous_headers):
447 | if i < self.initial_count_dangerous_headers - 1:
448 | if self.model_tab_config_dangerous.getValueAt(i,0):
449 | f.write("1 " + self.model_tab_config_dangerous.getValueAt(i,1) + '\n')
450 | self.dangerous_headers.append(self.model_tab_config_dangerous.getValueAt(i,1))
451 | else:
452 | f.write("0 " + self.model_tab_config_dangerous.getValueAt(i,1) + '\n')
453 | else:
454 | if self.model_tab_config_dangerous.getValueAt(i,0):
455 | f.write("1 " + self.model_tab_config_dangerous.getValueAt(i,1))
456 | self.dangerous_headers.append(self.model_tab_config_dangerous.getValueAt(i,1))
457 | else:
458 | f.write("0 " + self.model_tab_config_dangerous.getValueAt(i,1))
459 |
460 | f.close()
461 |
462 | f = open("potentially_dangerous_headers.txt","w")
463 | self.potentially_dangerous_headers = []
464 | for i in range(self.initial_count_potentially_dangerous_headers):
465 | if i < self.initial_count_potentially_dangerous_headers - 1:
466 | if self.model_tab_config_potentially_dangerous.getValueAt(i,0):
467 | f.write("1 " + self.model_tab_config_potentially_dangerous.getValueAt(i,1) + '\n')
468 | self.potentially_dangerous_headers.append(self.model_tab_config_potentially_dangerous.getValueAt(i,1))
469 | else:
470 | f.write("0 " + self.model_tab_config_potentially_dangerous.getValueAt(i,1) + '\n')
471 | else:
472 | if self.model_tab_config_potentially_dangerous.getValueAt(i,0):
473 | f.write("1 " + self.model_tab_config_potentially_dangerous.getValueAt(i,1))
474 | self.potentially_dangerous_headers.append(self.model_tab_config_potentially_dangerous.getValueAt(i,1))
475 | else:
476 | f.write("0 " + self.model_tab_config_potentially_dangerous.getValueAt(i,1))
477 | f.close()
478 |
479 | f = open("cookie_flags.txt","w")
480 | self.cookie_flags = []
481 | for i in range(self.initial_count_cookie_flags):
482 | if i < self.initial_count_cookie_flags - 1:
483 | if self.model_tab_config_cookie_flags.getValueAt(i,0):
484 | f.write("1 " + self.model_tab_config_cookie_flags.getValueAt(i,1) + '\n')
485 | self.cookie_flags.append(self.model_tab_config_cookie_flags.getValueAt(i,1))
486 | else:
487 | f.write("0 " + self.model_tab_config_cookie_flags.getValueAt(i,1) + '\n')
488 | else:
489 | if self.model_tab_config_cookie_flags.getValueAt(i,0):
490 | f.write("1 " + self.model_tab_config_cookie_flags.getValueAt(i,1))
491 | self.cookie_flags.append(self.model_tab_config_cookie_flags.getValueAt(i,1))
492 | else:
493 | f.write("0 " + self.model_tab_config_cookie_flags.getValueAt(i,1))
494 | f.close()
495 |
496 | def create_extra_info_window(self):
497 | # el extra info window lo defino aqui fuera para que exista desde un principio y al hacer doble click en las tablas solamente se haga visible, pero no se ee un nuevo frame por cada doble click
498 | self.extra_info = JFrame("Extended header info")
499 | self.extra_info_panel = JPanel()
500 | self.extra_info_panel.setLayout(BoxLayout(self.extra_info_panel, BoxLayout.Y_AXIS ) )
501 | self.extra_info.setSize(400, 350)
502 | self.extra_info.setLocation(840, 0)
503 | self.extra_info.toFront()
504 | self.extra_info.setAlwaysOnTop(True)
505 |
506 | self.extra_info_label1 = JLabel("Header Name:")
507 | self.extra_info_label1.putClientProperty("html.disable", None)
508 | #extra_info_label1 = JLabel("Header Name:".format(self.color1))
509 | self.extra_info_label1.setAlignmentX(JLabel.LEFT_ALIGNMENT)
510 | self.extra_info_textarea1 = JTextArea("Header Name", rows=1, editable=False)
511 | self.extra_info_textarea1.setLineWrap(True)
512 | self.scrollPane_1 = JScrollPane(self.extra_info_textarea1)
513 | self.scrollPane_1.setAlignmentX(JScrollPane.LEFT_ALIGNMENT)
514 |
515 | self.extra_info_label2 = JLabel("Header Description:")
516 | self.extra_info_label2.putClientProperty("html.disable", None)
517 | #extra_info_label2 = JLabel("Header Description:".format(self.color1))
518 | self.extra_info_label2.setAlignmentX(JLabel.LEFT_ALIGNMENT)
519 | self.extra_info_textarea2 = JTextArea("Description",rows=5, editable=False)
520 | self.extra_info_textarea2.setLineWrap(True)
521 | self.scrollPane_2 = JScrollPane(self.extra_info_textarea2)
522 | self.scrollPane_2.setAlignmentX(JScrollPane.LEFT_ALIGNMENT)
523 |
524 | self.extra_info_label3 = JLabel("Usage example:")
525 | self.extra_info_label3.putClientProperty("html.disable", None)
526 | #extra_info_label3 = JLabel("Usage example:".format(self.color1))
527 | self.extra_info_label3.setAlignmentX(JLabel.LEFT_ALIGNMENT)
528 | self.extra_info_textarea3 = JTextArea("Example",rows=3, editable=False)
529 | self.extra_info_textarea3.setLineWrap(True)
530 | self.scrollPane_3 = JScrollPane(self.extra_info_textarea3)
531 | self.scrollPane_3.setAlignmentX(JScrollPane.LEFT_ALIGNMENT)
532 |
533 | self.extra_info_label4 = JLabel("URL describing header:")
534 | self.extra_info_label4.putClientProperty("html.disable", None)
535 | #extra_info_label4 = JLabel("URL describing header:".format(self.color1))
536 | self.extra_info_label4.setAlignmentX(JLabel.LEFT_ALIGNMENT)
537 | self.extra_info_textarea4 = JTextArea("URL2",rows=2, editable=False)
538 | self.extra_info_textarea4.setLineWrap(True)
539 | self.scrollPane_4 = JScrollPane(self.extra_info_textarea4)
540 | self.scrollPane_4.setAlignmentX(JScrollPane.LEFT_ALIGNMENT)
541 |
542 | self.extra_info_label5 = JLabel("Potential risks associated with header:")
543 | self.extra_info_label5.putClientProperty("html.disable", None)
544 | #extra_info_label5 = JLabel("Potential risks associated with header:".format(self.color1))
545 | self.extra_info_label5.setAlignmentX(JLabel.LEFT_ALIGNMENT)
546 | self.extra_info_textarea5 = JTextArea("There are no potential risks associated with this header",rows=3, editable=False)
547 | self.extra_info_textarea5.setLineWrap(True)
548 | self.scrollPane_5 = JScrollPane(self.extra_info_textarea5)
549 | self.scrollPane_5.setAlignmentX(JScrollPane.LEFT_ALIGNMENT)
550 |
551 | for element in [self.extra_info_label1, self.scrollPane_1, self.extra_info_label2, self.scrollPane_2, self.extra_info_label3, self.scrollPane_3, self.extra_info_label4, self.scrollPane_4, self.extra_info_label5, self.scrollPane_5]:
552 | self.extra_info_panel.add(element)
553 |
554 | self.extra_info.add(self.extra_info_panel)
555 | self.dict_req_headers = {}
556 | self.req_headers_description = open('request_headers.txt','r')
557 | for line in self.req_headers_description.readlines():
558 | line_split = line.split('&&')
559 | header_name = line_split[0]
560 |
561 | header_description = line_split[1]
562 | if header_description.rstrip() == '':
563 | header_description = 'Description unavailable for header: ' + header_name
564 |
565 | header_example = line_split[2]
566 | if header_example.rstrip() == '':
567 | header_example = 'Example unavailable for header: ' + header_name
568 |
569 | header_url = line_split[3]
570 | if header_url.rstrip() == '':
571 | header_url = 'URL unavailable for header ' + header_name
572 |
573 | header_risk = line_split[4]
574 | if header_risk.rstrip() == '':
575 | header_risk = 'Potential risks information unavailable for header ' + header_name
576 | self.dict_req_headers[header_name] = (header_description, header_example, header_url, header_risk)
577 | self.req_headers_description.close()
578 |
579 | self.dict_resp_headers = {}
580 | self.resp_headers_description = open('response_headers.txt','r')
581 | for line in self.resp_headers_description.readlines():
582 | line_split = line.split('&&')
583 | header_name = line_split[0]
584 |
585 | header_description = line_split[1]
586 | if header_description.rstrip() == '':
587 | header_description = 'Description unavailable for header: ' + header_name
588 |
589 | header_example = line_split[2]
590 | if header_example.rstrip() == '':
591 | header_example = 'Example unavailable for header: ' + header_name
592 |
593 | header_url = line_split[3]
594 | if header_url.rstrip() == '':
595 | header_url = 'URL unavailable for header ' + header_name
596 |
597 | header_risk = line_split[4]
598 | if header_risk.rstrip() == '':
599 | header_risk = 'Potential risks information unavailable for header ' + header_name
600 | self.dict_resp_headers[header_name] = (header_description, header_example, header_url, header_risk)
601 | self.resp_headers_description.close()
602 |
603 | def compile_regex(self):
604 | """Compile regular expressions that will be used later by the extension to match URL parameters"""
605 | #matchea lo que haya entre = y & o entre = y ' ', para el ultimo parametro de la linea
606 | self.query_params = re.compile('=.*?&|=.*? ')
607 |
608 | # matchea numeros en la url tipo /asdf/1234/qwe/1234, matchearia los dos 1234 y secuencias de letras, numeros y guiones o puntos. igual algun caso raro se cuela, pero por lo que he visto pilla todo
609 | self.number_between_forwardslash = re.compile('\/[a-zA-Z]*\d+[a-zA-Z0-9-_\.]*')
610 |
611 | # match de headers
612 | self.meta = re.compile('')
613 |
614 | def find_host(self, req_headers):
615 | """Given a request-response object, find the Host to which it was requested"""
616 | for req_head in req_headers[1:]:
617 | if 'Host: ' in req_head:
618 | host = req_head.split(': ')[1]
619 | break
620 | return host
621 |
622 | def restore_save_thresholds_func(self, event):
623 | f = open('thresholds.txt', 'r')
624 | thresholds = f.readlines()
625 | f.close()
626 |
627 | check_box_security_value = thresholds[0].split(' ')[0]
628 | check_box_potentially_dangerous_value = thresholds[1].split(' ')[0]
629 | check_box_dangerous_value = thresholds[2].split(' ')[0]
630 |
631 | threshold_security_value = thresholds[0].split(' ')[1]
632 | threshold_potentially_dangerous_value = thresholds[1].split(' ')[1]
633 | threshold_dangerous_value = thresholds[2].split(' ')[1]
634 |
635 | security_checkbox_state = True if check_box_security_value == '1' else False
636 | potentially_dangerous_checkbox_state = True if check_box_potentially_dangerous_value == '1' else False
637 | dangerous_checkbox_state = True if check_box_dangerous_value == '1' else False
638 |
639 | self.check_box_security.setSelected(security_checkbox_state)
640 | self.check_box_potentially_dangerous.setSelected(potentially_dangerous_checkbox_state)
641 | self.check_box_dangerous.setSelected(dangerous_checkbox_state)
642 |
643 | self.threshold_count_security.setText(threshold_security_value)
644 | self.threshold_count_potentially_dangerous.setText(threshold_potentially_dangerous_value)
645 | self.threshold_count_dangerous.setText(threshold_dangerous_value)
646 |
647 | def save_threshold_config_func(self, event):
648 | f = open('thresholds.txt', 'w')
649 | selected_security = '1' if self.check_box_security.isSelected() == True else '0'
650 | selected_potentially_dangerous = '1' if self.check_box_potentially_dangerous.isSelected() == True else '0'
651 | selected_dangerous = '1' if self.check_box_dangerous.isSelected() == True else '0'
652 |
653 | f.write(selected_security + ' ' + self.threshold_count_security.getText() + '\n')
654 | f.write(selected_potentially_dangerous + ' ' + self.threshold_count_potentially_dangerous.getText() + '\n')
655 | f.write(selected_dangerous + ' ' + self.threshold_count_dangerous.getText())
656 |
657 | f.close()
658 | return
659 |
660 | def reset_threshold_config_func(self, event):
661 | self.threshold_count_security.setText(str(self.initial_count_security_headers))
662 | self.threshold_count_potentially_dangerous.setText("{}".format(self.initial_count_potentially_dangerous_headers))
663 | self.threshold_count_dangerous.setText("{}".format(self.initial_count_dangerous_headers))
664 |
665 | self.check_box_security.setSelected(False)
666 | self.check_box_dangerous.setSelected(False)
667 | self.check_box_potentially_dangerous.setSelected(False)
668 | return
669 |
670 | def create_advanced_config_frame(self):
671 | self.advanced_config_panel = JFrame("Advanced configuration")
672 | self.advanced_config_panel.setLayout(BorderLayout())
673 | self.advanced_config_panel.toFront()
674 | self.advanced_config_panel.setAlwaysOnTop(True)
675 | self.advanced_config_panel.setSize(800, 600)
676 | self.advanced_config_panel.setLocationRelativeTo(None)
677 |
678 | # --------------------- Theme selection ------------------------#
679 | self.theme_model = DefaultComboBoxModel()
680 | self.theme_model.addElement("Dark")
681 | self.theme_model.addElement("Light")
682 | theme_selector = JComboBox(self.theme_model)
683 | # ----------------------------------------------------------------#
684 |
685 |
686 |
687 | # --------------------- Headers selection ------------------------#
688 | self.table_config_security = []
689 | self.table_config_potentially_dangerous = []
690 | self.table_config_dangerous = []
691 | self.table_config_cookie_flags = []
692 |
693 | f = open('security_headers.txt','r')
694 | for line in f.readlines():
695 | active = line.split(' ')[0]
696 | if active == '1':
697 | self.table_config_security.append([True, line.split(' ')[1].strip('\n')])
698 | else:
699 | self.table_config_security.append([False, line.split(' ')[1].strip('\n')])
700 | f.close()
701 |
702 | f = open('potentially_dangerous_headers.txt','r')
703 | for line in f.readlines():
704 | active = line.split(' ')[0]
705 | if active == '1':
706 | self.table_config_potentially_dangerous.append([True, line.split(' ')[1].strip('\n')])
707 | else:
708 | self.table_config_potentially_dangerous.append([False, line.split(' ')[1].strip('\n')])
709 | f.close()
710 |
711 | f = open('dangerous_headers.txt','r')
712 | for line in f.readlines():
713 | active = line.split(' ')[0]
714 | if active == '1':
715 | self.table_config_dangerous.append([True, line.split(' ')[1].strip('\n')])
716 | else:
717 | self.table_config_dangerous.append([False, line.split(' ')[1].strip('\n')])
718 | f.close()
719 |
720 | f = open('cookie_flags.txt','r')
721 | for line in f.readlines():
722 | active = line.split(' ')[0]
723 | if active == '1':
724 | self.table_config_cookie_flags.append([True, line.split(' ')[1].strip('\n')])
725 | else:
726 | self.table_config_cookie_flags.append([False, line.split(' ')[1].strip('\n')])
727 | f.close()
728 |
729 | self.config_column_names = ("Use?", "Header name")
730 | self.config_column_names_flags = ("Use?", "Flag name")
731 |
732 | self.model_tab_config_security = ConfigTableModel(self.table_config_security, self.config_column_names)
733 | self.table_tab_config_security = JTable(self.model_tab_config_security)
734 |
735 | self.model_tab_config_potentially_dangerous = ConfigTableModel(self.table_config_potentially_dangerous, self.config_column_names)
736 | self.table_tab_config_potentially_dangerous = JTable(self.model_tab_config_potentially_dangerous)
737 |
738 | self.model_tab_config_dangerous = ConfigTableModel(self.table_config_dangerous, self.config_column_names)
739 | self.table_tab_config_dangerous = JTable(self.model_tab_config_dangerous)
740 |
741 | self.model_tab_config_cookie_flags = ConfigTableModel(self.table_config_cookie_flags, self.config_column_names_flags)
742 | self.table_tab_config_cookie_flags = JTable(self.model_tab_config_cookie_flags)
743 |
744 | self.table_tab_config_security.getColumnModel().getColumn(0).setMaxWidth(50)
745 | self.table_tab_config_security.getColumnModel().getColumn(1).setPreferredWidth(400)
746 |
747 | self.table_tab_config_potentially_dangerous.getColumnModel().getColumn(0).setMaxWidth(50)
748 | self.table_tab_config_potentially_dangerous.getColumnModel().getColumn(1).setPreferredWidth(400)
749 |
750 | self.table_tab_config_dangerous.getColumnModel().getColumn(0).setMaxWidth(50)
751 | self.table_tab_config_dangerous.getColumnModel().getColumn(1).setPreferredWidth(400)
752 |
753 | self.table_tab_config_cookie_flags.getColumnModel().getColumn(0).setMaxWidth(50)
754 | self.table_tab_config_cookie_flags.getColumnModel().getColumn(1).setPreferredWidth(400)
755 |
756 | c = GridBagConstraints()
757 | c.fill = GridBagConstraints.HORIZONTAL
758 |
759 | security_headers_tab = JPanel(GridBagLayout())
760 | security_headers_tab.add(JScrollPane(self.table_tab_config_security), c)
761 |
762 | dangerous_headers_tab = JPanel(GridBagLayout())
763 | dangerous_headers_tab.add(JScrollPane(self.table_tab_config_dangerous), c)
764 |
765 | dangerous_headers_tab = JPanel(GridBagLayout())
766 | dangerous_headers_tab.add(JScrollPane(self.table_tab_config_cookie_flags), c)
767 |
768 | potentially_dangerous_headers_tab = JPanel(GridBagLayout())
769 | potentially_dangerous_headers_tab.add(JScrollPane(self.table_tab_config_potentially_dangerous), c)
770 | # ----------------------------------------------------------------#
771 |
772 |
773 | # ------------------ Add contents to main tabs -------------------#
774 | aux_panel = JPanel(BorderLayout())
775 | theme_panel = JPanel(GridBagLayout())
776 |
777 | # Add buttons at the bottom inside a panel
778 | add_header_to_category_button = JButton("Add header to category", actionPerformed = self.add_headers_to_categories)
779 | add_header_to_category_button.putClientProperty("html.disable", None)
780 | add_header_to_category_button.setForeground(Color.WHITE)
781 | add_header_to_category_button.setBackground(Color(10,101,247))
782 |
783 | remove_header_from_category_button = JButton("Remove header from category", actionPerformed = self.remove_headers_from_categories)
784 | remove_header_from_category_button.putClientProperty("html.disable", None)
785 | remove_header_from_category_button.setForeground(Color.WHITE)
786 | remove_header_from_category_button.setBackground(Color(210,101,47))#Color(10,101,247)) Color(210,101,47)
787 |
788 | make_curr_selection_permanent_button = JButton("Apply changes", actionPerformed = self.make_chosen_headers_permanent)
789 | make_curr_selection_permanent_button.putClientProperty("html.disable", None)
790 | make_curr_selection_permanent_button.setForeground(Color.WHITE)
791 | make_curr_selection_permanent_button.setBackground(Color(10,101,247))
792 |
793 | button_panel = JPanel(GridBagLayout())
794 | e = GridBagConstraints()
795 | e.fill = GridBagConstraints.HORIZONTAL
796 | e.gridx = 0
797 | e.weightx = 1
798 | e.gridy = 0
799 | button_panel.add(add_header_to_category_button, e)
800 | e.gridx += 1
801 | button_panel.add(remove_header_from_category_button, e)
802 | e.gridx += 1
803 | button_panel.add(make_curr_selection_permanent_button, e)
804 |
805 |
806 | # Fill the tables for each category
807 | self.categories_tabs = JTabbedPane()
808 | self.categories_tabs.add("Security headers", JScrollPane(self.table_tab_config_security))
809 | self.categories_tabs.add("Potentially dangerous headers", JScrollPane(self.table_tab_config_potentially_dangerous))
810 | self.categories_tabs.add("Dangerous headers", JScrollPane(self.table_tab_config_dangerous))
811 | self.categories_tabs.add("Cookie Flags", JScrollPane(self.table_tab_config_cookie_flags))
812 | aux_panel.add(self.categories_tabs, BorderLayout.CENTER)
813 | aux_panel.add(button_panel, BorderLayout.SOUTH)
814 |
815 |
816 | #--------------- threshold panel ---------------------
817 | threshold_panel = JPanel(GridBagLayout())
818 | c = GridBagConstraints()
819 | c.anchor = GridBagConstraints.WEST
820 | c.gridx = 0
821 | c.gridy = 0
822 | c.weightx = 1
823 | c.fill = GridBagConstraints.HORIZONTAL
824 | threshold_panel.add(JLabel("Use threshold for Security headers?"), c)
825 | c.gridy += 1
826 | threshold_panel.add(JLabel("Use threshold for Potentially Dangerous headers?"), c)
827 | c.gridy += 1
828 | threshold_panel.add(JLabel("Use threshold for Dangerous headers?"), c)
829 |
830 | c.gridx = 1
831 | c.gridy = 0
832 | self.check_box_security = JCheckBox()
833 | threshold_panel.add(self.check_box_security, c)
834 | c.gridy += 1
835 | self.check_box_potentially_dangerous = JCheckBox()
836 | threshold_panel.add(self.check_box_potentially_dangerous, c)
837 | c.gridy += 1
838 | self.check_box_dangerous = JCheckBox()
839 | threshold_panel.add(self.check_box_dangerous, c)
840 |
841 | c.gridx = 2
842 | c.gridy = 0
843 | self.threshold_count_security = JTextField("{}".format(len(self.table_config_security)))
844 | threshold_panel.add(self.threshold_count_security, c)
845 | c.gridy += 1
846 | self.threshold_count_potentially_dangerous = JTextField("{}".format(len(self.table_config_potentially_dangerous)))
847 | threshold_panel.add(self.threshold_count_potentially_dangerous, c)
848 |
849 | c.gridy += 1
850 | self.threshold_count_dangerous = JTextField("{}".format(len(self.table_config_dangerous)))
851 | threshold_panel.add(self.threshold_count_dangerous, c)
852 |
853 | c.gridx = 0
854 | c.gridy += 1
855 | save_threshold_config = JButton("Save thresholds", actionPerformed = self.save_threshold_config_func)
856 | threshold_panel.add(save_threshold_config, c)
857 |
858 | c.gridx += 1
859 | restore_threshold_config = JButton("Restore saved thresholds", actionPerformed = self.restore_save_thresholds_func)
860 | threshold_panel.add(restore_threshold_config, c)
861 |
862 | c.gridx += 1
863 | reset_threshold_config = JButton("Reset to default", actionPerformed = self.reset_threshold_config_func)
864 | threshold_panel.add(reset_threshold_config, c)
865 |
866 |
867 |
868 | # Theme panel contents
869 | d = GridBagConstraints()
870 | d.fill = GridBagConstraints.HORIZONTAL
871 | d.gridx = 0
872 | d.gridy = 0
873 | theme_panel.add(JLabel("If you use Burp's dark theme, you will probably see better this extension by selecting 'dark', and vice versa."), d)
874 | d.gridy += 1
875 | theme_panel.add(theme_selector, d)
876 |
877 |
878 | # add the main tabs
879 | self.main_tabs = JTabbedPane()
880 | self.main_tabs.addTab('Configure headers criteria', aux_panel)
881 | self.main_tabs.addTab('Configure thresholds', threshold_panel)
882 | self.main_tabs.addTab('Theme', theme_panel)
883 |
884 | self.advanced_config_panel.add(self.main_tabs, BorderLayout.CENTER)
885 | # ----------------------------------------------------------------#
886 |
887 | return
888 |
889 | def show_advanced_config(self, event):
890 | """Show the advanced configuration window when clicking the gear button"""
891 | self.advanced_config_panel.setVisible(True)
892 |
893 | def get_categories_headers_length(self):
894 | """ Get how many headers are in each category when the extension is loaded. Used in read_headers()
895 | to tell it how many times it must loop to generate arrays of each category of headers"""
896 |
897 | f = open('security_headers.txt','r')
898 | # the filter in the next line removes all occurences of '', i.e. doesnt consider empty lines for counting the number of headers
899 | self.initial_count_security_headers = len(list(filter(('').__ne__, f.readlines())))
900 | #self.initial_count_security_headers = len(f.readlines())
901 | f.close()
902 |
903 | f = open('dangerous_headers.txt','r')
904 | self.initial_count_dangerous_headers= len(list(filter(('').__ne__, f.readlines())))
905 | #self.initial_count_dangerous_headers= len(f.readlines())
906 | f.close()
907 |
908 | f = open('potentially_dangerous_headers.txt','r')
909 | self.initial_count_potentially_dangerous_headers = len(list(filter(('').__ne__, f.readlines())))
910 | #self.initial_count_potentially_dangerous_headers = len(f.readlines())
911 | f.close()
912 |
913 | f = open('cookie_flags.txt','r')
914 | self.initial_count_cookie_flags = len(list(filter(('').__ne__, f.readlines())))
915 | #self.initial_count_dangerous_headers= len(f.readlines())
916 | f.close()
917 |
918 | def check_python_modules(self, event):
919 | # subprocess que guarde en una variable output de python -c import ...
920 |
921 | python_path = self.python_path_textfield.getText()
922 | os_type = sys.platform.getshadow()
923 | win_python_command = "py --version"
924 | linux_python_command = 'python3 --version'
925 | win_python_command = 'py docx.py'
926 | if 'win' in os_type:
927 | #proc = subprocess.Popen(win_python_command, stdout=subprocess.PIPE)
928 | if python_path != '':
929 | proc = subprocess.Popen("{} --version".format(python_path), stdout=subprocess.PIPE)
930 | else:
931 | proc = subprocess.Popen(["py","--version"], stdout=subprocess.PIPE)
932 | elif 'linux' in os_type:
933 | #proc = subprocess.Popen(linux_python_command, stdout=subprocess.PIPE)
934 | if python_path != '':
935 | proc = subprocess.Popen(["{}".format(python_path),"--version"], stdout=subprocess.PIPE)
936 | else:
937 | proc = subprocess.Popen(["python3","--version"], stdout=subprocess.PIPE)
938 | else:
939 | raise("Error identifying operating system type. Provide path to Python3 binary.")
940 |
941 |
942 | if 'docxtpl' not in sys.modules.keys():
943 | os.system('pip install docxtpl')
944 |
945 | if python_path != '':
946 | docxtpl_version = subprocess.Popen(["{}".format(python_path),"-c","import docxtpl; print('Docxtpl version:',docxtpl.__version__)"],stdout=subprocess.PIPE)
947 | else:
948 | docxtpl_version = subprocess.Popen(["python3","-c","import docxtpl; print('Docxtpl version:',docxtpl.__version__)"],stdout=subprocess.PIPE)
949 |
950 | print(python_path)
951 | output = proc.stdout.read()
952 | self.python_msg.setText(output.strip('\r\n') + '; ' + docxtpl_version.stdout.read())
953 |
954 | def create_docx_frame(self):
955 | self.docx_frame = JFrame("Configure .docx report")
956 | self.docx_frame.setLayout(GridBagLayout())
957 | self.docx_frame.setAlwaysOnTop(True)
958 | self.docx_frame.setSize(800, 600)
959 | self.docx_frame.setLocationRelativeTo(None)
960 | self.docx_frame.toFront()
961 | self.docx_frame.setAlwaysOnTop(True)
962 |
963 | c = GridBagConstraints()
964 | c.gridx = 0
965 | c.weightx = 0
966 | c.gridy = 0
967 | self.docx_frame.add(JLabel('Make sure your Python3 installation runs on windows by typing "py" on Powershell or python3 or bash\n'), c)
968 | c.gridy += 1
969 | self.docx_frame.add(JLabel('If your python executable is in a custom path, please write it in the next textbox'), c)
970 | c.gridy += 1
971 | c.weightx = 1
972 | c.fill = GridBagConstraints.HORIZONTAL
973 | self.python_path_textfield = JTextField()
974 | self.docx_frame.add(self.python_path_textfield, c)
975 |
976 | c.gridy += 1
977 | self.docx_frame.add(JLabel(' '))
978 | self.check_python_docx_modules = JButton("Check docx modules", actionPerformed = self.check_python_modules)
979 | self.docx_frame.add(self.check_python_docx_modules, c)
980 |
981 | c.gridy += 1
982 | self.export_docx = JButton("Export docx", actionPerformed = self.output_selected_summary)
983 | self.docx_frame.add(self.check_python_docx_modules, c)
984 |
985 | c.gridy += 1
986 | self.python_msg = JTextArea()
987 | self.docx_frame.add(self.python_msg, c)
988 |
989 | def registerExtenderCallbacks(self, callbacks):
990 | """Import Burp Extender callbacks and execute some preliminary functions for setting up the extension when it's loaded with proper configurations"""
991 | self._callbacks = callbacks
992 | self._helpers = callbacks.helpers
993 | callbacks.setExtensionName("Headers")
994 | callbacks.registerContextMenuFactory(self)
995 | callbacks.addSuiteTab(self)
996 | self.req_header_dict = {}
997 | self.resp_header_dict = {}
998 | self.for_table = [] # Items in this table will be shown in the Header-Host table (left side of the screen) for Requests and Responses headers
999 | self.header_host_table = [] # This holds data in three columns with Headers, Unique headers and Hosts and it's used for saving data to a file
1000 | self.for_req_table = []
1001 | self.for_resp_table = []
1002 | self.headers_already_in_table = []
1003 | self.meta_headers_already_in_table = []
1004 | self.last_len = 0
1005 | self.last_len_meta = 0
1006 | self.last_row = 0
1007 | self.last_row_meta = 0
1008 | ### global history1 # variable global con el history de requests
1009 | history1 = self._callbacks.getProxyHistory()
1010 | global burp_extender_instance
1011 | burp_extender_instance = self
1012 | self.config_dict = {}
1013 | self.apply_config()
1014 | self.compile_regex()
1015 | self.create_advanced_config_frame()
1016 | self.create_extra_info_window()
1017 | self.get_categories_headers_length()
1018 | self.read_headers()
1019 | self.create_docx_frame()
1020 | self.selected_host = ""
1021 | self.selected_header = ""
1022 | self.is_meta = False
1023 | self.dic_host_unique_endpoint = {}
1024 |
1025 |
1026 | return
1027 |
1028 | def getTabCaption(self):
1029 | """Name that will be shown in the extension's tab in the Burp interface"""
1030 | return "Headers"
1031 |
1032 |
1033 |
1034 | def ColorScore(self, value, total, type):
1035 | """ Make a color more intense if more headers of that type are present. To be used in the unique endpoints table."""
1036 |
1037 | if type == "security":
1038 | if total != 0: #this is done to avoid dividing by zero. if there are no selected headers for a category, return brown color
1039 | score = value * 255.0 / total
1040 | if self.check_box_security.isSelected() and value >= int(self.threshold_count_security.getText()):
1041 | score = 255.0
1042 | elif self.check_box_security.isSelected() and value < int(self.threshold_count_security.getText()):
1043 | score = 0.0
1044 | elif score > 255.0:
1045 | score = 255.0
1046 | color = "#00{}00".format(hex(int(score)).split('0x')[1].zfill(2))
1047 | # if there are no headers of this type show the symbol as brown, looks better
1048 | return color.replace("#000000", "#707070")
1049 | else:
1050 | return "#707070"
1051 |
1052 | elif type == "dangerous":
1053 | if total != 0:
1054 | score = value * 255.0 / total
1055 | if self.check_box_dangerous.isSelected() and value >= int(self.threshold_count_dangerous.getText()):
1056 | score = 255.0
1057 | elif self.check_box_dangerous.isSelected() and value < int(self.threshold_count_dangerous.getText()):
1058 | score = 0.0
1059 | elif score > 255.0:
1060 | score = 255.0
1061 | color = "#{}0000".format(hex(int(score)).split('0x')[1].zfill(2))
1062 | return color.replace("#000000", "#707070")
1063 | else:
1064 | return "#707070"
1065 |
1066 | elif type == "potential":
1067 | if total != 0:
1068 | score = value * 255.0 / total
1069 | if self.check_box_potentially_dangerous.isSelected() and value >= int(self.threshold_count_potentially_dangerous.getText()):
1070 | score = 255.0
1071 | elif self.check_box_potentially_dangerous.isSelected() and value < int(self.threshold_count_potentially_dangerous.getText()):
1072 | score = 0.0
1073 | elif score > 255.0:
1074 | score = 255.0
1075 | R_factor = hex(int(int(0x4F) * score)).split('0x')[1]
1076 | G_factor = hex(int(int(0xC3) * score)).split('0x')[1]
1077 | B_factor = hex(int(int(0xF7) * score)).split('0x')[1]
1078 | color = "#{0}{1}{2}".format(R_factor.zfill(2), G_factor.zfill(2), B_factor.zfill(2))
1079 | return color.replace("#000000", "#707070")
1080 | else:
1081 | return "#707070"
1082 |
1083 |
1084 | def extra_symbol(self, head):
1085 | """Creates the extra symbols for security [+], dangerous [X] and potentially dangrous [?] headers that are shown in the request header summary at the right side of the screen."""
1086 |
1087 |
1088 | if head.split(": ")[0].lower() in self.security_headers:
1089 | extra_symbol = ' [ + ] '
1090 | elif head.split(": ")[0].lower() in self.dangerous_headers:
1091 | extra_symbol = ' [ X ] '
1092 | elif head.split(": ")[0].lower() in self.potentially_dangerous_headers:
1093 | extra_symbol = ' [ ? ] '
1094 | else:
1095 | extra_symbol = ""
1096 | return extra_symbol
1097 |
1098 | def to_get_colors(self, url, host, from_click):
1099 | """Get the count of each symbol for the unique endpoints table. That number will be transformed into color strings by the self.ColorScore function"""
1100 | # get_colors == True -> just get how many symbols for categories there are. get_colors == False -> fill the summary
1101 | count_colors = {"dangerous":0, "security":0, "potential":0} #count how many of each categories are for a certain request/response pair. this is used in the unique endpoints table to add brighter or fainter color symbols
1102 |
1103 | if from_click:
1104 | val = url#val = tbl.getModel().getDataVector().elementAt(tbl.getSelectedRow())
1105 | #else:
1106 | # val = tbl.getModel().getDataVector().elementAt(0)
1107 |
1108 | for item in history1:
1109 | request = burp_extender_instance._helpers.bytesToString(item.getRequest()).split('\r\n\r\n')[0]
1110 | req_headers = request.split('\r\n')
1111 | endpoint = req_headers[0]
1112 |
1113 | endpoint = self.apply_regex(endpoint)
1114 |
1115 | if endpoint == val: # si coincide un endpoint del history con el que hemos seleccionado
1116 |
1117 | host = self.find_host(req_headers)
1118 |
1119 | if host == burp_extender_instance.selected_host: # si coincide el host del history con el que clickamos en la tabla de headers. este if no es inutil?? bueno, creo que valdria si dos host distintos tienen un mismo endpoint
1120 |
1121 | for req_head in sorted(req_headers[1:]): # este for encuentra el Host header
1122 |
1123 | extra_symbol = self.extra_symbol(req_head)
1124 | if "[ + ]" in extra_symbol:
1125 | count_colors["security"] += 1
1126 | elif "[ X ]" in extra_symbol:
1127 | count_colors["dangerous"] += 1
1128 | elif "[ ? ]" in extra_symbol:
1129 | count_colors["potential"] += 1
1130 |
1131 | response = burp_extender_instance._helpers.bytesToString(item.getResponse()).split('\r\n\r\n')[0]
1132 | resp_headers = response.split('\r\n')
1133 | for resp_head in sorted(resp_headers[1:]):
1134 |
1135 | extra_symbol = self.extra_symbol(resp_head)
1136 | if "[ + ]" in extra_symbol:
1137 | count_colors["security"] += 1
1138 | elif "[ X ]" in extra_symbol:
1139 | count_colors["dangerous"] += 1
1140 | elif "[ ? ]" in extra_symbol:
1141 | count_colors["potential"] += 1
1142 |
1143 | break
1144 |
1145 | return count_colors
1146 |
1147 | def replace_symbol(self, replace_here):
1148 | """Replace the wildcard symbol with an html formatted one for colors. Implemented as a separate function becaused it is called several times, to avoid code duplication."""
1149 | # it's important that the new symbol has spaces, some endpoints are so goddamn long that without spaces they don't fit and are completely hidden
1150 | replace_here = replace_here.replace('[*]',' * '.format(self.color6))
1151 | #replace_here = replace_here.replace('[*]','[ * ]'.format(self.color6))
1152 | return replace_here
1153 |
1154 | def clicked_endpoint(self, tbl, from_click):
1155 | """Fill the summary (panel at the right side of the extension tab) when an endpoint (either from the "Unique endpoints" table or from the "All endpoints table") is clicked. The summary contains the request and response headers, marked with symbols if they are security headers, dangerous headers, or potentially dangerous headers."""
1156 |
1157 | # update the headers categories to be considered, by looking at the checkboxes in the advanced config table
1158 | self.read_headers()
1159 |
1160 | if from_click:
1161 | val = tbl.getModel().getDataVector().elementAt(tbl.getSelectedRow())
1162 | else:
1163 | val = tbl.getModel().getDataVector().elementAt(0)
1164 | tbl.setRowSelectionInterval(0, 0)
1165 |
1166 | # lo siguiente es porque se selecciona (en principio) un endpoint de la lista de uniques, que han sido modificados, y hay que encontrar el bueno
1167 | for item in history1:
1168 | request = self._helpers.bytesToString(item.getRequest()).split('\r\n\r\n')[0]
1169 | req_headers = request.split('\r\n')
1170 | iter_host = self.find_host(req_headers)
1171 | endpoint = req_headers[0]
1172 | match = False
1173 |
1174 |
1175 | # the next three ifs are for matching to elements in the history if we choose from the unique endpoints or from all endpoints, must be processed differently for the comparison
1176 | if tbl.getModel() == self.model_unique_endpoints and self.is_meta == False and iter_host == self.selected_host:
1177 | match = self.replace_symbol(self.apply_regex(endpoint)) == val[0].split(' - ')[1].strip('').strip('')
1178 |
1179 | if tbl.getModel() == self.model_unique_endpoints and self.is_meta == True and iter_host == self.selected_host:
1180 | match = self.replace_symbol(self.apply_regex(endpoint)) == val[0]
1181 |
1182 | if tbl.getModel() == self.model_all_endpoints and iter_host == self.selected_host:
1183 | match = endpoint == val[0].strip('').strip('')
1184 |
1185 | if match:
1186 | host = self.find_host(req_headers) #del loop del history
1187 | clicked_header = self.selected_header
1188 |
1189 | # Run this block and exit this function if an item from the Meta headers tab was clicked
1190 | if self.is_meta:
1191 | metas = []
1192 | request = self._helpers.bytesToString(item.getRequest()).split('\r\n\r\n')[0]
1193 | host = self.find_host(req_headers) #del loop del history
1194 | response = self._helpers.bytesToString(item.getResponse()).split('\r\n\r\n')[0]
1195 | resp_headers = response.split('\r\n')
1196 | for k, resp_head in enumerate(resp_headers[1:]):
1197 | if "Content-Type: text/html" in resp_head:
1198 | resp_html_head = self._helpers.bytesToString(item.getResponse()).split('\r\n\r\n')[1].split('')[0]#.encode('utf-8')
1199 | metas = self.meta.findall(resp_html_head)
1200 |
1201 | buffer = "Meta headers
".format(self.color1)
1202 | buffer += "Host: {}
".format(host)
1203 | buffer += "Endpoint: {}
".format(endpoint)
1204 | for meta in metas:
1205 | meta_line = meta.encode('utf-8').replace('<','<').replace('>','>')
1206 | # Add colors to the meta fields in the summary pannel
1207 | meta_line = meta_line.replace('<meta', '<meta'.format(self.color2))
1208 | meta_line = meta_line.replace(' charset=', ' charset='.format(self.color3))
1209 | meta_line = meta_line.replace(' name=', ' name='.format(self.color3))
1210 | meta_line = meta_line.replace(' property=', ' property='.format(self.color3))
1211 | meta_line = meta_line.replace(' http-equiv=', ' http-equiv='.format(self.color3))
1212 | meta_line = meta_line.replace(' content=', ' content='.format(self.color3))
1213 | meta_line = meta_line.replace('>', '>'.format(self.color2))
1214 |
1215 | buffer += '' + meta_line + '\n'
1216 | buffer += '