标签 Security 下的文章

前言

无意翻到 Let's Encrypt (下称 LE )已支持 OCSP Must-Staple 扩展的消息
再加上之前也想升级双证书
于是又有了重新折腾一次的动力。
记录如下——

特别提示:
如果仅是希望使现有证书支持 OCSP Must-Staple ,
用原私钥重新生成 CSR 申请证书即可。
详见 CSR 一节。

准备

服务器环境

推荐使用 nginx-autoinstall 脚本进行安装,
原 Repo 仅适用于 Debian 8
其它发行版需要稍作修改。

编译并安装完可以使用如下工具测试相关特性:

工具选择

CA 当然选择免费且开源的 LE
工具方面则依旧选用 ACME-Tiny
官方列表中亦罗列不少,
包括最近很流行的国人作品 ACME.sh
若选用其它工具则签发流程会有所变化(相应环节会稍作说明)。

密钥强度及算法选择

RFC5480 ,对 ECDSA 证书的推荐组合如下:













- 阅读剩余部分 -

前言

 QuChao.com 的 Stapled OCSP Response

去年末“赶时髦”申请了 Let's Encrypt(下称 LE )的免费证书,
遗憾的是还不支持泛域名 ECC 证书(后者已被支持,中间证书则尚未提供),
不过一切都在进行之中。
有兴趣者请密切关注其论坛。

其中间证书虽已让被广泛支持的 IdenTrust 根证书 交叉签发,
然而它仍旧只是个刚“出道”的 CA ,
不少服务器和本地程序还未信任其证书,
一些朋友可能与我一样在配置 ssl_stapling_file 时遇到一些困难,
于是我将这部分配置过程从笔记里摘出来与大家分享、探讨。

关于 OCSP Stapling 的资料很多,
自己也还在学习之中,
这里仅给出一些链接:
简单地说它是一种的优化手段——
将原本需要客户端实时发起的 OCSP 请求转嫁给服务端;
利用 NginX 的 ssl_stapling_file 指令更可将业已缓存的查询结果直接返回,
使得缓存的更新时间更可加控。

环境

我使用的环境如下:

其中 NginX 版本最为关键,
它从 1.3.7+ 开始支持该特性。

  • CentOS/7.2.x
  • NginX/1.9.x
  • OpenSSL/1.0.1e

启用 OCSP Stapling

我们假定你已从 LE 申请到了证书,
并能正常提供服务。<!--more-->

推荐在服务器上使用 ACME-Tiny 来申请、续期证书。

我的配置参考了 H5BPNginX Boilerplate Configs
在其 /directive-only/ssl-stapling.conf 中推荐定义如下:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/certs/quchao.com/chained.pem;
resolver 8.8.8.8 8.8.4.4 216.146.35.35 216.146.36.36 valid=60s;
resolver_timeout 2s;

ssl_certificate 指令指定了完整的证书链,
ssl_trusted_certificate 可省略。

上述配置即方便地开启了 OCSP Stapling 特性。
当客户端访问时 NginX 将去指定的证书中查找 OCSP 服务的地址,
获得响应内容后通过证书链下发给客户端。

可通过此工具来查看证书的有效期及撤销状态,
配置成功可看到 (STAPLED) 状态关键词。

启用 ssl_stapling_file

2016-12-27 特别提示
目前由于 LE 的 OCSP 不支持多证书合并响应,
此指令也无法多次使用,
因此多证书情形下无法工作!

接下来我们更进一步——
直接将 OCSP 响应存成文件,
NginX 将其随证书下发而不实时查询。

首先我们需要找到 OCSP 服务地址:

chained.pem 即网站完整的证书链。

openssl x509 -in /path/to/certs/quchao.com/chained.pem -text | grep "OCSP - URI:" | cut -d: -f2,3

得到的地址是 http://ocsp.int-x1.letsencrypt.org/
自 26th Mar, 2016 已启用 http://ocsp.int-x3.letsencrypt.org/已兼容 WinXP )。

接着获取 OCSP 响应并写入 ocsp.resp 文件:

