fix(security): outbound URL 내부주소·host 검증 전수 강화
서버가 외부 입력으로 목적지가 제어되는 outbound HTTP 요청을 보내는 지점을 전수 조사해, 내부망(사설 IP·루프백·링크로컬·메타데이터)과 host 위조를 차단한다. KVE-2026-1546(이니시스 인증 URL 접두사 매칭 우회)과 동일 클래스의 지점(언어팩 URL 설치, 스케줄 URL 호출, 배송비 계산 API, GitHub URL 판정)을 코어 공용 유틸 App\Support\OutboundUrlValidator 로 수렴시켰다. - 접두사 매칭을 host 완전일치로 교체 — userinfo(@) 위장과 접미사 확장 도메인 우회 벡터 차단. - 사내 서버 호출이 정당한 관리자 지점(스케줄·외부 API)은 신규 설정 security.allow_internal_outbound_urls(기본 off)로 옵트인. - 언어팩 URL 설치는 원격 코드 다운로드라 옵트인과 무관하게 내부 주소 항상 차단. - 웹소켓 연결 테스트는 localhost·사설 IP 가 정상 구성이므로 구조적 위조만 차단. 각 지점에 red 재현 → 수정 → green 회귀 테스트 + 실브라우저 E2E 로 검증. 수기 API 문서가 자동 재생성으로 소실되는 사고를 막는 PreToolUse 훅을 함께 추가한다.
This commit is contained in:
1 parent
f848086fd6
commit
c4beb116ad
46 files changed
+1297
-55
No files matched your search
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [1.0.1] - 2026-07-09
|
||||
|
||||
### Security
|
||||
|
||||
- 본인인증 결과를 확인하러 이니시스 서버에 접속할 때, 접속할 주소가 실제 이니시스 주소가 맞는지 정확히 확인하도록 바로잡았습니다. 이전에는 주소의 앞부분만 맞으면 통과시켰기 때문에, 이니시스 주소를 앞에 붙인 가짜 주소가 검사를 통과할 수 있었습니다. 이를 악용하면 사이트 서버가 공격자의 서버나 외부에 공개되지 않은 내부 주소로 접속하게 만들 수 있었고, 공격자가 임의로 꾸민 인증 결과(이름·생년월일 등)를 진짜처럼 받아들이게 만들 수도 있었습니다. (KISA 측에서 제보해주셨습니다 — KVE-2026-1546)
|
||||
|
||||
### Changed
|
||||
|
||||
- 라이브 가맹점 MID 프리픽스를 이니시스 정책 변경에 맞춰 SRB 로 갱신했습니다 (설정 화면 고정 프리픽스 표시·입력 안내 문구 포함).
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Plugins\Sirsoft\VerificationKginicis\Services;
|
||||
|
||||
use App\Support\OutboundUrlValidator;
|
||||
use Illuminate\Support\Str;
|
||||
use Plugins\Sirsoft\VerificationKginicis\Exceptions\DecryptException;
|
||||
use Plugins\Sirsoft\VerificationKginicis\Exceptions\InvalidAuthUrlException;
|
||||
@@ -23,10 +24,10 @@ class InicisGateway implements InicisGatewayInterface
|
||||
/** STEP3 호출 timeout (초) */
|
||||
private const STEP3_TIMEOUT = 10;
|
||||
|
||||
/** 이니시스 표준 도메인 화이트리스트 (위조 차단) */
|
||||
private const ALLOWED_DOMAINS = [
|
||||
'https://kssa.inicis.com',
|
||||
'https://fcsa.inicis.com',
|
||||
/** 이니시스 표준 host 화이트리스트 (위조 차단) */
|
||||
private const ALLOWED_HOSTS = [
|
||||
'kssa.inicis.com',
|
||||
'fcsa.inicis.com',
|
||||
];
|
||||
|
||||
/** STEP3 응답 중 SEED CBC 로 암호화된 PII 필드 키 */
|
||||
@@ -42,18 +43,16 @@ class InicisGateway implements InicisGatewayInterface
|
||||
/**
|
||||
* 이니시스 콜백의 authRequestUrl 이 표준 도메인인지 검증한다.
|
||||
*
|
||||
* 콜백은 인증 없이 외부에서 들어오고 이 URL 이 그대로 STEP3 POST 의 목적지가 되므로,
|
||||
* host 를 완전 일치로 검증한다. 접두사 매칭은 `…@127.0.0.1` (userinfo) 이나
|
||||
* `....attacker.com` (접미사 확장) 형태의 위조 목적지를 통과시킨다.
|
||||
*
|
||||
* @param string $url 콜백으로 수신한 authRequestUrl
|
||||
* @return bool 표준 도메인 여부
|
||||
*/
|
||||
public function validateAuthUrl(string $url): bool
|
||||
{
|
||||
foreach (self::ALLOWED_DOMAINS as $allowed) {
|
||||
if (str_starts_with($url, $allowed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return OutboundUrlValidator::isHostAllowed($url, self::ALLOWED_HOSTS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Plugins\Sirsoft\VerificationKginicis\Tests\Unit\Services;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Plugins\Sirsoft\VerificationKginicis\Services\InicisGateway;
|
||||
use Plugins\Sirsoft\VerificationKginicis\Tests\PluginTestCase;
|
||||
|
||||
/**
|
||||
* InicisGateway::validateAuthUrl 의 목적지 위조 차단 검증.
|
||||
*
|
||||
* 콜백 body 의 `authRequestUrl` 은 인증 없이 외부에서 들어오는 값이며, 이 값이 그대로
|
||||
* 서버-서버 STEP3 POST 의 목적지가 된다. host 를 완전 일치로 검증하지 않으면 공격자가
|
||||
* 내부망 주소나 자신의 서버로 요청을 유도해 SSRF 및 본인인증 결과 위조가 가능하다.
|
||||
*/
|
||||
class InicisGatewayValidateAuthUrlTest extends PluginTestCase
|
||||
{
|
||||
private InicisGateway $gateway;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->gateway = new InicisGateway;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이니시스 표준 도메인은 통과한다.
|
||||
*
|
||||
* @param string $url 검증 대상 authRequestUrl
|
||||
*/
|
||||
#[Test]
|
||||
#[DataProvider('validAuthUrlProvider')]
|
||||
public function it_accepts_official_inicis_auth_urls(string $url): void
|
||||
{
|
||||
$this->assertTrue(
|
||||
$this->gateway->validateAuthUrl($url),
|
||||
"정상 이니시스 URL 이 거부됨: {$url}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 표준 도메인으로 위장한 URL 은 모두 거부한다.
|
||||
*
|
||||
* @param string $url 검증 대상 authRequestUrl
|
||||
* @param string $reason 차단 사유 (실패 메시지용)
|
||||
*/
|
||||
#[Test]
|
||||
#[DataProvider('forgedAuthUrlProvider')]
|
||||
public function it_rejects_forged_auth_urls(string $url, string $reason): void
|
||||
{
|
||||
$this->assertFalse(
|
||||
$this->gateway->validateAuthUrl($url),
|
||||
"위조 URL 이 통과함 ({$reason}): {$url}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정상 authRequestUrl 목록.
|
||||
*
|
||||
* @return array<string, array{string}>
|
||||
*/
|
||||
public static function validAuthUrlProvider(): array
|
||||
{
|
||||
return [
|
||||
'표준 kssa 도메인' => ['https://kssa.inicis.com/auth/result'],
|
||||
'표준 fcsa 도메인' => ['https://fcsa.inicis.com/auth/result'],
|
||||
'대소문자 혼용' => ['https://KSSA.INICIS.COM/auth/result'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 위조 authRequestUrl 목록 — 접두사 매칭 우회 벡터 전수.
|
||||
*
|
||||
* @return array<string, array{string, string}>
|
||||
*/
|
||||
public static function forgedAuthUrlProvider(): array
|
||||
{
|
||||
return [
|
||||
'userinfo 로 루프백 위장' => ['https://kssa.inicis.com@127.0.0.1/', 'userinfo(@) 뒤가 실제 목적지'],
|
||||
'userinfo 로 메타데이터 위장' => ['https://kssa.inicis.com@169.254.169.254/latest/meta-data/', '클라우드 메타데이터 탈취'],
|
||||
'userinfo 로 공격자 서버 위장' => ['https://kssa.inicis.com@attacker.example/fake', '인증결과 위조'],
|
||||
'접미사 확장 도메인' => ['https://kssa.inicis.com.attacker.example/fake', '공격자 소유 도메인'],
|
||||
'하이픈 접미사' => ['https://fcsa.inicis.com-evil.example/', '공격자 소유 도메인'],
|
||||
'내부 IP 직접 지정' => ['https://127.0.0.1/', '내부망 직접 타격'],
|
||||
'경로에만 표준 도메인' => ['https://attacker.example/https://kssa.inicis.com', '경로 위장'],
|
||||
'http 다운그레이드' => ['http://kssa.inicis.com/auth', '평문 전송'],
|
||||
'scheme 부재' => ['//kssa.inicis.com/auth', 'scheme 부재'],
|
||||
'빈 문자열' => ['', '입력 없음'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user