Input function
在python2中,通过input输入的内容将作为python代码执行。
1 | $ python2 |
安全的输入方式是使用raw_input()获取stdin内容,在python3中input已变得跟raw_input相等。
Assert
在python中使用assert语句使用断言,例如使用断言以判断程序是否可以继续执行。1
2
3
4def verify_credentials(username, password):
assert username and password, 'Credentials not supplied by caller'
... authenticate possibly null user with null password ...
这样使用并没有什么问题,但是当程序使用python -O 编译为优化的字节码时,会导致asser语句被忽略。
In Python 2.7, -O has the following effect:
- the byte code extension changes to .pyo
- sys.flags.optimize gets set to 1
- debug is False
- assert don’t get executed
可重复使用整数
1 | >>> 999+1 is 1000 |
这两个结果看起来有点匪夷所思,但实际上是我们对is有错误的理解。is操作符是在两个对象的标识上工作的,并不能用于比较数值。在python中,万物皆对象,每个对象都有一个唯一标识,可以用id函数来读取。要找出两个变量或两个属性是否都指向同一个对象,可以使用is操作符。
浮点数比较
1 | >>> 2.2 * 3.0 == 3.3 * 2.0 |
上面的结果是由于固有受限精度导致舍入错误:1
2
3
4>>> (2.2 * 3.0).hex()
'0x1.a666666666667p+2'
>>> (3.3 * 2.0).hex()
'0x1.a666666666666p+2'
float > 无穷
在python中,float类型支持无穷大的概念:float(‘infinity’)1
210**1000000 > float('infinity') >
False
因此我们有理由相信,一切都比无穷小,但是一不小心,又翻车了
任意type对象比无穷大1
2
3
4>>> float > float('infinity')
True
>>> int > float('infinity')
True
这一车祸现场在python3中被处理,type()不能与float()进行比较。
私有属性
python不支持对象隐藏属性,但是其提供一种使用双下划线__开头的属性隐藏方法。
1 | class Foo: |
定义上面的class1
2
3
4
5
6
7
8
9>>> f = Foo('boogle')
>>> f.N
222222
>>> f.__N
AttributeError: Foo instance has no attribute '__N'
>>> f.__f1()
AttributeError: Foo instance has no attribute '__f1'
>>> f.f2()
boogle
从上面的测试来看,使用__开头貌似确实实现了隐藏,而且其也成功在 getattr()/hasattr()中隐藏1
2 > f.has_private()
False
但这种隐藏只是一种语法上的变形,并没有达到真正意义上的隐藏
1 | >>>print Foo.__dict__ |
得到Foo类的所有属性跟函数。
那么有没有办法直接查看这些属性的内容呢?答案是肯定的,我们可以用下划线_加类名加属性名的方法查看其内容(注意类名前是单下划线)1
2
3
4 > f._Foo__N
111111
_ > f.__dict_
{'_Foo__Name': 'boogle'}
另外当对属性进行重新赋值时,这些隐藏的属性也会被显示出来
1 | 333333 > f.__N = |
模块注入
python的模块导入为python注入了活力,其功能强大而复杂,例如通过 1
2
3
4
python中模块和包可以通过定义在sys.path列表中的搜索路径找到的文件或目录名导入。搜索路径初始化是一个复杂的过程,它也依赖于Python版本,平台和本地配置。要想对其实施模块注入,就要知道其初始化的搜索路径,从来正确导入。
我们可以运行下面的脚本知道其实际的搜索路径
$ cat myapp.py
#!/usr/bin/python
import sys
import pprint
pprint.pprint(sys.path)
1 | ``` |
从上面程序执行结果可以看到,其当前工作目录被自动插入到sys.path中。
那么便可以在sys.path中出现的任意一个我们具有操作权限的目录写入我们要导入的模块,从而进行导入。
两外在沙箱环境中,很有可能会删除
然会我们继续查看,会发现sys下面又一个modules。我们对sys.moudles做一些改动,看看会发生什么1
2
3
4
5'os']=None > sys.modules[
> import os
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named os
果然如我们所料,将os从 sys.modules 中删掉之后,就不能再引入了。
那么当一个沙箱环境通过这种方式将可能带来危险的模块删除时,我们便可以重新导入
1 | > import sys |
另外如果sys也被禁止导入了呢?那么可以使用execfile()执行相应的代码1
2
3
4
5
6
7
8
9'/usr/lib/python2.7/os.py') > execfile(
'cat /etc/passwd') > system(
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
> getcwd()
'/usr/lib/python2.7'
subprocess shell注入
shell = True时
此时如果命令参数可控,即可进行shell注入
1 | s=subprocess.Popen('ls;id', shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) |
shell = False时
subprocess.call([]) 执行的是list拼接起来的命令,如果可控参数在拼接之后使得参数变成了参数选项,则存在命令注入风险1
2
3 > from subprocess import call
>
'/bin/ls', '/tmp']) > call([