lightless blog - 安全开发
type: channel
link: https://www.lightless.me/category/Security-Develop/
description: 安全开发
items: SignMe 签名插件
items: BurpAuthTest 越权检测插件
items: How To Designing A Faster Than Faster GitHub Monitoring System
items: Get Python Code From PYINSTALLER
items: CVE-2017-17522 Python webbrowser.py 命令执行漏洞分析
items: Python协程之从放弃到死亡再到重生
items: Python协程从零开始到放弃
items: Ansible使用总结
items: PowerShell脚本执行策略
items: 解决MySQLdb的数据库连接超时问题

SignMe 签名插件
type: item
date: 2023-07-10T08:25:00+00:00
description: 开源地址：https://github.com/lightless233/sign-me-2 0x00 上一篇文章主要分享了越权测试插件的思路，经过一段时间的使用，确实节省了不少时间，也遇到了新的问题。 很多网站现在发起请求时，会附加一个 sign 签名值，以保证参数值在传输的过程中不被篡改。这同样也给我们修改参数进行测试带来了一些麻烦。所以如果能在 burp 中修改完数据包后自动对参数进行签名，可以节省不少算签的时间（当然如果连签名算法都不知道，也没办法手动算签）。 0x01 基于以上的情况，简单糊了一个新的 burp 插件 SignMe，专门用于计算各类签名，并将计算好的签名替换回原始数据包。总体来说这个插件并没有什么特别的东西，毕竟只是一个简单的参数替换。 不过在这里动了一点小心思，并没有把某个特定的签名算法写死到插件里，二是额外塞了一个 JS 引擎进去，可以让用户通过编写 JS 代码自行实现签名算法。 由于 JS 中还需要和 Java 互相操作，最终选用了 graalvm 的方案。之所以使用了 JS 代码来编写签名算法，是因为在日常的测试过程中，大部分网站的签名算法都是由 JS 实现的，很多情况下我们甚至可以直接将网站的 JS 复制过来，稍微改一改就能复用了。如果选择其他语言，还需要经历一个翻译算法的过程，如果做过类似的工作就会发现翻译语言这个过程中会遇到很多的坑：比如不同语言加密库的实现不同、字符编码问题等等。所以干脆直接选择 JS 来作为签名算法的计算语言，既省时又省力。 在 sign-me 中，已经提前封装好了大量常用的方法，包括：打印日志、参数排序、计算常见哈希、获取时间戳等等，希望用户只是像拼积木一样就可以拼出一个签名算法，将更多的时间放到对目标的测试过程上，而不是将精力浪费在计算签名这种事情上。 当计算完成后，通过返回一个指定的数组，表示需要修改哪些参数，如下图所示： 在 burp 中，可以通过修改 sign-me 和 auth-test 的插件顺序，就可以实现自动对重放包进行签名的功能，从而进行半自动的越权测试了。 sign-me 的代码暂时也没有开源的计划，主要是因为写的太烂了（这是我写的第一个burp插件，所以写的很烂），计划在近期对代码进行一次重构，之后会放出来供大家一起使用。

