python通用规范-9

时间:2021-07-12 17:57:29   收藏:0   阅读:0

文章目录

9.1 异常行为
9.1.1 禁止抑制或者忽略已检查异常
9.1.2 禁止在异常中泄露敏感信息
9.1.3 方法发生异常时要恢复到之前的对象状态
9.2 运行环境
9.2.1 生产代码不能包含任何调试入口点
9.2.2 使用标准的API替代操作系统的系统命令
9.2.3 禁止从第3方源下载并使用软件包
9.3 其他
9.3.1 禁止在日志中保存口令、密钥等敏感信息
9.3.2 禁止将敏感信息硬编码在程序中
9.3.3 使用安全随机数
9.3.4 代码发布前务必书写开发者信息及包含开发者信息的注释内容
9.3.5 保存外来不可信数据前要先转义
9.1 异常行为

9.1.1 禁止抑制或者忽略已检查异常

说明: 编码人员常常会通过一个空的或者无意义的except块来抑制捕获的已检查异常。每一个except块都应该确保程序只会在继续有效的情况下才会继续运行下去。因此,except块必须要么从异常情况中恢复,要么重新抛出适合当前except块上下文的另一个异常以允许最邻近的外层try-except语句块来进行恢复工作。异常会打断应用原本预期的控制流程。例如,try块中位于异常发生点之后的任何表达式和语句都不会被执行。因此,异常必须被妥当处理。许多抑制异常的理由都是不合理的。例如,当对客户端从潜在问题恢复过来不抱期望时,一种好的做法是让异常被广播出来,而不是去捕获和抑制这个异常。
# 【错误示例】: 下面代码假设获取客户端输入的Email地址,在使用之前对Email进行数据格式的合法性校验。
try:
# to_do
pass
except Exception as ex:
import traceback

traceback.print_exc()
# 错误示例中, except 块只是简单地将异常堆栈轨迹打印出来。
# 虽然打印异常的堆栈轨迹对于定位问题是有帮助的,但是最终的程序运行逻辑等同于抑制异常。
# 注意,即使这个错误示例在发生异常时会打印一个堆栈轨迹,但是程序会继续运行,如同异常从未被抛出过。
# 换句话说,除了try块中位于异常发生点之后的表达式和语句不会被执行之外,发生的异常不会影响程序的其他行为。

# 【正确示例1】:
validFlag = False
while not validFlag:
try:
# If requested file does not exist, throws FileNotFoundException
# If requested file exists, sets validFlag to true
validFlag = True
pass
except FileNotFoundException:
import traceback
traceback.print_exc()


# 【正确示例2】:
# 继承Exception Reporter并过滤敏感异常。有时候异常必须对用户隐藏。
# 在这种情况下,一种可行的方式是继承Exception类,并且在重写默认的str方法的基础上,增加一个filter()方法。
class MyExceptionReporter(Exception):
def __init__(self, msg):
self.msg = msg

def __str__(self):
return filter(self.msg)

def filter(self):
# Sanitize sensitive data or replace sensitive exceptions with non-sensitive exceptions (whitelist)
# Return non-sensitive exception
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
例外情况:

在资源释放失败不会影响程序后续行为的情况下,释放资源时发生的异常可以被抑制。释放资源的例子包括关闭文件、网络套接字、线程等等。这些资源通常是在except或者fianlly块中被释放,并且在后续的程序运行中都不会再被使用。因此,除非资源被耗尽,否则不会有其他途径使得这些异常会影响程序后续的行为。在充分处理了资源耗尽问题的情况下,只需对异常进行净化和记录日志(以备日后改进)就足够了;在这种情况下没必要做其他额外的错误处理。
如果在特定的抽象层次上不可能从异常情况中恢复过来,则在那个层级的代码就不用处理这个异常,而是应该抛出一个合适的异常,让更高层次的代码去捕获处理,并尝试恢复。对于这种情况,最通常的实现方法是省略掉except语句块,允许异常被广播出去。
9.1.2 禁止在异常中泄露敏感信息

