akii 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- akii/__init__.py +1 -0
- akii/checks/__init__.py +7 -0
- akii/checks/cache.py +0 -0
- akii/checks/cookie.py +0 -0
- akii/checks/cors.py +43 -0
- akii/checks/csp.py +49 -0
- akii/checks/hsts.py +0 -0
- akii/core/__init__.py +0 -0
- akii/core/cli/__init__.py +0 -0
- akii/core/cli/main.py +13 -0
- akii/core/cli/parser.py +66 -0
- akii/core/controller.py +70 -0
- akii/core/flags/general.py +109 -0
- akii/core/reporter.py +48 -0
- akii/core/template_loader.py +10 -0
- akii/http/__init__.py +0 -0
- akii/http/client.py +28 -0
- akii/ui/__init__.py +0 -0
- akii/ui/banner.py +38 -0
- akii/ui/output.py +107 -0
- akii-0.1.0.dist-info/METADATA +170 -0
- akii-0.1.0.dist-info/RECORD +25 -0
- akii-0.1.0.dist-info/WHEEL +5 -0
- akii-0.1.0.dist-info/entry_points.txt +2 -0
- akii-0.1.0.dist-info/top_level.txt +1 -0
akii/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
akii/checks/__init__.py
ADDED
akii/checks/cache.py
ADDED
|
File without changes
|
akii/checks/cookie.py
ADDED
|
File without changes
|
akii/checks/cors.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from akii.core.template_loader import load_template
|
|
2
|
+
|
|
3
|
+
RULE = load_template("cors")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def cors_detect(config, response):
|
|
7
|
+
findings = {}
|
|
8
|
+
|
|
9
|
+
for check in RULE["checks"]:
|
|
10
|
+
header = check["header"]
|
|
11
|
+
|
|
12
|
+
if header in response.headers:
|
|
13
|
+
findings[header] = response.headers[header]
|
|
14
|
+
|
|
15
|
+
return findings
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def cors_analyze(findings):
|
|
19
|
+
results = []
|
|
20
|
+
|
|
21
|
+
for check in RULE["checks"]:
|
|
22
|
+
header = check["header"]
|
|
23
|
+
|
|
24
|
+
if header not in findings:
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
value = findings[header]
|
|
28
|
+
|
|
29
|
+
for rule in check.get("rules", []):
|
|
30
|
+
if value == rule["value"]:
|
|
31
|
+
results.append({
|
|
32
|
+
"header": header,
|
|
33
|
+
"value": value,
|
|
34
|
+
"severity": rule["severity"],
|
|
35
|
+
"message": rule["message"],
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return results
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def cors_runner(config, response):
|
|
42
|
+
findings = cors_detect(config, response)
|
|
43
|
+
return cors_analyze(findings)
|
akii/checks/csp.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from akii.core.template_loader import load_template
|
|
2
|
+
|
|
3
|
+
RULE = load_template("csp")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def csp_detect(config, response):
|
|
7
|
+
findings = {}
|
|
8
|
+
|
|
9
|
+
for check in RULE["checks"]:
|
|
10
|
+
header = check["header"]
|
|
11
|
+
|
|
12
|
+
if header in response.headers:
|
|
13
|
+
findings[header] = response.headers[header]
|
|
14
|
+
|
|
15
|
+
return findings
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def csp_analyze(findings):
|
|
19
|
+
results = []
|
|
20
|
+
|
|
21
|
+
for check in RULE["checks"]:
|
|
22
|
+
header = check["header"]
|
|
23
|
+
|
|
24
|
+
if header not in findings:
|
|
25
|
+
if check.get("required", False):
|
|
26
|
+
results.append({
|
|
27
|
+
"header": header,
|
|
28
|
+
"severity": "info",
|
|
29
|
+
"message": f"Missing required header: {header}",
|
|
30
|
+
})
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
value = findings[header]
|
|
34
|
+
|
|
35
|
+
for rule in check.get("rules", []):
|
|
36
|
+
if rule["value"] in value:
|
|
37
|
+
results.append({
|
|
38
|
+
"header": header,
|
|
39
|
+
"value": rule["value"],
|
|
40
|
+
"severity": rule["severity"],
|
|
41
|
+
"message": rule["message"],
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return results
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def csp_runner(config, response):
|
|
48
|
+
findings = csp_detect(config, response)
|
|
49
|
+
return csp_analyze(findings)
|
akii/checks/hsts.py
ADDED
|
File without changes
|
akii/core/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
akii/core/cli/main.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from akii.core.cli.parser import build_parser
|
|
2
|
+
from akii.core.controller import run
|
|
3
|
+
from akii.ui.output import banner
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
banner()
|
|
7
|
+
parser = build_parser()
|
|
8
|
+
args = parser.parse_args()
|
|
9
|
+
config = vars(args)
|
|
10
|
+
run(config)
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
main()
|
akii/core/cli/parser.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from akii.core.flags.general import (
|
|
4
|
+
INPUT_FLAGS,
|
|
5
|
+
REQUEST_FLAGS,
|
|
6
|
+
PERFORMANCE_FLAGS,
|
|
7
|
+
OUTPUT_FLAGS,
|
|
8
|
+
MISCELLANEOUS_FLAGS,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
FLAG_GROUPS = (
|
|
13
|
+
("Input", INPUT_FLAGS),
|
|
14
|
+
("Request", REQUEST_FLAGS),
|
|
15
|
+
("Performance", PERFORMANCE_FLAGS),
|
|
16
|
+
("Output", OUTPUT_FLAGS),
|
|
17
|
+
("Miscellaneous", MISCELLANEOUS_FLAGS),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_parser():
|
|
22
|
+
"""Create and configure the CLI argument parser."""
|
|
23
|
+
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="akii",
|
|
26
|
+
description="HTTP header analyzer",
|
|
27
|
+
formatter_class=lambda prog: argparse.HelpFormatter(
|
|
28
|
+
prog,
|
|
29
|
+
max_help_position=50,
|
|
30
|
+
width=130,
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Create and register all argument groups.
|
|
35
|
+
for title, flags in FLAG_GROUPS:
|
|
36
|
+
group = parser.add_argument_group(title)
|
|
37
|
+
register_flags(group, flags)
|
|
38
|
+
|
|
39
|
+
return parser
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register_flags(group, flags):
|
|
43
|
+
"""Register a collection of CLI flags to an argument group."""
|
|
44
|
+
|
|
45
|
+
for value in flags.values():
|
|
46
|
+
kwargs = {
|
|
47
|
+
"help": value["help"],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Copy optional keyword arguments if present.
|
|
51
|
+
for key in (
|
|
52
|
+
"action",
|
|
53
|
+
"type",
|
|
54
|
+
"default",
|
|
55
|
+
"choices",
|
|
56
|
+
"nargs",
|
|
57
|
+
"metavar",
|
|
58
|
+
"required",
|
|
59
|
+
):
|
|
60
|
+
if key in value:
|
|
61
|
+
kwargs[key] = value[key]
|
|
62
|
+
|
|
63
|
+
group.add_argument(
|
|
64
|
+
*value["flags"],
|
|
65
|
+
**kwargs,
|
|
66
|
+
)
|
akii/core/controller.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from akii.http.client import request
|
|
4
|
+
from akii.ui.output import display_http, csp_result, cors_result, display_target
|
|
5
|
+
from akii.checks.csp import csp_runner
|
|
6
|
+
from akii.checks.cors import cors_runner
|
|
7
|
+
from akii.core.reporter import txt_output, json_output
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
URL_RE = re.compile(r"https?://[^\s]+")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_url(line):
|
|
14
|
+
match = URL_RE.search(line)
|
|
15
|
+
return match.group(0) if match else None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_input(config):
|
|
19
|
+
if not config.get("wordlist"):
|
|
20
|
+
return [config]
|
|
21
|
+
|
|
22
|
+
configs = []
|
|
23
|
+
|
|
24
|
+
with open(config["wordlist"], encoding="utf-8") as f:
|
|
25
|
+
for line in f:
|
|
26
|
+
url = extract_url(line)
|
|
27
|
+
|
|
28
|
+
if not url:
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
target_config = config.copy()
|
|
32
|
+
target_config["target"] = url
|
|
33
|
+
configs.append(target_config)
|
|
34
|
+
|
|
35
|
+
return configs
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def process_target(config):
|
|
39
|
+
display_target(config)
|
|
40
|
+
|
|
41
|
+
response = request(config)
|
|
42
|
+
|
|
43
|
+
display_http(config, response)
|
|
44
|
+
|
|
45
|
+
cors_results = cors_runner(config, response)
|
|
46
|
+
cors_result(cors_results)
|
|
47
|
+
|
|
48
|
+
csp_results = csp_runner(config, response)
|
|
49
|
+
csp_result(csp_results)
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
"target": config["target"],
|
|
53
|
+
"cors": cors_results,
|
|
54
|
+
"csp": csp_results,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run(config):
|
|
59
|
+
inputs = load_input(config)
|
|
60
|
+
|
|
61
|
+
all_results = []
|
|
62
|
+
|
|
63
|
+
for target_config in inputs:
|
|
64
|
+
all_results.append(process_target(target_config))
|
|
65
|
+
|
|
66
|
+
if config.get("output"):
|
|
67
|
+
if config.get("json"):
|
|
68
|
+
json_output(all_results, config["output"])
|
|
69
|
+
else:
|
|
70
|
+
txt_output(all_results, config["output"])
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
INPUT_FLAGS = {
|
|
2
|
+
"Target": {
|
|
3
|
+
"flags": ["-T", "--target"],
|
|
4
|
+
"type": str,
|
|
5
|
+
"default": None,
|
|
6
|
+
"help": "Target URL or host to analyze.",
|
|
7
|
+
"metavar": "",
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
"Wordlist": {
|
|
11
|
+
"flags": ["-w", "--wordlist"],
|
|
12
|
+
"type": str,
|
|
13
|
+
"default": None,
|
|
14
|
+
"help": "Read targets from a wordlist file.",
|
|
15
|
+
"metavar": "",
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
REQUEST_FLAGS = {
|
|
20
|
+
"Method": {
|
|
21
|
+
"flags": ["-X", "--method"],
|
|
22
|
+
"type": str,
|
|
23
|
+
"default": "GET",
|
|
24
|
+
"help": "HTTP request method (default: GET).",
|
|
25
|
+
"metavar": "",
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
"Header": {
|
|
29
|
+
"flags": ["-H", "--header"],
|
|
30
|
+
"type": str,
|
|
31
|
+
"default": None,
|
|
32
|
+
"help": "Add a custom HTTP header (e.g. 'Authorization: Bearer <token>').",
|
|
33
|
+
"metavar": "",
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
"Cookie": {
|
|
37
|
+
"flags": ["-C", "--cookie"],
|
|
38
|
+
"type": str,
|
|
39
|
+
"default": None,
|
|
40
|
+
"help": "Send cookies with the request (e.g. 'session=abc123').",
|
|
41
|
+
"metavar": "",
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
"Data": {
|
|
45
|
+
"flags": ["-D", "--data"],
|
|
46
|
+
"type": str,
|
|
47
|
+
"default": None,
|
|
48
|
+
"help": "Send data in the request body.",
|
|
49
|
+
"metavar": "",
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
PERFORMANCE_FLAGS = {
|
|
54
|
+
"Concurrency": {
|
|
55
|
+
"flags": ["-c", "--concurrency"],
|
|
56
|
+
"type": int,
|
|
57
|
+
"default": 1,
|
|
58
|
+
"help": "Number of concurrent requests.",
|
|
59
|
+
"metavar": "",
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
OUTPUT_FLAGS = {
|
|
64
|
+
"Output": {
|
|
65
|
+
"flags": ["-o", "--output"],
|
|
66
|
+
"type": str,
|
|
67
|
+
"default": None,
|
|
68
|
+
"help": "Write results to the specified file.",
|
|
69
|
+
"metavar": "",
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
"JSON": {
|
|
73
|
+
"flags": ["-j", "--json"],
|
|
74
|
+
"action": "store_true",
|
|
75
|
+
"help": "Output results in JSON format.",
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
"Request_Header": {
|
|
79
|
+
"flags": ["--request"],
|
|
80
|
+
"action": "store_true",
|
|
81
|
+
"help": "Display the HTTP request headers.",
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
"Response_Header": {
|
|
85
|
+
"flags": ["--response"],
|
|
86
|
+
"action": "store_true",
|
|
87
|
+
"help": "Display the HTTP response headers.",
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
"Verbose": {
|
|
91
|
+
"flags": ["--verbose"],
|
|
92
|
+
"action": "store_true",
|
|
93
|
+
"help": "Display both the HTTP request and response.",
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
MISCELLANEOUS_FLAGS = {
|
|
98
|
+
"Version": {
|
|
99
|
+
"flags": ["-v", "--version"],
|
|
100
|
+
"action": "store_true",
|
|
101
|
+
"help": "Show version information.",
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
"Update": {
|
|
105
|
+
"flags": ["--update"],
|
|
106
|
+
"action": "store_true",
|
|
107
|
+
"help": "Update Akii to the latest version.",
|
|
108
|
+
},
|
|
109
|
+
}
|
akii/core/reporter.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
def txt_output(results, output):
|
|
5
|
+
output_path = Path(output).resolve(strict=False)
|
|
6
|
+
|
|
7
|
+
with output_path.open("w", encoding="utf-8") as f:
|
|
8
|
+
for target in results:
|
|
9
|
+
f.write("=" * 60)
|
|
10
|
+
f.write(f"\nTarget: {target['target']}\n\n")
|
|
11
|
+
|
|
12
|
+
# CORS
|
|
13
|
+
f.write("=== CORS Analysis ===\n")
|
|
14
|
+
|
|
15
|
+
if not target["cors"]:
|
|
16
|
+
f.write("No CORS issues found.\n\n")
|
|
17
|
+
else:
|
|
18
|
+
for count, result in enumerate(target["cors"], start=1):
|
|
19
|
+
f.write(f"({count}> [{result['severity'].upper()}] {result['header']}\n")
|
|
20
|
+
|
|
21
|
+
if "value" in result:
|
|
22
|
+
f.write(f" Value : {result['value']}\n")
|
|
23
|
+
|
|
24
|
+
f.write(f" Message : {result['message']}\n")
|
|
25
|
+
|
|
26
|
+
f.write("\n")
|
|
27
|
+
|
|
28
|
+
# CSP
|
|
29
|
+
f.write("=== CSP Analysis ===\n")
|
|
30
|
+
|
|
31
|
+
if not target["csp"]:
|
|
32
|
+
f.write("No CSP issues found.\n\n")
|
|
33
|
+
else:
|
|
34
|
+
for count, result in enumerate(target["csp"], start=1):
|
|
35
|
+
f.write(f"({count}> [{result['severity'].upper()}] {result['header']}\n")
|
|
36
|
+
|
|
37
|
+
if "value" in result:
|
|
38
|
+
f.write(f" Value : {result['value']}\n")
|
|
39
|
+
|
|
40
|
+
f.write(f" Message : {result['message']}\n")
|
|
41
|
+
|
|
42
|
+
f.write("\n")
|
|
43
|
+
|
|
44
|
+
def json_output(results, output):
|
|
45
|
+
output_path = Path(output).resolve(strict=False)
|
|
46
|
+
|
|
47
|
+
with output_path.open("w", encoding="utf-8") as f:
|
|
48
|
+
json.dump(results, f, indent=4, ensure_ascii=False)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
TEMPLATE_DIR = Path(__file__).parent.parent / "templates"
|
|
5
|
+
|
|
6
|
+
def load_template(name: str) -> dict:
|
|
7
|
+
template_path = TEMPLATE_DIR / f"{name}.yaml"
|
|
8
|
+
|
|
9
|
+
with template_path.open("r", encoding="utf-8") as f:
|
|
10
|
+
return yaml.safe_load(f)
|
akii/http/__init__.py
ADDED
|
File without changes
|
akii/http/client.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from akii import __version__
|
|
4
|
+
|
|
5
|
+
def request(config):
|
|
6
|
+
headers = {
|
|
7
|
+
"User-Agent": f"AkII/{__version__}",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
cookies = {}
|
|
11
|
+
|
|
12
|
+
if config.get("header"):
|
|
13
|
+
name, value = config["header"].split(":", 1)
|
|
14
|
+
headers[name.strip()] = value.strip()
|
|
15
|
+
|
|
16
|
+
if config.get("cookie"):
|
|
17
|
+
name, value = config["cookie"].split("=", 1)
|
|
18
|
+
cookies[name.strip()] = value.strip()
|
|
19
|
+
|
|
20
|
+
r = requests.request(
|
|
21
|
+
method=config["method"],
|
|
22
|
+
url=config["target"],
|
|
23
|
+
headers=headers or None,
|
|
24
|
+
cookies=cookies or None,
|
|
25
|
+
data=config.get("data"),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return r
|
akii/ui/__init__.py
ADDED
|
File without changes
|
akii/ui/banner.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
def get_banner():
|
|
2
|
+
# Archived logo
|
|
3
|
+
headerx = r"""
|
|
4
|
+
::: ::: :::::::::: :::::: ::::::::: :::::::::: ::::::::: ::: :::
|
|
5
|
+
:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
|
|
6
|
+
+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
|
7
|
+
+#++:++#+#+ +#++:++# +#++:++#++: +#+ +:+ +#++:++# +#++:++#: +#++:
|
|
8
|
+
+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+
|
|
9
|
+
#+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+#
|
|
10
|
+
### ### ######### ### ### ######### ########## ### ### ### ###
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
hdrx = r"""
|
|
14
|
+
|
|
15
|
+
::: ::: ::::::::: ::::::::: ::: :::
|
|
16
|
+
:+: :+: :+: :+: :+: :+: :+: :+:
|
|
17
|
+
+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
|
18
|
+
+#++:++#+#+ +#+ +:+ +#++:++#: +#++:
|
|
19
|
+
+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+
|
|
20
|
+
#+# #+# #+# #+# #+# #+# #+# #+#
|
|
21
|
+
### ### ######### ### ### ### ###
|
|
22
|
+
"""
|
|
23
|
+
# Current logo
|
|
24
|
+
akii = r"""
|
|
25
|
+
_____ ___ ___ ___ ___
|
|
26
|
+
:::::::╗ :::║ :::║ :::║ :::║
|
|
27
|
+
:+:╔══:+:╗ :+:║ :+:║ :+:║ :+:║
|
|
28
|
+
+:+╔╝ +:+╗ +:+║ +:+║ ═══╝ ═══╝
|
|
29
|
+
+++║____+++║ +++║__+++║ ___ ___
|
|
30
|
+
+#++:++#++:║ +#++:+╣__ +#+║ +#+║
|
|
31
|
+
+#+╔════+#+║ +#+╔══+#+║ +#+║ +#+║
|
|
32
|
+
#+#║ #+#║ #+#║ #+#║ #+#║ #+#║ v0.1.0
|
|
33
|
+
###║ ###║ ###║ ###║ ###║ ###║
|
|
34
|
+
═══╝ ═══╝ ═══╝ ═══╝ ═══╝ ═══╝
|
|
35
|
+
- MintTester-IO -
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
return akii
|
akii/ui/output.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from colorama import Fore, Style, init
|
|
2
|
+
|
|
3
|
+
from akii.ui.banner import get_banner
|
|
4
|
+
|
|
5
|
+
init(autoreset=True)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def severity_color(severity):
|
|
9
|
+
severity = severity.upper()
|
|
10
|
+
|
|
11
|
+
if severity == "CRITICAL":
|
|
12
|
+
return Fore.LIGHTRED_EX
|
|
13
|
+
if severity == "HIGH":
|
|
14
|
+
return Fore.RED
|
|
15
|
+
if severity == "MEDIUM":
|
|
16
|
+
return Fore.YELLOW
|
|
17
|
+
if severity == "LOW":
|
|
18
|
+
return Fore.GREEN
|
|
19
|
+
if severity == "INFO":
|
|
20
|
+
return Fore.BLUE
|
|
21
|
+
|
|
22
|
+
return Fore.WHITE
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def banner():
|
|
26
|
+
print(get_banner())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def display_http(config, response):
|
|
30
|
+
if config.get("verbose"):
|
|
31
|
+
print(Fore.CYAN + "=== Request ===")
|
|
32
|
+
print(f"{response.request.method} {response.request.url}\n")
|
|
33
|
+
|
|
34
|
+
for key, value in response.request.headers.items():
|
|
35
|
+
print(f"{Fore.GREEN}{key}{Style.RESET_ALL}: {value}")
|
|
36
|
+
|
|
37
|
+
print(Fore.CYAN + "\n=== Response ===")
|
|
38
|
+
print(f"Status: {response.status_code}\n")
|
|
39
|
+
|
|
40
|
+
for key, value in response.headers.items():
|
|
41
|
+
print(f"{Fore.GREEN}{key}{Style.RESET_ALL}: {value}")
|
|
42
|
+
|
|
43
|
+
print()
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
if config.get("request"):
|
|
47
|
+
print(Fore.CYAN + "=== Request ===")
|
|
48
|
+
print(f"{response.request.method} {response.request.url}\n")
|
|
49
|
+
|
|
50
|
+
for key, value in response.request.headers.items():
|
|
51
|
+
print(f"{Fore.GREEN}{key}{Style.RESET_ALL}: {value}")
|
|
52
|
+
|
|
53
|
+
print()
|
|
54
|
+
|
|
55
|
+
if config.get("response"):
|
|
56
|
+
print(Fore.CYAN + "=== Response ===")
|
|
57
|
+
print(f"Status: {response.status_code}\n")
|
|
58
|
+
|
|
59
|
+
for key, value in response.headers.items():
|
|
60
|
+
print(f"{Fore.GREEN}{key}{Style.RESET_ALL}: {value}")
|
|
61
|
+
|
|
62
|
+
print()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def csp_result(results):
|
|
66
|
+
print(Fore.CYAN + "=== CSP Analysis ===")
|
|
67
|
+
|
|
68
|
+
if not results:
|
|
69
|
+
print(Fore.GREEN + "No CSP issues found.\n")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
for count, result in enumerate(results, start=1):
|
|
73
|
+
color = severity_color(result["severity"])
|
|
74
|
+
|
|
75
|
+
print(f"({count}> {color}[{result['severity'].upper()}]{Style.RESET_ALL} {result['header']}")
|
|
76
|
+
|
|
77
|
+
if "value" in result:
|
|
78
|
+
print(f" {Fore.GREEN}Value{Style.RESET_ALL} : {result['value']}")
|
|
79
|
+
|
|
80
|
+
print(f" {Fore.GREEN}Message{Style.RESET_ALL} : {result['message']}")
|
|
81
|
+
|
|
82
|
+
print()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cors_result(results):
|
|
86
|
+
print(Fore.CYAN + "=== CORS Analysis ===")
|
|
87
|
+
|
|
88
|
+
if not results:
|
|
89
|
+
print(Fore.GREEN + "No CORS issues found.\n")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
for count, result in enumerate(results, start=1):
|
|
93
|
+
color = severity_color(result["severity"])
|
|
94
|
+
|
|
95
|
+
print(f"({count}> {color}[{result['severity'].upper()}]{Style.RESET_ALL} {result['header']}")
|
|
96
|
+
|
|
97
|
+
if "value" in result:
|
|
98
|
+
print(f" {Fore.GREEN}Value{Style.RESET_ALL} : {result['value']}")
|
|
99
|
+
|
|
100
|
+
print(f" {Fore.GREEN}Message{Style.RESET_ALL} : {result['message']}")
|
|
101
|
+
|
|
102
|
+
print()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def display_target(config):
|
|
106
|
+
print("=" * 60)
|
|
107
|
+
print(Fore.YELLOW + f"Target: {config['target']}\n")
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akii
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HTTP header analyzer
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.32.0
|
|
9
|
+
Requires-Dist: PyYAML>=6.0
|
|
10
|
+
Requires-Dist: colorama>=0.4.6
|
|
11
|
+
|
|
12
|
+
# AkII
|
|
13
|
+
|
|
14
|
+
> A fast and lightweight HTTP security header analyzer.
|
|
15
|
+
|
|
16
|
+
AkII is an open-source security tool for analyzing HTTP response headers and identifying common security misconfigurations, such as weak or missing security headers.
|
|
17
|
+
|
|
18
|
+
> **⚠️ Early Development**
|
|
19
|
+
>
|
|
20
|
+
> AkII is in an early stage of development. Some features are still incomplete, and bugs are expected. The project is under active development, and new functionality will be added in future releases.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Analyze HTTP security headers
|
|
25
|
+
- Detect common CORS misconfigurations
|
|
26
|
+
- Detect missing or insecure Content Security Policy (CSP)
|
|
27
|
+
- Human-readable terminal output
|
|
28
|
+
- JSON output for automation
|
|
29
|
+
- Lightweight and extensible YAML-based rule engine
|
|
30
|
+
|
|
31
|
+
## Why AkII?
|
|
32
|
+
|
|
33
|
+
AkII is designed for speed, automation, and extensibility.
|
|
34
|
+
|
|
35
|
+
AkII focuses on one task: analyzing HTTP security headers quickly and consistently.
|
|
36
|
+
|
|
37
|
+
It is suitable for:
|
|
38
|
+
|
|
39
|
+
- CI/CD pipelines
|
|
40
|
+
- Security automation
|
|
41
|
+
- Bug bounty workflows
|
|
42
|
+
- Batch scanning with wordlists
|
|
43
|
+
- Integration into custom tools
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### From PyPI (Recommended)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pipx install akii
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
or
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install akii
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### From Source
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/MintTester-IO/AkII.git
|
|
63
|
+
cd akii
|
|
64
|
+
pip install -e .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Uninstallation
|
|
68
|
+
|
|
69
|
+
If you installed AkII with **pipx**:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pipx uninstall akii
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If you installed AkII with **pip**:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip uninstall akii
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If you installed AkII from source using `pip install -e .`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip uninstall akii
|
|
85
|
+
cd ..
|
|
86
|
+
rm -rf akii
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
Scan a single target:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
akii -T https://example.com
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Scan multiple targets:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
akii -w targets.txt
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Show request headers:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
akii -T https://example.com --request
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Show response headers:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
akii -T https://example.com --response
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Verbose mode:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
akii -T https://example.com --verbose
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Save the report:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
akii -T https://example.com -o report.txt
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Export JSON:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
akii -T https://example.com -jo report.txt
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Current Checks
|
|
134
|
+
|
|
135
|
+
- Cross-Origin Resource Sharing (CORS)
|
|
136
|
+
- Content Security Policy (CSP)
|
|
137
|
+
|
|
138
|
+
More security header checks will be added in future releases.
|
|
139
|
+
|
|
140
|
+
## Project Structure
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
src/akii/
|
|
144
|
+
├── checks/ # Detection and analysis modules
|
|
145
|
+
├── core/ # Controller, CLI, reporting
|
|
146
|
+
├── http/ # HTTP client
|
|
147
|
+
├── templates/ # YAML rule definitions
|
|
148
|
+
└── ui/ # Terminal output
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Roadmap
|
|
152
|
+
|
|
153
|
+
- HSTS analysis
|
|
154
|
+
- Cookie analysis
|
|
155
|
+
- Cache-Control analysis
|
|
156
|
+
- Permissions-Policy analysis
|
|
157
|
+
- Referrer-Policy analysis
|
|
158
|
+
- HTML reports
|
|
159
|
+
- Improved JSON reporting
|
|
160
|
+
- Plugin system
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Contributions are welcome.
|
|
165
|
+
|
|
166
|
+
If you find a bug, have a feature request, or would like to improve AkII, please open an issue or submit a pull request.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
akii/__init__.py,sha256=Pru0BlFBASFCFo7McHdohtKkUtgMPDwbGfyUZlE2_Vw,21
|
|
2
|
+
akii/checks/__init__.py,sha256=rKnJQxLAUKdHGZ9U_AGQ_rUnM-sky0G0Z3m8SFhXDKA,106
|
|
3
|
+
akii/checks/cache.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
akii/checks/cookie.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
akii/checks/cors.py,sha256=ZLQeVhy33wkkn7rphqdOv5qFcZeq0IvsZdfStZiaf6s,969
|
|
6
|
+
akii/checks/csp.py,sha256=_Qq_yMFLX7LTSPwXciFN0Ujjn1GEeQ5s4xx5wDUmUnA,1215
|
|
7
|
+
akii/checks/hsts.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
akii/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
akii/core/controller.py,sha256=tMzGvXROtRJrWBIcI0ch_8gTZEFfVkcWvoNsjvjvI6I,1569
|
|
10
|
+
akii/core/reporter.py,sha256=zDxFcHTZOAt4_PSlKzskhZhNhIJYxSh3jDtrUajisZo,1604
|
|
11
|
+
akii/core/template_loader.py,sha256=2I6PuqnXbEKaio18z5bwoNUzKwprRWRr5YS-PunzQ0M,276
|
|
12
|
+
akii/core/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
akii/core/cli/main.py,sha256=9ajb1ECik7rSILeFocMe7uwqLGE3um63xbD76ChanWM,280
|
|
14
|
+
akii/core/cli/parser.py,sha256=AePMLO68c2QEldi9b564dI8Kxca6tdXU9h62Did2pcE,1481
|
|
15
|
+
akii/core/flags/general.py,sha256=44HoTx_UEIoDOZu7_qDjx1Yv6YRPnaZXbEk7ZKLJ_aY,2498
|
|
16
|
+
akii/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
akii/http/client.py,sha256=9ixxK_8TsZieRE1EDv3pEmfRQlP2CoQV3aoHawmEPSs,615
|
|
18
|
+
akii/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
akii/ui/banner.py,sha256=y5XVUAcOVCKZxT6lED8JWEy9aNwXB9Y1MSuDgI8nGiI,1520
|
|
20
|
+
akii/ui/output.py,sha256=QBjSeGowLq1vljrHEYGWFTD8bViqMF9A2N-giSIkCvM,2885
|
|
21
|
+
akii-0.1.0.dist-info/METADATA,sha256=DbGLU81WMa8RDU7Enh9zjbTjrVk4VXO28r1BWXt1qTM,3108
|
|
22
|
+
akii-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
23
|
+
akii-0.1.0.dist-info/entry_points.txt,sha256=-PaisvSUKkbp23Kt7e7SPHgrmgUkrIBm1R6qVDGBvZ8,49
|
|
24
|
+
akii-0.1.0.dist-info/top_level.txt,sha256=jjtw8hsJ0J2yXXwWXSPzapmlne9mPUwnN0gXwtSbeoU,5
|
|
25
|
+
akii-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
akii
|