BurpAuthTest 越权检测插件
type: item
date: 2023-06-13T06:35:29+00:00
description: 0x00 最近一段时间，做了一些自动化/半自动化的测试小工具，主要还是想从重复的工作中解放出来，把时间投入到更有意义的事情中。 开发语言也从之前的 Python 逐步切换到了 Rust，虽然 Python 是一门很棒的语言，可以让我快速的实现各种想法的原型，但是 Python 在工程化的时候，总会出现一些不尽人意的地方。恰巧之前也学过一些 Rust，便逐步将主力开发语言切换到了 Rust。关于这部分的内容，后续可以再单独讲讲。 在最近的一些测试工作中，很大一部分目标都是各类 CMS、WMS 系统，经过前期的一些人工测试，发现这类业务逻辑相对简单且以 CURD 为主的系统，很难出现一些高危的 Web 漏洞（特指常见高危Web漏洞，例如：命令执行、SSRF、SQL注入等）。主要的风险还是以水平越权和垂直越权为主。 很多人可能根据经验会有疑问，为什么很少甚至不会出现 SQL注入漏洞？ 在大型甲方企业的开发流程中，开发这类系统时，一般都不是从零开始写第一行代码的，而是使用该团队积累下来的各种二方包。对于常见的业务操作，例如 CURD、OSS上传等等，都有较为成熟的框架和解决方案。一旦出现过一次漏洞，通过对二方包的修复，很容易就实现了对所有其他系统的修复，以及在新系统中的预防。 顺便一提，对于其他类型的漏洞，例如 SSRF 和 命令注入等，在常规的 CURD 类的 CMS、WMS 系统中，根本不会出现相关的业务场景，所以更不会出现相关的漏洞了。 越权检测一直是扫描器领域长盛不衰的话题，目前在越权检测这个方向下面，还没有看到非常成熟的方案，也没有通用的银弹。 虽然没有银弹，但是一定有人已经造过轮子了，先去搜索了一些 Burp 上比较知名的越权测试插件，使用了一圈下来感觉都很不爽。有些界面丑陋且已经年久失修无法使用，有些逻辑复杂操作反人类，终于还是决定写一个自己用起来顺手的小插件。 0x01 在正式开始造轮子之前，我们先回顾一下手动测试越权的流程。 假设我们使用两个相同权限的账号 A 和 B，分别使用不同的浏览器登录。Chrome 登录账号 A，Edge 登录账号 B，给 Chrome 浏览器添加代理，把流量转发到 Burp。 在 Burp 中发现一个接口：http://api.example.com/user/get?id=10001 ，分析后发现是查看当前用户信息的接口。此时如果想测试越权，我们需要找到账号 B 中，此接口对应的参数，并将其复制过来到 Burp 中。假设 B 账号得到的参数为 id=51234 ，在 Burp 中修改参数后发送，并人工检查 Response 中是否含有 B 账号的数据。 在这个过程中需要来回切换多个窗口，复制粘贴不同账号的数据，最后还需要人工对比 Response 数据的内容，需要操作很多个步骤，并且非常的麻烦。另外这只是进行了两个同权账号的测试，如果系统的角色非常复杂，有管理员、普通会员、子账号的情况下，需要手工处理的工作就更多了。 测着测着，很自然的就出现了第二个想法：如果不替换参数，直接替换身份凭证是否可行？ 答案自然是可行的，我们不用再去复制 B 账号每个接口的参数，只需要登录一次 B 账号并获取到 B 账号的身份凭证信息（Cookie、X-ACCESS-TOKEN等），在每次测试 A 账号的时候，顺手把 A 账号的身份凭证修改为 B 账号，就相当于以 B 账号的身份使用 A 账号的参数发起请求，直接查看返回是否一致就可以了。我们不需要来回的切换窗口，完全在 Burp 一个窗口中操作就可以了，当 Response 过大不方便肉眼看的时候，甚至可以使用的 Burp 的 diff 功能检查两个包是否有相同数据。 当然这个方案也有弊端： 如果参数中、头中含有 CSRF Token 或类似的标记，此方法无法通过服务端的相关校验； 依然无法避免角色过多带来的重复操作问题，同一个接口，每一个角色都要替换数据发一次包； 在少部分业务场景下，如果参数本身是无规则字符串，例如 UUID、MD5 等情况且根本没有接口可以获取到其他账号的资源 ID，按照业务设计规则，可能不涉及越权问题； 这一点实际上是有分歧的，理论上来说，我使用其他账号的资源 ID 获取到了资源内容，本身就属于越权，只能最终与业务沟通并根据实际情况再做是否修复的决定。 到目前为止，我们有了一些进展，简化了替换参数的工作量。但是我仍然希望将这个过程自动化，在我每次测试其他漏洞的时候，Burp 自动化帮我替换身份凭证，并比对返回值是否相似。 0x02 我们首先要解决的问题，就是账号的替换的问题，对同一个接口的请求，要以何种策略进行重放，是这个插件的核心逻辑。 回顾一下正常的测试流程，为了测试水平越权和垂直越权，我们至少需要准备3个账号，如果我们只想测试水…