说明: 敏感数据的范围应该基于应用场景以及产品威胁分析的结果来确定。典型的敏感数据包括口令、银行账号、个人信息、通讯记录、密钥等。如果在传递异常的时候未对其中的敏感信息进行过滤常常会导致信息泄露,而这可能帮助攻击者尝试发起进一步的攻击。攻击者可以通过构造恶意的输入参数来发掘应用的内部结构和机制。不管是异常中的文本消息,还是异常本身的类型都可能泄露敏感信息。例如,对于IOError异常,其中的异常消息会透露文件系统的结构信息,而通过异常本身的类型,可以得知所请求的文件不存在。因此,当异常会被传递到信任边界以外时,必须同时对敏感的异常消息和敏感的异常类型进行过滤。
# 【错误示例】:
import sys
if len(sys.argv) != 2:
print("Invalid file")
else:
try:
open(sys.argv[1], "r")
except MyError as e:
# Log the exception
pass
# 如果传入一个文件名后,程序返回一个异常,则暗示该文件不存在,而如果不抛出异常则说明该文件是存在的。
# 使得系统面临暴力攻击的风险,攻击者可以多次传入不同的文件名进行暴力碰撞来发现有效文件。

# 【正确示例】:
import sys
if len(sys.argv) != 2:
print ("Invalid file")
else:
# 先把路径进行标准化
try:
truepath = os.path.realpath(sys.argv[1])
except Exception as ex:
print ("Invalid file")
# 校验路径是否以/home/python/开头
if truepath.startswith(‘/home/python/‘):
try:
open(truepath)
except IOError:
print("Invalid file")
else:
print("Invalid file")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
在这个正确示例中,规定用户只能打开/home/python/目录下的文件,用户不可能发现这个目录以外的任何信息。
在这个方案中,如果无法打开文件,或者文件不在合法的目录下,则会产生一条简洁的错误消息。

9.1.3 方法发生异常时要恢复到之前的对象状态

说明:当发生异常的时候,对象一般需要(关键的安全对象则必须)维持其状态的一致性。常用的可用来维持对象状态一致性的手段包括:输入校验(如校验方法的调用参数)调整逻辑顺序,使可能发生异常的代码在对象被修改之前执行当业务操作失败时,进行回滚对一个临时的副本对象进行所需的操作,直到成功完成这些操作后,才把更新提交到原始的对象避免去修改对象状态
# 【错误示例】:
PADDING = 2
MAX_DIMENSION = 10


class Dimensions(object):
def __init__(self, length, width, height):
self.length = length
self.width = width
self.height = height

def getVolumePackage(self, weight):
self.length += PADDING
self.width += PADDING
self.height += PADDING
try:
self.validate(weight)
volume = self.length * self.width * self.height
self.length -= PADDING
self.width -= PADDING
self.height -= PADDING
return volume
except Exception as e:
return -1

def validate(self, weight):
# do some validation and may throw a exception
if weight > 20:
raise Exception
pass


if __name__ == ‘__main__‘:
d = Dimensions(10, 10, 10)
print(d.getVolumePackage(21)) # Prints -1 (error)
print(d.getVolumePackage(19)) # Prints 2744 instead of 1728
# 在这个错误示例中,未有异常发生时,代码逻辑会恢复对象的原始状态。
# 但是如果出现异常事件,则回滚代码不会被执行,从而导致后续的 getVolumePackage() 调用不会返回正确的结果。

# 【正确示例1】回滚:
...
except Exception as e:
self.length -= PADDING
self.width -= PADDING
self.height -= PADDING
return -1
# 这个正确示例在getVolumePackage() 方法的except 块中加入了发生异常时恢复对象状态的代码。

# 【正确示例2】finally分支:
def getVolumePackage(self, weight):
self.length += PADDING
self.width += PADDING
self.height += PADDING
try:
self.validate(weight)
volume = self.length * self.width * self.height
return volume
except Exception as e:
return -1
finally:
self.length -= PADDING
self.width -= PADDING
self.height -= PADDING
# 这个正确示例使用一个finally子句来执行回滚操作,以保证不管是否发生异常,都会进行回滚。

