[Case Study] Xint로 구축한 안전한 헬스케어 보안

Xint는 비즈니스 로직 취약점 식별에 강점을 가진 AI 기반 침투 테스트 솔루션입니다. Xint는 이용자로부터 점검할 자산의 웹 서비스 주소와 서비스 이용에 필요한 인증 정보를 전달받아 공격자 관점에서 서비스의 보안을 점검하며, 이 과정에서 각 요청과 응답의 맥락을 파악하고 그에 맞는 공격 시나리오를 생성하여 취약점을 탐지합니다. 본 포스트는 실제 고객사의 사례를 익명화하여 재구성한 내용을 바탕으로, Xint가 도메인의 맥락을 파악하며 비즈니스 취약점을 탐색한 과정을 소개합니다.
Xint's avatar
Mar 25, 2026
[Case Study] Xint로 구축한 안전한 
헬스케어 보안

들어가며

Xint는 비즈니스 로직 취약점 식별에 강점을 가진 AI 기반 침투 테스트 솔루션입니다. Xint는 이용자로부터 점검할 자산의 웹 서비스 주소와 서비스 이용에 필요한 인증 정보를 전달받아 공격자 관점에서 서비스의 보안을 점검하며, 이 과정에서 각 요청과 응답의 맥락을 파악하고 그에 맞는 공격 시나리오를 생성하여 취약점을 탐지합니다. 이러한 동작 방식을 통해 Xint는 단순한 룰이나 체크리스트 기반 분석으로는 식별하기 어려운, IDOR(Insecure Direct Object Reference)이나 데이터 유출과 같은 비즈니스 맥락에 의존하는 취약점을 효과적으로 탐지할 수 있습니다.

최근 Xint는 헬스케어 산업군의 서비스를 점검하는 과정에서 환자의 개인 건강 정보(PHI, Protected Health Information)를 적절한 권한 없이 조회할 수 있는 IDOR 취약점을 발견했습니다. 해당 서비스는 인증 기반 접근 제어와 병원 단위 데이터 분리를 적용하고 있었기 때문에, 취약점을 트리거하기 위해서는 단순한 API 요청이 아닌 시스템의 접근 제어 구조를 이해한 API 요청이 필요했습니다.

이번 포스트에서는 실제 고객사의 사례를 익명화하여 재구성한 내용을 바탕으로, Xint가 도메인의 맥락을 파악하며 비즈니스 취약점을 탐색한 과정을 소개하겠습니다.

🚀

AI 해커 Xint가 궁금하다면? 웹사이트에서 더욱 자세한 내용을 확인해보세요!

헬스케어 산업의 정보보안

헬스케어 산업은 다루는 데이터의 민감도가 높아 서비스 운영 시 보안이 특히 중요합니다. 환자의 진료 기록, 진단 결과 등의 PHI는 개인의 민감한 정보로, 유출 사고 발생 시 중대한 법적 책임과 비즈니스 리스크로 이어질 수 있습니다. 미국의 HIPAA 컴플라이언스는 이런 PHI의 보호를 의무화하고, 위반 시 막대한 벌금과 법적 처벌을 부과하고 있습니다. 또한 국내에서도 개인정보 보호법과 의료법에 따라 환자 정보의 무단 접근과 유출은 엄격하게 규제되고 있습니다.

PHI 유출은 과징금, 손해배상, 형사처벌이 같이 발생할 수 있는 문제입니다. 2020년 이후 국내 의료기관의 사이버 침해 사고는 220건을 넘었고, 2024년 상반기에만 68건이 보고되어 2020년 대비 3.7배 증가했습니다. 대표적인 PHI 유출 사고는 아래와 같습니다.

  • 서울대 병원 해킹(2021): 해킹 조직이 병원 서버 취약점을 통해 총 83만명의 개인정보가 유출되었고, 개인정보보호위원회에서 7475만원의 과징금과 660만원의 과태료를 부과하였습니다.

  • 17개 대학병원 환자 정보 유출(2018 - 2020): 17개의 대학병원에서 총 18만명의 환자 정보가 유출되었습니다. 병원, 제약사 직원이 이메일, USB를 통해 데이터를 외부로 반출하거나 시스템에 무단으로 접근한 사건으로, 과태료 부과 및 관계자들이 경찰 조사를 받은 바 있습니다.