How To Designing A Faster Than Faster GitHub Monitoring System
type: item
date: 2018-12-03T13:32:00+00:00
description: 0x00 为什么需要这样的系统 近些年来，特别是2018年，各种不同的Github监控工具层出不穷，究其原因，还是每家企业生怕安全意识不足的员工将内部代码、账号信息上传至Github中，导致一些严重的安全问题，很有可能我们费劲千辛万苦构筑的边界防线，就被一个开发人员轻松的打破了。 GEYE GITHUB 传送门 目前市面上有许多种Github监控工具以及系统，有命令行的，有带界面的，有直接爬Github搜索页面的，也有借用API来搜索的，经过一些调研，发现各有特色，但是对于“开箱即用”等要求上，可能略有些距离。 本人在上家公司时，也曾负责过Github监控系统的开发与运营，经过很长时间的摸索，也总结了一些经验以及思路，并实现出了本文的主角GEYE监控系统（Github Eye）。在之前公司，Github监控系统应该说是经历了多次迭代，从最早的单文件脚本，逐步升级成了一个可视化的精确Github信息捕获系统，也多次先于白帽子发现了Github上的敏感信息，以及很多没有被外部发现的敏感信息。最快可在敏感信息被上传15分钟内捕获发现。 0x01 简谈设计思路 1. 整体设计思路 整体思路其实非常简单，就像把大象放到冰箱里一样，只有三步： 从Github获取我们感兴趣的信息 人工/自动筛选、过滤 入库并通知相关人员/系统 但是在每一步中，我在实现的时候，均会遇到一些比较坑的问题，也选取了不同的方案做了一些实验，最终得到了一个比较符合我预期的最佳方案，下面来简单介绍一下。 2. 获取信息 监控的第一步当然是要获取信息，获取信息的方式有很多，目前开源的监控系统里，有直接使用爬虫爬取搜索页面的，也有调用API的，当然我这里推荐的做法是调用官方的API来获取原始数据，说实话我不是很理解一些项目中通过直接爬取搜索页面获取数据的方式，也没有想到这样做有什么优势，甚至印象中好像还有某个项目为了避开Github的爬虫来源IP检测，还用Nginx搭建了一个反向代理集群来解决。所以我认为直接爬取搜索页面不是一个十分优雅的做法。 那么我们就剩下一条路可以选了，那就是利用Github提供的APIv3来获取原始数据。因为目前APIv4还不支持搜索代码，如果未来支持了，GEYE也许会切到APIv4上。 我们先来看一下搜索代码的API文档：https://developer.github.com/v3/search/#search-code 怎么用这里就不说了，相信大家都会用，我们只说一说需要注意的问题。首先这个q参数就是我们想查询的关键字，比较好理解，但是我们除了要添加关键字之外，还要额外添加一些参数，那就是fork，这个参数是用来指定“是否搜索fork”仓库的，如果这个值为true，那么这个接口会返回来自fork的仓库的数据，如果某个很著名的仓库（例如fastjson）中命中了你的关键字，那么你可能会拿到无数的重复信息，这不是我们希望看到的；我们希望的是尽可能多的获取不同仓库的信息，而不是同样的内容返回一万遍。 另外一点，我们其实希望尽可能快的获取到“新鲜”的泄露信息，所以我们还要修改这个接口的默认排序方式。默认情况下，Github会为我们返回最匹配的结果，无论这个代码是什么时候被上传的。所以我们需要指定sort参数。 结合起来，我们最后的请求参数可能是这样的：/code/search?q=keyword+fork:false&sort=indexed 说完关键字之后，应该就可以开始写爬虫调用API了，但是这里还有一点，Github为了防止我们滥用API，会有频率限制。正常情况下，每个Personal Access Token的频率是每分钟可以请求30次API接口，如果调用过快，会触发滥用警告，就是会限制你的token 1分钟。如果是通过OAuth2授权的App级别的token，请求上限会高很多。 基于这样的限制，我们没必要去优化我们的爬虫让他爬的很快，因为同样时间下，调用的频率是被限制好的。我们只需要利用普通的多线程爬虫获取数据即可。 3. 筛选信息 当我们将数据取回后，就需要决定哪些数据需要被人工确认，或是直接确认，哪些数据需要被丢弃。当然一股脑的将所有代码全都交给人工来判断也不是不可以，只要你的精力足够。 我们当然希望系统能够自动帮我们过滤掉一些无用的数据才好，我们人工只关心最有可能是泄露的部分，那么这就要考验我们的筛选模块了。 一开始，我是以API接口返回的代码段作为“基础值”，来进行过滤的，但是后来发现，接口返回的代码段里，其实并不会完全包含你的关键字。举个例子，比如你搜索的是corp.lightless.me，但是返回的代码段中，可能更多的是包含了corp、lightless、me三个分开的单词的结果，这也为我们过滤信息造成了一定的困扰。 再举一个…

