[KOR] 트위터 URL Redirection/Hijacking

If you are an English speaker, please check out the english version of this blog post here! (link TBD).

<Note: 이 프로젝트를 진행하는데 있어 실제 트위터 URL Redirection 및 도메인 구입/등록은 하지 않았습니다. 어디까지나 이런 일이 가능하다라는 것을 보여주기 위한 글입니다.>

요약

트위터를 사용할때 트윗안에 특정 사이트의 링크를 거는 경우가 있다. 이때 중요한 점은 해당 URL 의 최종 도착지가 변경되거나 도메인 자체가 만료된다고 하더라도 자신의 트윗 내용과 그 안에 삽입된 URL 자체는 변하지 않는다는 것이다. 만약 제 3자가 해당 URL이 가르키는 도메인을 구입해 버린다면, 트윗을 올린 사람은 졸지에 제3자의 웹사이트를 자신의 트윗에 링크를 해버린 것이 된다.

예시) 청와대가 올린 트윗

예를 들자면, 청와대 트위터 계정이 올린 트윗에는  petition-survey[.]com 이라는 URL이 삽입되어 있다. 그리고, 이 도메인은 만료가 된 상태라서 누구나 구입할 수 있다.

예시) 제3자가 구입한 도메인 (로컬 호스트를 이용한 예시일 뿐이다)

따라서 제 3자가 petition-survey[.]com 이라는 도메인을 구입한다면, 이제 위 청와대의 트윗은 제3자의 도메인을 링크를 하고 있는 셈이 된다. 이전 사례로는 2017년 벨기에의 한 보안 연구원이 트럼프 대통령의 트위터 링크를 바꾼 사례가 있다.

도메인 등록, 만료의 개념이야  1985년도부터 있던 개념이고, 특정한 리소스에 링크를 거는 Hyperlink의 개념이야 HTTP가 개발되었던 1989년도부터 있던 개념이다. 따라서 이 글은 뭔가 새로운 걸 발견했다는 내용의 블로그 글은 절대로 아니다. 시험이 끝나고 인턴 시작 전까지 몇 일 남았기에 이래저래 작은 사이드 프로젝트를 하나 해봤고, 느낀 점들을 이 글에 남겨봤다.

이 글의 목적은 트윗 URL Redirection/Hijacking이 일어날 수 있으며, 위험하고, SNS/보안 담당자들은 항상 자신들이 날리는 트윗을 조심해야 한다는 것을 알리기 위함이다.

트위터, 트윗, 그리고 URL

개인, 기업 뿐만 아니라 정치권도 트위터를 사용하는 시대다

소통을 중요시 하는 요즘 사회에, 사기업들뿐만 아니라 공기업, 관공서, 심지어는 정치인들까지 트위터를 하는 모습을 자주 볼 수 있다. 자신의 생각을 남들과 공유하고, 피드백을 받거나 다른 이들의 생각을 리트윗하는 것은 트위터의 가장 중요한 순기능 중 하나다.

인터넷에서 이런 소통을 할 때 가장 많이 공유되는 것들 중 하나가 바로 URL 링크다. 왠만한 것들이 모두 인터넷에서 구동되는 현 시점에서 URL을 공유하는 것은 자주 있는 일이다. 그런데 여기서 트윗과 URL의 차이점에서 오는 재미있는 일이 발생한다.

트윗은 변할 수 없다. 내가 일단 한 번 트윗을 날렸다면 그 트윗은 수정할 수 없다. 정말로 수정하고 싶다면 해당 트윗을 삭제한 후, 수정된 버전의 트윗을 새로 날려야한다. 이렇듯 한번 날린 트윗의 내용 자체는 변할 수 없기 때문에, 해당 트윗 안에 삽입된 URL 링크 또한 변하지 않는다. 내가 https://www.naver.com 이라는 링크를 내 트윗에 삽입 했다고 해보자. 저 네이버 링크는 내가 해당 트윗을 완전히 삭제하지 않는 이상, 평생 해당 트윗에 삽입되어 있을 것이다.  그리고 그 트윗이 지워지지 않는 한, 누군가 리트윗을 하거나, 해당 트윗의 URL을 다른 이들에게 공유할 수 있다.

