Hi, I'm making a site security tester which is basically a bot that scans a selected website for any php security holes such as sql injections then reports them to the user. I have managed to write the bot and all but the last piece left is the function that actually tests each individual page for security holes. The function that starts testing each individual page is as follows:

generate($url) {
$data=file_get_contents($url);
//now to do some tests on the page
}

As you can see, this function will be used on each and every valid url inside the website to perform tests on. But my question is, how do I test for php security holes and what security holes are possible?
I read something about if ;ls -la is placed in the url and not filtered it can display the contents of a web directory. But what would file_get_contents return if that is the case?

This is also an open source project so just let me know if you want the full code.

Recommended Answers

All 8 Replies

Hi, I'm making a site security tester which is basically a bot that scans a selected website for any php security holes such as sql injections then reports them to the user. I have managed to write the bot and all but the last piece left is the function that actually tests each individual page for security holes. The function that starts testing each individual page is as follows:

generate($url) {
$data=file_get_contents($url);
//now to do some tests on the page
}

As you can see, this function will be used on each and every valid url inside the website to perform tests on. But my question is, how do I test for php security holes and what security holes are possible?
I read something about if ;ls -la is placed in the url and not filtered it can display the contents of a web directory. But what would file_get_contents return if that is the case?

This is also an open source project so just let me know if you want the full code.

;ls -la is a linux shell command. The first part, ; is a command delimiter.

So if that were passed to the linux shell, it would first delimit any previous command, and then call the command ls -la

The -la option basically lists all files in the directory including hidden files (those preceded with .) and shows full information about them.

So ;ls -la would be an exploit against any php code that executes the shell. Example: exec(), shell_exec() or even process pipes like proc_open(), popen() etc.

This is call shell injection.
http://en.wikipedia.org/wiki/Code_injection#Shell_Injection

It is a type of code injection.

Example:

echo shell_exec('somecommand '.$_GET['param']);

http://php.net/shell_exec

In this case you wanted $_GET to be a parameter passed to the somecommand linux command. Instead someone could pass in ; ls -la

This would run "ls -la" after "somecommand ".

The secure way to do this is:

echo shell_exec('somecommand '. escapeshellarg ($_GET['param']));

http://php.net/escapeshellarg

Shell injection isn't common, as PHP rarely uses the shell. However, it is the most dangerous exploit as it allows any command to be run under the PHP user privileges.

The more common ones to test for a XSS, SQL injection, and XSRF.

http://en.wikipedia.org/wiki/Cross-site_scripting
http://en.wikipedia.org/wiki/Cross-site_request_forgery
http://en.wikipedia.org/wiki/SQL_injection

These are relatively easy to test for.

First thing you'll have to do is compile a list of possible HTTP Requests. (those that make sense).

That means traversing all pages and links on a page. Then traversing all HTML Forms on a page.

Then building a list of all possible GET and POST parameters that can be sent to a website from those links and forms.

For each possible HTTP Request, send a test XSS, XSRF or SQL Injection.

Because you don't want to hurt the site, you are only limited to immediate results, those that are reflected back in the HTTP Response, and not persisted server side. (modification of persistent data would hurt the site)

Basically the two types of code injection are persisted and reflected. So if you make an SQL injection that modifies a page content, it is persisted. But if you make one that just echo's your input in the page, then it is reflected.

Here you can only test for the later on public sites.

An example XSS test:

Raw URL:

http://example.com/page.php?param=<script>alert('hi')</script>

PHP:

$url = 'http://example.com/page.php';
$param = 'param';
$value = '<script>alert(\'xss\')</script>';

$body = file_get_contents($url.'?'.$param.'='.$value);

if (strpos($body, $value) !== false) {
echo 'Possible XSS code injection';
} else {
echo 'Failed XSS code injection';
}

This just passes a parameter to the site, and checks if it was reflected back in the HTML. if it was, then it is an XSS vulnerability.

There are many types of XSS. This is the simplest. You'll need to test for the major variations, either as separate HTTP requests, or even in the same request.

commented: very informative post +4

Hi, I've just written a script based on the previous post and the function that checks each webpage for php security flaws is as follows:

