Skip to content

Commit ee54026

Browse files
committed
gendkimmails: Script to generate DKIM vuln disclosure reports
1 parent 4a6f194 commit ee54026

2 files changed

Lines changed: 182 additions & 0 deletions

File tree

disclosure/gendkimmails

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env python3
2+
#
3+
# SPDX-License-Identifier: 0BSD
4+
# Part of badkeys: https://badkeys.info/
5+
6+
import argparse
7+
import email.message
8+
import json
9+
import os
10+
import pathlib
11+
import sys
12+
import tomllib
13+
14+
import badkeys
15+
import jinja2
16+
from sectxtparse import getreportingemails
17+
18+
msgs = {
19+
"blocklist/debianssl": """
20+
Key(s) created with an OpenSSL version vulnerable to the Debian
21+
OpenSSL bug (CVE-2008-0166): https://badkeys.info/docs/debian.html
22+
""",
23+
"blocklist/git": """
24+
The private key was found in a public Git repository:
25+
https://badkeys.info/docs/publicprivate.html
26+
""",
27+
"blocklist/pkg": """
28+
The private key is part of a software package:
29+
https://badkeys.info/docs/publicprivate.html
30+
""",
31+
"blocklist/softwaretests": """
32+
The private key is part of a software package:
33+
https://badkeys.info/docs/publicprivate.html
34+
""",
35+
"blocklist/rfc": """
36+
The private key is an example key in an IETF RFC or draft:
37+
https://badkeys.info/docs/publicprivate.html
38+
""",
39+
"smallfactors/valid": """
40+
The RSA key has a small prime factor, the private key can be trivially
41+
recovered.
42+
""",
43+
"privleak": """
44+
The DKIM record contains a private key instead of a public key.
45+
""",
46+
"smallrsa": """
47+
The RSA key has a key size of 512 bits or less. Such keys can be attacked
48+
within hours on a modern computer: https://badkeys.info/docs/keysize.html
49+
"""
50+
}
51+
52+
if __name__ == "__main__":
53+
54+
ap = argparse.ArgumentParser()
55+
ap.add_argument("logfile")
56+
ap.add_argument("-d", "--debug", action="store_true")
57+
ap.add_argument("--store", help="Store intermediate JSON data")
58+
ap.add_argument("--load", help="Load intermediate JSON data")
59+
ap.add_argument("-o", "--outdir", default="mails-dkim")
60+
args = ap.parse_args()
61+
62+
with open(os.path.expanduser("~/.badkeystools"), "rb") as fp:
63+
bkconfig = tomllib.load(fp)
64+
65+
if not args.store and os.path.exists(args.outdir):
66+
sys.exit(f"Output directory {args.outdir} already exists!")
67+
68+
issues = {}
69+
if args.load:
70+
jissues = pathlib.Path(args.load).read_text()
71+
issues = json.loads(jissues)
72+
else:
73+
with open(args.logfile) as f:
74+
for line in f:
75+
kdata = json.loads(line)
76+
if ("smallfactors" in kdata["results"]
77+
and kdata["results"]["smallfactors"]["subtest"] == "corrupt"):
78+
# ignore corrupt RSA keys
79+
continue
80+
issuetype = None
81+
if kdata["type"] == "rsa" and kdata["bits"] <= 512:
82+
issuetype = "smallrsa"
83+
keyid = "smallrsa"
84+
if kdata["type"] == "unparseable" and kdata["reason"] == "privleak":
85+
issuetype = "privleak"
86+
keyid = "privleak"
87+
if kdata["results"]:
88+
if len(kdata["results"]) > 1:
89+
print(f"NOTE: More than 1 result for {kdata['where']}")
90+
issuetype = next(iter(kdata["results"]))
91+
if "subtest" in kdata["results"][issuetype]:
92+
issuetype += "/" + \
93+
kdata["results"][issuetype]["subtest"]
94+
keyid = kdata["spkisha256"]
95+
if not issuetype:
96+
continue
97+
98+
# data structure:
99+
# {[repemail]: {
100+
# [issuetype]: {
101+
# [keyid]: [
102+
# ...,
103+
hostname = kdata["where"].split(
104+
"._domainkey.")[-1].split("[")[0]
105+
repemail = ",".join(getreportingemails(hostname))
106+
if repemail not in issues:
107+
issues[repemail] = {}
108+
if issuetype not in issues[repemail]:
109+
issues[repemail][issuetype] = {}
110+
if keyid not in issues[repemail][issuetype]:
111+
issues[repemail][issuetype][keyid] = []
112+
issues[repemail][issuetype][keyid].append(kdata)
113+
114+
if args.store:
115+
pathlib.Path(args.store).write_text(json.dumps(issues))
116+
sys.exit(1)
117+
118+
os.mkdir(args.outdir)
119+
120+
tp = os.path.join(os.path.dirname(__file__), "templates/dkim.txt")
121+
rawtemplate = pathlib.Path(tp).read_text()
122+
mailtemplate = jinja2.Template(rawtemplate)
123+
124+
for emailaddress, issuedata in issues.items():
125+
126+
content = ""
127+
mainrec = None
128+
for issuetype, keys in issuedata.items():
129+
for key in keys.values():
130+
hostnames = []
131+
for hv in key:
132+
hostnames.append(hv["where"].split("/")[-1].split("[")[0])
133+
if not mainrec:
134+
mainrec = hostnames[0]
135+
content += "Affected DKIM key(s):\n"
136+
content += "\n".join(hostnames) + "\n"
137+
if issuetype in msgs:
138+
content += "\nVulnerability type:" + msgs[issuetype] + "\n"
139+
else:
140+
print(f"WARNING: no description for {issuetype}")
141+
content += issuetype
142+
143+
if issuetype.startswith("blocklist/"):
144+
content += "You can find the private key here:\n"
145+
blid = key[0]["results"]["blocklist"]["blid"]
146+
lookup = key[0]["results"]["blocklist"]["lookup"]
147+
_, keyurl = badkeys.allkeys.urllookup(blid, lookup)
148+
content += f"{keyurl}\n"
149+
150+
if issuetype == "smallfactors/valid":
151+
p = int(key[0]["results"]["smallfactors"]["p"], 16)
152+
q = int(key[0]["results"]["smallfactors"]["q"], 16)
153+
e = int(key[0]["e"], 16)
154+
n = int(key[0]["n"], 16)
155+
privatekey = badkeys.rsakeys.rsarecover(p=p, q=q, n=n, e=e)
156+
content += f"The private key is:\n{privatekey}\n"
157+
158+
mailcontent = mailtemplate.render(
159+
content=content, footer=bkconfig["DISCLOSURE_FOOTER"])
160+
161+
msg = email.message.EmailMessage()
162+
msg.set_content(mailcontent, cte="quoted-printable")
163+
msg["Subject"] = f"Insecure DKIM key at {mainrec}"
164+
msg["To"] = emailaddress
165+
msg["From"] = bkconfig["DISCLOSURE_FROM"]
166+
if "DISCLOSURE_CC" in bkconfig:
167+
msg["Cc"] = bkconfig["DISCLOSURE_CC"]
168+
fp = os.path.join(args.outdir, emailaddress)
169+
pathlib.Path(fp).write_text(str(msg))

disclosure/templates/dkim.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Dear Sir or Madam,
2+
3+
I would like to report a security issue in your DKIM E-Mail setup.
4+
5+
{{content}}
6+
7+
You should replace the key(s) and remove the affected DNS record(s).
8+
9+
This vulnerability was discovered with badkeys: https://badkeys.info/
10+
11+
Yours faithfully,
12+
--
13+
{{footer}}

0 commit comments

Comments
 (0)