# 【正确示例3】输入校验:
def getVolumePackage(self, weight):
try:
self.validate(weight)
except Exception as e:
return -1
self.length += PADDING
self.width += PADDING
self.height += PADDING
volume = self.length * self.width * self.height
self.length -= PADDING
self.width -= PADDING
self.height -= PADDING
return volume
# 这个正确示例在修改对象状态之前执行输入校验。注意,try代码块中只包含可能会抛出异常的代码,而其他代码都被移到 try 块之外。

# 【正确示例4】未修改的对象:
def getVolumePackage(self, weight):
try:
self.validate(weight)
except Exception as e:
return -1
volume = (self.length + PADDING) * (self.width + PADDING) * (self.height + PADDING)
return volume
# 这个正确示例避免了需要修改对象,使得对象状态不可能不一致,也因此没有必要进行回滚操作。
# 相比之前的解决方案,更推荐使用这种方式。但是对于一些复杂的代码,这种方式可能无法实行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
9.2 运行环境

9.2.1 生产代码不能包含任何调试入口点

说明: 由于调试或者测试目的,开发者经常在代码中添加特定的调测代码,这些代码并没有打算与应用一起交付或者部署。当这类的调测代码不小心被留在了应用中,这个应用对某些特殊交互就是开放的。这些后门入口点可以导致安全风险。
【错误示例】:

def wrong(flag):
if not flag:
import pdb
pdb.set_trace()
print(‘.....‘)
...
# end def
1
2
3
4
5
6
7
【建议】:
代码在交付前务必删除所有调试相关的代码!

9.2.2 使用标准的API替代操作系统的系统命令

说明:如果可以使用标准的API替代运行系统命令来完成任务,则应该使用标准的API。
当前与Python相关的漏洞中命令注入占比非常大,所以在此呼吁不要直接使用系统命令,不建议使用os.system、subprocess等模块去运行系统命令,如遍历目录/文件/创建文件夹等操作。
【建议】: 在python的标准库中有多种方法实现与执行系统命令同样的功能,如os.listdir、glob.glob、glob.glob1 等模块都可以实现系统命令“ls” 或“dir”的功能,所以此处倡议不要直接调用系统命令:
dir
cmd.exe
powershell.exe
ls
awk
cat
等windows、linux及mac的系统命令

9.2.3 禁止从第3方源下载并使用软件包

说明: Python之所以如此流行的原因之一是其生态做的好, 其import非常灵活,方便使用的同时也存在着安全漏洞。目前Python依赖项中有5000多个已知的安全漏洞,这些都可能导致您自己的代码中出现严重的安全漏洞。别有用心的hacker已经在篡改了大量的软件包并发布的互联网上,一旦使用就会导致不可挽回的损失。
【建议】:
从官网下载并使用流行的软件包,使用前请确保全局的 site-packages 尽可能的干净(比如工程环境只用单独的虚拟环境),同时检查包签名。

9.3 其他

9.3.1 禁止在日志中保存口令、密钥等敏感信息

L说明:在日志中不能输出口令、密钥和其他敏感信息,口令包括明文口令和密文口令。对于敏感信息建议采取以下方法:不在日志中打印敏感信息。
若因为特殊原因必须要打印日志,则用固定长度的星号(*)代替输出的敏感信息。

9.3.2 禁止将敏感信息硬编码在程序中

说明: 如果将敏感信息(包括口令和加密密钥)硬编码在程序中,可能会将敏感信息暴露给攻击者。无需反编译pyc文件,任何能够访问到pyc文件的人都可以获取这些敏感信息。因此,不能将敏感信息硬编码在程序中。
【错误示例】:

class DefPassword:
defPassword = "123456"
1
2
恶意用户可以直接打开pyc文件发现其中硬编码的默认密码是:123456。
【建议】: 建议加密后使用。

9.3.3 使用安全随机数

说明: Python产生随机数的功能在random模块中实现,实现了各种分布的伪随机数生成器。产生的随机数可以是均匀分布,高斯分布,对数正态分布,负指数分布以及alpha,beta分布,但是这些随机数都是伪随机数,不能应用于安全加密目的的应用中。如果你需要一个真正的密码安全随机数,请使用/dev/random生成安全随机数;另外在python 3.6版本官方引入了一个secrets模块用于生成安全随机数。
# 【错误示例1】:
import random

sr = random.randint(0, 100)