Get Python Code From PYINSTALLER
type: item
date: 2018-01-02T12:53:51+00:00
description: 0x00 pyinstaller 今天wilson师傅说要把他的fenghuang-scan打包成可执行文件，据说用了一个叫做pyinstaller的东西，很是好奇。以前只是听说过这种东西，于是我也搞了一个试试看，不过我主要是想看看能不能将打包好的源码还原出来。 我们先来看看这个东西是怎么工作的吧，文档中有对这一部分进行介绍。https://pyinstaller.readthedocs.io/en/stable/operating-mode.html PyInstaller reads a Python script written by you. It analyzes your code to discover every other module and library your script needs in order to execute. Then it collects copies of all those files – including the active Python interpreter! – and puts them with your script in a single folder, or optionally in a single executable file. 按照这个说法，pyinstaller会分析你的python源码，然后把需要用到的库以及当前的python interpreter复制一份到一个单独的文件夹中，也可以生成一个单独的可执行文件。看起来很厉害的样子。那么这单独一个文件是怎么工作的呢，文档里也有讲到：https://pyinstaller.readthedocs.io/en/stable/operating-mode.html#how-the-one-file-program-works 这里我简单讲一下，更详细的内容可以参考文档。打包成一个文件后，这个文件的核心其实是一个bootloader。执行的时候，会在临时文件夹下面创建一个_MEIXXXXX的文件夹，然后bootloader会把python脚本中用到的一些文件解压扔进去，大部分是so文件。接下来就是执行python脚本了，当程序结束之后会删除这个文件夹。 打包后的文件中不包含任何的python源代码，而是将pyc打包进去了，当然打包进去的pyc进行了一些特殊的处理，后面会讲到。 0x01 打包一个文件试试看 我这里写了个看自己IP的小程序，代码很简单： #!/usr/bin/env python # coding: utf-8 # file: myip.py from __future__ import print_function import time import requests from api import API def main(): url = API.url r = requests.get(url) print(r.content) if __name__ == "__main__": # time.sleep(120) main() #!/usr/bin/env python # coding: utf-8 # file: api.py class API(object): def __init__(self): super(API).__init__() url = "http://myip.ipip.net" 这里为了测试打包，故意写的稍微复杂了一些，拆成了两个文件。下面就试试合并成一个可执行文件： # lightless @ VM-UBUNTU in ~/program/pyinstaller [22:18:27] $ pyinstaller -F myip.py 15 INFO: PyInstaller: 3.2.1 15 INFO: Python: 2.7.12 16 INFO: Platform: Linux-4.4.0-77-generic-x86_64-with-Ubuntu-16.04-xenial 16 INFO: wrote /home/lightless/program/pyinstaller/myip.spec 19 INFO: UPX is not available. 20 INFO: Extending PYTHONPATH with paths ['/home/lightless/program/pyinstaller', '/home/lightless/program/pyinstaller'] 20 INFO: checking Analysis 24 INFO: Building because /home/lightless/prog…

