Issues with output, multiplied of 2nd, 3rd choices

My first topic here on the forum, hopefully an easy fix but I’m stuck since several days and repeated attempts…

The below PHP does what I want, need additional security such as protection from sql injection etc of course but my current challenge is the output. It gives me expected output from server1, double from server2, tripple from server3…

What am I missing, whats casuing this behavior?


$allowedReferer = '';
 $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
	if ($referer !== $allowedReferer) {
    exit('Access denied.');

$hostname = $_GET['hostname'];
$region = $_GET['region'];
$queryType = $_GET['queryType'];

$servers = [
    'Internal' => ['dns-server1', 'dns-server2', 'dns-server3'],
    'External recursive' => ['dns-server8', 'dns-server9'],
    'External authoritative' => ['dns-server10', 'dns-server11'],
$selectedServers = isset($servers[$region]) ? $servers[$region] : $servers['Internal'];

$table = '<table>';
$table .= '<thead><tr><th>Server</th><th>Query</th><th>Response</th><th>Type</th><th>TTL</th></tr></thead>';
$table .= '<tbody>';

$responses = [];

foreach ($selectedServers as $server) {
     if (isset($responses[$server])) {

    $command = "dig @$server $queryType +noall +ans $hostname";

     exec($command, $output);

    $rows = [];
    foreach ($output as $line) {
        $columns = preg_split('/\s+/', $line);
        $query = $columns[0];
        $ttl = $columns[1];
        $type = $columns[3];
        $answer = $columns[4];

        $rows[] = [
            'Query' => $query,
            'Answer' => $answer,
            'Type' => $type,
            'TTL' => $ttl,

    $responses[$server] = $rows;

    $rowSpan = count($rows);
    foreach ($rows as $index => $row) {
        $table .= '<tr>';
        if ($index === 0) {
            $table .= '<td rowspan="' . $rowSpan . '">' . $server . '</td>';
        $table .= '<td>' . $row['Query'] . '</td>';
        $table .= '<td>' . $row['Answer'] . '</td>';
        $table .= '<td>' . $row['Type'] . '</td>';
        $table .= '<td>' . $row['TTL'] . '</td>';
        $table .= '</tr>';

$table .= '</tbody>';
$table .= '</table>';

echo $table;

Wow, looks like my formatting was dropped so it’s all a big mess.

You need to format your code with 3 tick marks ``` on a separate line before and after your code. Either that or the </> button on the tool bar. I’ve done it for you this time.

1 Like

Figured out it was because $output wasn’t flushed but kept being added with response from the next servers, put all including publishing to the originating html file into a loop and flushing $output , perhaps not the neatest solution but it works

This is explicitly called out in the Parameter declaration for $output in the Exec PHP Manual Page page. unset($output) before you use it in exec.

More vitally than that…


That’s not a calm “need additional security” level message, that’s a big, red, bold, underlined, DO NOT DO THIS.

If you want to know why, consider the following URL:

?queryType=A&region=Internal&url=; cat /etc/passwd

So now the command becomes

"dig @someServer A +noall +ans ; cat /etc/passwd"

and you’re outputting all users that exist on your server…

not to mention anything involving the word del… (your PHP user has access to files, right? What files?)

Good points, thx, so what steps should I look at to correct that?
User restrictions in place to ensure that won’t happen but I agree that should be stopped

Seems the function dns_get_record does what I need as well, it has some limitations such as no CAA lookup or ipv6 support but won’t be needing the planned consumers of this script to do those lookups just yet

You’d have to establish acceptable inputs; then handle it like you did for $region - if the queryType is in your list of acceptable query types, use it, otherwise, use a default.

Ok, make sense. $region and $queryType I could establish acceptable input for but I don’t see it for $hostname given the billions of possibilities.

If would allow only allow entries ending with the Internal domains that would still be a list of thousands domains today, and it’s constantly changing

Well, there could be some filtering you can do - are semicolons acceptable in hostname? spaces?
Some of these can thwart some of the more basic attacks (like the one @rpkamp mentioned) because it would defuse the ability to inject multiword commands…