function generate($genurl) {
    $data=file_get_contents($genurl);
    $urlvars=explode('?',$genurl);
    $newurl=$urlvars[0].'?';
    $error1='None';
    //error1
    if (isset($urlvars[1])) {
        $urlvar=explode('&',$urlvars[1]);
        unset($varb);
        foreach ($urlvar AS $var) {
            $newurl.=preg_replace('/([^=]+)=(.*)/',"$1=; echo \"<script>aaabbbcccdddeeabcefffggg</script>\";",$var).'&';
            $varb=1;
            }
        unset($var);
        if ($varb==1) {
            $newurl=substr_replace($newurl,'',-1);
            if (url_exists($newurl)) {
                $secondarydata=file_get_contents($newurl);
                if (preg_match('/\<script\>aaabbbcccdddeeabcefffggg\<\/script\>/is',$secondarydata)) {
                    $error1='Page open to url injections by injecting code into the page via url.<br>The test url was: '.$newurl;
                    }
                unset($secondarydata);
                }
            }
        unset($varb);
        }
    unset($newurl);
    //error2
    preg_match_all('/\<form[^\>]+([^m][^e][^t][^h][^o][^d][^\=]([^\']|[^\"]|[^])([^p][^o][^s][^t]))(.*)\<\/form\>/i',$data,$forms);
    $error2='None';
    foreach($forms[0] AS $form) {
        preg_match_all('/(input|textarea)[^\>]+name\=(\"|\'|)/i',$form,$field);
        $fields=preg_replace('/(input|textarea)[^\>]+name\=(\"|\'|)(.*)/i',"$2",$field);
        unset($field);
        $newurls=explode('?',$genurl);
        $newurl=$newurls[0];
        unset($newurls);
        $newurl.='?';
        foreach ($fields AS $field) {
            $newurl.=$field.'='.urlencode('<script>aaabbbcccdddeeabcefffggg</script>').'&';
            $varb==1;
            }
        unset($field);
        if ($varb==1) {
            $newurl=substr_replace($newurl,'',-1);
            if (url_exists($newurl)) {
                $secondarydata=file_get_contents($newurl);
                if (preg_match('/\<script\>aaabbbcccdddeeabcefffggg\<\/script\>/is',$secondarydata)) {
                    if ($error2=='None') { $error2='Forms may inject code into your page.<br>The page was: '.$genurl; }
                    }
                unset($secondarydata);
                }
            }
        }
    unset($newurl);
    unset($form);
    unset($forms);
    unset($varb);
    //error3
    $error3='None';
    preg_match_all('/\<form(.*)(user|password)(.*)\<\/form\>/i',$data,$forms);
    foreach ($forms[0] AS $form) {
        if (preg_match('/\<form([^\>]+)method\=(\"|\'|)post/i',$form)) {
            preg_match_all('/(input|textarea)[^\>]+name\=(\"|\'|)/i',$form,$field);
            $fields=preg_replace('/(input|textarea)[^\>]+name\=(\"|\'|)(.*)/i',"$2",$field);
            unset($field);
            $newurls=explode('?',$genurl);
            $newurl=$newurls[0];
            unset($newurls);
            $newurl.='?';
            foreach ($fields AS $field) {
                $newurl.=$field.'='.urlencode('\' OR \'1\'=\'1\'').'&';
                $varb==1;
                }
            unset($field);
            if ($varb==1) {
                $newurl=substr_replace($newurl,'',-1);
                if (url_exists($newurl)) {
                    $secondarydata=file_get_contents($newurl);
                    //change if statement to if access granted
                    if (!preg_match('/(Log|Sign)([ _])?in/i',$result) && !preg_match('/(Log|Sign)([ _])?out/i',$data) && $result!==$data && !preg_match('/Register/i',$result)) {
                        if ($error3=='None') { $error2='SQL injections are possible on this page.<br>The page was: '.$genurl; }
                        }
                    unset($secondarydata);
                    }
                }
            } else {
            preg_match_all('/(input|textarea)[^\>]+name\=(\"|\'|)/i',$form,$field);
            $fields=preg_replace('/(input|textarea)[^\>]+name\=(\"|\'|)(.*)/i',"$2",$field);
            unset($field);
            $postvars='';
            foreach ($fields AS $field) {
                $postvars.=$field.'=\' OR \'1\'=\'1\'&';
                $varb==1;
                }
            unset($field);
            if ($varb==1) {
                $postvars=substr_replace($postvars,'',-1);
                $ch = curl_init();
                // set the target url
                curl_setopt($ch, CURLOPT_URL,$genurl);
                // howmany parameter to post
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS,$postvars);
                $result= curl_exec ($ch);
                curl_close ($ch);
                if (!preg_match('/(Log|Sign)([ _])?in/i',$result) && !preg_match('/(Log|Sign)([ _])?out/i',$data) && $result!==$data && !preg_match('/Register/i',$result)) {
                    if ($error3=='None') { $error2='SQL injections are possible on this page.<br>The page was: '.$genurl; }
                    }
                }
            }
        }
    unset($result);
    unset($varb);
    unset($form);
    unset($forms);
    unset($data);
    return '<tr><td colspan=3 bgcolor="#CCCCCC"><b>'.$genurl.'</b></td></tr>
    <tr><td bgcolor="#CCCCCC"><b>Url to page code injections</b></td><td bgcolor="#CCCCCC"><b>$_GET Form to page code injections</b></td><td bgcolor="#CCCCCC"><b>Password entry SQL injections</b></tr>
    <tr><td>'.$error1.'</td><td>'.$error2.'</td><td>'.$error3.'</td></tr>';
    }