URL이 포함된 하이퍼링크(Hyperlink)는 변할 수 있다. 정확히는 URL이 최종적으로 가르키는 대상은 변할 수 있다.  몇가지 변수들을 보자면, DNS 설정에서 오는 도메인의 여부, DNS 서버들과  캐싱에서 발생할 수 있는  변화, 도메인이 만료된 경우, Redirection 의 여부,  특정 엔드포인트가 사라진 경우 (링크를 눌러봤는데 404 찾을 수 없습니다가 나온적이 있을 것이다) 등이 있다. URL과 유저가 그것을 클릭했을 때 최종적으로 방문하게 되는 서버 IP 주소 및 엔드포인트 사이에 영향을 줄 수 있는 변수는 정말 많다.

바로 이런 트윗과 링크의 차이점 때문에 트위터 URL Redirection/Hijacking 이 일어나게 된다.

시나리오

트위터 URL Redirection/Hijacking 공격도

(예시를 드는 시나리오일 뿐이다.)

트위터 URL Redirection/Hijacking 의 가상 시나리오를 만들어보자. 특정 URL 이 들어간 트윗을 날린 뒤, 시간이 흐른다. 그 시간동안 해당 URL이 가르키고 있던 도메인은 만료가 된다. 이를 알아챈 공격자는 똑같은 이름의 도메인을 구입하고, 해당 트윗을 리트윗 한다. 이제 해당 리트윗을 보는 유저들은 공격자의 웹사이트를 방문하게 된다.

예를 들어 내가 청와대에 근무하고 있는 SNS 담당 공무원이라고 해보자. 2019년 , 오늘도 열심히 출근해서 업무를 처리한다. 국민 청원을 받는 웹사이트가 만들어졌으니 트위터를 통해 홍보를 하라는 지시가 떨어졌다. https[:]//petition-survey[.]com 라는 해당 URL과 국민 청원에 관련된 내용을 적어 트윗을 날리면 된다고 한다. 어렵지 않은 일이기에 오탈자 검사를 끝낸 뒤, 해당 트윗을 올린다.

예시) 청와대의 트윗

그렇게 시간이 흘러 2020년 5월이 되었다. 하루에도 몇개의 트윗을 날리고, 다른 SNS 계정을 관리하는 직업 특성상 1년전에 올려놨던 트윗은 까마득하게 잊고 지내고 있었다. 하지만 시간이 지나면서 국민 청원 프로젝트 기간이 끝난 웹사이트의 petition-survey[.]com 도메인은 만료가 된다. 그것을 알아챈 다른 제3자가 해당 도메인을 구입하고, 자신만의 서버를 구축해놓는다.

이제 해당 트윗을 통해 petition-survey[.]com 을 방문하게 되면 제3자의 서버가 나오게 된다.

예시로 보여지는 제3자의 서버 (실제로는 로컬호스트다)

SNS 담당 공무원인 나는 이 사실을 당연히 모른다. 수백개의 트윗을 기억하는 것도 아니고, 무려 1년전의 트윗에서 링크한 도메인이 만료됐다는 것을 어떻게 알겠는가? 하지만 내가 알건 말건, 해당 트윗은 트위터에, 인터넷에 존재하고 있다. 해당 트윗이 리트윗이 되고, 트윗의 URL이 널리 퍼지게 된다. 사람들은 무려 공식 청와대 트윗을 통해 이제는 제3자의 서버가 되버린 petition-survey[.]com 을 방문하게 된다.

URL 하이재킹과 무기화

트윗 URL Hijacking의 무기화

IT쪽에 관심이 없는 분이라면 "그래서 어쩌라고?" 라고 생각하실 것이고, 관심이 있는 분이라면 꽤 무서운 일이라고 생각하실 것이다. 다른 개인, 사기업, 공기업, 관공서, 정치인이 날린 트윗을 통해서 제3자의 서버를 방문하는 일은 매우 무서운 일이다. 평범한 피싱(Phishing) 이라면 대부분 모르는 사람의 이메일, 문자, SNS 메시지로 오는 것이 대부분이다. 모르는 사람이 보낸 링크이기 때문에, 사람들은 자연히 조심스럽게 그 링크를 접근하거나 무시해버린다. 하지만 특정 링크가 이미 검증된 기업, 관공서, 연예인, 정치인의 트윗에서 발견된다면, 그 링크를 자연스럽게 믿게된다. "설마 XYZ라는 개인/단체가 피싱 사이트 URL을 트윗하겠어?" 라는 마음가짐으로 말이다.

