kingsjw

Software Engineer

  • Profile
  • Tech
  • Movie
  • Travel

K-MOOC 서비스 취약점 분석

먼저 이 글에 목적은 취약점을 악용하기 위함이 아닌 취약점을 분석하는 과정과 해당 취약점을 발생하지 않기 위해선 어떠한 노력이 필요한지 공유하기 위함임을 밝힌다.

대학교에서 온라인 강의 수강할 때 사용하는 K-MOOC 서비스를 이용하다가 발견한 취약점을 글로 작성하고자한다.

K-MOOC이란?

2015년 운영을 시작한 K-MOOC는 한국형 온라인 공개강좌로 국가평생교육진흥원에서 운영하는 한국형 MOOC이다.
2019년 12월 31일 기준으로 116개 이상의 대학(단체)들이 참여하고, 745개가 넘는 강좌들이 종강, 진행 중 혹은 개강 예정이다. 2019년 7월부터 학점은행제를 통해서 학점을 인정을 받을 수 있게 된다.
출처

취약점 발견 경로

재학중인 광운대학교 2학년 2학기에 온라인 강좌를 K-MOOC을 통해 수강하게 되었다.
그런데 영상을 모두 시청했음에도 진도율이 정상적으로 저장되고 있지 않는 현상을 겪게 되었다.
항상 이 문제가 발생한 것은 아니고 정상적으로 진도율이 반영될 때도 있었다.

어쨋든 영상을 모두 수강하면 아래와 같이 체크박스에 체크표시가 되어야한다.
정상 수강 화면

그러나 영상을 모두 수강했음에도 아래와 같이 체크표시가 나타나지 않은 것이다.
비정상 수강 화면

이 문제에 원인이 무엇인지 궁금해서 브라우저 개발자 도구를 통해 해당 수강 진도율 로직을 분석했다.
그 과정에서 사용자가 영상을 수강하지 않아도 해당 영상에 대한 수강 완료 처리를 할 수 있는 취약점을 발견하게 되었다.

취약점 분석

다음과 같은 방법으로 취약점을 분석했다.

단순히 수강 진도율이 정상적으로 집계되지 않는 문제를 파악하기 위함이였기 때문에 해당 로직을 디버깅하고 문제되는 로직을 찾은 후 해결 방법을 찾고자했다.

먼저 롱폴링(long polling) 방식으로 사용자가 영상을 수강하고 있을 때 주기적으로 서버에 진도율을 저장하는 방식일 것이라 가설을 세우고 아래와 같은 방법으로 취약점을 분석했다.

많은 네트워크 요청 중 진도율을 저장하는 요청이 무엇인지 찾기 위해 Fetch/XHR 필터를 걸어 이미지나 정적인 데이터 요청은 제외하고 Fetch/XHR 요청들만 확인했다.

그러면 위와 같은 요청들이 남는데... 몇개 없으니 하나씩 확인해보던 중...

action.php..? 저장 되었습니다...?

아무래도 이 요청이 진도율을 저장하는 네트워크 요청으로 파악된다.

그렇다면 해당 요청은 어떤 데이터를 어떻게 보내고 있는지 분석해보자.

네트워크 탭4 네트워크 탭5

해당 요청에 header와 payload를 보면 post 방식으로 아래 데이터를 보내고 있다.

courseid: 9648 => 해당 강의 고유 id
cmid: 1276828 => 해당 강의의 각 수업 영상에 대한 id
sesskey: "Y08NOdjEsk" => 로그인 세션 키?
attempt: 25 => 몇회 해당 강의를 수강했는지?
state: 2 => 상태?
positionfrom: 7.505445 => 몇초 듣고 있는지?
positionto: 7.505445 => 몇초 듣고 있는지?
unixtime: 1702565186 => 현재 unix시간
coursemostype: "trackDetail"

요청에 보내고 있는 데이터들이 의미하는 값들을 추측하자면 위와 같다.

그래서 해당 요청이 정상적으로 동작하는지 확인하기 위해 임의에 데이터로 요청을 보내보았다.
아래와 같이 간단한 ajax 요청을 작성하면 해당 요청을 보낼 수 있다.