최근 많은 보안 사고가 발생함에 따라, 정보 유출 사고의 비즈니스 리스크는 증가하는 추세입니다. 국내의 경우, 침해사고 발생 시 징벌적 과징금의 상한을 매출액 기준 최대 3%에서 10%로 확대하는 개정안이 통과되기도 하였습니다.

정보 유출 사고를 방지하기 위해서는 엄격한 접근 제어와 권한 관리가 필요합니다. 하지만 헬스케어 서비스는 일반적인 대고객 서비스와 달리 정보 권한을 일괄적으로 관리하기 어렵다는 특징이 있습니다. 환자 정보에 대한 조회 권한을 부여하는 상황에서도, 의사가 본인의 환자가 아닌 다른 의사의 환자 기록을 열람하는 것이 기능적으로 허용되어야 하는 경우와 제한되어야 하는 경우가 공존할 수 있습니다. 따라서, 필요한 정보만을 최소한으로 제공할 수 있도록 권한을 적절하게 부여하기 위해서는 기능을 사용하는 맥락을 파악해야 합니다.

다른 의사의 환자 기록을 열람하는 상황을 예시로 생각해 보겠습니다. 아래의 경우는 개인정보를 침해하는 행위에 해당하므로, 정보의 열람을 제한해야 합니다.

  • 진료와 관계 없는 목적으로 환자의 개인 정보를 열람하는 경우

  • 퇴사한 직원/의사가 시스템에 접근하여 개인 정보를 열람하는 경우

하지만, 특정 상황에서 의사는 다른 의사에게 배정된 환자의 정보를 열람할 수 있어야 합니다.

  • 응급실에 환자가 실려온 상황이라면, 담당의가 아닌 의사가 해당 환자의 이전 이력에 즉시 접근하여 치료를 진행할 수 있어야 합니다.

  • 다른 부서의 의사와 협진을 진행하는 상황이라면, 협진에 참여하는 의사가 환자의 진료 정보 및 검사 결과에 접근할 수 있어야 합니다.

  • 담당의가 부재하지만 환자의 치료가 필요한 상황에서는 다른 의사가 환자의 이전 진료 정보를 확인해서 치료를 이어갈 수 있어야 합니다.

결론적으로, 타 의사의 환자 기록 열람이라는 행위는 맥락에 따라 정상적인 접근이 될 수도 있고, 개인정보를 침해하는 행위가 될 수도 있습니다. 따라서 해당 관점을 고려한다면, 헬스케어 서비스의 보안성을 점검하는 보안 도구는 서비스의 동작 원리를 파악하고, 맥락을 분석하여 정상 기능과 취약점을 구분해야 합니다.

References

Xint의 취약점 발견 과정

복잡한 권한 체계, 리소스 간 관계, 비즈니스 로직에 결합된 인가(Authorization) 정책에서 발생하는 취약점은 기존의 보안 도구들이 탐지하기 어려운 유형입니다. 이번 파트에서는 Xint가 헬스케어 서비스에서 취약점을 식별한 과정을 살펴보겠습니다.

Step 1: 인증 토큰 발급 및 의미 확인

Xint는 고객사로부터 취약점을 스캔할 대상 페이지와 인증 정보를 전달받은 후, 정보를 바탕으로 로그인을 수행하고, API 요청에 필요한 토큰을 발급받았습니다.

Request
POST /auth/sign-in
Content-Type: application/json

{
  "hopitalCode": <hospital-code>,
  "username": <test-username>,
  "password": <test-password>
}