이제 트윗 URL Redirection이 어떻게 무기화 (Weaponization) 되는지 알아보자.

리트윗, 봇, 그리고 인플루언서

리트윗

무기화 시키기 앞서, 일단 URL 하이재킹이 완성된 트윗을 가장 많은 사람들에게 알려야한다. 가장 쉽고 빠르게 무기화시킬 수 있는 법은 바로 리트윗, 봇, 그리고 인플루언서들을 이용하는 방법이다. 도메인 만료 --> 제3자의 도메인 구입이 완성된 시점에서 URL이 들어가 있는 트윗을 리트윗 하기 시작하면, 더 많은 사람들이 그 링크를 보게된다. 금전을 사용해 트위터 봇들 및 파워 트위터리안 (혹은 인플루언서) 들을 이용하는 방법도 있다.

굳이 트위터를 이용하지 않더라도, 다른 SNS 및 커뮤니티에 해당 트윗의 URL 을 올릴 수도 있다. 짧은 트윗의 특성상, 해당 트윗의 URL 이 아닌 사진을 찍어서 올릴 수도 있을 것이다.

기술적 공격

유저들이 리트윗, 트위터 URL, 다른 SNS 및 커뮤니티 등으로 URL을 클릭해 제3자의 웹사이트에 접속을 했다면, 그 다음부터는 전형적인 공격자의 기법을 활용할 수 있게 된다. 유저들이야 공신력이 있는 트윗에 링크된 URL 을 타고 접근한 웹사이트이니, 그 웹사이트를 신용을 하고 있는 상태일 것이다.

이때부터는 공격자가 원하는 방식을 사용할 수 있다. Social Engineering Toolkit 등으로 Credential Harvesting 을 실행하는 가짜 사이트를 만들수도 있고, 악성코드를 다운받게 할 수 있으며, 수준 높은 공격자의 경우 익스플로잇 킷을 이용한 Drive by Download를 사용할 수도 있다. "트위터 로그인이 필요하다" 라는 Credential Harvesting 페이지를 만든 뒤 트윗을 타고 들어온 트위터리안들의 비밀번호를 빼갈 수도 있다.

브랜드 이미지 공격

복잡한 기술적 공격이 아니라, 단순한 브랜드 이미지 공격을 할 수도 있다. 공격자의 도메인이 정상적이지 않거나 불쾌한 웹사이트로 리다이렉트 되는 경우에는, 해당 URL을 걸어놓은 트윗 유저의 브랜드 이미지에 큰 해를 가할 수 있다.

유저의 트윗 –> 트윗 속 URL –> 공격자 서버 –> 불쾌한 웹사이트, 동영상, 이미지 등

이렇게 공격이 진행될 경우 불쾌한 웹사이트로의 Redirection은 공격자 서버에서 일어난 것이지만, 문제의 원인 제공 자체는 유저의 트윗이 된다. 어쨋든 사람들은 유저의 트윗에 링크된 URL을 타고 공격자 서버를 방문한 것이니까 말이다. 그리고 그 원인 제공을 했기에 해당 트위터 유저의 이미지는 실추될 것이다.

어느 날 자고 일어나 보니 자신이 과거에 날렸던 트윗에는 성인 사이트가 링크되어 있고, 남들이 그 트윗을 찾아내서 수백번 리트윗 했다고 생각하면 이해가 빠를 것이다.

Tweepy 를 이용한 트윗 검색

트위터 API 를 사용하게 해주는 Tweepy 모듈을 사용해보자

앞서 개념과 왜 트윗 URL Redirection이 무서운지 알았으니, 이제 만료된 도메인의 URL이 링크된 트윗을 찾아보자. 트위터 URL Redirect/Hijacking 이 가능한 트윗과 URL 을 찾는 논리 자체는 복잡하지 않다.

  1. 타겟 유저를 정한다.
  2. 유저가 과거에 날렸던 트윗들 중, URL이 삽입된 트윗들을 모두 찾는다
  3. 링크된 URL 들을 방문해보고, 방문에 실패하면 해당 URL을 반환한다.
  4. URL안의 도메인을 whois 에 검색해본 뒤, 도메인 등록이 되어있지 않거나 도메인이 만료된 경우 해당 URL과 트윗을 반환한다.