# 【错误示例2】:
import random

foo = random.SystemRandom() # 伪随机数
print(foo.random())
print(foo.randint(0, 10))

# random 模块还提供 SystemRandom 类,它使用系统函数 os.urandom() 从操作系统提供的源生成随机数。
# 注意: os.urandom 在linux系统环境中生成的随机数不安全,不符合我司标准。
# 在Python 3.6及更高版本中添加了secrets模块,可以使用secrets模块来实现:生成安全随机数、密码及一次性密码、随机token及会话密钥等功能。

# 【正确示例1】:linux环境下推荐使用此方法
import platform

randLength = 16 # 长度请参见密码算法规范,不同场景要求长度不一样
if platform.system() == ‘Linux‘:
with open("/dev/random", ‘rb‘) as file:
sr = file.read(randLength)
print(sr)

# 【正确示例2】:windows环境下推荐
import platform
import os

randLength = 16 # 长度请参见密码算法规范,不同场景要求长度不一样
if platform.system() == ‘Windows‘:
_randLst = list(os.urandom(randLength))
print(_randLst)
_randBytes = os.urandom(randLength)
print(_randBytes)

# 【正确示例3】:
# 适用于python3.6之后的版本
import secrets

# 生成随机整数
number = secrets.randbelow(10)
print("Secure random number is ", number)
# Secure random number is 0
secretsGenerator = secrets.SystemRandom()
randomNumber = secretsGenerator.randint(0, 50)
print("Secure random number is ", randomNumber)
# Secure random number is 26
# 指定范围并设置步长
randomNumber = secretsGenerator.randrange(5, 50, 5)
print("Secure random number within range is ", randomNumber)
# Secure random number within range is 15
# 从指定的数据集中选择
number_list = [6, 12, 18, 24, 30, 36, 42, 48, 54, 60]
secure_choice = secretsGenerator.choice(number_list)
print("Secure random choice using secrets is ", secure_choice)
# Secure random choice using secrets is 30
secure_sample = secretsGenerator.sample(number_list, 3)
print("Secure random sample using secrets is ", secure_sample)
# Secure random sample using secrets is [54, 6, 42]
# 随机安全浮点数
secure_float = secretsGenerator.uniform(2.5, 25.5)
print("Secure random float number using secrets is ", secure_float)
# Secure random float number using secrets is 9.445927455984885
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
9.3.4 代码发布前务必书写开发者信息及包含开发者信息的注释内容

说明:RPA层面为了防止人员流动引起的后期维护困难 , 所有python脚本代码文件开头书写开发者信息的注释内容。
【建议】:

# -*- coding : utf-8
# -*- Author : x00000000
# -*- Date : 20200505
# -*- version : Python 3.7.4
1
2
3
4
代码发布前,书写所有与开发者信息相关的注释信息。

9.3.5 保存外来不可信数据前要先转义

说明:此规则来源于CSV命令注入漏洞案例。
CSV命令注入漏洞:CSV的命令注入漏洞是当用户打开存在特殊字符构造成表达式的CSV格式或Excel文档时,宿主程序(微软的Excel软件)会解析并执行该表达式,从而运行攻击者的指令,达到攻击本地计算机系统的目的。这种攻击多发生于web系统的导出日志功能上,攻击形式多样,下面是windows系统上启动了包含特殊字符串的csv文件:=cmd|’/c calc’Hi,2012lib
查看日志文档的的管理员用户(因事先了解这仅是自己的日志文件,一般会放松警惕而忽略系统的安全警示)打开该文件时就会发现系统的计算器也被执行了。
所以正确的建议就是谨慎处理用户的数据,参考前面的规则,引入黑白名单机制,或者对特殊字符进行转义,此处CSV漏洞属于特别的场景,可以对=做处理,比如在=号前面插入字符\t,如让微软的Excel程序正确解释即可。提示,包含以上字符串的csv文件被Symantec杀毒软件报毒为Trojan.Slakexec!gen4。
【建议】:
在保存文件时,要考虑该文件的阅读器是否存在缺陷(如此处的Excel注入漏洞)
————————————————
版权声明:本文为CSDN博主「zhao12501」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhao12501/article/details/115473231

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!