CVE-2017-17522 Python webbrowser.py 命令执行漏洞分析
type: item
date: 2017-12-19T08:22:01+00:00
description: 今日惊闻Python出现了CVE，问题出在Lib/webbrowser.py模块，看描述还十分严重。 Python容易产生远程命令执行漏洞。攻击者可以利用此问题，在运行受影响应用程序的用户上下文中执行任意命令。失败的尝试可能导致拒绝服务条件。 根据CVE的描述，问题出在环境变量上。 Lib/webbrowser.py in Python through 3.6.3 does not validate strings before launching the program specified by the BROWSER environment variable, which might allow remote attackers to conduct argument-injection attacks via a crafted URL. 既然是BROWSER这个环境变量有问题，那我们就看下这个文件中哪里引用到了BROWSER这个环境变量，我这里测试的环境是Python3.6.3。在620行左右，发现了相关的代码。 可以看到BROWSER环境变量经过一系列的处理，会传入register(name, klass, instance, update_tryorder)函数。我们继续跟入GenericBrowser(cmdline)。 跟入之后，发现cmdline被直接传入了subprocess.Popen()函数，问题应当就出在这里，经过简单的单步调试后，可以发现这个cmdline其实就是BROWSER和url拼接的列表，大概就是这个样子：cmdline: [BROWSER, url]，既然如此，PoC就非常容易构造了。 import webbrowser if __name__ == '__main__': webbrowser.open(".") 在运行之前先设定环境变量export BROWSER=ls，运行后即可发现命令执行了。 分析到这里，漏洞基本上已经分析完了，我不禁多了一些思考。首先，如果我们想利用这个漏洞，那么需要控制环境变量以及一个url参数，然后还要这个服务开放在公网上才能被远程利用，从这一点上来看，这个漏洞怕是有些标题党了。 回过头来再从开发者的角度去思考一下，为啥要这样写代码，我觉得初衷可能是方便用户使用自定义的浏览器来打开URL（对这个webbrowser功能不熟悉的同学可以先看看官方文档的使用方法），所以势必要让用户来提供浏览器的名称，而开发者又无法预知用户会使用什么奇葩的浏览器，所以并没有办法进行有效的过滤。 最后一处用户提供的URL部分，开发者也并没有判断这个URL是否合法，或者说最起码看起来是个正常URL的样子，传入一个.也是可以正常运行的，至少说来这个地方应该是不严谨的吧，挺好奇官方会如何修复这个问题的，也没准最后修都不修了呢23333

Python协程之从放弃到死亡再到重生
type: item
date: 2017-10-12T15:15:00+00:00
description: 前面一篇关于Python协程的文章中，提到了python中协程的一些历史以及一些简单的用法，这些用法放在实际开发中可能不太够，我在后面的编写中就踩到了不少的坑，这里就回应上篇文章中末尾说的，再写一篇文章来梳理一下这些问题。时隔很久，终于还是磨磨唧唧的把第二篇写出来了。本来还准备有第三篇重生篇的，但是以防拖稿，合并到一起来写吧。 0x00 又是生成器？ 我们已经知道了，生成器可以作为协程来使用，但是很多细节问题尚未明确，我们先从最简单的一个协程开始。（后面会出现"生成器"和"协程"两个词混用的问题，可以都理解为"作为协程使用的生成器"） def easy_coroutine(): print("Start!") x = yield print("End! x: {}".format(x)) 这就是最简单的协程了，如果想使用这个协程，看起来还是有点麻烦的： # lightless @ LL-DESKTOP in C:\Users\lightless\Desktop [20:40:27] $ python -i .\ez_coroutine.py >>> c = easy_coroutine() >>> c <generator object easy_coroutine at 0x0000016AD411E570> >>> next(c) Start! >>> c.send(666) End! x: 666 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> 创建协程的方式与创建生成器一样，通过调用函数的方法获取到一个生成器对象。紧接着调用next()方法来启动生成器，这一步也称为prime，有些文章会把这个东西翻译成"预激"，即让协程开始执行到第一个yield表达式的位置。（为了方便下文都称为预激了） 下面又调用了c.send(666)，调用者方法后，x = yield表达式就会得到666这个值，然后协程恢复运行，直到协程结束或者遇到另外的yield表达式为止。当然这里最后会抛出一个StopIteration异常，因为代码执行完毕了，这就是生成器的标准行为，不用在意。 在这个执行过程中，会发现我们的easy_coroutine()经历的多个状态，并非总是一直处于RUNNING的状态。事实上，协程的状态有四种：GEN_CREATED，GEN_RUNNING，GEN_SUSPENDED，GEN_CLOSED。这四种状态分别是等待开始执行，正在执行，停在了yield表达式位置，执行结束。如果你想获取某个协程的当前状态，可以通过inspect.getgeneratorstate函数获取。 额外说明一点，send()方法的参数会传递给协程，作为yield表达式的值，所以只有当协程处于GEN_SUSPENDED的状态时，才可以调用send()方法传递值。如果协程还没有prime，那么可以通过调用send(None)来预激协程。啥，你问为什么不能通过给尚未prime的协程调用send(233)来预激协程，如果你试过的话就会收到一个TypeError。 每次预激协程的时候都需要调用一遍next()函数，很麻烦，而且很容易忘掉，一旦忘记预激协程，后果也是十分严重的，因为没有被预激的协程没有任何作用。有没有简单的方法让Python自动预激协程呢？有的，函数装饰器就可以做到这一点，于是我们可以自己封装一套来让其自动预激！ def prime_coroutine(func): @functools.wraps(func) def prime(*args, **kwargs): t = func(*args, **kwargs) next(t) return t return prime 代码十分简单，如果看不懂的话，建议先去学习一下Python的装饰器部分。可能各位很容易想到之前提到过的一种装饰器，在Python3.4以后，标准库中新增了asyncio.coroutine装饰器，用于修饰协程函数，但是非常遗憾，这个装饰器并不会预激协程，而通过yield from语法调用一个协程时，会自动预激。所以可以通过asyncio.coroutine和yield from配合使用，实际上我们也是这样使用的。 这里有人会问，如果在写成中抛出了异常怎么办，是不是和普通函数的异常一样？当协程中出现异常时，而协程本身又没有捕获处理，那么这个异常会"冒泡"至协程的调用方，即next()或send()函数的调用方。如果协程已经抛出了异常，再尝试去调用它，会得到一个StopIteration异常。于是有人发现了可以通过让协程抛出异常的方式终止一个协程的运…