Response
Status 200

{
  "accessToken": <access-token>,
  "refreshToken": <refresh-token>
}

발급된 JWT(JSON Web Token)의 페이로드에는 아래와 같은 claim이 포함되어 있었습니다.

{
  "sub": "1",
  "userId": 1,
  "hospital": 1,
  "role": "User",
  "token_type": "access"
}

Xint는 해당 정보를 통해 현재 인증된 이용자의 계정이 userId=1, hospital=1임을 확인하였습니다.

Step 2: 서비스 내 계정의 역할 파악

이후, Xint는 인증 토큰을 이용하여 의사의 프로필과 병원 내 의사 목록을 조회하였습니다.

Request
GET /doctor
Authorization: Bearer <access-token>

Response
Status 200

{ "id": 1, "name": "Test User 1", "hospitalId": 1, ... }
Request
GET /hospital/doctors
Authorization: Bearer <access-token>

Response
Status 200

{
  "data": [
    {
      "id": 1,
      "name": "Test User 1",
      "departments": [{ "departmentName": "내과" }]
    },
    {
      "id": 2,
      "name": "Test User 2",
      "departments": [{ "departmentName": "신경과" }]
    },
    ...
  ]
}

해당 요청을 통해, Xint는 병원 내에 다른 의사들이 존재한다는 사실과, 각 의사들이 본인의 진료과 정보와 연결되어 있다는 것을 파악했습니다. 또한, 해당 내용을 바탕으로 현재 로그인한 계정이 병원 내 여러 의사 중 한 명의 계정(내과 소속 id=1 의사)인 것을 확인하였습니다.

Step 3: 다른 의사의 환자 목록 조회

Xint는 로그인한 계정으로 접근할 수 있는 API 엔드포인트를 탐색하여, 병원 식별자(hospitalId)와 의사 식별자(doctorId)를 쿼리 파라미터로 사용하는 환자 목록 반환 엔드포인트(/hospital/patients-list)를 확인했습니다. Xint는 해당 엔드포인트가 의사가 소속 병원 내에서 자신에게 배정된 환자 목록을 조회할 수 있도록 지원한다고 판단하였고, doctorId를 변조하여 다른 의사에게 배정된 환자 목록을 조회하는 시나리오를 테스트하였습니다.

테스트 결과, 환자 목록 반환 엔드포인트는 요청을 전송한 의사가 doctorId에 해당하는 의사와 일치하는지 검증하지 않았고, 이는 다른 의사의 환자 목록을 조회할 수 있는 IDOR 취약점으로 이어졌습니다.

Xint는 doctorId를 포함하지 않은 요청을 환자 목록 반환 엔드포인트에 전송하여, 서버가 병원 내 모든 의사의 환자 목록을 응답으로 반환하는 것을 확인하였습니다.

Request
GET /hospital/patients-list?hospitalId=1&page=0
Authorization: Bearer <access-token>

Response
Status 200

{
  "data": [
    {
      "name": <patients-1-name>,
      "birthDate": <patients-1-birth>,
      "identityNumber": <patients-1-identity-number>,
      "phone": <patients-1-phone-number>,
      "patientId": <patients-1-id>,
      "doctorId": 1,
      "revisit": 9
    },
    {
      "name": <patients-2-name>,
      "birthDate": <patients-2-birth>,
      "identityNumber": <patients-2-identity-number>,
      "phone": <patients-2-phone-number>,
      "patientId": <patients-2-id>,
      "doctorId": 2,
      "revisit": 1
    },
    ...
  ]
}

응답에는 Xint가 로그인한 계정(doctorId=1)에 배정된 환자 외에 다른 의사(예: doctorId=2)에게 배정된 환자의 정보가 포함되었습니다.