Does this code look correct or would you recommend any modification? Also my entire script is attached to this post in case your interested.
Thanks.

Hi, I've just written a script based on the previous post and the function that checks each webpage for php security flaws is as follows:

function generate($genurl) {
    $data=file_get_contents($genurl);
    $urlvars=explode('?',$genurl);
    $newurl=$urlvars[0].'?';
    $error1='None';
    //error1
    if (isset($urlvars[1])) {
        $urlvar=explode('&',$urlvars[1]);
        unset($varb);
        foreach ($urlvar AS $var) {
            $newurl.=preg_replace('/([^=]+)=(.*)/',"$1=; echo \"<script>aaabbbcccdddeeabcefffggg</script>\";",$var).'&';
            $varb=1;
            }
        unset($var);
        if ($varb==1) {
            $newurl=substr_replace($newurl,'',-1);
            if (url_exists($newurl)) {
                $secondarydata=file_get_contents($newurl);
                if (preg_match('/\<script\>aaabbbcccdddeeabcefffggg\<\/script\>/is',$secondarydata)) {
                    $error1='Page open to url injections by injecting code into the page via url.<br>The test url was: '.$newurl;
                    }
                unset($secondarydata);
                }
            }
        unset($varb);
        }
    unset($newurl);
    //error2
    preg_match_all('/\<form[^\>]+([^m][^e][^t][^h][^o][^d][^\=]([^\']|[^\"]|[^])([^p][^o][^s][^t]))(.*)\<\/form\>/i',$data,$forms);
    $error2='None';
    foreach($forms[0] AS $form) {
        preg_match_all('/(input|textarea)[^\>]+name\=(\"|\'|)/i',$form,$field);
        $fields=preg_replace('/(input|textarea)[^\>]+name\=(\"|\'|)(.*)/i',"$2",$field);
        unset($field);
        $newurls=explode('?',$genurl);
        $newurl=$newurls[0];
        unset($newurls);
        $newurl.='?';
        foreach ($fields AS $field) {
            $newurl.=$field.'='.urlencode('<script>aaabbbcccdddeeabcefffggg</script>').'&';
            $varb==1;
            }
        unset($field);
        if ($varb==1) {
            $newurl=substr_replace($newurl,'',-1);
            if (url_exists($newurl)) {
                $secondarydata=file_get_contents($newurl);
                if (preg_match('/\<script\>aaabbbcccdddeeabcefffggg\<\/script\>/is',$secondarydata)) {
                    if ($error2=='None') { $error2='Forms may inject code into your page.<br>The page was: '.$genurl; }
                    }
                unset($secondarydata);
                }
            }
        }
    unset($newurl);
    unset($form);
    unset($forms);
    unset($varb);
    //error3
    $error3='None';
    preg_match_all('/\<form(.*)(user|password)(.*)\<\/form\>/i',$data,$forms);
    foreach ($forms[0] AS $form) {
        if (preg_match('/\<form([^\>]+)method\=(\"|\'|)post/i',$form)) {
            preg_match_all('/(input|textarea)[^\>]+name\=(\"|\'|)/i',$form,$field);
            $fields=preg_replace('/(input|textarea)[^\>]+name\=(\"|\'|)(.*)/i',"$2",$field);
            unset($field);
            $newurls=explode('?',$genurl);
            $newurl=$newurls[0];
            unset($newurls);
            $newurl.='?';
            foreach ($fields AS $field) {
                $newurl.=$field.'='.urlencode('\' OR \'1\'=\'1\'').'&';
                $varb==1;
                }
            unset($field);
            if ($varb==1) {
                $newurl=substr_replace($newurl,'',-1);
                if (url_exists($newurl)) {
                    $secondarydata=file_get_contents($newurl);
                    //change if statement to if access granted
                    if (!preg_match('/(Log|Sign)([ _])?in/i',$result) && !preg_match('/(Log|Sign)([ _])?out/i',$data) && $result!==$data && !preg_match('/Register/i',$result)) {
                        if ($error3=='None') { $error2='SQL injections are possible on this page.<br>The page was: '.$genurl; }
                        }
                    unset($secondarydata);
                    }
                }
            } else {
            preg_match_all('/(input|textarea)[^\>]+name\=(\"|\'|)/i',$form,$field);
            $fields=preg_replace('/(input|textarea)[^\>]+name\=(\"|\'|)(.*)/i',"$2",$field);
            unset($field);
            $postvars='';
            foreach ($fields AS $field) {
                $postvars.=$field.'=\' OR \'1\'=\'1\'&';
                $varb==1;
                }
            unset($field);
            if ($varb==1) {
                $postvars=substr_replace($postvars,'',-1);
                $ch = curl_init();
                // set the target url
                curl_setopt($ch, CURLOPT_URL,$genurl);
                // howmany parameter to post
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS,$postvars);
                $result= curl_exec ($ch);
                curl_close ($ch);
                if (!preg_match('/(Log|Sign)([ _])?in/i',$result) && !preg_match('/(Log|Sign)([ _])?out/i',$data) && $result!==$data && !preg_match('/Register/i',$result)) {
                    if ($error3=='None') { $error2='SQL injections are possible on this page.<br>The page was: '.$genurl; }
                    }
                }
            }
        }
    unset($result);
    unset($varb);
    unset($form);
    unset($forms);
    unset($data);
    return '<tr><td colspan=3 bgcolor="#CCCCCC"><b>'.$genurl.'</b></td></tr>
    <tr><td bgcolor="#CCCCCC"><b>Url to page code injections</b></td><td bgcolor="#CCCCCC"><b>$_GET Form to page code injections</b></td><td bgcolor="#CCCCCC"><b>Password entry SQL injections</b></tr>
    <tr><td>'.$error1.'</td><td>'.$error2.'</td><td>'.$error3.'</td></tr>';
    }

