Sylar Labs

| Português |

Pickle e seus problemas de segurança

November 13, 2018 | 3 Minutos de leitura

O módulo pickle do python, é utilizado para transformar um objeto qualquer em uma série de bytes, ou seja, serializar um objeto. Assim, “Pickling” é o processo de converter o objeto em uma série de bytes e “Unpickling” é o de converter uma série de bytes em um objeto.

Exemplo de uso do pickle

>>> import pickle
>>> class Foo(object):
...     def __init__(self, bar):
...         self.bar = bar
... 
>>> foo = Foo('foo')
>>> pickle.dumps(foo)
"ccopy_reg\n_reconstructor\np0\n(c__main__\nFoo\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'bar'\np6\nS'foo'\np7\nsb."
>>> pickled_foo = pickle.dumps(foo)
>>> pickle.loads(pickled_foo)
<__main__.Foo object at 0x7f7b3a5f2e50>
>>> unpickled_foo = pickle.loads(pickled_foo)
>>> unpickled_foo.bar
'foo'

O problema de segurança

Ao abrir a documentação oficial do módulo pickle, logo de cara vê-se um alerta bem evidente na página:

Warning: The pickle module is not secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source.

Apesar do alerta evidente na página, muitos desenvolvedores acabam não dando a devida atenção a isto e confiam em dados externos vindo serializados, por exemplo, recebendo essa dado num endpoint de sua api.

Exemplo de código vulneravel

def insert_user(request):
    params = request.params
    user = pickle.load(StringIO(base64.b64decode(params['user'])))

Supondo que o código acima seja a definição de um endpoint aberto de uma api qualquer (https://foocompany.com/api/insert_user), este código basicamente receberá como parametro GET ou POST uma string (user) pickled em base64, decodificar, armazenar num StringIO, e então fazer o processo de unpickling.

O código acima está vulneravel a uma execução de código arbitrário por parte de um atacante externo.

Executando código arbitrário e explorando a falha

O pickle permite que objetos declarem como eles devem ser serializados definindo o método reduce que deve retornar uma string ou uma tupla descrevendo como o objeto deve ser reconstruido no processo de unpickling. A tupla retornada pelo metodo reduce deve conter um objeto chamável e uma tupla de argumentos para esse objeto ser chamado. Então ao fazer unpickle, o objeto definido na tupla será executado.

A partir disso, podemos fazer com que o objeto ao fazer unpickling execute um comando no sistema, por exemplo:

import cPickle
import base64
import os
class Exploit(object):
  def __reduce__(self):
    return (os.system, ('touch /tmp/you_were_owned',))
print base64.b64encode(cPickle.dumps(Exploit()))

O código acima irá gerar um base64, e este poderá ser passado como parametro no endpoint de api definido, da seguinte forma: https://foocompany.com/api/insert_user?user=[base64_gerado_pelo_script_acima], e irá executar o comando touch direto no sistema. E assim você poderá usar sua criatividade para explorar o sistema agora que consegue executar qualquer código no sistema.

Como evitar

Simples, siga o que diz a documentação, não utilize o pickle para desserializar dados aos quais você não pode garantir autenticidade.