sgcIndy 2026.6은 서버 측에 주목합니다. 이번 릴리스는 TLS, TCP, HTTP 서버 컴포넌트에 일련의 선택형 보안 강화 옵션을 추가하여, 기반이 되는 Indy 코드가 한 번도 방어하지 못했던 여러 잘 알려진 공격 유형을 차단합니다. 예를 들면 검증 실패 시에도 통과시키는(fail-open) 인증서 검증 콜백, 천천히 흘려보내는 Slowloris 연결, 무제한 요청 본문 및 헤더, 그리고 HTTP 요청 스머글링 등이 있습니다. 또한 최초의 ML-KEM-768 포스트 양자 키 캡슐화 프리미티브도 함께 제공합니다.
모든 새 보호 기능은 이전 동작을 기본값으로 사용하므로, 직접 켜기 전까지 기존 애플리케이션은 영향을 받지 않습니다. 이 글에서는 바로 붙여 넣어 쓸 수 있는 Delphi 코드 조각과 함께 각 기능을 차례로 살펴봅니다.
강화된 TLS 서버
가장 중요한 수정 사항은 OpenSSL 피어 검증 콜백에 있습니다. 기본 Indy에서는 검증 콜백이 실패 시에도 통과(fail open)될 수 있습니다. 서버가 클라이언트 인증서를 요청할 때 최종 결정이 사용자 콜백에 의해 좌우되었고 OpenSSL 자체의 판정을 무시했기 때문에, 성공을 반환하는 콜백은 만료되었거나 자체 서명되었거나 신뢰할 수 없는 인증서를 그대로 수락했습니다. 새로운 TIdSSLOptions.StrictVerify 플래그는 OpenSSL의 결과를 강제하므로, 사용자 지정 OnVerifyPeer 핸들러는 결정을 더 엄격하게 제한할 수만 있을 뿐 절대 완화할 수 없습니다.
세 개의 추가 플래그는 헤더에는 이미 선언되어 있었지만 라이브러리가 적용한 적이 없던 OpenSSL 옵션을 연결합니다. DisableCompression은 CRIME을 완화하고, DisableRenegotiation은 OpenSSL 1.1.0h 이상에서 클라이언트가 시작하는 재협상(CPU 비대칭 DoS)을 차단하며, ServerCipherPreference는 협상에서 클라이언트가 아닌 서버의 암호화 스위트 순서가 우선하도록 합니다.
uses
IdHTTPServer, IdSSLOpenSSL;
var
oServer: TIdHTTPServer;
oSSL: TIdServerIOHandlerSSLOpenSSL;
begin
oServer := TIdHTTPServer.Create(nil);
oSSL := TIdServerIOHandlerSSLOpenSSL.Create(oServer);
oSSL.SSLOptions.CertFile := 'server.pem';
oSSL.SSLOptions.KeyFile := 'server.key';
oSSL.SSLOptions.SSLVersions := [sslvTLSv1_2, sslvTLSv1_3];
// opt-in hardening, every flag defaults to False
oSSL.SSLOptions.StrictVerify := True; // enforce the OpenSSL verdict
oSSL.SSLOptions.DisableCompression := True; // CRIME
oSSL.SSLOptions.DisableRenegotiation := True; // renegotiation DoS
oSSL.SSLOptions.ServerCipherPreference := True; // server picks the cipher
oServer.IOHandler := oSSL;
oServer.Active := True;
end;
알 수 없는 클라이언트 인증서를 실제로 거부하는 상호 TLS를 원한다면 VerifyMode를 [sslvrfPeer, sslvrfFailIfNoPeerCert]로 설정하고 StrictVerify를 함께 사용하십시오.
Slowloris 차단
Slowloris 클라이언트는 많은 연결을 열고 한 번에 1바이트씩만 찔끔찔끔 보내면서 요청을 결코 완료하지 않아, 모든 작업자 스레드를 묶어 둡니다. 자연스러운 방어책은 읽기 타임아웃처럼 보이지만, 알아 둘 만한 미묘한 점이 있습니다. Indy의 ReadTimeout은 비활성 타임아웃입니다. 바이트가 도착할 때마다 초기화되므로, 몇 초마다 한 바이트씩 보내는 클라이언트는 연결을 영원히 살려 둡니다.
2026.6은 SetReadDeadline을 통해 TIdIOHandler에 진정한 전체 읽기 데드라인을 추가합니다. 이는 모든 읽기마다 검사되므로, 바이트가 계속 흘러들어 오는 중에도 발동됩니다. HTTP 서버는 새로운 RequestReadTimeout 속성을 통해 이를 자동으로 연결하며, 이 속성은 요청 라인과 헤더를 수신하는 시간을 제한하고 keep-alive 요청마다 초기화됩니다.
uses
IdHTTPServer;
var
oServer: TIdHTTPServer;
begin
oServer := TIdHTTPServer.Create(nil);
oServer.DefaultPort := 8080;
// close any client that has not finished sending the request
// line and headers within 5 seconds, even if it keeps dripping bytes
oServer.RequestReadTimeout := 5000;
oServer.Active := True;
end;
사용자 지정 TIdTCPServer의 경우 읽기 루프 주변에서 동일한 데드라인을 직접 적용할 수 있습니다. 데드라인을 해제하려면 0을 전달하십시오.
procedure TForm1.ServerExecute(AContext: TIdContext);
var
vLine: string;
begin
// bound the whole request to 5 seconds of total read time
AContext.Connection.IOHandler.SetReadDeadline(5000);
try
vLine := AContext.Connection.IOHandler.ReadLn;
// ... handle the request ...
finally
AContext.Connection.IOHandler.SetReadDeadline(0);
end;
end;
HTTP 서버용 요청 제한
클라이언트가 선언하는 대로 무엇이든 읽는 HTTP 서버는 손쉬운 메모리 고갈 표적입니다. 단일 요청이 Content-Length: 2000000000을 선언하면 서버는 2기가바이트를 버퍼링하려 시도하거나, 끝없는 청크 본문을 스트리밍하거나, 수백만 바이트의 헤더를 보낼 수 있습니다. 2026.6은 TIdCustomHTTPServer에 세 가지 한도와 스머글링 검사를 추가합니다.
MaxRequestBodySize는 Content-Length와 합산된 청크 본문을 제한하고, 초과 시 413 Payload Too Large로 응답합니다. MaxHeaderTotalSize는 전체 헤더 바이트를 제한하고 431 Request Header Fields Too Large로 응답합니다. StrictRequestParsing은 의도적으로 모호한 요청을 거부합니다. 예를 들어 Content-Length와 Transfer-Encoding: chunked를 모두 포함하는 메시지(고전적인 요청 스머글링 벡터)나 음수 청크 크기를 가진 요청을 400 Bad Request로 응답하며 거부합니다. 청크 트레일러 헤더 루프 역시 이제 제한되므로, 공격자는 더 이상 무한한 트레일러 줄 스트림으로 연결을 열어 둘 수 없습니다.
oServer.MaxRequestBodySize := 10 * 1024 * 1024; // 10 MB, else 413
oServer.MaxHeaderTotalSize := 64 * 1024; // 64 KB, else 431
oServer.StrictRequestParsing := True; // reject CL+TE smuggling, else 400
원시 TCP 계층에서 TIdIOHandler.MaxInputBufferSize는 IOHandler 위에 구축된 모든 프로토콜의 누적 입력 버퍼를 제한하여, 길이 접두사 읽기나 지나치게 긴 줄이 버퍼를 무한정 키우는 것을 막습니다.
// inside OnConnect / OnExecute of any Indy server
AContext.Connection.IOHandler.MaxInputBufferSize := 1024 * 1024; // 1 MB cap
설계상 선택형
이 옵션들 중 어느 것도 기본 동작을 변경하지 않습니다. 필드는 모두 끄기를 기본값으로 합니다(크기 및 타임아웃 한도는 0, 불리언 플래그는 False). 따라서 2026.6으로 업그레이드하는 프로젝트는 2026.5에서와 정확히 동일하게 동작합니다. 배포 환경에 필요한 보호 기능만 정확히 활성화하면 되며, 동일한 코드가 Delphi 7부터 RAD Studio 13 및 Free Pascal까지 그대로 컴파일됩니다.
이번 릴리스의 그 밖의 변경 사항
2026.6은 OpenSSL 3.5 이상에서 사용할 수 있는 최초의 ML-KEM-768 포스트 양자 캡슐화 및 역캡슐화 프리미티브를 도입합니다. 이들은 단순한 TBytes API를 노출하므로, 고전적인 ECDH 교환과 함께 하이브리드 핸드셰이크에 포스트 양자 키 캡슐화 단계를 계층화할 수 있습니다.
빌드 측면에서는, RAD Studio가 공백이 포함된 경로에 설치되어 있을 때 C++Builder 패키지 컴파일이 더 이상 MSBuild 오류 MSB1008로 실패하지 않습니다. 이제 DCC_BpiOutput 매개변수가 따옴표로 묶입니다.
업그레이드
2026.6은 즉시 적용 가능한 업그레이드입니다. 모든 새 보호 기능이 선택형이므로 호환성을 깨는 변경 사항이 없고 마이그레이션할 것도 없습니다. 위의 코드 조각을 검토하고 서버에 맞는 옵션을 활성화하십시오.
sgcIndy는 무료입니다. 최신 빌드는 esegece.com/products/sgcindy/download에서 다운로드하십시오.
질문, 피드백 또는 서버 강화에 도움이 필요하신가요? 문의하기를 이용하시면 코드를 직접 작성한 사람들에게서 답변을 받으실 수 있습니다.
