UDP Portscanning in PHP

    Jason Perkins
    Share

    Portscanning serves a legitimate role in system administration/ownership. By confirming exactly what ports a computer accepts connections on, it’s possible to ensure that an operating system hasn’t opened unnecessary ports to the world at large as part of a default install. It also allows us to check that our system hasn’t been compromised and had ports opened to allow a cracker to send remote commands to our machine via the Internet.

    While TCP port scanning of one’s own ports is common, many underestimate the potential hazards of open UDP ports — this can lead to compromise, or indicate that a compromise has occurred. As the nmap manpage says:

    "There is also the cDc Back Orifice backdoor program which hides on a configurable UDP port on Windows machines. Not to mention the many commonly vulnerable services that utilize UDP such as snmp, tftp, NFS, etc."

    The single largest impediment to the average home user conducting port scans of their own machines is the lack of simple software to conduct the scan for them. Don’t get me wrong — nmap is one of the best portscanning tools available — but how comfortable is Joe Average User going to be using a *nix command line tool? It seems to me that a tool that only requires Mr. Average User to go to a specific URL and wait while a PHP script runs a TCP and UDP scan against their machine, and then returns the results of the scan to them, is just what the doctor ordered.

    With this in mind, I started searching the Internet and found an implementation of a TCP port scanner that Jim Barcelona had coded and made available at php wizard

    Great! With half of the work done, I should be able to whip out a UDP port scanner in no time. I mean, how much harder could coding a UDP port scanner be? Considerably harder, as it turns out.

    TCP Portscanning in PHP

    When a TCP socket is created using the fsockopen function, you specify the IP address of the remote machine and the port number to which you want to connect. Using the underlying socket functionality, PHP will then attempt to create a virtual circuit to the remote machine on the specified port, in order to allow further communication to occur. If the destination port is unavailable, then the TCP provider on the remote machine will reject the connection request and the fsockopen function will be return a boolean value of failed.

    So we now have an easy to understand and implement TCP portscanner. We set up a loop that specifies the minimum and maximum port numbers that we wish to scan, and within the loop, attempt to open a connection at the current value of the loop’s index. If the attempt to open the port fails, there’s no service at that port. If we’re able to successfully open a socket to that port, then there’s a service at that port which we log in our array of open ports as a key. We then insert the value for that key with a call to getservbyport, which will return the unix service that’s normally registered at that port. Lastly, we close the open socket and move onto the next iteration of our loop.

    UDP Portscanning in PHP

    When an application wishes to send data over the network using the UDP protocol, it gives the data to UDP through the assigned port number, also telling UDP which port on the destination system the data should be sent to. UDP then creates a UDP message, marking the source and destination port numbers, which is then passed off to IP for delivery.

    When a UDP packet is received by the destination machine, the UDP software looks at the packet’s header for the destination port number, and hands the payload off to whatever application has registered as using that port number. If no application has registered for the specified port number, then an "ICMP Destination Unreachable: Port Unreachable" Error Message is returned to the remote machine, and the payload is discarded.

    Creating a UDP socket in PHP is similar to creating a TCP socket, with a couple of differences. You still call the fsockopen function, but you must specify:

    • that you want to use the UDP protocol,
    • the IP address of the remote computer and
    • the port number that you want to connect with.

    In contrast to the TCP socket, at this point no connection exists to the remote computer.

    function _scanPort ($portNumber) {  
    $handle = fsockopen($this-> targetIP, $portNumber, &$errno, &$errstr, 2);  
     
    if (!$handle) {  
    echo "$errno : $errstr <br/>";  
    }  
     
    socket_set_timeout ($handle, $this-> timeout);  
    $write = fwrite($handle,"x00");  
    if (!$write) {  
    echo "error writing to port: $index.<br/>";  
    next;  
    }  
     
    $startTime = time();  
    $header = fread($handle, 1);  
    $endTime = time();  
    $timeDiff = $endTime - $startTime;  
     
    if ($timeDiff >= $this-> timeout) {  
    fclose($handle);  
    return 1;  
    } else {  
    fclose($handle);  
    return 0;  
    }  
    }

    Because a call to read from our UDP socket will block (wait until it receives data), we set a value to the set_socket_timeout function. This tells the socket to only listen (block) on the socket for a response from the remote machine for a specific period of time. If that time is exceeded, then the socket will stop listening, and the code will continue to run. We’ll see how we use this to discern an open port shortly.

    As UDP is connectionless, at this point a virtual circuit is not setup with the remote machine. Instead, you have to pass the data that you want to send to the remote machine through the socket using the fwrite function. We then immediately log the time that we begin listening for a response from the remote machine, and use fread to listen to the socket. At this point, one of two things can happen:

    1. the remote server returns an "ICMP Destination Unreachable: Port Unreachable" Error Message to us, and the fread ends, or
    2. the socket times out waiting for a response.

    In either case, we now log the time that the listening ended, and derive the total time spent listening by subtracting the end time from the start time. We compare this number with the socket’s timeout value that we set. If it’s less than the timeout value, then the remote server returned an "ICMP Destination Unreachable: Port Unreachable" Error Message to us, and we know that the port is closed. If the socket timed out, then we know one of two other things occurred: the application at that port was waiting to receive a valid command, or the packet was lost in transit (UDP doesn’t offer guaranteed delivery, and if a packet is lost en route, we’re not going to receive any information to that effect).

    So, we’ve established a means to discern whether there’s anything at the port (ie. the socket returned before it timed out), and we now need a way to tell whether a socket timing out was the result of an application waiting for legitimate data, or the packet was lost in transit. The easiest way to do this would be to send multiple packets to that port, and if we get one response back that doesn’t timeout, then we know that there’s not an application there and the port is closed. But how can we do this in a manner that’s not too wasteful of bandwidth, and minimizes the application’s run time?

    Prior to our full scan of the UDP ports that the class was instructed to scan, we’re going to conduct a smaller port scan very high in the port range, to minimize the finding of legitimate open ports, and test the network conditions between the machines. This initial scan is carried out in our _networkProbe method. Since most of the open UDP ports that are open are at or below port 1024, we’ll do a scan up in the port 55000 range. Any ports that time out this high in the port range we can assume did so because of lost datagrams, and not because we’ve detected an open port.

    function _networkProbe ($noTrials=100, $startPortNumber=55000) {   
    $endPortNumber = $startPortNumber + $noTrials;  
     
    // temporarily set timeout to 2 seconds. we'll modify this with the  
    // data that we get from this method  
     
    $this-> timeout = 2;  
     
    // setup a for loop to scan the ports  
     
    for ($portNumber = $startPortNumber; $portNumber < $endPortNumber;  
     $portNumber++) {  
    $startTime = $this-> _getmicrotime();  
    $result = $this-> _scanPort($portNumber);  
    $endTime = $this-> _getmicrotime();  
    $timeDiff = $endTime - $startTime;  
     
    if (!$result) {  
    $responsesArray[] = $timeDiff;  
    $totalTime += $timeDiff;  
    }  
    }  
     
    $noResponses = count($responsesArray);  
     
    // if more than 40% of the datagrams timed out, abort the scan  
     
    if ($noResponses < (.6 * $noTrial)) {  
    echo "The connection is losing too many packets. Scan aborted. <br/>";  
    exit;  
    }  
     
    $averageResponseTime = $this-> _calcAvgResponseTime ($noResponses,  
     $totalTime);  
    $standardDeviation = $this-> _calcStdrDeviation ($responsesArray);  
     
     // calculate the timeout value  
     
    $timeoutValue = ceil($averageResponseTime + 4 * $standardDeviation);  
     
    // calculate number of cleanup iterations we'll need  
    // percentFalsePositive is the % of datagrams that we sent in  
    // the trial that timed out  
     
    $percentFalsePositives = ($noTrials - $noResponses)/$noTrials;  
     
    // percentResponses is the % of datagrams that we sent in the trial  
    // that returned (eg -- didn't timeout)  
     
    $percentResponses = $noResponses/$noTrials;  
     
    // calculate the total number of ports to be scanned in the  
    // real scan  
     
    $portRange = $this-> maxPort - $this-> minPort + 1;  
     
    // estFalsePositives is the estimated number of false positives we  
    // anticipate getting from the real scan  
     
    $estFalsePositives = $portRange * $percentFalsePositives;  
     
    $this-> cleanupIterations = $this->  
    _calcNoIterations ($estFalsePositives, $percentResponses, $portRange);  
     
    if ($this-> debug == 1) {  
    echo "<br/>";  
    echo "total time $totalTime<br/>";  
    echo "timeout value: " . $this-> timeout . "<br/>";  
    echo "cleanup iterations: " . $this-> cleanupIterations . "<br/>";  
    echo "<br/>";  
    flush();  
    }  
    }

    So we now know:

    • the number of packets that were lost from our sample,
    • the total number of packets that were sent and
    • the average time that it we had to listen for each packet that did respond without blocking.

    With the number of packets lost from our sample, the total packets sent with our initial scan, and the range of how many ports we’re going to scan in the main scan, we can calculate the number of iterations that we’ll have to run, and retest the open ports detected, to eliminate false positives. The formula that we use to do this is an exponential decay logarithm and it’s functionallity can be found in the _calcNoIterations method of the class.

    We’re also going to use the average response time of the fread calls that returned an "ICMP Destination Unreachable: Port Unreachable" Error Message and didn’t block, to calculate the standard deviation of these individual times. We multiply the standard deviation by a factor of four (four sigma) and add it to our average response time. This allows us to minimize the timeout value, and still be reasonably certain that we’re not eliminating too many scans that would’ve returned had it not timed out. At this point, this check is actually superfluous given that the set_socket_timeout value can’t be set to a value of less than one second, which is where most of the derived timeout values are going to be. However, if the socket timeout value is ever modified to accept values of less than one second, we can anticipate a runtime decrease up to a factor of five in eliminating the ports that don’t have a service on them.

    Using the UDP Portscanning Class

    The UDP Port Scanning Class does an excellent job of abstracting the complexities of UDP port scanning. Use of the class is simplicity itself: include the class and new up an object based on the class that’s passing it the the target ip address, and optionally, the port to start scanning at, the port to end scanning at, and whether the object should output information as it conducts the scan. By default, the start port is set to 1, the end port is set to 1024, and output is on.

    Then call the objects doScan method and assign its output to a variable that will hold the results of the scan as an array. Here’s an example:

    include ('classes/udpPortScanner.inc');   
     
    $udpScanner = new udpPortScanner("$REMOTE_ADDR");  
    $ports = $udpScanner-> doScan();  
     
    if (count($ports) == 0) {  
    echo "no open udp ports detected.<br/>";  
    } else {  
    echo "open udp ports:<br/>";  
    foreach ($ports as $portNumber => $service) {  
    echo "$portNumber ($service)<br/>";  
    }  
    }

    Closing

    There are two modifications that I have to make and another that I’d like to make. First, the calculated value of the number of iterations to eliminate false positives resulted in a number that is too low if the network conditions are good, and I was getting ports returned that were not, in fact, open. I modified the code to bump the minimum number of cleanup iterations up to a value of five in order to compensate for this.

    A UDP port scanner is heavily reliant upon the network conditions that exist between the two machines. In testing, I had friends in both Australia and South Africa (I’m located in the U.S.) volunteer to be scanned, and the scanner was having a really difficult time detecting closed ports. This was because the network conditions were leading to a very large number of packets being lost in transit, and very long runtimes were encountered. In defense of the scanner, an nmap scan of the first 1024 UDP ports on the host in South Africa took nearly an hour, indicating that this is endemic to UDP port scanning, not to this scanner’s implementation. With this in mind, it became obvious that some of the network connections were just too bad to complete a scan in a reasonable amount of time. Because of this, I modified the code to abort if, during the initial network testing scan, more than 40% of the packets were lost. Feel free to eliminate or modify this value, but be warned that scanning over a bad network span can take a considerable amount of time.

    Finally, I’d also like to collect information as to what ports Microsoft places its various services on, to complement the getservbyport function, which will only return services that are mapped to the traditional ports on a *nix box. I could then specify which Microsoft service is running on a port, and in a later version, indicate how comprimising this is, and describe methods to disable the service and close the port if it’s not in use.

    I’d like to thank David LaCroix of suddendecelaration.com for his assistance in setting up tcpdump for me to analyze the UDP port scans that I was conducting against one of his volunteered machines. His assistance was instrumental in completing the debugging of this software. Also, thanks to Daniel Bogan of waferbaby.com and Zak McGregor of carfolio.com, both of whom were helpful in determining that trans-oceanic UDP port scans are not that great an idea.

    For additional information on Internet protocols, I highly recommend O’Reilly and Associates "Internet Core Protocols: The Definitive Guide," by Erik A. Hall. You can find more information on O’Reilly’s Website.

    The official home for the the tcpPortScan and udpPortScan classes is here. You can check there for the latest updates to the classes and to report any problems, issues or suggestions that you have.