现在能在网上找到很多很多的学习资源,有免费的也有收费的,当我拿到1套比较全的学习资源之前,我并没着急去看第1节,我而是去审视这套资源是否值得学习,有时候也会去问一些学长的意见,如果可以之后,我会对这套学习资源做1个学习计划,我的学习计划主要包括规划图和学习进度表。
分享给大家这份我薅到的免费视频资料,质量还不错,大家可以跟着学习
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
1)死锁形式1
subprocess.call
subprocess.check_call
subprocess.check_output
以上三个函数在使用stdout=PIPE or stderr=PIPE 存在死锁风险
处理方案:
若要使用stdout=PIPE or stderr=PIPE,建议使用popen.communicate()
subprocess 官方文档在上面几个函数中都标注了安全警告:
2) 死锁形式2
对于popen , popen.wait() 可能会导致死锁
处理方案:
那死锁问题如何避免呢?官方文档里推荐使用 Popen.communicate()。这个方法会把输出放在内存,而不是管道里,所以这时候上限就和内存大小有关了,一般不会有问题。而且如果要获得程序返回值,可以在调用 Popen.communicate() 之后取 Popen.returncode 的值。
3)死锁形式3
call、check_call、popen、check_output 这四个函数,参数shell=True,命令参数不能为list,若为list则引发死锁
处理方案:
参数shell=True时,命令参数为字符串形式
0×02. 关闭subprocess.Popen 子进程时存在子进程关闭失败而成为僵尸进程的风险
Python 标准库 subprocess.Popen 是 shellout 一个外部进程的首选,它在 Linux/Unix 平台下的实现方式是 fork 产生子进程然后 exec 载入外部可执行程序。
于是问题就来了,如果我们需要一个类似“夹具”的子进程(比如运行 Web 集成测试的时候跑起来的那个被测试 Server), 那么就需要在退出上下文的时候清理现场,也就是结束被跑起来的子进程。
最简单粗暴的做法可以是这样:
@contextlib.contextmanager
def process_fixture(shell_args):
proc = subprocess.Popen(shell_args)
try:
yield
finally:
# 无论是否发生异常,现场都是需要清理的
proc.terminate()
proc.wait()
if __name__ == '__main__':
with process_fixture(['python', 'SimpleHTTPServer', '8080']) as proc:
print('pid %d' % proc.pid)
print(urllib.urlopen('http://localhost:8080').read())
那个 proc.wait() 是不可以偷懒省掉的,否则如果子进程被中止了而父进程继续运行, 子进程就会一直占用 pid 而成为僵尸,直到父进程也中止了才被托孤给 init 清理掉。
这个简单粗暴版对简单的情况可能有效,但是被运行的程序可能没那么听话。被运行程序可能会再fork 一些子进程来工作,自己则只当监工 —— 这是不少 Web Server 的做法。 对这种被运行程序如果简单地 terminate ,也即对其 pid 发 SIGTERM , 那就相当于谋杀了监工进程,真正的工作进程也就因此被托孤给 init ,变成畸形的守护进程…… 嗯没错,这就是我一开始遇到的问题,CI Server上明明已经中止了 Web Server 进程了,下一轮测试跑起来的时候端口仍然是被占用的。
处理方案:
这个问题稍微有点棘手,因为自从被运行程序 fork 以后,产生的子进程都享有独立的进程空间和pid ,也就是它超出了我们触碰的范围。好在 subprocess.Popen 有个 preexec_fn 参数,它接受一个回调函数,并在 fork 之后 exec 之前的间隙中执行它。我们可以利用这个特性对被运行的子进程做出一些修改,比如执行 setsid() 成立一个独立的进程组。Linux 的进程组是一个进程的集合,任何进程用系统调用 setsid 可以创建一个新的进程组,并让自己成为首领进程。首领进程的子子孙孙只要没有再调用 setsid 成立自己的独立进程组,那么它都将成为这个进程组的成员。 之后进程组内只要还有一个存活的进程,那么这个进程组就还是存在的,即使首领进程已经死亡也不例外。 而这个存在的意义在于,我们只要知道了首领进程的 pid(同时也是进程组的 pgid ), 那么可以给整个进程组发送 signal ,组内的所有进程都会收到。
因此利用这个特性,就可以通过 preexec_fn 参数让 Popen 成立自己的进程组, 然后再向进程组发送 SIGTERM 或 SIGKILL ,中止 subprocess.Popen 所启动进程的子子孙孙。当然,前提是这些子子孙孙中没有进程再调用 setsid 分裂自立门户。
前文的例子经过修改是这样的:
import signal
import os
import contextlib
import subprocess
import logging
import warnings
@contextlib.contextmanager
def process_fixture(shell_args):
proc = subprocess.Popen(shell_args, preexec_fn=os.setsid)
try:
yield
finally:
proc.terminate()
proc.wait()
try:
os.killpg(proc.pid, signal.SIGTERM)
except OSError as e:
warnings.warn(e)
Python 3.2 之后 subprocess.Popen 新增了一个选项 start_new_session ,Popen(args, start_new_session=True) 即等效于 preexec_fn=os.setsid 。这种利用进程组来清理子进程的后代的方法,比简单地中止子进程本身更加“干净”。基于 Python 实现的 Procfile 进程管理工具 Honcho 也采用了这个方法。当然,因为不能保证被运行进程的子进程一定不会调用 setsid , 所以这个方法不能算“通用”,只能算“相对可用”。如果真的要百分之百通用,那么像 systemd 那样使用 cgroups 来追溯进程创建过程也许是唯一的办法。也难怪说 systemd是第一个能正确地关闭服务的 init 工具。
0×04. 参数拼接引发的命令注入风险
1)命令注入场景1:shell=True时,命令参数可控
案例:
s=subprocess.Popen('ls;id', shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
处理方案:
1)shell=True,使用 pipes.quote() 对参数进行过滤
如果是python3,推荐使用shlex.quote()
2)shell=False,参数使用list,此时能防止部分命令注入(其他风险见2))
缺点是写参数时会稍微麻烦点
2)命令注入场景2:shell=False时,参数选项拼接引发的命令注入风险
使用subprocess执行命令的时候,如果使用外部传入参数,且参数可控,要注意,参数不要变成命令中的 参数选项
像subprocess.call([]) 执行的是list 拼接起来的命令,如果可控参数 在拼接之后使得参数变成了参数选项,则存在命令注入风险
案例:
import subprocess
query = '--open-files-in-paper=id;'
r = subprocess.call(['git', 'grep', '-i', '--line-number', query, 'master'], cwd='/root/op-scripts')
默认情况下,python的subprocess接受的是一个列表。我们可以将用户输入的query放在列表的一项,这样也就避免了开发者手工转义query的工作,也能从根本上防御命令注入漏洞。但可惜的是,python帮开发者做的操作,也仅仅相当于是PHP中的escapeshellarg。我们可以试试令query等于–open-files-in-pager=id;:
php 中方式命令注入的两个函数
escapeshellcmd
一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、学习软件
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
三、入门学习视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!