openssl ocsp -no_nonce \
             -respout /path/to/certs/quchao.com/ocsp.resp \
             -issuer /path/to/certs/lets-encrypt-x3-cross-signed.pem \
             -cert /path/to/certs/quchao.com/quchao.com_chained.pem \
             -url http://ocsp.int-x3.letsencrypt.org/ \
             -header "HOST" "ocsp.int-x3.letsencrypt.org"

注意相关文件的权限;
lets-encrypt-x3-cross-signed.pemLE中间证书
有些 openssl 版本不支持指定 -header 参数,请选择合适的版本。

若运行时报错如下:

Response Verify Failure
140060623058848:error:27069076:OCSP routines:OCSP_basic_verify:signer certificate not found:ocsp_vfy.c:85:

则服务器可能并未信任 LE 的根证书或中间证书;
有多种途径解决:

  • 直接将这些证书加入系统的 ca-bundle.crt 里;
  • 生成一个专用的 bundle 。

下载相关根证书和中间证书

它们是:

  • 根证书:DST Root CA X3
  • 根证书:ISRG Root X1
  • 中间证书:Let’s Encrypt Authority X1 (分别被上述两个根证书签发)
  • X1 的候补中间证书:Let’s Encrypt Authority X2
  • 中间证书:Let’s Encrypt Authority X3 (兼容 WinXP)
  • X3 的候补中间证书:Let’s Encrypt Authority X4

不管采用哪种方式,
我们先将证书全部保存到本地。

mkdir -p /path/to/tmp/
cd /path/to/tmp/
curl -o ./DST_Root_CA_X3.pem -L https://ssl-tools.net/certificates/dac9024f54d8f6df94935fb1732638ca6ad77c13.pem

curl -O -L https://letsencrypt.org/certs/isrgrootx1.pem

curl -O -L https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx3.pem
curl -O -L https://letsencrypt.org/certs/lets-encrypt-x4-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx4.pem

curl -O -L https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx1.pem
curl -O -L https://letsencrypt.org/certs/lets-encrypt-x2-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx2.pem

方法一:将它们加入系统的 ca-bundle.crt

以 CentOS/7 为例。