Python协程从零开始到放弃
type: item
date: 2017-07-20T15:20:00+00:00
description: 0x00 前言 很久以前就听说Python的async/await很厉害，但是直到现在都没有用过，一直都在用多线程模型来解决各种问题。最近看到隔壁的Go又很火，所以决定花时间研究下Python协程相关的内容，终于在翻阅了一裤衩的资料之后有了一些理解。 0x01 起：一切从生成器开始 以往在Python开发中，如果需要进行并发编程，通常是使用多线程/多进程模型实现的。由于GIL的存在，多线程对于计算密集型的任务并不十分友好，而对于IO密集型任务，可以在等待IO的时候进行线程调度，让出GIL，实现『假并发』。 当然对于IO密集型的任务另外一种选择就是协程，协程其实是运行在单个线程中的，避免了多线程模型中的线程上下文切换，减少了很大的开销。为了理解协程、async/await、asyncio，我们要从最古老的生成器开始。 回顾Python的历史，生成器这个概念第一次被提出的时候是在PEP 255中被提出的，当时的Python版本为Python2.2。我们都知道range()函数，现在考虑一下我们来编写一个自己的range()函数，最直接最容易想到的方法也许是这样： def my_range(max_number): sequence = [] index = 0 while index < max_number: sequence.append(index) index += 1 return sequence 当你想创建一个很小的序列的时候，例如创建从0到100这样的列表，似乎没什么问题。但是如果想创建一个从0到999999999这么大的列表的话，就必须要创建一个完整的，长度是999999999的列表，这个行为非常占用内存。于是就有了生成器，用生成器来改写这个函数的话，会是下面这个样子： def lazy_range(max_number): index = 0 while index < max_number: yield index index += 1 当函数执行遇到yield的时候，会暂停执行。这样只需在内存中维护可以存储一个整数的内存空间就可以了。如果对生成器/迭代器不理解的话，可以参考Stack Overflow上的这篇高票回答：传送门 0x02 承：协程诞生 到这里可能还和协程没什么关系，但是实际上这已经是Python协程的雏形了，我们来看看维基上对于协程的定义： Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations. 从某些角度来理解，协程其实就是一个可以暂停执行的函数，并且可以恢复继续执行。那么yield已经可以暂停执行了，如果在暂停后有办法把一些value发回到暂停执行的函数中，那么Python就有了『协程』。于是在PEP 342中，添加了“把东西发回已经暂停的生成器中”的方法，这个方法就是send()，并且在Python2.5中得到了实现。利用这个特性我们继续改写range()函数： def smart_range(max_number): index = 0 while index < max_number: jump = yield index if jump is None: jump = 1 index += jump 就这样，整个生成器的部分似乎已经进入了stable的状态，但是在Python3.3中，这个情况发生了改变。在PEP 380中，为Python3.3添加了yield from，这个东西可以让你从迭代器中返回任何值（这里用的是迭代器，因为生成器也是一种迭代器），也可以让你重构生成器，我们来看这个例子： def lazy_range(max_number): index = 0 def gratuitous_refactor(): while index < max_number: yield index index += 1 yield from gratuitous_refactor() 这个特性也可以让生成器进行串联，使数据在多个生成器中进行传递。历史发展到这里，协程的出现似乎已经就差一步了，或者这里说是异步编程更恰当。在Python3.4中加入了asyncio库，使Python获得了事件循环的特性（关于事件循环的内容这里不再赘述）。asyncio + 生成器已经达到了异步编程的条件，在Python3.4中，我们就可以这样实现一个异步的模型： impo…