$.ajax({
  url: "https://lms.kmooc.kr/mod/vod/action.php",
  data: {
    courseid: 9648,
    cmid: 1276828,
    sesskey: "Y08NOdjEsk",
    attempt: 25,
    state: 2,
    positionfrom: 28 * 60 + 45, // 해당 강의 영상에 총 초 길이: 28분 45초
    positionto: 28 * 60 + 45, // 해당 강의 영상에 총 초 길이: 28분 45초
    unixtime: Math.floor(new Date().getTime() / 1000), // 현재 유닉스 시간
    coursemostype: "trackDetail"
  }
});

사실 이 요청이 정상적으로 서버에 요청되어서 진도율이 저장되면 안된다.
영상을 모두 수강하지 않은 상태에서 요청 데이터에 positionfrom, positionto 값을 영상의 총 초 길이로 조작해서 보내면 영상을 모두 수강한 것처럼 저장되기 때문이다.

그런데 이게 왠걸... 요청이 정상적으로 동작한다.
그리고 실제 영상에 체크 표시가 되면서 해당 강좌 수강 진도율에 정상적으로 반영이 되는 것을 확인했다.

취약점 방지

이러한 취약점이 발생하는 것을 미연에 방지하기 위해선 어떻게 해야할까?
해당 취약점은 어느 부분이 문제이길래 발생하는 것일까?

해당 취약점은 K-MOOC이라는 어플리케이션 서비스에서 제공하고 있는 수강 진도율 저장 API에 요청 데이터를 임의로 조작하여 요청할 수 있으며 해당 요청이 성공적으로 진도율을 저장함으로써 진도율 데이터에 무결성이 침해되는 것이다.
여기서 핵심적으로 문제가 되는 부분은 요청이 성공적으로 진도율을 저장 하는 것이다.

문제를 단계별로 분리해서 생각해보자.

우리가 사용하고 있는 것은 K-MOOC 어플리케이션이다.
해당 어플리케이션은 수강 진도율을 저장하기 위해 클라이언트에서 서버로 HTTP 통신을한다.
서버는 요청 받은 수강 진도율 데이터에 대해 정상적인 요청인지 검증 하지 않는다.
따라서 클라이언트는 진도율 데이터를 원하는 값으로 조작해서 서버에 요청할 수 있다.
요청된 데이터는 성공적으로 저장된다.

서버가 요청 받은 수강 진도율 데이터에 대한 검증을 하지 않는게 문제로 보인다.
그렇다면 해당 요청이 정상적인 진도율 데이터를 보내는 요청인지 어떻게 알 수 있을까?

사실 알기 어렵다.
임의로 개발자 도구 콘솔에 로직을 작성해서 요청하던 개발자가 작성한 코드가 동작해서 요청하던 요청을 받는 서버 입장에선 같은 요청이기 때문이다.
하지만 몇가지 방법은 있다.

  1. 로그 분석
    사용자의 모든 요청을 로그로 기록한 후 로그를 분석하여 사용자의 진도율이 합리적으로 증가하는지 확인한다.
    진도율이 짧은 시간 동안 비정상적으로 큰 폭으로 변경되면 무시하거나 오류로 처리할 수 있다.

  2. 타임스탬프 검증
    각 요청에 타임스탬프를 포함시키고, 서버에서는 이 타임스탬프를 기반으로 요청이 합리적인 시간 내에 이루어졌는지 확인한다.
    예를 들어, 강의를 수강하는 데 필요한 최소 시간이 있으므로, 이 시간 이내에 진도율이 100%로 증가하는 요청은 무시하거나 오류로 처리할 수 있다.

  3. 서버 측에서 진도율 계산
    클라이언트는 강의 수강을 시작하거나 완료하는 등의 이벤트만 서버에 보고하고, 서버에서는 이 정보를 바탕으로 진도율을 계산한다.

  4. 서버 측 유효성 검사
    클라이언트에서 보낸 진도율이 서버에서 설정한 규칙, 예를 들어 진도율이 갑자기 크게 증가하지 않도록 하는 등의 규칙에 부합하는지 검사한다.

위 방법들은 가장 기본적인 방법들이다.
분명 위 방법들을 적용해도 무결성을 침해할 수 있는 방법들은 얼마든지 있을 것이다.
하지만 최소한 기본적인 보안은 갖추어야할 필요가 있다.
대문을 열고 외출하는 것과 자물쇠를 잠구고 외출하는 것은 큰 차이가 있다.

Copyright © 2019 All Right 서재우

kingsjw7@gmail.com . GitHub . Blog . Repository