트윗 몇백, 몇천개를 일일히 스크롤 내리며 확인하기에는 시간이 너무 아까우니, 파이썬과 Tweepy 를 활용해 트위터의 API를 이용하도록 한다. Tweepy 는 트위터 API를 파이썬을 통해 사용하게 해주는 라이브러리다. 전체 PoC 코드는 악용될 소지가 있어 공개하지 않는다. 또한,  파이썬 수업도 아니고 스크립트를 한 줄 한 줄 분석해가며 블로그 글을 올리는 것은 큰 의미가 없다고 생각한다.  정 궁금하신 분들은 github 등에 가면 이런 스크립트들은 널려있을 것이다.

세세한 코드 분석 보다는 "이런 과정을 통해서 트윗을 찾는다" 라고 생각하고 봐주시면 좋겠다.

트위터 API 인증 및 Tweepy 선언

    # Authenticate to Twitter
    auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
    auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)

    # Create API object
    api = tweepy.API(auth)
인증 및 Tweepy 선언

트위터 개발자로 가입한 뒤, API 토큰을 받아온다. 그 후엔 Tweepy 를 이용해 api를 선언해준다.

해당 유저의 트윗 검색

# "Scraping" TWEETNUM amount of USERID's tweets 
for status in tweepy.Cursor(api.user_timeline, id=USERID, include_entities=True, tweet_mode='extended').items(TWEETNUM):  

    try:
        # Return tweets which have URLs inside them
        url = status.entities['urls'][0]['expanded_url']
        
        # Ignore tweets with "normal" URLs - youtube, twitter, facebook, etc... 
        if any([x in url for x in BLACKLIST]):
            continue
        
        # Save the tweet, date, and url in a dictionary
        targetDict['tweet'] = BASEURL + status.id_str
        targetDict['date'] = status.created_at.strftime("%m/%d/%Y") 
        targetDict['url'] = url

        targetList.append(targetDict.copy())

	# The Tweet does not have a URL inside it. We don't need it, continue
    except:
        continue
트위터 스크래핑 및 "타겟" 딕셔너리 생성, 이후 타겟 리스트에 추가

그 뒤로는 특정 유저의 트윗 중 URL이 삽입된 트윗을 반환해온다. 트윗 반환 및 검색에는 tweepy.Cursor 가 사용된다. 이 때 USERIDTWEETNUM 은 당연히 argparse 혹은 sys.argv[] 등을 이용한 사용자 입력값을 받는다. 그 뒤로는 tweepy.Cursor 를 이용해 특정 유저의 트윗을 TWEETNUM 갯수 만큼 받아오면 된다.

트윗들을 "받아올 때", tweepy 는 트위터 API 를 이용하여 Tweet Status 라는 오브젝트를 반환한다. 1개의 트윗 당 아주 어마어마한 길이의 json 이 반환되는데, 여기서 우리에게 중요한 것은 status.entities['urls']status.entities['urls'][0]['expanded_url'] 이다. status.entities['urls'] 는 해당 트윗과 관련된 다양한 url들의 list 를 보여주는데, 이 urls 중에는 트윗 자체의 URL, 트윗에 삽입된 링크의 URL, 트윗에 삽입된 이미지의 URL, 트윗에 삽입된 동영상의 URL 등, 다양한 URL 들이 들어가 있다.

이 중 우리가 주목해야할 URL 은 가장 첫번째 URL status.entities['urls'][0] 인데, 바로 이것이 트윗에 삽입된 링크의 URL 이기 때문이다. 그리고 그 URL 중 우리가 관심있는 것은 바로 expanded_url , 즉 트위터의 URL shortener (트위터는 자체적으로 t.co 로 시작하는 url shortener 를 사용한다) 를 거친 뒤 마지막으로 나오는 순수한 URL 이다.

# Ignore tweets with "normal" URLs - youtube, twitter, facebook, etc... 
if any([x in url for x in BLACKLIST]):
    continue
정상적인 URL들은 무시해주자.