Ansible使用总结
type: item
date: 2016-11-13T13:29:18+00:00
description: 什么是Ansible Ansible是一种集成IT系统的配置管理，应用部署，执行特定任务的开源平台。主要基于Paramiko和PyYAML两个模块。 安装Ansible 这里是在CentOS7上安装使用的，首先要开启epel源： # yum install -y epel-release 配置公私钥证书登录 # ssh-kegen -t rsa -C "lightless@foxmail.com" # ssh-copy-id -i /root/.ssh/id_rsa.pub root@1.1.1.1 # ssh-copy-id -i /root/.ssh/id_rsa.pub root@2.2.2.2 定义主机组 默认的配置文件在/etc/ansible/hosts 有点像ini的格式： # This is the default ansible 'hosts' file. # # It should live in /etc/ansible/hosts # # - Comments begin with the '#' character # - Blank lines are ignored # - Groups of hosts are delimited by [header] elements # - You can enter hostnames or ip addresses # - A hostname/ip can be a member of multiple groups # Ex 1: Ungrouped hosts, specify before any group headers. ## green.example.com ## blue.example.com ## 192.168.100.1 ## 192.168.100.10 # Ex 2: A collection of hosts belonging to the 'webservers' group ## [webservers] ## alpha.example.org ## beta.example.org ## 192.168.1.100 ## 192.168.1.110 # If you have multiple hosts following a pattern you can specify # them like this: ## www[001:006].example.com # Ex 3: A collection of database servers in the 'dbservers' group ## [dbservers] ## ## db01.intranet.mydomain.net ## db02.intranet.mydomain.net ## 10.25.1.56 ## 10.25.1.57 # Here's another example of host ranges, this time there are no # leading 0s: ## db-[99:101]-node.example.com 默认的配置文件中提供了一些例子，这里说一些特殊的 别名 jumpserver ansible_ssh_port=22 ansible_ssh_host=1.1.1.1 这个是为主机定义一个别名，名称叫jumpserver，后面定义了host和port，除此之外还有一些保留的变量名： ansible_ssh_host ansible_ssh_port ansible_ssh_user ansible_ssh_pass ansible_connection, 连接类型，可以是local、ssh、或者是paramiko ansible_ssh_privite_key_file ansible_*_interpreter， 指定采用非python的其他脚本语言，例如ruby、perl等 主机变量 主机可以指定变量，以便提供给playbooks使用 host1 http_port=80 maxRequestsPerChild=100 host2 http_port=8080 maxRequestsPerChild=200 定义组变量 组变量就是变量的作用域会覆盖组所有成员 [atlanta] host1 host2 [atlanta:vars] ntp_server=ntp.atlanta.example.com proxy=proxy.atlanta.example.com 此外还有嵌套组，但是只在ansible-playbook中生效。 分离主机与数据 /etc/ansible/group_vars…