Does this code look correct or would you recommend any modification? Also my entire script is attached to this post in case your interested.
Thanks.

It doesn't seem to be working when I tested it on:

http://xss-login.appjet.net/

This page has an XSS vulenerability that I put i there to demonstrate a browser vulnerability.

I just had a look at the HTTP traffict sent by the PHP script and it only requests the url, but doesn't test the login form.

If it did it would notice the XSS in the username field.

The example:

$url = 'http://xss-login.appjet.net/';
$param = 'username';
$value = '<script>alert(\'xss\')</script>';

$body = file_get_contents($url.'?'.$param.'='.$value);

if (strpos($body, $value) !== false) {
	echo 'Possible XSS code injection';
} else {
	echo 'Failed XSS code injection';
}

confirms the xss injection.

I just re-checked my code to see why that is and it seems for forms with XSS injection the feature needed adding (very simular to the SQL injection feature) however I have a problem with a preg_match_all statement I use. The following is the preg match all statement and it just returns empty arrays even though theoratically it should match the form. Could anybody help me debug the following code to return the form if the form contains the word 'user' or the word 'password'.

<?
preg_match_all('/\<form(.*)(user|password)(.*)\<\/form\>/i',file_get_contents('http://xss-login.appjet.net/'),$forms);

//display the result
echo '<xmp>';
print_r($forms);
echo '</xmp>';
?>

I have tried many variations of this code with no luck.
Thanks for the help.

I just re-checked my code to see why that is and it seems for forms with XSS injection the feature needed adding (very simular to the SQL injection feature) however I have a problem with a preg_match_all statement I use. The following is the preg match all statement and it just returns empty arrays even though theoratically it should match the form. Could anybody help me debug the following code to return the form if the form contains the word 'user' or the word 'password'.

<?
preg_match_all('/\<form(.*)(user|password)(.*)\<\/form\>/i',file_get_contents('http://xss-login.appjet.net/'),$forms);

//display the result
echo '<xmp>';
print_r($forms);
echo '</xmp>';
?>

I have tried many variations of this code with no luck.
Thanks for the help.

You mean like this ?

preg_match_all('/\<form(.*)(user|password)(.*)\<\/form\>/is',file_get_contents('http://xss-login.appjet.net/'),$forms);

I think it would be more readable to use an XML parser, such as the SimpleXML parser build into PHP5.

And to use a modular approach. After a each page is parsed, to send the results to each vulnerability test, such as XSS, SQL injection, Shell injection etc.

regular expressions are a bit hard to follow.. don't you think?

You mean like this ?

preg_match_all('/\<form(.*)(user|password)(.*)\<\/form\>/is',file_get_contents('http://xss-login.appjet.net/'),$forms);

Cool, your modification works. I guess I will need to pay more attention to the eliminator in future. I'll try and see what else is preventing the XSS detection.

I have just completed the xss detection and have tested the sql injection tester. I have attached the script to this post and does anybody recommend any changes to this script such as additional features?

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.