O guia definitivo sobre o pickle do Python
Thomas Daniels
20 de abril de 2022
0 minutos de leituraDurante o desenvolvimento de aplicações, muitas vezes precisamos persistir dados complexos (como objetos) para usá-los em diferentes execuções. No entanto, manter estruturas de dados e objetos complexos persistentes está longe de ser simples. No Python, você pode usar a biblioteca pickle integrada para lidar com esse processo. O pickle pode serializar um objeto Python em um fluxo de bytes simples (pickling) e também transformar um fluxo de bytes de volta em um objeto Python (unpickling).
Para persistir dados complexos, é preciso representá-los como um fluxo simples de bytes, que pode ser armazenado em disco ou enviado pela rede. Esse processo de transformar uma estrutura de objetos em um fluxo de bytes é chamado de empacotamento ou serialização. Quando nossa aplicação lê ou recebe o fluxo de bytes, ela faz o processo inverso, transformando-o de volta na estrutura de objetos. Isso é chamado de desempacotamento ou desserialização.
Observação: como o pickle é específico do Python, é fácil usá-lo em qualquer aplicação Python. No entanto, isso também significa que ele não pode ser usado para trocar dados entre aplicações escritas em linguagens diferentes.
Este artigo ensina você a usar o pickle com segurança nas suas aplicações. Embora o pickle seja muito prático em diversas situações, é importante lembrar que você deve fazer unpickle somente de dados em que confia! Fazer unpickle de dados não confiáveis pode levar à execução de código arbitrário e é uma fonte comum de vulnerabilidades críticas de segurança.

Como usar o pickle do Python
O pickle é simples de usar, então vamos direto ao ponto. Todas as funções relevantes para fazer pickle e unpickle estão no módulo pickle. Para começar, vamos importar esse módulo.
Ao fazer pickle de um objeto Python, podemos gravá-lo diretamente em um arquivo ou em um objeto bytes para usar mais tarde no código. Nos dois casos, basta chamar um método.
Para gravar um objeto em um arquivo usando pickle, chame pickle.dump(object, file). Para obter apenas os bytes serializados, chame pickle.dumps(object).
Como mencionamos, é fácil usar pickle em Python. Você pode fazer pickle de vários tipos de dados, como:
Tipos primitivos do Python
Estruturas de coleções aninhadas, como tuplas, listas, conjuntos e dicionários — desde que os dicionários contenham apenas objetos compatíveis com pickle
A maioria das instâncias de classes
A documentação do Python contém uma lista completa do que pode ser serializado com pickle.
Se você tentar fazer pickle de um objeto incompatível, o Python gera um PicklingError.
Exemplos de pickle no Python
Vamos praticar como fazer pickle. Abaixo está uma estrutura de dados que representa o estado de um jogo hipotético. Nele, há um jogador em determinado local do mundo do jogo, representado por uma tupla de coordenadas. O mundo também tem obstáculos em locais específicos, representados por tuplas. O jogador possui itens com nome e custo. As classes são assim:
Agora, vamos criar um estado específico e gravá-lo em um arquivo usando pickle.
O pickle é um formato binário ilegível para pessoas. Por isso, se examinarmos o conteúdo do arquivo state.bin que acabamos de criar, ele não fará muito sentido. Só reconheceremos alguns identificadores, como GameState e obstacles.
Agora que salvamos o estado do jogo em um arquivo, vamos tentar carregá-lo para implementar as funcionalidades de salvar e retomar o jogo! Tanto pickle.dump quanto pickle.dumps têm funções correspondentes para fazer unpickle: pickle.load(_file_) carrega de um arquivo um objeto Python serializado com pickle; pickle.loads(_bytes_) faz o mesmo com os bytes fornecidos.
Como você pode ver, o básico do pickle é muito simples. Basta chamar um método para armazenar e carregar um objeto Python.
Como explorar o pickle do Python
Como mencionamos na introdução, você deve fazer unpickle somente de dados em que confia, porque o módulo pickle não é seguro. Se você fizer unpickle do fluxo de bytes de um invasor, poderá executar código arbitrário. Isso é consequência do poder do formato pickle.
Um objeto Python pode especificar como deve ser serializado com pickle usando o método especial _reduce_. Esse método deve retornar uma string ou uma tupla. Uma string representa o nome de uma variável global. Uma tupla representa código chamável (como uma função ou classe), os argumentos desse código e algumas informações opcionais que não são relevantes para este exemplo. Durante o unpickling, o código chamável especificado é invocado com os argumentos correspondentes.
Com esse conhecimento, você pode construir um objeto serializado com pickle que chama qualquer função que queira executar durante o unpickling. Por exemplo, você poderia executar um comando do sistema chamando a função os.system durante o unpickling ou usar eval para executar qualquer código Python!
Veja um exemplo (inofensivo) desse ataque usando a função eval (que simplesmente chamará a função print quando o objeto for carregado):
Se executarmos esse código, o número 3 será exibido no console, porque fazer unpickle desses dados executará eval("print(1+2)").
Como usar o pickle do Python com segurança
Se você precisa aceitar dados de um cliente não confiável, não pode usar pickle por causa dos riscos mencionados acima. Em vez disso, use outro formato de serialização de dados, como a Notação de Objetos JavaScript (JSON).
É possível fazer isso usando o módulo json do Python. A desvantagem é que o módulo json é bem menos poderoso que o pickle, pois não oferece suporte nativo a tipos de dados complexos, como objetos personalizados. Além disso, os tipos de dados nativos do JSON são limitados. Por exemplo, não existe no JSON um tipo equivalente ao conjunto do Python, então você precisaria usar um codificador personalizado para conjuntos. Ainda assim, o JSON é um formato seguro para lidar com dados não confiáveis.
Imagine que uma aplicação confiável gere dados serializados com pickle, mas que você não consiga garantir sua integridade entre a serialização e a desserialização. Talvez não seja possível confiar nos dados porque eles estão sendo enviados por uma rede insegura ou armazenados de forma persistente em um local ao qual um invasor pode ter acesso.
Em ambos os casos, uma solução é gerar uma assinatura criptográfica para os dados serializados com pickle usando um HMAC. Em seguida, os dados são enviados ou armazenados junto com a assinatura. Antes de fazer unpickle, quem recebe os dados pode validar a assinatura para verificar sua integridade.
Você pode gerar uma assinatura assim (usando SHA256):
OBSERVAÇÃO: Na prática, sua chave secreta ficaria armazenada com segurança em uma aplicação no servidor, nunca exposta a ambientes não confiáveis. Não inclua uma chave secreta diretamente no código. =)
Quem recebe os dados pode calcular o resumo esperado e verificar se ele corresponde ao resumo fornecido.
Nesse cenário, mesmo que invasores alterem os dados serializados com pickle, não conseguirão adulterar a assinatura com sucesso (nem induzir quem recebe os dados a executar código não confiável) se não tiverem a chave secreta.
Considerações finais
Agora você sabe usar o módulo pickle do Python para serializar e desserializar objetos Python complexos com segurança — e isso é um grande feito.
Lembre-se: como o pickle pode causar vulnerabilidades críticas de segurança no código, nunca faça unpickle de dados em que não confia. Se precisar aceitar dados de um cliente não confiável, use o formato JSON, mais seguro. E, se transferir dados serializados com pickle entre aplicações confiáveis, mas precisar de uma proteção extra contra adulterações, gere uma assinatura HMAC que possa ser verificada antes de fazer unpickle.
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo sob demanda ao nosso workshop virtual introdutório.