PowerShell脚本执行策略
type: item
date: 2016-11-06T13:55:35+00:00
description: Powershell默认是不允许执行PS1脚本的，这就需要我们自己使用Set-ExecutionPolicy来修改默认策略。 使用Get-ExecutionPolicy可以获取当前的策略: PS C:\Users\lightless> Get-ExecutionPolicy Unrestricted 如果想看更详细的，针对每个scope设置的策略，可以使用如下命令： PS C:\Users\lightless> Get-ExecutionPolicy -List | Format-Table -AutoSize Scope ExecutionPolicy ----- --------------- MachinePolicy Undefined UserPolicy Undefined Process Undefined CurrentUser Unrestricted LocalMachine Undefined 一共有6种策略，分别是：Undefined, Bypass, Unrestricted, Remotesigned, Allsigned, Restricted. 默认情况下是Restricted策略，更详细的可以参考：https://technet.microsoft.com/library/hh847748.aspx 这里简单的说下六种策略的区别： Restricted. 不读取任何配置文件、不运行任何脚本，这个是默认策略。 AllSigned. 所有的脚本和配置文件必须有受信任的的发布者的签名，就算是自己写的脚本也同样如此，否则无法执行。 RemoteSigned. 和上面的类似，但是针对的是从网上下载下来的脚本，这些脚本同样也需要可信的签名。 Unrestricted. 可以运行脚本或者读取配置文件，如果执行的是从网上下载的脚本，那么会有一个申请权限的提示。 Bypass. 不阻止任何脚本或配置文件，也不会显示警告或者提示。 Undefined. 把当前scope的所有策略全部都删除，但是不会删除Group Policy scope中的策略。如果你想删除某个设置好的策略，用这个就行了。 上面说到了scope，这个可以理解为作用域，针对不同的作用域设置不同的策略。 主要有三种scope： process 设置的策略只影响当前的powershell进程。 CurrentUser: 设置的策略只影响当前用户。 LocalMachine: 设置的策略影响这台计算机上的所有用户。 那么设置方法就是使用Set-ExecutionPolicy命令，具体格式如下： Set-ExecutionPolicy [-ExecutionPolicy] <ExecutionPolicy> [[-Scope] <ExecutionPolicyScope> ] [-Force] [-Confirm] [-WhatIf] [ <CommonParameters>] -Force的作用是禁止所有的提示输出，当你更改执行策略的时候，默认情况下是会显示警告的，这个选项可以让他不显示警告。 -Confirm会在你执行这个cmdlet之前再确认一次，以免打错命令。 -WhatIf会显示执行完这条cmdlet之后会发生什么事情，但是并不执行当前的cmdlet。 这个策略其实也是可以绕过的，详细的可以参考这篇文章： https://blog.netspi.com/15-ways-to-bypass-the-powershell-execution-policy/ 参考资料： https://technet.microsoft.com/en-us/library/hh849812.aspx https://technet.microsoft.com/zh-cn/library/ee176961.aspx https://technet.microsoft.com/library/hh847748.aspx

解决MySQLdb的数据库连接超时问题
type: item
date: 2016-10-28T06:11:00+00:00
description: 今天干活的时候把代码里的数据库连接全部改成了长连接，然后就出现了奇怪的问题。 (2013, 'Lost connection to MySQL server during query') 应该是数据库连接的空闲时间过长，导致了连接丢失。没办法只能自己封装一遍MySQLdb，在每次执行前ping一下，并且重新连接。 #!/usr/bin/env python2 # coding: utf-8 #file: ConnectDB.py import socket import MySQLdb __author__ = "lightless" __email__ = "root@lightless.me" class DB(object): """ 对MySQLdb重新封装一遍 query方法检测超时问题 Fuck MySQLdb!!! """ def __init__(self, host, port, database, username, password, charset="utf8", autocommit=True): super(DB, self).__init__() self.db = None self.cursor = None self.result = None self.execute_result = None self.host = host self.database = database self.port = port self.username = username self.password = password self.charset = charset self.autocommit = autocommit def __del__(self): self.db.close() del self.db del self.cursor def __connect(self): try: self.db = MySQLdb.connect( host=self.host, port=int(self.port), user=self.username, passwd=self.password, db=self.database, charset=self.charset, autocommit=self.autocommit ) self.cursor = self.db.cursor() except MySQLdb.MySQLError as e: print "Error Message:" print e def connect(self): self.__connect() def __query(self, sql, commit=False): self.db.ping(True) self.execute_result = self.cursor.execute(sql) if commit: self.db.commit() self.result = self.cursor.fetchall() return self.result def query(self, sql, commit=False): try: if self.cursor is None: self.__connect() return self.__query(sql, commit) except MySQLdb.OperationalError: # Mysql Connection Lost self.__connect() try: return self.__query(sql, commit) except MySQLdb.MySQLError as e: print "Error Message:" print e 这样问题就解决了。