위 코드에서 특별히 주의해야하는 구간이다. 트윗 안에서 정상적인 url 혹은 URL Shortner 들 - youtube, twitter, facebook, bit.ly, goo.gl 등이 들어가 있는 URL 이라면 무시한다. 유투브, 트위터, 페이스북의 도메인이 만료되는 일은 수십년안에는 일어나지 않을 것이기 때문이다. 또한, URL Shortner 들은 다양한 이유에 따라서 Redirection/Hijacking이 일어날 경우가 거의 없다. 따라서 BLACKLIST 라는 string 리스트를 만들어놓고, 그 안에다가 정상적인 도메인 이름들을 적어놓은 뒤,  위에서 찾은 url 변수가 BLACKLIST 중 하나에 만족한다면 무시해준다.

링크 방문

headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.1916.47 Safari/537.36'}

try:
	res = requests.get(url, headers=headers, allow_redirects=True, timeout=10, verify=False)

except:
	return url
링크를 직접 방문한다

이제 URL이 삽입된 트윗들도 모두 찾았고, URL 자체들도 찾았으니, 방문하는 일만 남았다. 방문이 성공적이라면, 도메인이 살아있는 것이다. HTTP Status Code 가 200, 300, 400, 500 번대 인 것은 중요하지 않다. Success 건, Redirection 이건 Client Failure 건 Server Failure 건 간에, 방문을 할 수 있다는 것은 곧 요청에 답해줄 수 있다는 웹서버/어플리케이션이 도착지에 있다는 것이기 때문이다.

우리가 관심있는 것은 바로 requests.get 자체가 실패했을 경우다. 이때는 HTTP 요청에 답해줄 수 있는 웹서버/어플 자체가 없다는 뜻으로, 도메인이 살아있을 확률이 매우 낮다.

Python-Whois

try:    
    domain = get_fld(url)
    whoisResult = ''
    try:
        whoisResult = whois.query(domain)
        
        # If whois result returns None, it means the domain is expired
        if whoisResult is None:
            return url
    except Exception as e:
        pass 
except Exception as e:
    pass 
Python-whois 를 통해 만료된 도메인을 찾아준다

마지막으로는 도메인 만료 확인을 위해 python-whois 를 통해 후이즈 검색을 실행한다. 검색 했을 때 도메인이 만료되었다고 나온다면 (python-whois 가 None 을 반환한다), 제3자가 도메인을 등록할 수 있다는 것이다.

PoC를 완성하며  

#) python3 twitterProj.py -u <USERNAME> -n <NUMBER_OF_TWEETS>

혹은 

#) python3 twitterProj.py -f <userlist_file.txt> -n <NUMBER_OF_TWEETS>
내 스크립트의 경우 이렇게 실행한다
PoC 스크립트 결과값

좀 더 효율적으로 만드려면 Concurrency 를 이용해야한다. 유저 리스트를 준비한 뒤 Asyncio 를 활용해 여러명의 유저들의 트윗을 한꺼번에 트위터 API에서 요청해오고, AioHTTP 를 이용해 링크 방문을 병렬처리를 한다면 훨씬 더 빠르게, 더 많은 결과를 얻을 수 있을 것이다.

PoC 결과와 통계

(PoC 악용의 여지가 있어 현재 검증된 단체, 개인의 연락이 아니라면 PoC는 공개하지 않고 있습니다.)

PoC 스크립트를 이용해 총 한국 내 기업, 정치, 연예 3종류의 트위터 유저 각 20명씩을 조사해봤다. 총 60명의 유저가 조사 되었으며, 각 유저 1명당 3000개의 트윗을 조사했다. 트위터 유저들의 핸들은 당연히 비공개 처리한다. 참고로 트위터 개발자 API의 Rate Limiting을 존중하기 위해서 더 많은 숫자의 유저 및 트윗을 조사할 수 없었다.

PoC 스크립트의 출력 화면 중 하나

조사 결과는 다음과 같다.

하이재킹 가능한 트윗 갯수 그래프

총 133개의 하이재킹/Redirection이 가능한 트윗이 발견되었다. 정치인/정당쪽에서는 38개의 트윗이, 기업쪽은 77개가, 연예인쪽은 18개의 트윗이 만료된 도메인을 직접 링크한 트윗을 날렸던 것이 발견되었다. 아무래도 기업 트위터 계정의 특성상 다양한 이벤트 페이지 및 제품 페이지를 링크하기 때문에 제일 높은 것 같다. 오히려 연예인 분들은 특별한 커스텀 도메인보다는 유튜브, 인스타그램, 페이스북 등의 링크를 달았기 때문에 별로 발견이 안된 것 같다.

