In the case study, we are going to learn how to conduct threat intelligence mapping that stems from a specific ASN and examine company metadata information. This tutorial will make use of Business tier API service data, which includes:
import requests # For communicating to the IPinfo API
import pandas as pd # for dataframe stuff
import duckdb as db # easier to write SQL than pandas code sometimes
import netaddr # aggregating IPs to cidr/range
The following IP address is something I found from honeypot
target_ip_address = '200.52.65.31'
url = f'https://ipinfo.io/{target_ip_address}?token={token}'
ip_data = requests.get(url).json()
Getting a closer look at the target IP address
ip_data
{'ip': '200.52.65.31',
'hostname': 'service-static-52.65.31.mcm-telecom.com.mx',
'city': 'Mexico City',
'region': 'Mexico City',
'country': 'MX',
'loc': '19.4285,-99.1277',
'postal': '03020',
'timezone': 'America/Mexico_City',
'asn': {'asn': 'AS14178',
'name': 'Megacable Comunicaciones de Mexico, S.A. de C.V.',
'domain': 'mcmtelecom.com',
'route': '200.52.65.0/24',
'type': 'isp'},
'company': {'name': 'Help Desk Solution de Mexico',
'domain': 'e-net.com.mx',
'type': 'business'},
'privacy': {'vpn': False,
'proxy': False,
'tor': False,
'relay': False,
'hosting': False,
'service': ''},
'abuse': {'address': 'Help Desk Solution de Mexico Juan Escutia 32, Col. Condesa Mexico City, DF, 111,, 00000 - Ciudad - ME',
'country': 'MX',
'email': 'msalas@e-net.com.mx',
'name': 'Miguel Salas',
'network': '200.52.65.0/26',
'phone': '+52 5252111444'},
'domains': {'page': 0, 'total': 0, 'domains': []}}
As IPinfo’s IP-company metadata comes from WHOIS records, it is not clear who actually owns the IP address. But we can take a closer look:
- Check the ASN (Optional)
- Check the ASN route information
- Identify other organizations with the route
target_route = ip_data['asn']['route']
target_route
'200.52.65.0/24'
target_asn_address = ip_data['asn']['asn']
target_asn_address
'AS14178'
target_company = ip_data['company']
target_company
{'name': 'Help Desk Solution de Mexico',
'domain': 'e-net.com.mx',
'type': 'business'}
url = f'https://ipinfo.io/{target_asn_address}?token={token}'
asn_data = requests.get(url).json()
Preview the IPv4 prefixes owned by the target ASN.
as_prefixes = [prefix['netblock'] for prefix in asn_data['prefixes']]
print("\n".join(as_prefixes[:5]))
131.161.56.0/22
131.161.56.0/23
131.161.56.0/24
131.161.57.0/24
131.161.58.0/23
Getting the very high level netblock information from the ASN data
for prefix in asn_data['prefixes']:
if prefix['netblock'] == target_route:
as_prefix_data = prefix
# There is only going to be one match. There is no need to continue checking.
break
# Preview data
as_prefix_data
{'netblock': '200.52.65.0/24',
'id': 'IPA',
'name': 'Megacable Comunicaciones de Mexico, S.A. de C.V.',
'country': 'MX',
'size': '256',
'status': 'REALLOCATED',
'domain': 'mcm.net.mx'}
If you notice something curious, it is that the information shown above does not match the information we have on the company payload.
target_company
{'name': 'Help Desk Solution de Mexico',
'domain': 'e-net.com.mx',
'type': 'business'}
Prefix level information only points to a combination of data present in the WHOIS records. The entire network is not necessarily operated by ‘Megacable Comunicaciones de Mexico, S.A. de C.V.’ as shown. The entire ASN prefix can be shared by multiple different organizations.
The prefix information shown on the ASN API is based on accurate range ownership data, and organizational metadata comes from WHOIS records. Instead of listing all the organizations on the prefix, IPinfo picks one based on internal methods.
To properly conduct threat intelligence mapping of a specific route/prefix/netblock/iprange, we need to perform bulk enrichment and analyze data at the company level.
Here I am going to user our CLI.
ipinfo bulk 200.52.65.0/24 -c > route_data.csv
This will result in route_data.csv
which will contain all the necessary IP information for each individual IP address.
dtypes = {"ip": str, "hostname": str, "bogon": bool, "anycast": bool, "city": str, "region": str, "country": str, "country_name": str, "country_flag_emoji": str, "country_flag_unicode": str, "country_flag_url": str, "country_currency_code": str, "country_currency_symbol": str, "continent_code": str, "continent_name": str, "isEU": str, "loc": str, "org": str, "postal": str, "timezone": str, "asn_id": str, "asn_asn": str, "asn_domain": str, "asn_route": str, "asn_type": str, "company_name": str, "company_domain": str, "company_type": str, "carrier_name": str, "carrier_mcc": str, "carrier_mnc": str, "privacy_vpn": bool, "privacy_proxy": bool, "privacy_tor": bool, "privacy_relay": bool, "privacy_hosting": bool, "privacy_service": str, "abuse_address": str, "abuse_country": str, "abuse_country_name": str, "abuse_email": str, "abuse_name": str, "abuse_network": str, "abuse_phone": str, "domains_total": str}
# We don't need all the columns
usecols = ['ip', 'hostname', 'city', 'region', 'country', 'loc', 'org', 'postal', 'timezone', 'asn_id', 'asn_asn', 'asn_domain', 'asn_route', 'asn_type', 'company_name', 'company_domain', 'company_type', 'carrier_name', 'carrier_mcc', 'carrier_mnc', 'privacy_vpn', 'privacy_proxy', 'privacy_tor', 'privacy_relay', 'privacy_hosting', 'privacy_service', 'abuse_country', 'abuse_country_name', 'abuse_email', 'abuse_name', 'abuse_network', 'domains_total']
route_data = pd.read_csv('route_data.csv', usecols=usecols, dtype=dtypes, true_values=['true'], false_values=['false'])
route_data
ip | hostname | city | region | country | loc | org | postal | timezone | asn_id | ... | privacy_tor | privacy_relay | privacy_hosting | privacy_service | abuse_country | abuse_country_name | abuse_email | abuse_name | abuse_network | domains_total | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 200.52.65.26 | service-static-52.65.26.mcm-telecom.com.mx | Mexico City | Mexico City | MX | 19.4285,-99.1277 | NaN | 03020 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | msalas@e-net.com.mx | Miguel Salas | 200.52.65.0/26 | 0 |
1 | 200.52.65.73 | service-static-52.65.73.mcm-telecom.com.mx | Cuauhtémoc | Mexico City | MX | 19.4318,-99.1449 | NaN | 06693 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | colivera@toshiba.com.mx | Carlos Olivera | 200.52.65.64/26 | 0 |
2 | 200.52.65.218 | service-static-52.65.218.mcm-telecom.com.mx | Miguel Hidalgo | Mexico City | MX | 19.4389,-99.2100 | NaN | 11200 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | ipmaster@MCMTELECOM.COM.MX | IPMASTER ADMINISTRATOR | 200.52.65.0/24 | 0 |
3 | 200.52.65.100 | service-static-52.65.100.mcm-telecom.com.mx | Cuauhtémoc | Mexico City | MX | 19.4318,-99.1449 | NaN | 06693 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | colivera@toshiba.com.mx | Carlos Olivera | 200.52.65.64/26 | 0 |
4 | 200.52.65.44 | service-static-52.65.44.mcm-telecom.com.mx | Mexico City | Mexico City | MX | 19.4285,-99.1277 | NaN | 03020 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | msalas@e-net.com.mx | Miguel Salas | 200.52.65.0/26 | 0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
251 | 200.52.65.203 | service-static-52.65.203.mcm-telecom.com.mx | Miguel Hidalgo | Mexico City | MX | 19.4389,-99.2100 | NaN | 11200 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | ipmaster@MCMTELECOM.COM.MX | IPMASTER ADMINISTRATOR | 200.52.65.0/24 | 0 |
252 | 200.52.65.7 | service-static-52.65.7.mcm-telecom.com.mx | Mexico City | Mexico City | MX | 19.4285,-99.1277 | NaN | 03020 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | msalas@e-net.com.mx | Miguel Salas | 200.52.65.0/26 | 0 |
253 | 200.52.65.135 | service-static-52.65.135.mcm-telecom.com.mx | Miguel Hidalgo | Mexico City | MX | 19.4389,-99.2100 | NaN | 11200 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | raul.ocampo@reuters.com | Raul Ocampo | 200.52.65.128/26 | 0 |
254 | 200.52.65.211 | service-static-52.65.211.mcm-telecom.com.mx | Miguel Hidalgo | Mexico City | MX | 19.4389,-99.2100 | NaN | 11200 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | ipmaster@MCMTELECOM.COM.MX | IPMASTER ADMINISTRATOR | 200.52.65.0/24 | 0 |
255 | 200.52.65.147 | service-static-52.65.147.mcm-telecom.com.mx | Miguel Hidalgo | Mexico City | MX | 19.4389,-99.2100 | NaN | 11200 | America/Mexico_City | AS14178 | ... | False | False | False | NaN | MX | Mexico | raul.ocampo@reuters.com | Raul Ocampo | 200.52.65.128/26 | 0 |
256 rows × 32 columns
Now that we have the IP address metadata loaded for the entire range. Lets take a closer look.
# Identify the companies and domain count
db.query('''
SELECT company_name, company_domain, count(*) ip_count
FROM route_data
GROUP BY company_name, company_domain
ORDER BY ip_count DESC
''')
┌──────────────────────────────────────────────────┬────────────────┬──────────┐
│ company_name │ company_domain │ ip_count │
│ varchar │ varchar │ int64 │
├──────────────────────────────────────────────────┼────────────────┼──────────┤
│ Toshiba de Mexico, S.A. de C.V. │ toshiba.com.mx │ 64 │
│ Help Desk Solution de Mexico │ e-net.com.mx │ 64 │
│ Reuters de Mexico, SA de CV │ reuters.com │ 64 │
│ Megacable Comunicaciones de Mexico, S.A. de C.V. │ mcm.net.mx │ 64 │
└──────────────────────────────────────────────────┴────────────────┴──────────┘
From the beginning, we can see that the route (200.52.65.0/24) is equally shared between 4 companies:
- The ASN prefix level company is “Megacable Comunicaciones de Mexico, S.A. de C.V. (mcm.net.mx)”, which is an ISP.
- Our target company behind the target IP address is “Help Desk Solution de Mexico (e-net.com.mx)”, which I have no clue what it is. Maybe a business of some sort.
- Then we have two other companies: a multinational news agency and a multinational electronics manufacturing organization.
So, for our threat intelligence mapping, pointing to the entire /24
range as malicious should not be a good thing to do. We have to be more specific and target the company behind the IP address. So, let’s get all their IP addresses. We are going to use the company_domain
field to identify the target IP addresses and sister domains within the netblock.
target_sister_ips = db.query('''
SELECT ip
FROM route_data
WHERE company_domain = 'e-net.com.mx'
''').to_df()['ip'].to_list()
# Preview data
target_sister_ips[:5]
['200.52.65.26', '200.52.65.44', '200.52.65.5', '200.52.65.60', '200.52.65.45']
From here, all we have to do is merge the IP address into CIDR. We are going to use the third-party library netaddr for this. This will return a list.
netaddr.cidr_merge(target_sister_ips)
[IPNetwork('200.52.65.0/26')]
Making this information more friendly
route_to_ban = str(netaddr.cidr_merge(target_sister_ips)[0])
route_to_ban
'200.52.65.0/26'
That’s about it. That is how you can perform threat intelligence mapping using our standard products. If you want more control and granularity, you can use WHOIS databases to delve into finer details. However, in most cases, using a combination of ASN and company data will get the job done. Feel free to leave a comment or let me know what you think!