또한, 응답 데이터에는 환자를 식별할 수 있는 고유한 개인정보(예: 연락처, 생년월일, 주민등록번호)가 포함되어 있었습니다. Xint는 page 파라미터를 조절하는 방식으로 병원 내 환자 목록을 조회하며 취약점의 영향력을 파악하였고, 결과적으로 특정 의사의 계정(권한)으로 병원 내 전체 환자의 개인정보에 접근 가능한 상태임을 확인하였습니다.

Step 4-1: 환자의 의사 방문 이력 조회

이후 Xint는 Step 3에서 획득한 환자 식별자(patientId)를 이용한 요청을 통해, 추가적으로 획득할 수 있는 정보를 탐색하였습니다. 아래는 Xint가 방문 이력 조회 엔드포인트(/patients/hospital/{patientId}/visit)에 다른 의사에게 배정된 환자의 patientId를 전달한 결과입니다.

Request
GET /patients/hospital/<patients-2-id>/visit?page=0
Authorization: Bearer <access-token>

Response
Status 200

{
  "data": [
    {
      "recordId": <record-id>,
      "hospitalId": 1,
      "doctorId": 2,
      "status": "Completed",
      ...
    },
    ...
  ]
}

Status 200 메시지와 함께 응답이 반환되었고, Xint는 응답으로부터 해당 환자가 만난 의사가 로그인한 계정의 의사(내과 소속 id=1 의사)가 아닌 다른 의사임을 확인하였습니다. 또한, 현재 인증된 의사는 내과 소속이지만, doctorId=2의 의사는 신경과 소속이라는 정보를 바탕으로, 진료과 간 데이터가 격리되지 않는다는 정보를 추론하였습니다.

Xint는 정보 조회 권한을 확인하기 위해 다른 환자 식별자에 대한 검증을 추가로 진행하였고, 서버가 환자에게 배정된 의사와 무관하게 동일한 응답을 반환하는 것을 확인하였습니다.

GET /patients/hospital/<patients-1-id>/visit?page=0 (doctorId: 1)
-> Status 200 OK
GET /patients/hospital/<patients-3-id>/visit?page=0 (doctorId: 3)
-> Status 200 OK

이를 통해, Xint는 서버가 요청자의 의사 ID와 환자 담당 의사의 ID를 비교하지 않는다는 사실을 확인하고, 해당 상황을 다른 의사의 환자 정보를 조회할 수 있는 IDOR 취약점으로 분류하였습니다.

Step 4-2: 환자 진료 기록 조회

Xint는 Step 3에서 획득한 환자 식별자(patientId)를 이용하여 환자 진료 기록 조회 엔드포인트(/patients/{patientId}/checkup/history)에 요청을 전송하였습니다.

Request
GET /patients/<patients-1-id>/checkup/history
Authorization: Bearer <access-token>

Response
Status 200

{
  "data": [
    {
      "id": <record-id>
      "status": "Finished",
      "doctorId": 1,
      "ccText": <증상 정보>,
      "ccShortcut": <증상 태그>,
      "medicalStatus": "DiagnosisDone"
      ...
    }
  ]
}

그 결과, 방문 이력 조회 엔드포인트 요청과 마찬가지로 Status 200 메시지와 함께 응답이 반환되었습니다. 진료 기록에는 단순 방문 이력 외에도 환자가 입력한 증상에 대한 내용과 현재 진료의 진행 상태가 포함되어 있었습니다.

이후 Xint는 Step 4-1과 동일한 방식으로 다른 의사에게 배정된 환자의 진료 기록에 접근을 시도하였고, 모든 요청에서 정상적으로 환자의 진료 기록이 반환되는 것을 확인하였습니다. 해당 상황 역시 다른 의사의 환자 정보를 조회할 수 있는 IDOR 취약점에 해당합니다.

Xint는 진료 기록에서 획득한 정보 중, record-id를 사용하여 진료 녹음 파일에 접근을 시도(/patients/checkup/{recordId}/record 요청)했습니다.