만료된 도메인 갯수

만료된 도메인의 갯수는 정치/정당에서 31개, 기업에서 23개, 연예인쪽에서 16개가 발견되었다. 몇개의 예외도 있었는데, 특히 특정 도메인을 몇년동안 사용하면서 트위터에 1년 2년 내내 링크하다가 그 도메인이 만료된 경우도 있었다.

만료된 도메인 링크와 트윗은 많이 알려진 위험요소인데도 불구하고 많은 트윗들이 발견된 것은 우려스럽다.

해결방법

  1. 내가 날린 트윗들을 모두 보여주고, 그 중 URL이 들어간 트윗들만 모아서 보관해주는 솔루션 및 웹 어플이 있다면 사용하는 것이 좋다.
  2. URL을 굳이 링크를 해야한다면 goo.gl, bit.ly 등의 URL Shortner을 사용하는 것이 좋다. 글자수도 줄여줄 뿐더러, 도메인 링크를 자신의 트윗에 직접적으로 링크를 하는 것이 아닌, URL Shortner 가 링크되기 때문이다. URL Shortner 서비스에 따라 너무 오래된 링크는 만료를 시켜버리거나, 특정 URL에 도달할 수 없으면 그 링크를 만료시켜 버리는 서비스들도 있으니, 어느정도는 도움이 된다고 볼 수 있다.
  3. 위협 모델링을 돌려봤을 때 내가 트위터에서 영향력이 있거나 공인이라면 IT직종의 사람을 고용해 스크립트를 만들어 과거 트윗들을 찾아낸 뒤, 삭제하는 것이 가장 좋은 방법일 것 같다.

그 외에는 사실 이런 상황들은 HTTP 라는 프로토콜의 한계에서 오는 것이고, 도메인 등록/만료라는 어쩔 수 없는 상황에서 발생하는 것이다. 따라서 트위터쪽에서 도와주지 않는 이상 유저 자신이 조심하는 수 밖에는 없다.

느낀 점

프로젝트를 진행하면서 너무 애매한 부분이 많았던 것 같다. 첫째, 이건 취약점이 아니다. 오래된 URL과 도메인 만료는 취약점이 아닌, HTTP 와 DNS 라는 프로토콜을 인터넷에서 사용하며 당연히 일어나는 일들이다. 둘째, 취약점은 아니지만, 악용될 우려도 있다. 그렇기 때문에 이 블로그에서 PoC 스크립트를 공개하지 않았다. 셋째, 그래서 블로그 글 공개와 도메인 공개를 하기전에 다양한 단체들의 IT보안 연락처를 찾은 뒤 개인적으로 연락을 취하려고 했다. 하지만 security.txtsecurity@<domain> 을 활용하는 외국 단체와는 달리, 연락할 곳을 찾을 수 없었다. 그나마 찾은 몇 곳은 공인인증서나 아이핀이라던지 핸드폰 사용자 인증을 요구하니, 외국에 있는 입장에서 할 수 있는 일이 없었다.

트위터를 사용하시는 분들 중, 단체나 기업, 정부 소속의 분들은 알아놔야할 것 같은 개념인데도, 사적으로 이것을 알릴 방법이 없었다. 정말정말 심각한 취약점이나 제로데이였다면 당연히 올바른 Responsible Disclosure 절차를 밟아 꼭 알렸을 테지만, 이것 애당초 그렇게까지 위험한 취약점이 아니다. 아니, 아예 취약점 자체가 아니다. 그래서 그냥 블로그 글로 남겨봤다. 참 애매한 프로젝트이지 않았나 싶다.

타임라인

  • 5/7/2020 - 기말고사 끝. 프로젝트 시작 결정
  • 5/9/2020 - 스크립트 및 리서치 시작
  • 5/11/2020 - 스크립트 초안 완성
  • 5/13/2020 - 스크립트 완성
  • 5/15/2020 - 완성된 스크립트로 조사 시작
  • 5/22/2020 - 블로그 글 작성