前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >web.py使用不当可能造成代码执行

web.py使用不当可能造成代码执行

作者头像
phith0n
发布2020-10-15 10:11:50
2980
发布2020-10-15 10:11:50
举报
文章被收录于专栏:离别歌 - 信息安全与代码审计

    实话说我标题党了,这只是一个小tip,不能算漏洞(因为我想swart就是这样设计的)。不过文档中似乎没有把这个说出来,我想如果有一千万分之一的概率被程序员这样写了,那的确能造成代码执行。 

    先举个简单例子吧,如下代码:

代码语言:javascript
复制
#!/usr/bin/env python
#coding=utf-8
__author__ = 'Phtih0n'
import web

urls = (
	'.*', 'game'
)

class game:
	def GET(self):
		data = web.input()
		return data

if __name__ == "__main__":
	app = web.application(urls, globals())
	app.run()

    最简单的一个网站代码,运行以后访问即可看到输出的是GET过来的一个字典:

    Storage是web.py中定义的一个类,其基类是dict,也就是说其实Storage就是被封装过的一个字典。

    web.input是一个接收GET或POST参数的重要函数,我们可以为参数设置一个默认值。

    比如data = web.input(xdsec = "xdsec.org"),那么,如果访问http://localhost/?xdsec=123,则data['xdsec'] 就是 "123",但如果访问http://localhost/不带参数,则data['xdsec']就取默认值"xdsec.org"。如下:

    我们看到web.py的源码,它是怎么处理input函数的:

代码语言:javascript
复制
def input(*requireds, **defaults):
    """
    Returns a `storage` object with the GET and POST arguments. 
    See `storify` for how `requireds` and `defaults` work.
    """
    _method = defaults.pop('_method', 'both')
    out = rawinput(_method)
    try:
        defaults.setdefault('_unicode', True) # force unicode conversion by default.
        return storify(out, *requireds, **defaults)
    except KeyError:
        raise badrequest()

    我们看到,他调用了storify函数返回,我们再跟一下storify函数:

代码语言:javascript
复制
def storify(mapping, *requireds, **defaults):
    _unicode = defaults.pop('_unicode', False)

    # if _unicode is callable object, use it convert a string to unicode.
    to_unicode = safeunicode
    if _unicode is not False and hasattr(_unicode, "__call__"):
        to_unicode = _unicode
    
    def unicodify(s):
        if _unicode and isinstance(s, str): return to_unicode(s)
        else: return s
        
    def getvalue(x):
        if hasattr(x, 'file') and hasattr(x, 'value'):
            return x.value
        elif hasattr(x, 'value'):
            return unicodify(x.value)
        else:
            return unicodify(x)
    
    stor = Storage()
    for key in requireds + tuple(mapping.keys()):
        value = mapping[key]
        if isinstance(value, list):
            if isinstance(defaults.get(key), list):
                value = [getvalue(x) for x in value]
            else:
                value = value[-1]
        if not isinstance(defaults.get(key), dict):
            value = getvalue(value)
        if isinstance(defaults.get(key), list) and not isinstance(value, list):
            value = [value]
        setattr(stor, key, value)

    for (key, value) in defaults.iteritems():
        result = value
        if hasattr(stor, key): 
            result = stor[key]
        if value == () and not isinstance(result, tuple): 
            result = (result,)
        setattr(stor, key, result)
    
    return stor

    关键看其中这几个点:

    1._unicode = defaults.pop('_unicode', False)

        从defaults(defaults是两个星号**defaults,也就是web.input时的默认值)中取出_unicode的值。

    2.if _unicode is not False and hasattr(_unicode, "__call__"):         to_unicode = _unicode

        如果_unicode不是false而且含有__call__这个属性的话,就赋值给to_unicode

    3.if _unicode and isinstance(s, str): return to_unicode(s)

        如果_unicode非false而且s是一个字符串则执行to_unicode(s)

    也就是说,如果我将一个含有__call__属性的对象赋值给_unicode,就能够在这个函数中允许它。__call__是python中的“魔术变量”之一,当一个类含有__call__这个方法的时候,我们就能直接通过“类名()”的方式执行它。

    所以,如果有一个参数的名字是_unicode,默认值是exp,那就会写作web.input(_unicode=exp),这时候就会在web.input中执行exp这个函数,造成代码执行。

    不过为什么说是特性而不是漏洞呢,因为程序员是没有理由把程序写成web.input(_unicode=exp)的,就算_unicode的值真的是"exp"的话,也会写成web.input(_unicode="exp"),因为“exp”只是一个字符串。

    web.py的开发者留这个参数的用意应该在于处理字符编码,当传入的参数不是unicode的情况下,能通过传入一个_unicode方法去处理这个字符串,类似于一个回调函数。

    不过通过这个特性也能看到在python中一样可能出现安全问题,只是看你有没有心罢了。

    我们可以来做个试验,如下代码:

代码语言:javascript
复制
#!/usr/bin/env python
#coding=utf-8
__author__ = 'Phtih0n'
import web

urls = (
	'.*', 'game'
)

def exp(a):
	print "Hello, hacker"
	return a

class game:
	def GET(self):
		data = web.input(_unicode=exp, xdsec = 'xdsec.org')
		return data

if __name__ == "__main__":
	app = web.application(urls, globals())
	app.run()

    运行以后访问一下http://localhost/,我们看到控制台,可以看到,里面输出了"hello, hacker",每访问一次localhost就会输出一个hello:

    实际上也就是exp函数被执行了。因为我把_unicode变量的值赋为了exp。如果我们把_unicode=exp换成_unicode=sys.exit,就能执行exit函数,导致进程退出,造成拒绝服务。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档