“This is the second day of my participation in the First Challenge 2022.

Application scenarios

Domain name asset monitoring: You can enter a master domain name to find the port status of the server corresponding to the IP address of the domain name. By doing this regularly, it helps to know the full extent of your exposure.

Required prefix skills

Python simple use of Linux operating system simple use of DNS principle

tool

Pycharm Professional XShell is used to deploy scripts

steps

Obtain the CORRESPONDING IP address through the domain name

First, we know that there are many types of record values that can be set for A domain name, such as A record, AAAA record, SOA record, TXT record, and so on. The mapping between common recorded values and their meanings in the security field is as follows:

code describe
A IPv4 Address Record
AAAA IPv6 Address Record
CNAME Canonical name record, alias of a host name:

The domain name system will continue to try to find new names
MX Email Interaction Record
NS Name server record
PTR Most commonly used to run reverse DNS lookups
SOA The beginning of authoritative records
TXT Text record

We will use A record (currently IPv6 is not widely used). Obtain the IP address corresponding to A domain name through A record. The python method is as follows:

def dns_a(domain: str) :
    dns_servers = '114.114.114.114 114.114.115.115, 223.5.5.5 223.6.6.6, 180.76.76.76, 119.29.29.29, 119.29.29.29, 1.2.4.8, 210.2.4.8, 117.50.11. 11,52.80. 66.66 101.226.4.6 218.30.118.6, 123.125.81.6, 140.207.198.6, 8.8.8.8, 8.8.4.4, 9.9.9.9, 208.67.222.222 and 208.67.220.220 '.split(
        ', ')
    loop = asyncio.new_event_loop()
    resolver = aiodns.DNSResolver(loop=loop)
    resolver.nameservers = dns_servers
    try:
        record_a = loop.run_until_complete(resolver.query(domain, 'A'))
        return record_a
    except:
        pass
    return []
Copy the code

In the code, a DNS_server, also known as DNS query server, is randomly assigned for each query. Due to convergence, different query servers may have different results. Therefore, you can consider to do a better, here only one query, or multiple DNS server query, take the union (union is not to miss all possible cases, the intersection is accurate but may miss). Finally, the method can obtain the IP address corresponding to the domain name. This method uses two packages asyncio and aiODns. The installation method is as follows:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn asyncio,aiodns
Copy the code


Let’s take www.mengwa.store as an example to see what happens:







You can see that you have the IP address, but you still need to extract it from the results. The method is as follows:

record_a = dns_a(domain)
ips = []
if type(record_a) == list:
ips = sorted([answer.host for answer in record_a])
Copy the code

The IPS store a sorted list of parsed records.

Perform port scanning for IP addresses


The IP address is found through the above steps. The next step is to perform port scanning for the IP address. However, before scanning, we need to do one more step, need to determine whether the current IP machine is alive.



You may immediately think of a ping method, but in the security world, many companies require servers to block ICMP. In other words, the ping method may return a timeout, but the machine is not necessarily not enabled, but actively blocked the request for you.



Here, we use the -spn parameter of Nmap for scanning. Take the IP address 47.119.137.0 corresponding to www.mengwa.store as an example:







We then implement this logic in code, using the python-nmap package, as follows:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn python-nmap
Copy the code

The code is as follows:

def check_ip_reachable(ip: str) :
    nm = nmap.PortScanner()
    xx = nm.scan(hosts=ip, arguments='-sP') # -spn also works
    if 'scan' in xx:
        i = xx.__getitem__('scan')
        if ip in i:
            j = i.__getitem__(ip)
            if 'status' in j:
                status = j['status']
                if 'state' in status:
                    state = status['state']
                    print(ip, state)
                    return state
    print(ip, 'down')
    return 'down'
Copy the code

The prerequisite for this step is that you have NMAP installed on your computer. Judging from this, if the machine where the IP resides is enabled, then we can conduct port scanning for it. The code is as follows:

Implement the port scan and return the result. Use NMAP
def port_scan(ip: str) :
    second = check_ip_reachable(ip)
    if second is not None and second == 'up':
        nm = nmap.PortScanner()
        xx = nm.scan(ip)
        print(nm.command_line())
        ans = []
        if 'scan' in xx:
            i = xx.__getitem__('scan')
            if ip in i:
                j = i.__getitem__(ip)
                if 'tcp' in j:
                    tcp = j['tcp']
                    for port in tcp:
                        ans.append(port)
        ans = sorted(ans)
        return ans
    else:
        write_to_file('output/' + time_now + '/ips.txt', ip + 'Unreachable \n')
    return []
Copy the code


The nm.command_line() method prints out the currently executed command line. (This package is essentially a command line call package that is then parsed)



Taking 47.119.137.0 as an example, the results are as follows:







In this method, common Ports scanned by NMAP are used. If necessary, you can add Ports parameters to the Scan method to customize the Ports to be scanned, or even all Ports.

In series, through the domain name to port monitoring

def handle_ip(ip: str) :
    if not task_ip_set.__contains__(ip):
        try:
            print('ip:' + ip, 'Start port scan')
            port_result = port_scan(ip)
            val = ' '
            print('ip:', ip, 'Discovery port', port_result)
            for i in port_result:
                val = val + '\n' + ip + "--" + i.__str__()
            write_to_file('output/' + time_now + '/ip_ports.txt', val)
            task_ip_set.add(ip)
        except Exception as e:
            print(e)
            
def handle_domain(domain: str) :
    if not task_domain_set.__contains__(domain):
        print('domain:' + domain)
        record_a = dns_a(domain)
        ips = []
        if type(record_a) == list:
            ips = sorted([answer.host for answer in record_a])
        val = ' '
        for i in ips:
            val = val + '\n' + domain + "--" + i.__str__()
        write_to_file('output/' + time_now + '/domain_ips.txt', val)
        print(ips)
        for i in ips:
            handle_ip(i)
Copy the code

Task_domain_set and task_ip_set are used to deduplicate the target. So, is our job done here? There is no

Subdomain discovery

If my master domain asset has many secondary domain names, and I do not know which subdomains I have, then I need to do subdomain discovery. Then call the previous method for each subdomain to find its corresponding IP open port. Generally speaking, there are three ways to discover the subdomain name: the first way is to crawl the main domain name website to resolve the subdomain name from the content; The second way is to obtain results from external query websites, such as CRT, virustotal, fOFa, etc. The third way is to obtain the result through dictionary blasting. The DNS server is constantly polled to check whether it has the value recorded by A. If so, it indicates that the subdomain name exists; if not, it is temporarily considered that the subdomain name does not exist. The first method is to implement a crawler and extract the subdomain name in the website by regular expression after getting the content of the website. The extraction method is as follows:

def get_subdomains(value: str, domain: str) :
    x = f'(? P<subdomain>[a-zA-Z0-9.]+{domain}) '
    pattern = re.compile(x)
    y = pattern.findall(value)
    ans = set(a)for i in y:
        ans.add(i)
    print(ans)
    return ans
Copy the code

The second method, using the interface call, can see the related API interface. The third method is subdomain blasting. The core code is as follows

answers = await self.resolvers[j].query(cur_domain, 'A') Find A record
If the subdomain name exists, the subdomain name exists
Copy the code

Once you have completed the above steps and de-duplicated, you can use these subdomains as input to the previous steps. In this way, for the user needs to monitor a master domain name, the scope of monitoring is improved a lot. Do not use it to monitor non-existing domain names. The loss caused has nothing to do with me. The above test data are my existing domain name and IP.