mv /path/to/tmp/*.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

执行完毕可看看 /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt 是否已包含上述证书。

方法二:生成专用的 ca-bundle.pem

我使用该方式。

cat /path/to/tmp/*.pem > /path/to/certs/ca-bundle.pem

特别注意
letsencryptauthorityx2.pem 文件为 Dos 格式,
会导致 concat 后 PEM 格式错误,
请尝试将其转换为 Unix 格式(末尾增加换行)后重新生成 Bundle 。

再次尝试缓存 ocsp.resp 文件

此时再执行:

openssl ocsp -no_nonce \
             -respout /path/to/certs/quchao.com/ocsp.resp \
             -issuer /path/to/certs/lets-encrypt-x3-cross-signed.pem \
             -cert /path/to/certs/quchao.com/quchao.com_chained.pem \
             -CAfile /path/to/certs/ca-bundle.pem \
             -VAfile /path/to/certs/ca-bundle.pem \
             -url http://ocsp.int-x3.letsencrypt.org/ \
             -header "HOST" "ocsp.int-x3.letsencrypt.org"

注意新增的 -CAfile-VAfile 参数。

若无其它意外,
你会看到如下结果:

Response verify OK
/path/to/certs/quchao.com/chained.pem: good
    This Update: Jan 17 06:00:00 2016 GMT
    Next Update: Jan 24 06:00:00 2016 GMT

证明 OCSP 请求缓存成功,
且告知了过期时间。

开启特性

接下来放心大胆地在 NginX 配置中增加一行:

ssl_stapling_file /path/to/certs/quchao.com/ocsp.resp;

重载配置后,
执行如下命令测试:

echo QUIT | openssl s_client -connect quchao.com:443 -status 2> /dev/null | grep -A 17 'OCSP response:' | grep -B 17 'Next Update'

若得到类似 OCSP Response Status: successful 的答复即算成功。(亦可通过之前的工具进行验证)

定期更新

我们注意到响应内容中有一行 Next Update: Jan 24 06:00:00 2016 GMT
表示我们所缓存的响应内容将于这个时间点过期。
为了避免过期的情形,
我们可以编写脚本并设定 CronJob 来一劳永逸地解决问题。

脚本

如下是我所使用的脚本(参考),
使用时请注意相关文件的路径权限问题。

#!/bin/bash
#############################
#
# OCSP Cache Updater v0.2.1
#   by Qu Chao
#
# ---------------------------
#
# Usage:
#   % update_ocsp_cache "example.com"
#
# ---------------------------
#
# v0.1.0 - Initialization.
# v0.2.0 - Use X3 intermediate certs by default.
# v0.2.1 - Add generation of intermediates as an option.
#
#############################

if [ -z $1 ]; then
    echo -e "No domain is specified!" >&2
    exit 1
fi

############################
# CONFIGS
############################
#LE_DIR="/usr/local/etc/nginx/ssl"

LE_DIR="/etc/nginx/ssl"
GEN_NUM="x3"

############################
# DEFAULTS
############################
DOMAIN=$1
CERT_DIR="${LE_DIR}/${DOMAIN}"
CHAINED_CERT="${CERT_DIR}/chained.pem"

ISSUER_CERT="${LE_DIR}/lets-encrypt-${GEN_NUM}-cross-signed.pem"
CA_FILE="${LE_DIR}/ca-bundle.pem"
OCSP_RESP_FILE="${CERT_DIR}/ocsp.resp"
OCSP_REPLY_FILE="${CERT_DIR}/ocsp.reply"

############################
# MAIN
############################
# Functions
existence_pattern_check(){
    [ -n "$1" ] && [ -e "$1" ] && ( [ "${2:-file}" = "file" ] && ( [ -f "$1" ] || [ -L "$1" ] ) || [ -d "$1" ] ) && [ -r "$1" ]
}

# Params Validation
if ! existence_pattern_check "${LE_DIR}" "dir"; then
    echo -e "Let's Encrypt folder is missing!" >&2
    exit 1
elif ! existence_pattern_check "${CERT_DIR}" "dir"; then
    echo -e "Domain cert folder is missing!" >&2
    exit 1
elif ! existence_pattern_check "${CHAINED_CERT}" || ! existence_pattern_check "${ISSUER_CERT}" || ! existence_pattern_check "${CA_FILE}"; then
    echo -e "Required certs file is missing!" >&2
    exit 1
fi

# Get OCSP URI & HOST
OCSP_URL=$(openssl x509 -in "${CHAINED_CERT}"  -text | grep "OCSP - URI:" | cut -d: -f2,3)
OCSP_HOST=$(echo "${OCSP_URL}" | awk -F/ '{print $3}')

# Output OCSP response
openssl ocsp -no_nonce \
             -respout "${OCSP_RESP_FILE}.new" \
             -issuer "${ISSUER_CERT}" \
             -cert "${CHAINED_CERT}" \
             -CAfile "${CA_FILE}" \
             -VAfile "${CA_FILE}" \
             -url "${OCSP_URL}" \
             -header HOST "${OCSP_HOST}" > "${OCSP_REPLY_FILE}" 2>&1

# Check if it's all okay?
if  grep -q "Response verify OK" "${OCSP_REPLY_FILE}" && grep -q "${CHAINED_CERT}: good" "${OCSP_REPLY_FILE}" ; then
    if  cmp -s "${OCSP_RESP_FILE}.new" "${OCSP_RESP_FILE}" ; then
        # No news is good news
        rm "${OCSP_RESP_FILE}.new"

        echo -e "OCSP cache is up-to-date!"
    else
        # Update the cache file
        mv "${OCSP_RESP_FILE}.new" "${OCSP_RESP_FILE}"

        # reload nginx's config
        /usr/sbin/nginx -s reload

        echo -e "OCSP cache is updated!"
    fi
else
    # Bad things happen all the time
    cat "${OCSP_REPLY_FILE}" >&2

    echo -e "Failed to update OCSP cache!" >&2
fi

# Make a backup
mv "${OCSP_REPLY_FILE}" "${OCSP_REPLY_FILE}.old"
echo -e "Detailed log located at ${OCSP_REPLY_FILE}.old"

将脚本保存为 /path/to/certs/scripts/update_ocsp_cache.sh 并赋予执行权限。

计划任务

接下来新增一个 Hourly CronJob :

1 * * * * /path/to/certs/scripts/update_ocsp_cache.sh quchao.com 2>&1 >/dev/null | (test -s /dev/stdin && /usr/bin/mail -s "$HOSTNAME - Hourly OCSP Cache Update" mail@to.me || echo 'OCSP Cache is Up-to-date!')

这样一来,
服务器每小时都会尝试缓存新的 OCSP 响应,
当响应内容有变化便会自动更新缓存重载配置
若过程中发生异常则会通过 mailx 邮件通知我们上线处理。

更新日志

20160327 - LE 中间证书已改用 X3 (兼容 WinXP ),OCSP 服务器亦有调整;本文已更新。












































- 阅读剩余部分 -

2014.11.15 更新

不愧是上市公司,动作就是快


2014.11.06 更新

木马已由 PANWClaud Xiao 确认并命名为 WireLurker (aka MacHook),
大家现在可下载其推出的排查脚本来源)来检查系统了。
然而清除工作目前仍需要手工完成,可参看文末。


2014.11.05 更新

网友 littledew 已找到了木马作者嫌疑人,
群众与他正在 V2EX 论战中(已被删帖,点此查看备份);
后续情况详情见 Livid 的声明(请关注声明中提到的某个域名)。


2014.06.01 发布

最近我所有未越狱的 iOS 设备都出现了一个怪现象:
与 Mac 同步之后会莫名出现若干企业应用。
第一次是 5 月 21 日出现的“PP助手正版”(下图),
接着一周后出现了“乱世之刃2”。

PP助手正版

第二次出现奇怪的应用之时引起了我的注意。(头一次被我手快直接删除了)
首先在“描述文件”中皆出现了开发商证书(下图);
其次首次启动该应用会弹出确认。

PP助手正版证书

于是我决定花一点时间研究一下其源头。<!--more-->
起初考虑了很多方向,均被证明“不可能”,这里不再冗述。
后来受这篇文章的启发,
我决定从系统内多处 LaunchAgentsLaunchDaemons 目录排查起。

由于我记得 App 被安装的确切时间,
所以根据文件修改时间排序,
/Library/LaunchDaemons/ 下找到如下若干符合目标时间区间的配置文件:

  • /Library/LaunchDaemons/com.apple.globalupdate.plist
  • /Library/LaunchDaemons/com.apple.itunesupdate.plist
  • /Library/LaunchDaemons/com.apple.machook_damon.plist
  • /Library/LaunchDaemons/com.apple.watchproc.plist

看 Vendor 还以为是系统自带的吧?(这也侧面揭示了其恶意目的)

顺着上面的配置文件找到了如下可执行文件或目录:

  • /usr/bin/globalupdate
  • /usr/bin/itunesupdate
  • /usr/local/machook/
  • /usr/bin/WatchProc

其中 /usr/local/machook/ 是一个包含其主要程序的目录,
由于我并不知道这套恶意程序叫什么名字,
所以下面咱们姑且将它称为 MacHook

然后根据时间区间还找到一个可疑目录:

  • /usr/local/ipcc/

后来证明里面存放的就是被程序偷偷下载下来的 .ipa 文件(下图)。

PPAppInstall_qudaobao.app 包内容

另外,
在刚才找到的 /usr/local/machook/ 目录下,
我们找到了一个名为 foundation 的 Sqlite DB 文件(下图)。

machook 目录内容

用工具打开后你会看到四张表,
分别用于存放——

  • .ipa 文件下载地址(下图);
  • 安装过上述应用的设备 UDID 及安装时间;
  • 这套恶意程序的执行文件及其版本;
  • 设备 UDID ;

machook 目录内容

所以它们记录 UDID 是想做什么呢?
Interesting.

当然大家可能更好奇的是:
这些程序是如何被安装到我的电脑的呢?

排查工作更是费尽周折——
由于我知道恶意程序需要通过守护进程来工作,
而将 Plist 文件写入 LaunchDaemons 目录又是需要管理员权限的,
所以我那几天特别留意了一下异常的授权请求,
终于被我发现在启动某应用时会要求授权。
(由于它是个与开发相关应用,所以头一次询问我并未在意)

经回忆发现这个宿主应用下载自“麦芽地”,
解开这个被恶意修改过的宿主应用,
在其 Resource 目录下发现如下两个“可疑文件”,
(后确认原版应用中没有)

  • /Resource/FontMap1.cfg
  • /Resource/start.sh

其中 FontMap1.cfg 是个压缩包,
解压后你会发现就是初始状态的 /usr/local/machook/ 目录(下图)!
start.sh 则显而易见是用来部署恶意程序并清除安装文件的。

machook 目录内容

至此我们大致了解了整个排查过程。
目前我只知道恶意程序可能是通过 Xcode 的命令行工具实现静默安装应用的,(设备需开启开发模式)

2017-01-01 更新——
有位网名雪山飛狐的朋友询问应用安装的细节。
实际上它应该是通过 USB 使用 itms-services 协议直接将企业签名的应用安装到设备的,
并未使用什么高深技术。
我之前的描述有误,特此更新。也谢谢这位朋友。

希望有逆向实力的朋友帮忙看看其中还有什么猫腻。

点此下载恶意程序压缩档。

P.S.
目前还不知道这个恶意程序是哪一方建立的。
但包含恶意程序的宿主应用下载自“麦芽地”,
(据我在微博上联系到另外两例用户得知:
出现问题的宿主应用全都从“麦芽地”下载而来)
但首个被“推送”下来的应用是“PP助手正版”;
所以具体是哪一方建立了这个通道,
还请大家自行判断。


如下是我写的卸载脚本 machook_removal.sh点此下载),
“卸载”后会把文件全部备份到 ~/ppappinstaller/ 供你继续研究。
有一点需要提醒的是——
如果你不找出包含恶意程序的宿主应用,
那么有可能清除后它还会回来!
判断宿主文件的方法是执行特定操作时(如启动)会询问管理员权限
假如遇到莫名索取权限被授权后恶意程序又“复原”了,
那它必是宿主文件无疑!
请从麦芽地下载的应用排查起!
也可以通过如下命令进行检查(但不一定适用于所有情况):

#!/bin/bash
mkdir -p ~/ppappinstaller/Library/LaunchDaemons ~/ppappinstaller/usr/bin ~/ppappinstaller/usr/local

sudo launchctl unload /Library/LaunchDaemons/com.apple.globalupdate.plist
sudo mv /Library/LaunchDaemons/com.apple.globalupdate.plist ~/ppappinstaller/Library/LaunchDaemons
sudo mv /usr/bin/globalupdate ~/ppappinstaller/usr/bin

sudo launchctl unload /Library/LaunchDaemons/com.apple.itunesupdate.plist
sudo mv /Library/LaunchDaemons/com.apple.itunesupdate.plist ~/ppappinstaller/Library/LaunchDaemons
sudo mv /usr/bin/itunesupdate ~/ppappinstaller/usr/bin

sudo launchctl unload /Library/LaunchDaemons/com.apple.machook_damon.plist
sudo mv /Library/LaunchDaemons/com.apple.machook_damon.plist ~/ppappinstaller/Library/LaunchDaemons
sudo mv /usr/local/machook ~/ppappinstaller/usr/local/machook

sudo launchctl unload /Library/LaunchDaemons/com.apple.watchproc.plist
sudo mv /Library/LaunchDaemons/com.apple.watchproc.plist ~/ppappinstaller/Library/LaunchDaemons
sudo mv /usr/bin/WatchProc ~/ppappinstaller/usr/bin

sudo mv /usr/local/ipcc ~/ppappinstaller/usr/local/ipcc

echo -e "Done!"




















































- 阅读剩余部分 -