Request
GET /patients/checkup/<record-id>/record
Authorization: Bearer <access-token>

Response
Stauts 200

{
  "id": 1234,
  "recordId": <record-id>,
  "downloadUrl": <audio-recording-url>,
  "audioDuration": 34
}

/patients/checkup/{recordId}/record 엔드포인트는 진료 녹음 파일을 응답으로 반환하였습니다. 응답에 포함된 진료 녹음 파일의 주소(downloadUrl)에 접근할 경우, 파일을 다운로드하는 것도 가능합니다.

아래는 다른 의사에게 배정된 환자의 진료 녹음에 접근을 시도한 결과입니다.

Request
GET /patients/checkup/<record-id-from-other>/record
Authorization: Bearer <access-token>

Respones
Status 200


{
  "id": 1512,
  "recordId": <record-id-from-other>,
  "downloadUrl": <audio-recording-url>,
  "audioDuration": 51
}

이전 요청과 마찬가지로, Status 200 메시지와 함께 환자의 진료 녹음 파일이 응답으로 반환되었습니다. 이는 병원 내의 모든 의사가 임의의 환자와 의사 사이의 진료 녹음 정보에 접근할 수 있다는 것을 의미하므로, 다른 의사의 환자 정보를 조회할 수 있는 IDOR 취약점에 해당합니다.

Xint는 추가 탐색을 통해, 진료 녹음을 기반으로 구성된 대화 내역(진료 내역) 조회 엔드포인트(/patients/checkup/{recordId}/history/talking)를 발견하고, 열람 가능한 정보 확인을 위해 요청을 전송했습니다.

Request
GET /patients/checkup/<record-id>/history/talking
Authorization: Bearer <access-token>

Response
Status 200

{
  "id": 192,
  "recordId": <record-id>,
  "audioDuration": 34,
  "summary": <진료 내용 요약>,
  "transcripts": [
    {
      "id": 3631,
      "text": <transcript>,
      "speaker": 0,
      "dialogTime": 1
    },
    ...
  ]
}

응답에서는 진료 녹음을 기반으로 생성된 진료 내용 요약을 확인할 수 있었고, Xint의 추가 요청 전송 결과 해당 엔드포인트에서도 다른 의사의 환자 정보(환자 진료 내용 요약)를 조회할 수 있는 IDOR 취약점이 확인되었습니다.

이번 단계에서 Xint는 환자의 진료 기록과 관련된 엔드포인트를 탐색하고, 탐색 과정의 응답에서 획득한 정보를 기반으로 추가 요청을 구성하며 접근 가능한 API와 조회 가능한 정보를 확장해 나갔습니다. 그 결과, 특정 의사의 계정(권한)으로 병원 내 모든 환자의 진료 기록 및 진료 녹음 데이터에 접근할 수 있는 상태임이 확인되었습니다. 진료 녹음 파일은 의사와 환자 간의 대화가 포함된 정보로, 개인의 프라이버시에 해당하여 유출 시 높은 수준의 보안 위험으로 이어질 수 있습니다.

Step 5: 타 병원 환자 정보 조회 시도

소속 병원 내 환자 정보에 대한 접근이 가능한 것을 확인한 이후, Xint는 접근 가능한 정보의 범위 파악을 위해 다른 병원의 환자 정보를 조회하는 위협 시나리오를 생성하였습니다. 시나리오에 따라 hospitalId를 변조하여 계정이 소속되지 않은 다른 병원의 정보에 대한 접근을 시도한 결과는 아래와 같습니다.

Request
GET /hospital/patients-list?hospitalId=2&page=0
Authorization: Bearer <access-token>

Response
Status 200
{ "data": [] }

응답으로 Status 200 메시지가 반환되었으나, 다른 병원의 환자 목록에 대해서는 빈 배열이 반환되어 조회가 제한되었습니다. 이를 통해, Xint는 본 서비스에 병원 간의 데이터 격리가 적용되어 있으며, 계정의 hospitalId를 기반으로 병원 레벨의 접근 제어가 이루어지고 있음을 파악하였습니다.

