免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。
只供对已授权的目标使用测试,对未授权目标的测试作者不承担责任,均由使用本人自行承担。
文章正文
0x00 前言
在目标资产梳理的过程中,总能遇到一些IP的Whois信息中存在与目标的关联。
例如上图中218.108.40.56属于218.108.40.56 - 218.108.40.63,整个段的网络名称为xihu-***,描述为Hangzhou xihu ***,根据这个描述和名称推测与之相关联的极有可能是杭州市西湖区****。
这种关联给了我们另一种寻找目标资产的思路,即通过一定的手段处理目标资产名称为关键词,在数据库中查找与之相关联的IP段。但我在网络中没有寻找到适合红队进行此类资产梳理的工具(应该是我菜),因此决定自己动手。
不想看过程的师傅们可直接在下边下载工具用。
工具(开源)与数据库下载:后台回复“0321" 获取
总共两个功能,根据IP查询所属IP段信息、根据关键词查询IP段信息。
0x01 数据来源
要进行IP Whois信息的查询,首先得知道从哪里获取这些信息,通过搜索引擎一把梭得知:
IP地址的划分,有RIR机构来进行统筹管理。负责亚洲地区IP地址分配的,就是APNIC,总部位于澳大利亚墨尔本。各大RIR机构都提供了关于IP地址划分的登记信息,即whois记录。
APNIC提供了每日更新的亚太地区IPv4,IPv6,AS号分配的信息表,访问url是
http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest
向上级目录访问,并浏览一番后得知
http://ftp.apnic.net/apnic/whois/apnic.db.inetnum.gz
http://ftp.apnic.net/apnic/whois/apnic.db.inet6num.gz
这里存放着亚洲地区所有IP地址段信息。
那这就很方便了,免除了编写爬虫的过程,只需要读取下载的文件进行解析即可。
0x02 数据解析
解压后的apnic.db.inetnum文件有足足500m,直接对文本文件进行检索性能绝对有问题,那就需要对数据进行解析后存进数据库,在对数据库进行检索。为了方便到处跑,这里选自包含的、无服务的、零配置的sqlite3数据库
既然要存数据库,那必然得建表,这里写了个小脚本统计到底有多少像这样的列。
inetnums =[]
withopen("apnic.db.inetnum","rb")as f:
inetnums = f.read().split(b"n")
result =[]
for line in inetnums:
ifb": "in line:
key = line.split(b":")[0]
if key notin result:
result.append(key)
for each in result:
print(each.decode("UTF-8"))
inetnum
netname
country
descr
admin-c
tech-c
status
mnt-by
mnt-routes
last-modified
source
remarks
org
abuse-c
mnt-lower
mnt-irt
geoloc
language
主键就以inetnum中的ip段的起始ip的十进制数字形式存储为start字段,ip段结束ip存储为end字段,而不是以字符串形式存储ip段,这样会方便搜索ip属于哪个ip段。
为了方便搜索ip属于哪个ip段,这里添加ip段的起始ip的十进制数字形式存储为start字段,ip段结束ip为end字段。
一个IP段中可能存在多个descr,那么可以将多个重复项通过空格合并写入数据库的统一列,不影响数据搜索。
这就可以建表了,建表语句如下
CREATETABLE"ipseg"(
"start"INTEGERNOTNULLCOLLATEBINARY,
"end"INTEGERNOTNULL,
"inetnum" TEXT NOTNULLUNIQUE,
"netname" TEXT,
"country" TEXT,
"descr" TEXT,
"admin-c" TEXT,
"tech-c" TEXT,
"status" TEXT,
"mnt-by" TEXT,
"mnt-routes" TEXT,
"last-modified" TEXT,
"source" TEXT,
"remarks" TEXT,
"org" TEXT,
"abuse-c" TEXT,
"mnt-lower" TEXT,
"mnt-irt" TEXT,
"geoloc" TEXT,
"language" TEXT,
PRIMARY KEY("inetnum")
);
再写一个小脚本负责解析并导入sqlite
import sqlite3
importIPy
whoisinfo =[]
def analysis_whois(whois_str):
start=0
end=0
inetnum =""
netname =""
country =""
descr =""
admin_c =""
tech_c =""
status =""
mnt_by =""
mnt_routes =""
last_modified =""
source =""
remarks =""
org =""
abuse_c =""
mnt_lower =""
mnt_irt =""
geoloc =""
language=""
for _line in whois_str.split(b"n"):
if _line == b"":
continue
line = _line.decode(encoding='UTF-8',errors='ignore')
line = line.replace(""","")
if"inetnum:"in line:
inetnum = line.split(":")[1].strip()
#print(inetnum)
if" - "notin inetnum:
continue
ip = inetnum.split(" - ")
start =int(IPy.IP(ip[0]).strDec())
end=int(IPy.IP(ip[1]).strDec())
elif"netname:"in line:
netname = line.replace("netname:","").strip()
elif"country:"in line:
country = line.replace("country:","").strip()
elif"descr:"in line:
descr += line.replace("descr:","").strip()+" "
elif"admin-c:"in line:
admin_c += line.replace("admin-c:","").strip()+" "
elif"tech-c:"in line:
tech_c += line.replace("tech-c:","").strip()+" "
elif"status:"in line:
status += line.replace("status:","").strip()+" "
elif"mnt-by:"in line:
mnt_by += line.replace("mnt-by:","").strip()+" "
elif"mnt-routes:"in line:
mnt_routes += line.replace("mnt-routes:","").strip()+" "
elif"last-modified:"in line:
last_modified += line.replace("last-modified:","").strip()+" "
elif"source:"in line:
source += line.replace("source:","").strip()+" "
elif"remarks:"in line:
remarks += line.replace("remarks:","").strip()+" "
elif"org:"in line:
org += line.replace("org:","").strip()+" "
elif"abuse-c:"in line:
abuse_c += line.replace("abuse-c:","").strip()+" "
elif"mnt-lower:"in line:
mnt_lower += line.replace("mnt-lower:","").strip()+" "
elif"mnt-irt:"in line:
mnt_irt += line.replace("mnt-irt:","").strip()+" "
elif"geoloc:"in line:
geoloc += line.replace("geoloc:","").strip()+" "
elif"language:"in line:
language += line.replace("language:","").strip()+" "
if" - "notin inetnum or"."notin inetnum:
return
sql ="""INSERTINTO ipseg(start,end,inetnum,netname,country,descr,"admin-c","tech-c",status,"mnt-by","mnt-routes","last-modified",source,remarks,org,"abuse-c","mnt-lower","mnt-irt",geoloc,language) VALUES ( """+""{}","*19+""{}" )"
sql = sql.format(start,end,inetnum,netname,country,descr[:-1],admin_c[:-1],tech_c[:-1],status[:-1],mnt_by[:-1],mnt_routes[:-1],last_modified[:-1],source[:-1],remarks[:-1],org[:-1],abuse_c[:-1],mnt_lower[:-1],mnt_irt[:-1],geoloc[:-1],language[:-1])
try:
conn.execute(sql)
exceptBaseExceptionas e:
print(inetnum,"error",e,descr)
conn = sqlite3.connect("IP.db")
with open("apnic.db.inetnum","rb")as f:
whoisinfo = f.read().split(b"nn")
count =0
for info in whoisinfo:
analysis_whois(info)
if count %1000==0:
conn.commit()
count +=1
conn.close()
运行后看起来效果还不戳。
不想麻烦的师傅们可以直接进github的Release找到IP.zip下载。
数据库有了,接下来就是查询工具的实现。
语言方面我选择能一次编码到处运行的Golang。
0x03 IP查询IP段
在线版IPwhois
http://ip.webmasterhome.cn/ipwhois.asp?ip=1.1.1.1
这个功能是为了实现IPWhois的离线版,解决在线查询目标资产大量IP会比较慢,也会给服务器造成负担的问题。
前边建表的时候为了方便此功能,增加了start与end字段,这时候该派上用场了。
比如我要搜索222.222.222.222的IP段信息,转换得到十进制IP为3739147998,那就搜索start比这个小于等于,end比这个大于等于的ip段。
出现了三个包含目标的IP段,选范围最小最精细的。那就用end-start排序一下,最后只输出最小的那个。
最终sql语句与代码如下
select inetnum,netname,country,descr,status,"last-modified"from ipseg wherestart<=3739147998andend>=3739147998orderbyend-startASC limit 0,1;
funcqueryInfoByIP(ip int, db *sql.DB){
var inetnum string
var netname string
var country string
var descr string
var status string
var last_modified string
query :="select inetnum,netname,country,descr,status,"last-modified" from ipseg where start <= %d and end >= %d order by end-start ASC limit 0,1;"
query = fmt.Sprintf(query, ip, ip)
rows, err := db.Query(query)
if err !=nil{
log.Fatal(err)
}
defer rows.Close()
for rows.Next(){
err = rows.Scan(&inetnum,&netname,&country,&descr,&status,&last_modified)
if err !=nil{
log.Fatal(err)
}
}
fmt.Println("IP段:", inetnum)
fmt.Println("名称:", netname)
fmt.Println("描述:", descr)
fmt.Println("国家:", country)
fmt.Println("状态:", status)
fmt.Println("最后修改:", last_modified)
}
0x04 关键词查询IP段
一般与目标名称关联的信息在descr与netname中,只要信息中出现关键词即可输出。
sql语句很简单。
select inetnum,netname,descr from ipseg where descr like"%key%"or netname like"%key%";
多个key查询只需要稍稍构造下sql语句。
funcqueryInfoByKey(keys []string, db *sql.DB){
query :="select inetnum,netname,descr from ipseg where "
descrQuery :="("
for _, key :=range keys {
descrQuery += fmt.Sprintf("descr like "%%%s%%" and ", key)
}
descrQuery = descrQuery[:len(descrQuery)-5]+") "
query += descrQuery
query +="or "
netnameQuery :="("
for _, key :=range keys {
netnameQuery += fmt.Sprintf("netname like "%%%s%%" and ", key)
}
netnameQuery = netnameQuery[:len(netnameQuery)-5]+")"
query += netnameQuery +";"
rows, err := db.Query(query)
if err !=nil{
log.Fatal(err)
}
defer rows.Close()
count :=0
for rows.Next(){
var inetnum string
var netname string
var descr string
err = rows.Scan(&inetnum,&netname,&descr)
if err !=nil{
log.Fatal(err)
}
count +=1
fmt.Println("序号:", count)
fmt.Println("IP段:", inetnum)
fmt.Println("名称:", netname)
fmt.Println("描述:", descr,"n")
// 限制数量
if count >2000{
break
}
}
}
搜开头对应的西湖****IP段就直接这么调用
queryInfoByKey([]string{"xihu","***"}, db)
可见还是可以获取到目标IP段的,但会有奇怪的东西混进来,需要人工筛选。
0x05 使用场景
场景一
护网中拿到目标名称为杭州市西湖区***,根据西湖可查询xihu.
使用IPSearch直接搜索相关IP段。
coco@Mac IPSearch%./IPSearch-k xihu,***,***
序号:1
IP段:218.108.40.56-218.108.40.63
名称: xihu-***
描述:Hangzhou xihu ***.
序号:2
IP段:222.133.244.192-222.133.244.207
名称: LCZFXXH
描述: liaocheng***ermentxinxihua
序号:3
IP段:61.164.41.224-61.164.41.255
名称: XIHU-***-INFORMATION-CENTER
描述: XIHU District***ernment InformationCenter
序号:4
IP段:58.241.40.64-58.241.40.127
名称: XINXIHUABANGONGSHI
描述: XINXIHUABANGONGSHI,WUXI,JIANGSU PROVINCE
序号:5
IP段:115.238.92.224-115.238.92.239
名称: HZ-XIHU
描述:The people's ***ernment of Hangzhou City,Xihu District
可以得到五个结果,通过对描述的详细观察,序号为1,5的IP段内的IP极大概率为杭州市西湖区的资产。
场景二
演练中收集到一个与目标关联的IP,也许会查看当前IP下的所有资产是否与目标有关,但这个C段范围也许太大了,很可能耗费大量时间却打偏了。但将收集到的ip塞入IPSearch工具查询,通过ip信息查找相同段的资产,再看描述先过滤一次能降低日偏的风险,节约宝贝的攻击时间。
技术交流
原文始发于微信公众号(Z2O安全攻防):根据IP地址登记信息进行目标信息收集
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论