liuch / dmarc-srg

A php parser, viewer and summary report generator for incoming DMARC reports.
GNU General Public License v3.0
218 stars 32 forks source link

Feature request - resolve IPs to DNS names #75

Open williamdes opened 1 year ago

williamdes commented 1 year ago

There is two things I did find frustrating when reading reports today:

So what about reverse DNS support ?

liuch commented 1 year ago

When I click on the IP link all the information is useless to me as I would have needed

FYI: You can set up another service to display this information.

I need to think how and where to accurately display this information. BTW: there is already a suitable pull request: https://github.com/liuch/dmarc-srg/pull/5

KlaasBonnema commented 1 year ago

The current link to whois from an IP address link does not give much usefull information. Mostly it gives the owner information of a complete ip block. What you want to know is if there is a proper mail server behind the ip-address. If it resolves to something funny or does not resolve at all then it is time to start digging (with another tool maybe.)

My suggestion is to create a link that will attempt to resolve a domain name from the ip-address and display it as a popup. Opening up a new page is too distracting for a quick check. Alternative is to resolve all ip-addresses and add as a title attribute when you create the HTML page. This may however incur a lot of dns overhead and delays.

Note that an ip-address to domain lookup may not come up with the actual domain because there may be hundreds of domains associated with the ip-address. However since the ip-reverse check is very common in determining valid mail servers you may expect that proper mail handlers will set this up in a decent way.

KlaasBonnema commented 1 year ago

I made some changes that will do a hostname lookup on server IP and then validate that a reverse lookup for ip-address using the hostname returns the original ip-address. A failing lookup of ip-address match will mark the ip-address - hostname entry as red while a match will mark the entry as green. This is similar to the ip-rev check that mail receivers will perform to spot improper mail senders. The actual lookup is being performed by a PHP script. It is called in parallel by an asynchronous fetch so that the dns lookups are executed also in parallel and will not adversly impact page load times.

common.js

    static makeIpElement(ip) {
        let url = null;
        let type = ip.includes(":") && 6 || 4;
        switch (type) {
            case 4:
                url = Common.ipv4_url;
                break;
            case 6:
                url = Common.ipv6_url;
                break;
        }
        let tn = document.createTextNode(ip);
        if (url) {
            url = url.replace("{$ip}", ip).replace("{$eip}", encodeURIComponent(ip));
            let el = document.createElement("a");
            el.setAttribute("href", url);
            el.setAttribute("target", "_blank");
            el.setAttribute("title", "IP address - hostname");
            el.appendChild(tn);
            this.resolveIp(ip, type)
                .then((response) => {
                    el.innerHTML = el.innerHTML + " - " + response.hostname;
                    if (response.status == 'valid')
                        el.setAttribute("class", 'report-result-pass');
                    else
                        el.setAttribute("class", 'report-result-fail');
                })
                .catch(console.log);
            return el;
        }
        else {
            this.resolveIp(ip, type)
                .then((response) => {
                    tn.textContent = tn.textContent + " - " + response.hostname;
                    if (response.status == 'valid')
                        tn.parentNode.setAttribute("class", 'report-result-pass');
                    else
                        tn.parentNode.setAttribute("class", 'report-result-fail');
                })
                .catch(console.log);
        }
        return tn;
    }

    static resolveIp = async (ip, type) => {
        const response = await fetch("resolve_ip.php?ip=" + ip + "&type=" + type);
        if (response.ok) {
            return response.json();
        }
    };

resolve_ip.php

<?php
/**
 * Resolve ip-address to hostname and perform reverse lookup to match resolved hostname with ip-address.
 * Mail servers should be set up in dns with a dedicated static hostname. 
 * Flag invalid and non-matching ip-address - hostname pairs.  
 * @var string $ip
 */
$response = array();
$ip = $_GET['ip'];
$type = $_GET['type'];

// empty $ip parameter is invalid
if (empty($ip))
{
    $response['status'] = "invalid";
    $response['hostname'] = "hostname lookup invalid";
    echo json_encode($response);
    exit();
}

// get the hostname by ip
$hostname = gethostbyaddr($ip);
if ($hostname === false)
{
    $response['status'] = "invalid";
    $response['hostname'] = "hostname lookup failed";
    echo json_encode($response);
    exit();
}

// lookup dns to get an array of ip_addresses by hostname
switch ($type)
{
    case "6":
        {
            // lookup ipv6 
            $ip_res = dns_get_record($hostname, DNS_AAAA);
            break;
        }
    case "4":
    default:
        {
            // lookup ipv4
            $ip_res = dns_get_record($hostname, DNS_A);
            break;
        }
}

// invalid if result is not an array
if (! is_array($ip_res))
{
    $response['status'] = "invalid";
    $response['hostname'] = $hostname;
    echo json_encode($response);
    exit();
}

// match our input ip with the result ip's. A match returns a valid status
foreach ($ip_res as $entry)
{
    if (($type == "6" && isset($entry['ipv6']) && inet_ntop(inet_pton($entry['ipv6'])) == $ip) || (isset($entry['ip']) && $entry['ip'] == $ip))
    {
        $response['status'] = "valid";
        $response['hostname'] = $hostname;
        echo json_encode($response);
        exit();
    }
}

// no match found, return invalid status
$response['status'] = "invalid";
$response['hostname'] = $hostname;
echo json_encode($response);
exit();
liuch commented 1 year ago

I will try to implement something like this. At the very least, your php code lacks input parameter validation and access level validation. There are questions to the js code as well. But the main thing is your idea. Thank you.