[翻译] 关于近期爆发的加密勒索软件 WannaCry / WannaDecrypt0r 的简报

本文翻译自《WannaCry|WannaDecrypt0r NSA-Cyberweapon-Powered Ransomware Worm》 revision 58,是对此次勒索软件事件的一个阶段性总结,包含了很多实用信息,因此翻译过来。该 gist 下还在进行活着跃的编辑和讨论。如果你发现翻译有错误,请以中文在本文下评论指出;如果对内容有任何建议或意见,请直接使用英文在原文下与原作者和评论者沟通。

阅读剩余部分 -

年末服务器维护 [完成]

预计从 2016 年 12 月 30 日晚间 20 时(GMT+8)至 2017 年 1 月 2 日晚间 20 时进行服务器维护,届时可能会出现域名无法解析、访问时出现故障页面等情况。服务器维护可能会提前结束,届时将更新本篇文章内容。

2017 年 1 月 1 日 20 时更新

服务器维护已经结束,并完成了所有既定目标,包括:

  • 从阿里云主机上迁出;
  • 更换主域名证书提供商(由 RapidSSL 更换为 Let's Encrypt);
  • 清理不再使用的子域名;
  • 提升安全性(SSL test 达到 A+ 级别);
  • 解决偶尔出现的邮件发送问题;

如果你在访问本站(或任何子站点)时遇到任何问题,请发送邮件到 joker#mynook.info (将 # 替换为 @)。

快速配置 HTTPS

已经是快到 2017 年,很多网站居然还不支持 HTTPS 访问。要知道 Chrome 可是要开始将 HTTP 站点标记为“不安全”了。那么这一篇就说一下如何最快速地将一个站点配置为 HTTPS 访问。

注意:一旦将你的网站配置为 HTTPS 访问,引用的所有脚本、图片、样式表等都必须是有效的 HTTPS 资源,否则页面将被标记为不安全。

首先,我们使用的组合是这样的:DNSPod + Let's Encrypt 服务 + nginx。也就是说在本文的例子中,域名 NS 服务使用的是 DNSPod,证书是由 Let's Encrypt 颁发的,服务器使用 nginx。

关于 nginx 的版本,建议是卸载服务器系统源中的版本,从 nginx 官方源安装,具体操作步骤参考 nginx 的官方文档。需要注意的是,nginx 在 Ubuntu 14.04 上不支持 HTTP2,具体原因和解决方法参考这里

第一件事情:在服务器上配置好你的域名,最简单的 nginx 配置如下。

server {
    server_name example.com;
    listen 80;
    rewrite ^(.*) https://example.com$1 permanent;
}

server {
    server_name example.com;
    listen 443 http2;
    ssl on;
    include /etc/nginx/ssl_params;
    ssl_certificate /home/user/example/keys/ssl/example.com.fullchain.cer;
    ssl_certificate_key /home/user/example/keys/ssl/example.com.key;

    include /etc/nginx/hsts_headers;

    location / {
        return 204;
    }
}

以上配置文件示例中,我们让 nginx 在 80 端口监听 HTTP,在 443 端口监听 HTTPS(并且使用 HTTP2),并将 HTTP 请求转向 HTTPS 地址。最终使用的证书和密钥存放在 /home/user/example/keys/ssl/ 目录中,这个路径你可以任意指定,只要 nginx 有权限读取对应文件就行。

此外,配置中还有两个 include 的文件 /etc/nginx/ssl_params/etc/nginx/hsts_headers。后者只是添加了一些 HSTS 头,内容如下。

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

关于 HSTS 头有什么作用,以及为什么要添加这些响应头,请参考维基百科的 HSTS 页面

/etc/nginx/ssl_params 这个文件是一些 SSL 相关的配置,一个简单的例子如下。

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_dhparam /etc/nginx/dhparams.pem; # See https://weakdh.org/sysadmin.html for more details
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_prefer_server_ciphers   on;
ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";

其中 /etc/nginx/dhparams.pem 文件的生成以及 SSL ciphers 的选择请参考 Guide to Deploying Diffie-Hellman for TLS

编辑好配置文件后,可以按一般习惯存放在 /etc/nginx/sites-available/example.com,然后软链接到 /etc/nginx/sites-enabled/example.com。暂时先不要重启 nginx 服务,因为我们还没有申请证书,重启是肯定会失败的。

第二件事情就是去 DNSPod 将你的域名解析配置好,并生成 API token。域名解析应该不用多说,API token 是在通过 DNSPod 的 API 来操作 NS 记录时授权使用的,可以认为就是应用程序密码。关于如何新建一个 API token,请参考 DNSPod 的官方文档。这里个人建议是一台服务器上只需要申请一个 API token,名称要描述清楚 token 的用途和使用的机器,方便日后查询和管理。token 生成后只显示一次,这时候需要复制下来,供下一步使用。

第三步就是去 Let's Encrypt 申请证书了,我们使用的是 acme.sh 工具。关于这个工具的优点有很多,我个人选择它的原因大概是:

  • 安装、使用、卸载都非常简单
  • 对于一个域名只需要手动操作一次,之后续期和重启服务器都是自动的

自动续期是很重要的一点,因为 Let's Encrypt 颁发的都是 90 天有效的短期证书。

从零开始使用 acme.sh 的步骤如下。

# 首先安装这个工具
curl https://get.acme.sh | sh

# 安装路径在 ~/.acme.sh
# 安装好之后看一看命令是否可用,如果有问题,请手动修改你的 shell 配置文件

which acme.sh
# acme.sh: aliased to /home/user/.acme.sh/acme.sh

# 创建我们最终存放证书的路径
mkdir -p /home/user/example/keys/ssl

# 设置你的 DNSPod API token
# 注意:多数 shell 会记录你的操作历史,安全起见,操作完毕后请手动删除 ~/.zsh_history 或 ~/.bash_history 中对应的内容
export DP_Id="1234"
export DP_Key="sADDsdasdgdsf"

# 申请证书
# acme.sh 会自动调用 DNSPod API 去设置证书申请期间需要使用的 NS 记录,并会等待 120 秒让记录生效
acme.sh --issue --dns dns_dp -d example.com

申请完毕之后,acme.sh 会打印出所有相关文件的绝对路径。但是不要急着去使用这些文件,我们还要将它们安装到指定的位置。

说点多余的,其实这一步不是必须的,你完全可以直接使用这些文件。但是这些文件都存放在 ~/.acme.sh 中,之后 acme.sh 升级时,内部目录结构可能会改变,造成你的配置文件失效;并且通过额外的安装步骤,acme.sh 可以在续期后帮你自动重启 nginx 服务。后一点是很重要的,因为 nginx 只是在启动时读取证书和密钥,在续期后如果不重启,它还是使用旧的证书,你可能直到旧证书失效才会发现。

安装证书的步骤很简单。

acme.sh --installcert -d example.com --keypath /home/user/example/keys/ssl/example.key --fullchainpath /home/user/example/keys/ssl/example.com.fullchain.cer --reloadcmd "sudo /usr/sbin/service nginx reload"

只是将证书和密钥安装到指定的路径中,并且 reload nginx 配置。

需要注意的是,如果你的 sudo 命令需要密码,这里可能会有麻烦。但是也不要就因此直接用 root 用户来运行 acme.sh。你可以参考这篇文章,将当前用户配置为不需要输入 sudo 密码,或者配置为运行指定命令时不需要密码。

安装完证书后就可以访问你的网站了,按照前面的 nginx 配置,网站会返回 204 空响应,你可以通过 Chrome 开发工具的 Security 面板来检查网站的 HTTPS 证书。建议同时通过 SSL Server Test 来检查一下,根据检测结果来修正 SSL 相关设置,尽量达到 A 级或 A+。

最后,如果你想确认 acme.sh 会不会帮你网站的证书做自动续期,运行 crontab -l 来查看当前用户的定时任务,你应当会看到 acme.sh 自动添加的条目。这个任务会每天运行一次,如果一个域名的证书已经颁发超过 60 天,acme.sh 就会帮你自动续期。

本文中使用的例子是 DNSPod,但 acme.sh 还支持很多其他的 NS 服务提供商,具体请参考官方文档。

附一篇 nginx 相关配置的文章:本博客 Nginx 配置之安全篇 | JerryQu 的小站

PyQt5 使用 QSettings

虽然写的是 PyQt5 中遇到的问题,PyQt4 中也可能会遇到类似问题。

中秋假期在家继续改进之前写的小工具,第一件事把设置数据的存取从直接读写 yaml 文件改成了使用 QSettings。什么是 QSettings?摘一段 Qt 文档的翻译。

QSettings 类提供平台无关的持久化应用程序设置。

用户通常期待应用程序在不同会话中保留其设置(窗口大小和位置、设置项等等)。这些信息在 Windows 上经常存储在注册表中,在 Mac OS X 和 iOS 上则保存在 plist 文件中。在 Unix 系统上,由于缺少标准,许多应用程序(包括 KDE 程序)使用 INI 文本文件(来存储设置)。

QSettings 是围绕这些技术提供的一个抽象层,让你使用一种可移植的方式来存储和恢复应用程序设置。它同时支持自定义存储格式。

阅读剩余部分 -

Qt 中的窗口大小

本文中所说的窗口都是指 decorated top-level widget,对于简单程序来说通常就是 QMainWindow 的子类对象。

之前玩 Qt 的时候一直以为 QMainWindow 的大小就是 .geometry() 报告的大小(也就是 .width().height()),结果今天发现其实不对的。

起因是我想将一个窗口放在整个桌面的右下角。那么这里有几个问题。

首先 Windows 平台默认下面是任务栏,那么我们肯定不希望窗口的内容被任务栏挡住(或者挡住任务栏),所以这里需要使用 QDesktopWidget.availableGeometry() 来获取可用的屏幕。在 Windows 平台上,这个方法计算除了任务栏之外的屏幕区域;在 Mac 上会计算除了 Dock 栏和菜单栏的屏幕区域。(什么?Linux?抱歉我已经不在桌面上使用 Linux)

那么可能我们就会写出这样的代码

class MyWindow(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
        self.move(
            desktop_geometry.width() - self.width(),
            desktop_geometry.height() - self.height()
        )

那么显然我们就是 too young。因为根据 Qt 文档.geometry() 这一类的方法返回窗口 widget 区域(相对应地,.width().height() 都是返回窗口 widget 的宽高),而真实的窗口还带有操作系统窗口管理器附加的装饰,例如标题栏和额外的边框。看下图就懂了(图片来自 Qt 文档,链接在本段头部)。

geometry.png

好吧,那么我们现在改成这样就可以了吧?

class MyWindow(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
        self_geometry = self.frameGeometry()
        self.move(
            desktop_geometry.width() - self_geometry.width(),
            desktop_geometry.height() - self_geometry.height()
        )

那么显然我们又是 too simple。因为我测试了发现,如果这个窗口还没显示(__init__ 里显然还没显示),那么 .frameGeometry() 这类方法的返回结果其实跟 .geometry() 这一类是一样的。换句话说,只有窗口被至少显示过一次,窗口管理器才有机会去装饰它,这样 Qt 才能知道最终窗口的实际占用区域大小。

那么我们只要在首次 showEvent 信号被触发的时候来获取窗口大小,然后据此移动窗口就可以了(当然如果你想每次显示都移动一下我也没话说 =,=)。

class MyWindow(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.__first_show = True

    def showEvent(self, evt):
        evt.accept()
        if self.__first_show:
            desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
            self_geometry = self.frameGeometry()
            self.move(
                desktop_geometry.width() - self_geometry.width(),
                desktop_geometry.height() - self_geometry.height()
            )
            self.__first_show = False

写了一个键盘计数器

起因是有个朋友说想写一个小工具看看自己每天敲了多少次键盘,当时因为很忙就直接回复用 pyHook。后来没忍住,自己业余时间写了个图形界面的工具,用了几天感觉还可以了,放出一个 exe 下载。

项目在 Microcore/KeyCounter,下载在项目的 release 里,目前只支持 Windows。主要原理就是

  • 用 pyHook 监听键盘按键被按下的事件
  • 创建一个屏幕大小的窗口,背景设置为透明,在上面绘制数字
  • 每次按键事件,更新数字并且重绘窗口上的文字
  • 创建一个托盘图标,通过菜单来重置计数、调整文字透明度和退出

代码写得很简单,基本就是去调用各种 Windows API,不过设置透明度的窗口是用 MFC 做的。

中间还发现三个问题。第一是如果你监听 KeyDown 事件,那么按住一个键一段时间后,这个事件就会不停触发(后来想了一下这是合理的);二是如果你监听 KeyUp 事件,那么基本上可以断定你会遇到 pyHook 的头号 bug,目前还没有修复,而我也不想直接修改它的源码;最后一个就是,我尝试监听鼠标键按下的事件,发现会对鼠标指针的移动速度造成很大影响(我是速度调得比较高并且去掉指针加速的,如果你是老年人速度那么可以尝试搞一搞),就是鼠标移动快一点的话,比如跨屏快速定位,指针酒会变得一卡一卡的。最终我放弃了鼠标键计数,专心做键盘计数。

想要验证签名的话请使用这个公钥 0A662175C3997E84A6FA879499321BCE08B738B8,关于如何验证签名请参考 Making and verifying signatures

最后提一下一个 Simon Brunning 写的一个很有用的 Windows 系统托盘图标类,以及一个实用的量化生活工具(其中包含了记录键盘活动,并能进行历史搜索和统计等操作)selfspy