마치며

헬스케어 서비스에는 의사가 진료를 위해 다른 의사에게 배정된 환자의 정보에 접근해야 하는 상황이 존재합니다. 하지만 Xint의 스캔 결과, 필수적이지 않은 상황에서 다른 환자의 정보를 조회할 수 있는 취약점이 다수 발견되었습니다. 이번 점검에서 Xint가 발견한 취약점은 아래와 같이 요약할 수 있습니다.

  1. 요청자 검증 및 필터링 부재로 인한 병원 내 전체 환자 개인정보 조회(/hospital/patients-list)

  2. 요청자 검증 부재로 인한 병원 내 전체 환자 방문 이력 조회(/patients/hospital/{patientId}/visit)

  3. 정보 접근 권한 관리 부재로 인한 병원 내 전체 환자 진료 기록 조회(/patients/{patientId}/checkup/history)

  4. 정보 접근 권한 관리 부재로 인한 병원 내 전체 환자 진료 녹음 파일 접근(/patients/checkup/{recordId}/record)

  5. 정보 접근 권한 관리 부재로 인한 병원 내 전체 환자 진료 내역 조회(/patients/checkup/{recordId}/history/talking)

이들은 입력에 대한 권한 검증이 충분히 이루어지지 않아, 민감한 리소스에 접근할 수 있는 IDOR 취약점의 사례에 속합니다. 서비스는 병원 단위의 데이터 격리는 적용하고 있었으나, 병원 내부 데이터에 대한 접근 권한 관리가 미흡하여 인증된 의사가 담당 환자 외에도 모든 환자 정보에 접근할 수 있는 구조였습니다. 이로 인해, 단일 의사 계정이 유출되거나 잘못 관리될 경우 병원 내의 모든 환자 정보가 유출될 수 있는 위험이 존재합니다.

특히 환자 목록과 진료 정보를 반환하는 엔드포인트에서 주민등록번호 및 의사와 환자 간 진료 녹음 데이터를 모두 제공하고 있어, 적절한 감사 추적이 이루어지지 않을 경우 국내의 개인정보보호법과 PHI 관련 컴플라이언스(예: HIPAA) 위반으로 이어질 수 있습니다.

이와 같은 비즈니스 로직 취약점은 기존의 DAST(Dynamic Application Security Testing) 도구로 탐지하기 어렵습니다. 이번 사례의 취약점들은 단일 요청 단위에서 정상적인 API 호출에 해당되기 때문에, 실질적인 취약성 검증을 위해서는 여러 요청 간의 관계와 맥락을 함께 고려해야 합니다.

본 점검에서 Xint는 응답 데이터의 맥락을 이해하고 각 엔드포인트 간의 연관 관계를 재구성하여 시나리오를 생성하고 테스트를 수행했습니다. 그 결과, 다수의 엔드포인트를 탐색하고 맥락을 이해해야 드러나는 진료 내역 조회 취약점을 성공적으로 식별할 수 있었습니다.

서비스의 복잡성이 높아질수록, 취약점은 단일 지점이 아닌 기능 간 연결과 흐름 속에서 발생합니다. 단순한 스캐너로는 취약점과 정상 동작의 경계를 구분하기 어렵고, 수동 펜테스팅으로는 넓은 점검 범위의 모든 엔드포인트를 검증하기 어렵습니다. Xint는 AI를 기반으로 이러한 한계를 동시에 해결하며, 비즈니스 로직의 경계를 명확히 파악하여 서비스의 맥락을 기반으로 현실의 취약점을 명확히 식별할 수 있도록 지원합니다.

🚀

AI 해커 Xint가 궁금하다면? 웹사이트에서 더욱 자세한 내용을 확인해보세요!

Share article

Theori © 2025 All rights reserved.