由奇安信集团、清华大学网络研究院、蚂蚁集团、腾讯安全大数据实验室、Coremail论客主办的DataCon2022大数据安全分析竞赛线上赛和决赛已圆满落幕,五大赛道第一名也已各归其主。今天要为大家分享的是域名体系安全赛道排名第一的NDNS战队writeup。
来自国防科技大学DNSLab的NDNS战队组建于2021年,指导老师是许成喜老师。NDNS战队依托国防科技大学网络空间测绘科研团队,主要成员为国防科技大学在读硕博士研究生,关注互联网基础设施安全及测量、网络黑产检测等领域的前沿技术与研究。成员们主要研究方向为DNS安全、黑灰产检测及异常检测、网络空间测绘、机器学习、数据挖掘等。NDNS战队曾获DataCon2021 域名体系安全第二名、2021强网杯人工智能挑战赛口令猜解第二名。
欢迎收藏转发,随时浏览查看]
第一部分 域名分类
一、背景知识
○ 具有大量的 CNAME 记录,服务商的 SLD 下具有大量的 FQDN,IP 数量多,地理分布广泛(这个特征再比赛数据中并不明显)
○ 判定指标:SLD 的 CNAME 记录数大于 N(100),解析 IP 地理位置数量大于 M(0/2)
○ 推测为监管域名,根据已有研究[1],监管域名的响应记录由两个特性
■ 响应 IP 的 AS 信息比较集中,常见为 Google,Twitter,Facebook 和 Dropbox
■ 响应 IP 的数量有限
○ 判定指标:IP ASO 为以上四种,且每个 IP 解析域名数量大于 14W。
○ 具有较多子域名,每个子域名解析的 IP 不固定
○ parking 服务商较集中,一个 IP 解析大量域名,且统一服务商的对每个域名的解析策略是一致的,如对任何 parked 域名响应 12 个IP
○ 判定指标:单个 IP 解析域名大于 N,为特定的 parking 服务商,且解析策略一致
○ 服务商较集中,具有大量子域名,单一 IP 解析大量的 FQDN,取决于服务商大小,IP 数量有限
○ 判定指标:单个 IP 解析域名大于 N,特定的 hosting 服务商
○ sinkhole 的 IP 数量有限,可能一个 IP 解析大量的恶意域名
○ 单一域名解析多个 IP 地址
○ 单一域名解析大量 IP 地址,同时每个 IP 上解析量分布是均匀的
import csv
import os
SLD_Ccount = {}
i=0
file_names = os.listdir('./datacon_cdn_basic')
for file_name in file_names:
i+=1
file_path = os.path.join('./datacon_cdn_basic',file_name)
print('Processing with '+ str(i) +' file!')
with open(file_path, encoding='utf-8-sig') as f:
f_csv = csv.reader(f)
#headers = next(f_csv) #headers:['sld_md5', 'all_md5_fqdn', 'request_cnt', 'rtype', 'all_md5_rdata', 'country_code', 'country_name', 'region_name', 'asn_sort']
#['86f066f08908de0c617243cb33081050', '2ddfe6b1ebd12b3d1807eff0274d883c.86f066f08908de0c617243cb33081050', '1', '5', 'ccd4d6df14af7f4215db6b77945fc02a.95074d472f19c7108ee72233cd65f543', 'null', 'null', 'null', '0']
for row in f_csv:
if row[3]=='5':
if row[4]:
sld = row[4].split('.')[-1]
if sld in SLD_Ccount:
SLD_Ccount[sld] += 1
else:
SLD_Ccount[sld] = 1
SLD_Ccount_sorted = dict(sorted(SLD_Ccount.items(), key=lambda x: x[1], reverse=True))
file1 = open('./mydata/SLD_Ccount_sorted.csv', 'w')
writer = csv.writer(file1)
count_all = 0
for k,v in SLD_Ccount_sorted.items():
writer.writerow([k,v])
count_all += v
print(count_all)
print('Success!')
#共 29581826 条 CNAME 记录
2. 统计 SLD 的所有 FQDN 数量。
import csv
import os
CDN_list1 = []
CDN_fqdn_count = {}
fqdn_count = {}
with open('./mydata/SLD_Ccount_sorted.csv', encoding='utf-8-sig') as f:
f_csv = csv.reader(f)
for row in f_csv:
#if int(row[1]) >= 397:
CDN_list1.append(row[0])
for sld in CDN_list1:
CDN_fqdn_count[sld] = 0
i=0
file_names = os.listdir('./datacon_cdn_basic')
for file_name in file_names:
i+=1
file_path = os.path.join('./datacon_cdn_basic',file_name)
print('Processing with '+ str(i) +' file!')
with open(file_path, encoding='utf-8-sig') as f:
f_csv = csv.reader(f)
for row in f_csv:
sld = row[0]
if sld in CDN_list1:
fqdn = row[1]
if fqdn in fqdn_count:
fqdn_count[fqdn] += 1
else:
fqdn_count[fqdn] = 1
CDN_fqdn_count[sld] += 1
CDN_fqdn_count_sorted = dict(sorted(CDN_fqdn_count.items(), key=lambda x: x[1], reverse=True))
file1 = open('./mydata/CDN_fqdn_count_sorted.csv', 'w')
writer = csv.writer(file1)
count_all = 0
for k,v in CDN_fqdn_count_sorted.items():
writer.writerow([k,v])
count_all += v
print(count_all)
print('Success!')
3. 统计 FQDN 的解析 IP 分布,即:统计 FQDN 对应的 A 记录地区分布及数量。
import csv
import os
CDN_list1 = []
fqdn_region_list = {}
fqdn_region_count = {}
with open('./mydata/SLD_Ccount_sorted.csv', encoding='utf-8-sig') as f:
f_csv = csv.reader(f)
for row in f_csv:
CDN_list1.append(row[0])
#print(CDN_list1)
i=0
file_names = os.listdir('./datacon_cdn_basic')
for file_name in file_names:
i+=1
file_path = os.path.join('./datacon_cdn_basic',file_name)
print('Processing with '+ str(i) +' file!')
with open(file_path, encoding='utf-8-sig') as k:
k_csv = csv.reader(k)
for row in k_csv:
if row[3]=='1':
sld = row[0]
if sld in CDN_list1:
fqdn = row[1]
region = row[7]
if fqdn in fqdn_region_list:
if region in fqdn_region_list[fqdn]:
pass
else:
fqdn_region_list[fqdn].append(region)
else:
fqdn_region_list[fqdn] = [region]
for k,v in fqdn_region_list.items():
fqdn_region_count[k]=len(v)
fqdn_region_count_sorted = dict(sorted(fqdn_region_count.items(), key=lambda x: x[1], reverse=True))
file1 = open('./mydata/fqdn_region_count_sorted.csv', 'w')
writer = csv.writer(file1)
for k,v in fqdn_region_count_sorted.items():
writer.writerow([k,v])
print('Success!')
4. 将 FQDN 的地区分布情况进行合并,得到 SLD 的地区分布情况及数量。
import csv
sld_region_list = {}
sld_region_count = {}
for k,v in fqdn_region_list.items():
sld = k.split('.')[-1]
if sld in sld_region_list:
for _ in v:
if _ in sld_region_list[sld]:
pass
else:
sld_region_list[sld].append(_)
else:
sld_region_list[sld] = v
for k,v in sld_region_list.items():
sld_region_count[k]=len(v)
sld_region_count_sorted = dict(sorted(sld_region_count.items(), key=lambda x: x[1], reverse=True))
file1 = open('./mydata/sld_region_count_sorted.csv', 'w')
writer = csv.writer(file1)
for k,v in sld_region_count_sorted.items():
writer.writerow([k,v])
print('Success!')
5. 提取非 CNAME 记录相关的 FQDN 的 IP 统计信息。
cdn_name_path = "./mydata_level2/SLD_CNAME_count_sorted.csv"
fp = open(cdn_name_path,"r")
cdns = fp.readlines()
cdns = [ci.split(",")[0] for ci in cdns][:200]
fp.close()
file_names = os.listdir('./cdn_level2/datacon_cdn_ad')
# fo = open("./mydata_level2/level2_sld_geo_data.txt","w")
sld_dict = {}
for file_name in tqdm(file_names):
file_path = os.path.join('./cdn_level2/datacon_cdn_ad',file_name)
with open(file_path,"r") as fp:
for line in fp:
if line.startswith("sld,md5_fqdn"):
continue
line = line.strip().split(",")
try:
if line[3]=="5" and (line[0] in cdns or ".".join(line[4].split(".")[1:]) in cdns):
continue
except Exception as e:
print(e)
pass
if line[0] not in sld_dict:
sld_dict[line[0]] = [0,0,0,0,set(),set(),set()]# FQDN, A,CNAME, 被 CNAME, geo,IP,ASO
if line[3]=="5":
sld_dict[line[0]][2] += 1
if ".".join(line[4].split(".")[1:]) not in sld_dict:
sld_dict[".".join(line[4].split(".")[1:])] = [0,0,0,0,set(),set(),set()]
sld_dict[".".join(line[4].split(".")[1:])][3] += 1
if line[3]=="1" and line[0] != line[1]:
sld_dict[line[0]][0] += 1
sld_dict[line[0]][4].update([line[10]])
sld_dict[line[0]][5].update([line[4]])
sld_dict[line[0]][6].update([line[6]])
if line[3]=="1" and line[0] ==line[1]:
sld_dict[line[0]][1] += 1
sld_dict[line[0]][4].update([line[10]])
sld_dict[line[0]][5].update([line[4]])
sld_dict[line[0]][6].update([line[6]])
sld_dict_sorted = dict(sorted(sld_dict.items(), key=lambda x: x[1], reverse=True))
fo = open("./mydata_level2/sld_fqdn_a_canme_r_ip_aso.txt","w")
for key in tqdm(sld_dict_sorted):
fo.write(",".join([key]+list(map(str,sld_dict_sorted[key][:4]+[len(sld_dict_sorted[key][4])]+[len(sld_dict_sorted[key][5])]+[len(sld_dict_sorted[key][6])])))+"n")
fo.close()
6. 统计每个 IP 解析的域名数量,并标记判定类别。
# step 统计每个 IP 的解析域名的数量,并标记已知的类别
fp = open("./mydata_level2/level2_sld_geo_data.txt","r")
cdn_ips = {}
hidden_ips = {}
other_ips = {}
for line in tqdm(fp):
line = line.split(",")
if line[0] in bloom_cdn:
if line[1] not in cdn_ips:
cdn_ips[line[1]] = 1
else:
cdn_ips[line[1]] += 1
elif line[0] in bloom_hidden:
if line[1] not in hidden_ips:
hidden_ips[line[1]] = 1
else:
hidden_ips[line[1]] += 1
else:
if line[1] not in other_ips:
other_ips[line[1]] = 1
else:
other_ips[line[1]] += 1
fo = open("./mydata_level2/all_ip_count.txt","w")
cdn_ips_sorted = dict(sorted(cdn_ips.items(), key=lambda x: x[1], reverse=True))
hidden_ips_sorted = dict(sorted(hidden_ips.items(), key=lambda x: x[1], reverse=True))
other_ips_sorted = dict(sorted(other_ips.items(), key=lambda x: x[1], reverse=True))
for key in cdn_ips_sorted:
fo.write(key+","+str(cdn_ips_sorted[key])+","+"1"+"n")
for key in hidden_ips_sorted:
fo.write(key+","+str(hidden_ips_sorted[key])+","+"2"+"n")
for key in other_ips_sorted:
fo.write(key+","+str(other_ips_sorted[key])+","+"0"+"n")
fo.close()
域名类型判定
○ 取SLD的CNAME 记录数大于 100,解析IP地理位置数量大于 0,level 1 得分:100
○ 取SLD的CNAME 记录数大于 100,解析IP地理位置数量大于 2,level 2 得分:22
○ IP ASO 为以上四种,且每个 IP 解析域名数量大于 14W, 得分:24
import os
from tqdm import tqdm
from pybloom_live import ScalableBloomFilter, BloomFilter
# step 0 提取特定 ASO 信息的 IP 地址
# step 1 统计每个 IP 解析的 FQDN 数
fp = open("./answer/cdn_level2/result.txt","r")
hdomains = fp.readlines()
hdomains = [hi.strip().split("t")[1] for hi in hdomains]
print(hdomains[9])
file_names = os.listdir('./cdn_level2/datacon_cdn_ad')
ip_dict = {}
for file_name in tqdm(file_names):
file_path = os.path.join('./cdn_level2/datacon_cdn_ad',file_name)
with open(file_path,"r") as fp:
for line in tqdm(fp):
if line.startswith("sld,md5_fqdn"):
continue
line = line.strip().split(",")
if line[3]=="5":
continue
if line[3] == "1" and line[0] in hdomains:
if line[4] not in ip_dict:
ip_dict[line[4]] = 0
ip_dict[line[4]] += 1
ip_dict_sorted = dict(sorted(ip_dict.items(), key=lambda x: x[1], reverse=True))
fo = open("./mydata_level2/hidden_ip_fqdn_count_sorted.txt","w")
for key in ip_dict_sorted:
fo.write(key+","+str(ip_dict_sorted[key])+"n")
fo.close()
# step 2 取前 591 个 IP,这些 IP 每个平均解析了 14 万的域名,找出其解析过的 SLD
fp = open("./mydata_level2/hidden_591.ip","r")
hips = fp.readlines()
hips = [hi.strip() for hi in hips]
file_names = os.listdir('./cdn_level2/datacon_cdn_ad')
sld_dict = set()
for file_name in tqdm(file_names):
file_path = os.path.join('./cdn_level2/datacon_cdn_ad',file_name)
with open(file_path,"r") as fp:
for line in tqdm(fp):
if line.startswith("sld,md5_fqdn"):
continue
line = line.strip().split(",")
if line[3]=="5":
continue
if line[3] == "1" and line[4] in hips:
sld_dict.update([line[0]])
fo = open("./mydata_level2/hidden_ip_591_to_sld.txt","w")
for si in list(sld_dict):
fo.write(si+"n")
● domain parking,webhosting 和动态域名
○ 统计每个 IP 解析的域名数量,及 SLD 的 QFDN 数量,之后进行服务商的判定
○ domain parking 和 webhosting 设定每个 IP 解析域名数量大于 1000
○ 动态域名设定每个 IP 的解析域名小于 3 个,且对应 SLD 的 FQDN 数量大于 20,同时收集动态域名列表进行匹配[2]
○ 得分:9
[ ]
[ ]
第二部分 域名接管
一、背景知识
DMARC 邮件安全协议
v=DMARC1; p=none; fo=1; ruf=mailto:[email protected]; rua=mailto:[email protected]
其中和本题目相关的,rua:用于接收收件方对该来源邮件检测完毕后,生成的汇总报告。因此可以通过探测该类的子域名解析记录,找到疑似的 CDN 平台管理员账户.
● 一台公网 DNS 服务器
【level1】
;; QUESTION SECTION:
;datacon2022.secrank.cn. IN A
;; ANSWER SECTION:
datacon2022.secrank.cn. 30 IN CNAME admin.datacon.cdn.
【level2】
;; ANSWER SECTION:
datacon2022.secrank.cn. 30 IN CNAME admin.datacon.cdn.
;; ANSWER SECTION:
secrank.cn. 60 IN TXT "5ca162f89d93a4e7e5661a4b290b6d33"
要接管的子域名的 CNAME 解析为 admin.datacon.cdn.,直接设置 CDN 记录,即可完成验证。
最后修改并验证 HTTP 页面的指定KEY值,获得 flag。
【level3】
查询 datacon2022.secrank.cn 的 A 记录、secrank.cn 的 TXT 记录,情况如下:
;; ANSWER SECTION:
datacon2022.secrank.cn. 30 IN CNAME admin.datacon.cdn.
;; ANSWER SECTION:
secrank.cn. 60 IN TXT "fdf82d037bff4fbb42495e6232c1d7f3"
重置环境不变,说明 md5 是使用了固定的加盐值(也可能没有加盐),和某些固定参数计算出来的。
用户名 aaa [email protected] admin.datacon.cdn
secrank.cn TXT e1f6c5df7297f459d2eae4783361613a
用户名 a [email protected] admin.datacon.cdn
secrank.cn TXT 982a0de018edf5bf12d042284629106a
用户名 admin [email protected] admin.datacon.cdn
secrank.cn TXT fdf82d037bff4fbb42495e6232c1d7f3
用户名 admin [email protected] admin1.datacon.cdn
secrank.cn TXT fdf82d037bff4fbb42495e6232c1d7f3
用户名 admin [email protected] dgdsag.datacon.cdn
secrank.cn TXT fdf82d037bff4fbb42495e6232c1d7f3
上述及其他测试结果,表明生成 归属权验证码 MD5值的原字符串至少包含了:
● CDN 服务 注册邮箱
● CND 服务 自定义域名的etld
可能存在
● 固定盐值(可能每道题不同)
● 且该值和以下值无关
● 自定义域名的三级域名
● 提供 CDN 服务的子域名
● 生成时间等变化参数
datacon2022.secrank.cn. 30 IN CNAME admin.datacon.cdn.
admin.datacon.cdn. 3600 IN A xxx.xxx.xxx.xxx(IP)
【level4】
● 本题提示:datacon2022.secrank.cn 的管理员一直在使用了本站服务,并未注销用户,并且他的邮箱也不再是 [email protected]
● 改为使用_verify.secrank.cn 的 TXT 记录验证域名的归属权
● 实际测试,使用了 DMARC 邮件安全协议,造成了关键用户信息泄露
● 对 MD5 生成规则猜测的实际验证和利用,起到了关键性作用
_verify.secrank.cn. 86400 IN TXT "f3c5e853dbd3c50b66d1545aaa074b86"
_dmarc.secrank.cn. 86400 IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]"
以第三题对MD5 的归属权验证码生成规则的对照测试结果为标准,在被攻击域名 datacon2022.secrank.cn 不变的条件下,猜测 MD5 生成规则为固定数据+邮箱+用户名+固定数据的形式,顺此思路,针对 [email protected] + da1ef3b3 的组合注册如下账号:
邮箱:[email protected]
用户名:nda1ef3b3
【level5】
$TTL 1D
@ IN SOA _verify.secrank.cn. rname.invalid. (
0 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum
NS _verify.secrank.cn.
A xxx.xxx.xxx.xxx(攻击者 DNS IP 地址)
AAAA ::1
TXT "b6406817af968a444aa934fc2e0c6f3f"
使用本地DNS服务器再次查询TXT记录,确认缓存污染完毕,完成归属权验证。
● 服务过期后,历史记录如何处理的问题
● 验证逻辑的完善性
● 验证码的强度问题,与用户信息相关,但也应该加入随机因素
● DNS服务器解析流程的缓存策略问题
微信号:DataConofficial
获取更多大数据安全知识
进群还有超多活动、福利
DataCon定制服饰、背包等精彩好礼
等你来拿!
原文始发于微信公众号(DataCon大数据安全分析竞赛):冠军Writeup大放送 | DataCon2022域名体系安全赛道之“NDNS”战队