阅读视图

发现新文章,点击刷新页面。
🔲 ☆

GPG 的正确使用姿势

关于 GPG 的基本概念,这里就不过多阐述了,没有基础知识的同学建议先学一些网络上的基础教程。要先对 GPG 有一定的基础理解。

[scode type="yellow"]本文中生成的 GPG 密钥仅为示例使用,本人并不使用文中的密钥,请注意鉴别。[/scode]

准备工作

  1. 了解 GPG 基本概念,熟悉命令行操作
  2. 安装好虚拟机或/和下载 Tails 系统镜像
  3. 一颗追求信息安全的 ❤️

核心思想

平时只使用子密钥,将主密钥存储在受信的地方例如TPM或进行加密。

创建主密钥

首先创建一个新的 GPG 密钥对,一定要在虚拟机或者 Tails 或者你信任的电脑上操作

使用 --full-generate-key--expert 来生成密钥对,由于 RSA4096 比 ECC Curve 25519 要安全一点,这里我们的主密钥使用 RSA4096,且只用来 Certify。

rainshaw@ubuntu:~$ gpg --full-generate-key --expert
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
   (9) ECC and ECC
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (13) Existing key
  (14) Existing key from card
Your selection? 8

选择 8 自定义用途

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Sign Certify Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? s

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Certify Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? e

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Certify 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q

去掉 Sign 签名、Encrypt 加密用途,只保留 Certify 证明用途。

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096

使用 4096位 RSA 密钥更安全,基本无法破解。

Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 
Key does not expire at all
Is this correct? (y/N) y

设置密钥的有效期,我们可以仔细的存放主密钥,这里可以选择永不过期,当然你也可以自己选择有效期,当有效期临近时,再根据需求自行决定是启用新密钥还是将旧密钥延长有效期。

GnuPG needs to construct a user ID to identify your key.

Real name: Rainshaw
Email address: xxxx@live.com
Comment: 
You selected this USER-ID:
    "Rainshaw <xxxx@live.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o

输入你的个人信息,Comment 注释可以不填。

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 0xF2FE7008B02578B4 marked as ultimately trusted
gpg: directory '/home/rainshaw/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/rainshaw/.gnupg/openpgp-revocs.d/B5C100450331878DD5535261F2FE7008B02578B4.rev'
public and secret key created and signed.

pub   rsa4096/F2FE7008B02578B4 2021-08-16 [C]
      Key fingerprint = B5C1 0045 0331 878D D553  5261 F2FE 7008 B025 78B4
uid                              Rainshaw <xxxx@live.com>

接着输入你主密钥的密码,建议使用从未使用过的且易记的密码,长度越长越好。

[scode type="blue"]如果你之前使用过 GPG,你可能有疑问,密码越长之后使用的时候不会很繁琐么,每次都要输入。不要担心,后面会教给大家怎么解决这个问题,方法是给子密钥设置与主密钥不同的密码,一方面方便日常使用,另一方面也是对主密钥的进一步保护。[/scode]

格式化密钥

如果你观察仔细的话,会发现我最终打印出来的密钥格式和你可能不太一样,不要担心,这只是格式化,下面教给大家如何格式化密钥输出,懒癌福利。

不论你用什么编辑器,只需在 ~/.gnupg/gpg.conf 文件中输入以下内容即可

keyid-format long
with-fingerprint

这样之后输出密钥时就会自动打印密钥的长ID和指纹啦。

创建子密钥

刚刚生成的主密钥名为 F2FE7008B02578B4 ,它只有 [C] 用途,只能用来签发子密钥,为了满足日常使用我们需要新建三个子密钥,分别用来 Sign 签名、Encrypt 加密、Authenticate 认证。

Sign 签名密钥

使用 --expert--edit-key F2FE7008B02578B4 (将其中的密钥 id 换成你的)。

rainshaw@ubuntu:~$ gpg --expert --edit-key F2FE7008B02578B4
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
sec  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
[ultimate] (1). Rainshaw <xxxx@live.com>

gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
  (14) Existing key from card
Your selection? 8

8 自定义用途。

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? e

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits

只保留 Sign 签名用途,并输入长度为 4096.

Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 1y
Key expires at Tue 16 Aug 2022 02:52:10 AM PDT
Is this correct? (y/N) y
Really create? (y/N) y

输入子密钥有效期,建议不要选择永久有效,当子密钥临近过期或已过期时,我们可以使用主密钥对子密钥进行延长有效期,这样可以进一步保证安全。

[scode type="yellow"]当然这样之后会更加麻烦一些,比如你需要替换 GitHub 上的签名公钥等。[/scode]

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

sec  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
[ultimate] (1). Rainshaw <xxx@live.com>

gpg> save

最后不要忘记使用 save 命令保存。

此时再看一下现在的密钥:

rainshaw@ubuntu:~$ gpg -K
/home/rainshaw/.gnupg/pubring.kbx
---------------------------------
sec   rsa4096/F2FE7008B02578B4 2021-08-16 [C]
      Key fingerprint = B5C1 0045 0331 878D D553  5261 F2FE 7008 B025 78B4
uid                   [ultimate] Rainshaw <xxxx@live.com>
ssb   rsa4096/0E45187E21327343 2021-08-16 [S] [expires: 2022-08-16]

Encrypt 加密密钥

同样使用 --expert--edit-key F2FE7008B02578B4 (将其中的密钥 id 换成你的)。

rainshaw@ubuntu:~$ gpg --expert --edit-key F2FE7008B02578B4
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
[ultimate] (1). Rainshaw <xxxx@live.com>

gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
  (14) Existing key from card
Your selection? 12
Please select which elliptic curve you want:
   (1) Curve 25519
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
Your selection? 1
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 1y
Key expires at Tue 16 Aug 2022 03:06:34 AM PDT
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

sec  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
ssb  cv25519/7A3B9781DAD1C286
     created: 2021-08-16  expires: 2022-08-16  usage: E   
[ultimate] (1). Rainshaw <xxxx@live.com>

gpg> save

这里尝试使用 ECC 作为加密解密的算法,速度会更快一点。来源于博主 ulyc 的介绍:

[scode type="share"]美国国家标准与技术研究院(NIST)系列椭圆曲线、Brainpool系列椭圆曲线、secp256k1都存在不同的安全风险,不建议使用。

ECC算法比RSA的优势在于,在同等强度下,ECC的密钥长度要小的多,性能也会好一些。

不提密钥长度说安全性都是耍流氓 ,RSA 4096 的安全强度是要 好于 ed25519 的。[/scode]

Authenticate 认证密钥

认证密钥用处不多,平时我们连 SSH 使用普通的 RSA 密钥就可以了,当然如果你愿意也可以用 GPG 生成的子密钥。同样使用 --expert--edit-key F2FE7008B02578B4 (将其中的密钥 id 换成你的)。

rainshaw@ubuntu:~$ gpg --expert --edit-key F2FE7008B02578B4
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
ssb  cv25519/7A3B9781DAD1C286
     created: 2021-08-16  expires: 2022-08-16  usage: E   
[ultimate] (1). Rainshaw <xxx@live.com>

gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
  (14) Existing key from card
Your selection? 8

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? a

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign Encrypt Authenticate 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? s

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Encrypt Authenticate 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? e

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Authenticate 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 1y
Key expires at Tue 16 Aug 2022 03:12:10 AM PDT
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

sec  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
ssb  cv25519/7A3B9781DAD1C286
     created: 2021-08-16  expires: 2022-08-16  usage: E   
ssb  rsa4096/4FFFC7DA1ABE8BE5
     created: 2021-08-16  expires: 2022-08-16  usage: A   
[ultimate] (1). Rainshaw <xxx@live.com>

gpg> save

操作大同小异,不再赘述。

导出密钥

复习下我们的核心思想,主密钥必须存储在安全的地方或被加密,子密钥用来日常使用。为了安全,我们将不会存储主密钥的明文,虽然这个明文也被我们之前设置的密码保护着,但为了更加安全,我们再套一层(有点吃饱了撑的)

刚才我们已经生成了加密密钥,所以只要加密密钥被保护好,我们就可以使用加密密钥给主密钥套一层。

导出公钥

第一种是导出所有的公钥,常用于公开到特定服务器。

rainshaw@ubuntu:~/Desktop$ gpg -ao pk.key --export F2FE7008B02578B4
rainshaw@ubuntu:~/Desktop$ cat pk.key
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBGEaMF8BEADD1NPg6UCRKEjLnVjYvMWzz26YTRWXmgux58hcFp3u+UE/o3Ji
eJiNtAGMXYWrvl1f/KhpUxQHKmZ+C30Jh1Vhk3Tu9POXDQb10BfBNtHIj3+Ei+3k

中间很长很长

XGyiVo7R4BCiGHekJk8+jqqu1YcRWlimHGgyBhYDvOg88qovp5+fBRpZWA3gRSm4
BOBkecuPQJ7tcdXz1a++eECgUBVdmvPT4yR9jEJRJSPNcVXSqw5G
=VhBK
-----END PGP PUBLIC KEY BLOCK-----

[scode type="yello"]注意这个公钥仅用于示例,请不要将这个公钥和博主关联起来 (●'◡'●)[/scode]

第二种是导出某个子密钥的公钥,常用于当有多个同一用途的密钥时指定密钥用。

rainshaw@ubuntu:~/Desktop$ gpg -a --export --output pk.key F2FE7008B02578B4
File 'pk.key' exists. Overwrite? (y/N) y
rainshaw@ubuntu:~/Desktop$ gpg -a --export --output pk.key F2FE7008B02578B4!
File 'pk.key' exists. Overwrite? (y/N) N
Enter new filename: pk2.key
rainshaw@ubuntu:~/Desktop$ gpg -a --export --output pk-sign.key 0E45187E21327343!
rainshaw@ubuntu:~/Desktop$ gpg -a --export --output pk-enc.key 7A3B9781DAD1C286!
rainshaw@ubuntu:~/Desktop$ gpg -a --export --output pk-auth.key 4FFFC7DA1ABE8BE5!

导出主密钥私钥

使用子加密密钥加密主密钥,注意将下面命令的 Rainshaw 改为你之前设置的名字,如果含有空格的话需要在两端加引号。

rainshaw@ubuntu:~/Desktop$ gpg -a --export-secret-key F2FE7008B02578B4 | gpg -e -r Rainshaw | dd of=sk.key
17+2 records in
17+1 records out
9095 bytes (9.1 kB, 8.9 KiB) copied, 5.82587 s, 1.6 kB/s

[scode type="green"]你可以分步执行,看看每步到底做了什么。[/scode]

导出子密钥私钥

使用子加密密钥加密 Sign 和 Authenticate 密钥,注意将下面命令的 Rainshaw 改为你之前设置的名字,如果含有空格的话需要在两端加引号。同时一定要注意密钥ID一定要改,且感叹号(英文)不能省略!

rainshaw@ubuntu:~/Desktop$ gpg -a --export-secret-subkeys 0E45187E21327343! | gpg -e -r Rainshaw | dd of=sign.key
8+2 records in
8+1 records out
4508 bytes (4.5 kB, 4.4 KiB) copied, 5.10913 s, 0.9 kB/s
rainshaw@ubuntu:~/Desktop$ gpg -a --export-secret-subkeys 4FFFC7DA1ABE8BE5! | gpg -e -r Rainshaw | dd of=auth.key
7+2 records in
7+1 records out
3935 bytes (3.9 kB, 3.8 KiB) copied, 5.79333 s, 0.7 kB/s

对于加密子密钥就不能再用加密子密钥加密了(禁止套娃)。对于上面生成的这些文件,我们可以随意上传、复制,只要我们保证自己的加密子密钥是安全的即可。现在问题来了,怎么存储加密子密钥呢?

这里我也没有什么特别好的方法,我采用的是使用DES对称加密对GPG加密子密钥加密(好绕口。。。),这样能保证子密钥比不加密安全一丢丢。

rainshaw@ubuntu:~/Desktop$ gpg -a --export-secret-subkeys 7A3B9781DAD1C286! | openssl des3 -salt | dd of=enc.key
enter des-ede3-cbc encryption password:
Verifying - enter des-ede3-cbc encryption password:
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
5+1 records in
5+1 records out
2640 bytes (2.6 kB, 2.6 KiB) copied, 18.5623 s, 0.1 kB/s

过程中会先让你输入 GPG 的密码,然后再输入一个对称加密的密码,建议这两个采用不同的值(由于前者就是主密钥的密码,很复杂,所以我们可以对其稍作改动作为后者,比如更换其中的数字、符号,或者将密码顺序颠倒之类的。)

导出吊销证书

gpg: key 0xF2FE7008B02578B4 marked as ultimately trusted
gpg: directory '/home/rainshaw/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/rainshaw/.gnupg/openpgp-revocs.d/B5C100450331878DD5535261F2FE7008B02578B4.rev'
public and secret key created and signed.

pub   rsa4096/F2FE7008B02578B4 2021-08-16 [C]
      Key fingerprint = B5C1 0045 0331 878D D553  5261 F2FE 7008 B025 78B4
uid                              Rainshaw <xxxx@live.com>

在创建主密钥的同时,GPG 为我们生成了一个主密钥的吊销证书,位于 .gnupg/openpgp-revocs.d/ 目录下。我们需要对其更加精心备份和保护,为什么呢,因为如果你主密钥没丢,而这个吊销证书丢了,那你的主密钥就可能被攻击者吊销,这样一来,你对主密钥的精心保护还有啥用呢?这里偷个懒,使用的是之前生成的加密密钥。

rainshaw@ubuntu:~/Desktop$ cat ~/.gnupg/openpgp-revocs.d/B5C100450331878DD5535261F2FE7008B02578B4.rev | gpg -e -r Rainshaw | dd of=rev.key
2+2 records in
2+1 records out
1376 bytes (1.4 kB, 1.3 KiB) copied, 0.00671809 s, 205 kB/s

如果你追求更安全,那我建议你再生成一个 Encrypt 加密密钥,不导出它,只用它来加密这个吊销证书,这样只有在你的主密钥泄露之后,才有可能泄露这个吊销证书,而这时我们肯定是要吊销的,所以安全度很高。由于此时你的库中有两个加密密钥,导致在进行加密时会自动选择加密密钥(一般会用较新的),此时为了指定加密密钥,我们需要采用与上文不同的命令。

rainshaw@ubuntu:~/Desktop$ gpg -K
/home/rainshaw/.gnupg/pubring.kbx
---------------------------------
sec   rsa4096/F2FE7008B02578B4 2021-08-16 [C]
      Key fingerprint = B5C1 0045 0331 878D D553  5261 F2FE 7008 B025 78B4
uid                 [ultimate] Rainshaw <xxx@live.com>
ssb   rsa4096/0E45187E21327343 2021-08-16 [S] [expires: 2022-08-16]
ssb   cv25519/7A3B9781DAD1C286 2021-08-16 [E] [expires: 2022-08-16]
ssb   rsa4096/4FFFC7DA1ABE8BE5 2021-08-16 [A] [expires: 2022-08-16]
ssb   cv25519/82A36E1B874BE22B 2021-08-16 [E] [expires: 2022-08-16]

rainshaw@ubuntu:~/Desktop$ gpg -a --export --output pk-enc2.key 82A36E1B874BE22B!
rainshaw@ubuntu:~/Desktop$ cat ~/.gnupg/openpgp-revocs.d/B5C100450331878DD5535261F2FE7008B02578B4.rev | gpg -e -f pk-enc2.key | dd of=rev.key
2+2 records in
2+1 records out
1379 bytes (1.4 kB, 1.3 KiB) copied, 0.00649605 s, 212 kB/s

[scode type="red"]如果你刚刚生成了一个新的加密子密钥,记得重新导出主密钥![/scode]

rainshaw@ubuntu:~/Desktop$ gpg -a --export-secret-key F2FE7008B02578B4 | gpg -e -f pk-enc.key | dd of=sk2.key
18+3 records in
19+1 records out
9774 bytes (9.8 kB, 9.5 KiB) copied, 6.38228 s, 1.5 kB/s

[scode type="yellow"]注意这里使用的是第一个加密密钥![/scode]

传输文件

好,到目前为止,我们生成了几个非常安全的文件,他们可以随意分发上传(但建议还是不要随意分发),之后每次使用时只需将所需的私钥文件下载然后解密安装即可。

此时如果你使用虚拟机启动 Tails OS 作为生成密钥的系统,那你可能会发现好像无法把子密钥从虚拟机中复制出来,这就很蛋疼了,我的解决方法是在客户端电脑上开设一个 SSH Server,在 Tails 中使用 SCP 命令将文件传输出来,具体命令大家可以自行百度。如果你使用的是非虚拟机运行的 Tails OS,那你可以插入一个新的U盘存储这些文件。如果你是其他的情况,请自行解决。

销毁原文件

当你把上述文件成功传输出来之后,根据不同的情况,你可能需要销毁原始的密钥文件。如果是虚拟机,那你可以直接删除虚拟机即可,如果是 Tails 可以使用鼠标右键点击 .gnupg 文件夹然后选择 wipe 即可,如果是其他情况,请自行搜索擦除文件的方法。

导入密钥

导入密钥是十分简单的,首先对加密密钥进行解密,然后导入

rainshaw@ubuntu:~/Desktop$ openssl des3 -d -in enc.key | gpg --import
enter des-ede3-cbc decryption password:
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
gpg: key F2FE7008B02578B4: public key "Rainshaw <xxx@live.com>" imported
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key F2FE7008B02578B4: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

然后利用这个加密密钥对你在某个机器上所要使用的子密钥进行解密然后导入

如要导入签名密钥:

rainshaw@ubuntu:~/Desktop$ gpg -d sign.key | gpg --import
gpg: encrypted with 256-bit ECDH key, ID 7A3B9781DAD1C286, created 2021-08-16
      "Rainshaw <xxx@live.com>"
gpg: key F2FE7008B02578B4: "Rainshaw <xxx@live.com>" 1 new signature
gpg: key F2FE7008B02578B4: "Rainshaw <xxx@live.com>" 1 new subkey
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key F2FE7008B02578B4: secret key imported
gpg: Total number processed: 1
gpg:            new subkeys: 1
gpg:         new signatures: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

如要导入认证密钥:

rainshaw@ubuntu:~/Desktop$ gpg -d auth.key | gpg --import
gpg: encrypted with 256-bit ECDH key, ID 7A3B9781DAD1C286, created 2021-08-16
      "Rainshaw <xxx@live.com>"
gpg: key F2FE7008B02578B4: "Rainshaw <xxx@live.com>" 1 new signature
gpg: key F2FE7008B02578B4: "Rainshaw <xxx@live.com>" 1 new subkey
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key F2FE7008B02578B4: secret key imported
gpg: Total number processed: 1
gpg:            new subkeys: 1
gpg:         new signatures: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

修改信任级别

由于我们导入的证书并不是生成的,所以其信任级别为未知,需要修改信任级别

rainshaw@ubuntu:~/Desktop$ gpg --expert --edit-key Rainshaw
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret subkeys are available.

pub  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: unknown       validity: unknown
ssb  cv25519/7A3B9781DAD1C286
     created: 2021-08-16  expires: 2022-08-16  usage: E   
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
ssb  rsa4096/4FFFC7DA1ABE8BE5
     created: 2021-08-16  expires: 2022-08-16  usage: A   
[ unknown] (1). Rainshaw <xxx@live.com>

gpg> trust
pub  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: unknown       validity: unknown
ssb  cv25519/7A3B9781DAD1C286
     created: 2021-08-16  expires: 2022-08-16  usage: E   
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
ssb  rsa4096/4FFFC7DA1ABE8BE5
     created: 2021-08-16  expires: 2022-08-16  usage: A   
[ unknown] (1). Rainshaw <xxx@live.com>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

pub  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: unknown
ssb  cv25519/7A3B9781DAD1C286
     created: 2021-08-16  expires: 2022-08-16  usage: E   
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
ssb  rsa4096/4FFFC7DA1ABE8BE5
     created: 2021-08-16  expires: 2022-08-16  usage: A   
[ unknown] (1). Rainshaw <xxx@live.com>
Please note that the shown key validity is not necessarily correct
unless you restart the program.

gpg> quit
rainshaw@ubuntu:~/Desktop$ gpg -K
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
/home/rainshaw/.gnupg/pubring.kbx
---------------------------------
sec#  rsa4096/F2FE7008B02578B4 2021-08-16 [C]
      Key fingerprint = B5C1 0045 0331 878D D553  5261 F2FE 7008 B025 78B4
uid                 [ultimate] Rainshaw <xxx@live.com>
ssb   cv25519/7A3B9781DAD1C286 2021-08-16 [E] [expires: 2022-08-16]
ssb   rsa4096/0E45187E21327343 2021-08-16 [S] [expires: 2022-08-16]
ssb   rsa4096/4FFFC7DA1ABE8BE5 2021-08-16 [A] [expires: 2022-08-16]

修改密码

[scode type="yellow"]这个方法是在 GnuPG 2.2 的时候测试通过的,GnuPG 2.3 实测已无法只修改子密钥密码。[/scode]

上面提到过,我们在生成主密钥时使用了一个非常非常不常用、非常非常复杂的密码,在上面的操作中,我们几乎每个操作都要输入至少一次这个密码,很不方便。这里教给大家如何修改子密钥的密码。

如果你搜索 gpg subkey change passphrase 这些关键词的话,你会看到很多帖子说 GPG 无法实现这种需求,但在我实际操作后发现,虽然给子密钥修改密码会提示错误,但实际密码是修改成功了的。

首先我们在机器上导入需要的子密钥,如上面操作就可,注意一定不要导入主密钥!!!

然后我们直接修改密码:

rainshaw@ubuntu:~/Desktop$ gpg --expert --edit-key Rainshaw
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret subkeys are available.

pub  rsa4096/F2FE7008B02578B4
     created: 2021-08-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  cv25519/7A3B9781DAD1C286
     created: 2021-08-16  expires: 2022-08-16  usage: E   
ssb  rsa4096/0E45187E21327343
     created: 2021-08-16  expires: 2022-08-16  usage: S   
ssb  rsa4096/4FFFC7DA1ABE8BE5
     created: 2021-08-16  expires: 2022-08-16  usage: A   
[ultimate] (1). Rainshaw <xxx@live.com>

gpg> passwd
gpg: key F2FE7008B02578B4/F2FE7008B02578B4: error changing passphrase: No secret key

gpg> save
Key not changed so no update needed.

注意到我们完成修改密码操作后虽然提示我们error changing passphrase,但你可以测试一下,密码是修改成功了的!

导入主密钥

[scode type="yello"]一定不要跳着看!!否则功亏一篑!![/scode]

当你完成上面的步骤后,此时如果机器是你信任的或者有TPM芯片或智能卡设备的话,你可以选择导入主密钥:

rainshaw@ubuntu:~/Desktop$ gpg -d sk2.key | gpg --import
gpg: encrypted with 256-bit ECDH key, ID 7A3B9781DAD1C286, created 2021-08-16
      "Rainshaw <xxx@live.com>"
gpg: key F2FE7008B02578B4: "Rainshaw <xxx@live.com>" 1 new signature
gpg: key F2FE7008B02578B4: "Rainshaw <xxx@live.com>" 1 new subkey
gpg: key F2FE7008B02578B4: secret key imported
gpg: Total number processed: 1
gpg:            new subkeys: 1
gpg:         new signatures: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1
gpg:  secret keys unchanged: 1

此时你可以发现主密钥的密码和子密钥的密码是不同的,我们能够很好的保护主密钥的同时,减少我们日常使用时的麻烦。

如果有 TPM 芯片可以在 --edit-key 时,使用 keytotpm 命令,将密钥转移到 TPM 芯片上,博主未尝试,请自行测试。如果有智能卡设备如 YubiKey ,则可以使用 keytocard 命令。

常见问题与解答

  1. 是否应该上传自己的公钥到公钥服务器上?
    根据博主 ulyc 的博客,不推荐将公钥上传到公钥服务器上,除非你真的需要。该博主的博客里列举了公钥服务器的诸多问题,如滥用、投毒、签名Dos、爆破、隐私问题。
  2. 如何公布自己的公钥?
    我们应该多途径、分散的公开自己的公钥,并在信任公钥时先确认指纹是否正确。你可以将公钥发布到 GitHub、个人博客、社交软件、公开论坛中。

补充

使用PGP为git commit 签名

打开终端,运行

$ gpg -K --keyid-format=long
/Users/hubot/.gnupg/secring.gpg
------------------------------------
sec   4096R/3AA5C34371567BD2 2016-03-10 [C]
uid                          Hubot 
ssb   4096R/42B317FD4BA89E7A 2016-03-10 [S]

找到你要用的 GPG 密钥 ID,上面的这个示例中为 42B317FD4BA89E7A。然后设置 Git

$ git config --global user.signingkey 42B317FD4BA89E7A
$ git config --global commit.gpgsign true

第一行是告诉 Git 用哪个密钥进行签名,第二行是告诉 Git 每次 commit 的时候自动签名,如果不加第二行,在每次 commit 时需要增加 -S 参数才会签名。

[scode type="green"] 不要忘了把公钥上传到 GitHub 。 [/scode]

使用PGP 进行SSH

[scode type="blue"] 关于服务端如何启用密钥登陆,请自行查找教程,这里只介绍如何配置本地电脑。 [/scode]

Win10

CMD

[post url="https://lab.jinkan.org/2021/08/01/using-gpg-for-ssh-authentication-on-windows-10/" title="在 Windows 10 上用 GPG 完成 SSH 认证" cover="https://lab.jinkan.org/wp-content/themes/kagami-master/assets/images/fallback_cover.jpg" /]

Git Bash

[scode type="blue"] 网上教程很多,我配置了好几次都没成功,最后按照下面这个步骤成功了,有很多注意事项。 [/scode]

[scode type="yellow"] 这个教程说不要用 CRLF 要用 LF,在 Win10 上你可以用 vscode 来设置换行符,设置的地方在 vscode 的右下角。 [/scode]

首先,新建文件 ~/.gnupg/gpg.conf 内容如下,最后不要换行:

$ cat ~/.gnupg/gpg.conf
use-agent

然后创建 ~/.gnupg/gpg-agent.conf 文件,内容如下,同样最后不要换行:

$ cat ~/.gnupg/gpg-agent.conf
enable-ssh-support

然后创建 ~/.gnupg/sshcontrol 文件,内容如下,不要换行:

$ gpg -K --with-keygrip
sec   rsa2048 2019-03-21 [C] [expires: 2021-03-20]
      96F33EA7F4E0F7051D75FC208715AF32191DB135
      Keygrip = 90E08830BC1AAD225E657AD4FBE638B3D8E50C9E
uid           [ultimate] Brian Exelbierd
ssb   rsa2048 2019-03-21 [E] [expires: 2021-03-20]
      Keygrip = 5FA04ABEBFBC5089E50EDEB43198B4895BCA2136
ssb   rsa2048 2019-03-21 [A]
      Keygrip = 7710BA0643CC022B92544181FF2EAC2A290CDC0E
$ cat ~/.gnupg/sshcontrol
7710BA0643CC022B92544181FF2EAC2A290CDC0E

然后修改终端配置文件:

$ cat .bashrc

export GPG_TTY=$(tty)
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
echo UPDATESTARTUPTTY | gpg-connect-agent 1> /dev/null

重启终端。

然后重启 gpg-agent, 注意每次修改配置后,都要执行这一步(不过一般这种东西配置一次也就够了)。

$ gpg-connect-agent killagent /bye
OK closing connection

$ gpg-connect-agent /bye
gpg-connect-agent: no running gpg-agent - starting 'C:\Program Files (x86)\Gpg4win\..\GnuPG\bin\gpg-agent.exe'
gpg-connect-agent: waiting for the agent to come up ... (5s)
gpg-connect-agent: connection to agent established

然后检查你的配置是否成功:

$ ssh-add -L
# 这里会打印出你的公钥

如果打印出你的公钥,如果没有输出说明还没有配置好,如你确定是按照本教程一步步做的,那可能本教程已过时,请再搜寻。

将公钥传递到服务器端便可使用 GPG 认证密钥进行 SSH 登陆了!

Linux(以 Ubuntu 为例)

首先导入对应的私钥,然后启用 gpg-agent 接替 ssh-agent 处理 SSH 请求:

cat enable-ssh-support >> ~/.gnupg/gpg-agent.conf

然后指定我们要使用的密钥:

$ gpg -K --with-keygrip
sec   rsa2048 2019-03-21 [C] [expires: 2021-03-20]
      96F33EA7F4E0F7051D75FC208715AF32191DB135
      Keygrip = 90E08830BC1AAD225E657AD4FBE638B3D8E50C9E
uid           [ultimate] Brian Exelbierd
ssb   rsa2048 2019-03-21 [E] [expires: 2021-03-20]
      Keygrip = 5FA04ABEBFBC5089E50EDEB43198B4895BCA2136
ssb   rsa2048 2019-03-21 [A]
      Keygrip = 7710BA0643CC022B92544181FF2EAC2A290CDC0E

$ echo 7710BA0643CC022B92544181FF2EAC2A290CDC0E >> ~/.gnupg/sshcontrol

注意替换上面的 7710BA0643CC022B92544181FF2EAC2A290CDC0E 为你所要用的。然后我们修改默认的 SSH 工具:

$ cat ~/.bashrc
...
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent
...

也就是将这两行加入到你的终端配置文件中,接下来你需要重启终端才能生效。

此时你可以检查你的配置是否正确:

$ ssh-add -L
# 这里会打印出你的公钥

将公钥传递到服务器端便可使用 GPG 认证密钥进行 SSH 登陆了!

清理 PGP 密码缓存

Linux:

$ gpgconf --kill gpg-agent

Windows:

$ echo "RELOADAGENT" | gpg-connect-agent
🔲 ☆

2018 NDSS IoTFuzzer 物联网模糊测试工具

物联网模糊测试工具

使用基于移动应用的模糊测试挖掘物联网设备中的内存漏洞

背景

物联网技术迅速发展,物联网设备大多缺乏保护并广泛存在漏洞的情况为网络犯罪分子提供了广泛的平台。 物联网攻击的重要target是设备固件中的安全漏洞。由于一方面难以获取固件(大多供应商并没有公开提供固件映像),另一方面由于物联网设备大多较为简单,没有提供调试端口也无法从主板转储映像,导致进行系统的检测漏洞是十分困难的。此外,当获得固件后如何解密固件、解密固件后由于不同固件适用于不同的底层体系结构(内存布局、指令集等)使得无法获取NVRAM参数,这将导致在仿真器中运行的程序频繁崩溃。

解决思路

与传统嵌入式设备不同,大多数物联网设备都会提供给用户一个可以在手机上运行的移动应用APP来控制物联网设备。这样的APP可以认为是物联网设备的手机端控制面板,其内部包含丰富的设备信息,且可以与物联网设备固件直接(通过Wi-Fi、蓝牙等)或间接(通过云服务)通信。

文章提出IoT Fuzzer,运行协议指导的模糊测试,利用上述APP进行测试。通过执行动态分析识别应用中形成的将要传递给target设备的消息的内容,并自动在运行测试时改变这些内容,实现使用APP的程序逻辑来产生有意义的测试用例,进而对目标固件进行模糊测试。

挑战

  1. 自动更改协议字段
    如果工具了解协议格式,则其可以将有效输入和容易被程序拒绝的无效输入区分开来,虽然用公开的协议格式模糊消息比较容易,但当面对未知协议时却非常困难。因此需要工具能够自动识别协议字段并对其进行模糊。
  2. 处理加密消息
    如果物联网设备和应用程序之间的通信是加密的,则测试工具也要以相同的加密算法(使用相同的密钥)发送加密的测试消息。识别应用程序中使用的加密算法可能需要大量的逆向工作。因此,需要工具能够重用应用程序中的消息加密功能。
  3. 监控崩溃
    由于无法在本地监视物联网设备系统中运行的进程,当测试消息导致程序崩溃时,我们无法知道物联网设备的实时状态,另一方面,物联网设备可能具有不同的应用层协议和不同的异常处理机制,导致难以根据错误消息的语义自动识别崩溃。另外,有的物联网设备可能内置看门狗程序能够在发生崩溃后恢复。因此,需要设计一种有效的机制来自动监控远程设备状态。

方案

  1. 在数据源处修改协议字段
    由于对未知协议进行逆向工程是非常繁琐复杂且可能毫无收获的,论文提出可以在数据源处改变在协议消息中使用的数据,相应的,根据程序逻辑,可以断言,这些变异的字符串最终会变成协议字段。
  2. 运行时宠用加密函数
    由于在数据源处修改数据,因此正常的程序将会对其进行正常加密并做其他的准备。因此,工具无需重新实现加密逻辑。
  3. 通过心跳机制监测状态
    虽然无法在本地监控正在运行的设备状态,但可以通过发送心跳消息来推断程序或系统是否处于活动状态。

实现细节

IoT Fuzzer 分为两个阶段,APP分析阶段和模糊测试阶段。

APP 分析阶段

以物联网APP为输入,分析触发网络事件的UI操作,跟踪应用协议字段的传播过程,IoT Fuzzer记录了所有协议字段和对应的变异函数。

UI分析

执行静态分析,确定最终会导致发送消息的UI元素,将不同活动中的UI元素与目标网络API相关联。

首先使用 Androguard 构建应用程序调用图,从目标网络通信API开始,构造到UI事件处理程序的反向代码路径。由于存在隐式控制流,需要列出并添加由系统EdgeMiner获得的隐式边。

然后构造活动转换图,使得在模糊测试过程中能触发特定活动发送消息。使用 Monkeyrunner 通过基于事件执行顺序的简单策略与每个活动中的UI元素进行交互,以此获得一系列UI事件以及触发顺序。同时记录将当前活动转化为另以活动的事件。对每个活动中的事件,根据调用路径过滤掉不会导致消息发送的事件。

数据流分析

由于物联网APP中的命令消息通常是使用硬编码字符串、用户输入或系统API构建的,所以采用修改版本的TaintDroid动态污点跟踪技术识别这些数据。论文指出,只需在数据第一次被使用时修改他们,就可以达到改变发送消息字段的目的,于是可以记录受污染数据作为参数的函数,在这些函数处对字段进行变异。

模糊测试阶段

测试框架使用动态的插桩改变感兴趣的协议字段,并监视物联网设备的崩溃。

运行时变异

通过污点跟踪,识别处协议字段及其经过的相应函数,动态地hook记录的函数并在运行时改变协议字段参数以生成测试消息。具体方法是获得记录的函数的唯一集合,然后将其与Xposed框架动态hook,

为了调度模糊测试过程,使用如下算法将突变分配给函数参数。对于每条消息,一般无需变异所有字段,所以需要随机选择要变异的字段子集。首先确定消息中的字段数c,随机生成一个小于c的数s作为变异总数,然后找一组解满足$t_1+t_2+\dots+t_n=s$,其中$t_i$是给第$i$​函数的变异量。

模糊规则,采用如下启发式突变规则来实现具有结构和类型信息的模糊策略。

  1. 更改基于栈或基于堆的溢出和出界访问的字符串长度。
  2. 更改整数溢出和越界访问的整数值、双精度值或浮点值。
  3. 更改数据类型,或提供空值来测试未初始化的变量漏洞。

响应监测

对于基于TCP的连接,通过查看连接状态来推断系统是否崩溃。

对于基于UDP的连接,如果程序崩溃,不会向APP发回响应,需要确定这个无响应是由崩溃引起的还是仅仅是内部系统对于错误的处理。论文采用心跳插入机制,区分这两种情况。

局限

测试范围,虽然IoT Fuzzer实现了很高的协议覆盖率,但对于固件的代码覆盖率和攻击面覆盖率是有限的。IoT Fuzzer主要关注点是内存漏洞,没有关注其他漏洞如绕过身份验证漏洞。

连接方式,论文中的实现只关注与手机APP有Wi-Fi连接的设备,论文指出将实现扩展到蓝牙、Zigbee等并没有技术障碍。

云中继,论文指出对于云作为消息中继、代理的物联网设备,APP请求消息将被发送到云端,然后再返回给物联网设备。这样的消息可能被云过滤从而影响IoT Fuzzer的运作。

结果判断,IoT Fuzzer 无法生成内存漏洞的类型和根本原因,需要一些人工的努力才能定位漏洞。

结果准确性,IoT Fuzzer存在假阳和假阴。

思考

工业受控系统大多有专业的控制软件,可以借鉴 IoT Fuzzer 的思想,对控制软件进行hook,进而生成测试消息。

🔲 ⭐

区块链常见隐私保护机制的总结分析

区块链提供了一种挂在开放环境中存储信息、执行事务、执行功能和建立信任的方法. 许多人认为区块链是密码学和网络安全领域的技术突破, 例如在全球范围部署的比特币等加密货币系统、智能合约、物联网上的智能电网等. 本文对区块链中常见的隐私保护技术进行了全面的概述, 我们首先给出了区块链应用程序所需的隐私属性, 然后我们介绍了基于区块链系统中实现这些隐私属性的保护机制, 包括混合协议、匿名签名、非交互式零知识证明等.

区块链技术是在开放的网络系统中, 在没有中央权威机构的情况下进行安全计算的突破. 从数据管理的角度来看, 区块链是一个分布式数据库, 它通过将事务记录组织成一个分层的块链来记录不断变化的事务记录列表. 从安全的角度来看, 区块链是通过点对点覆盖网络创建和维护的, 并通过智能和分散使用加密和人群计算来保护.

对区块链的隐私性的研究主要集中在两个方面: (1) 发现迄今为止基于区块链的系统所遭受的一些攻击; (2) 提出具体的建议, 使用一些最先进的对策来对付一部分此类攻击.

我们将本文的其余部分组织如下. 第1节描述了区块链系统所需要的隐私属性. 第2节讨论了目前在区块链上使用的隐私保护技术.

区块链应用所需的隐私属性

区块链例如比特币可用来进行在线交易, 这对其提出了许多安全和隐私要求. 本节中我们描述了区块链应用所需的隐私属性.

在线交易的隐私要求

我们简单的讲在线交易的隐私要求分为以下三类:

交易的保密性

在大多数的在线交易中, 用户希望在网上交易系统中对他们的交易和账户信息尽可能少的泄漏. 这一要求包括: (1) 用户的交易信息不能被任何未经授权的用户访问; (2) 未经用户允许, 系统管理员或网络参与者不可以将用户信息泄露给他人; (3) 所有用户数据应被一致和安全地存储和访问, 即使发生意外故障或恶意网络攻击也必须保证数据的安全性和一致性.

用户身份的匿名性

用户可能在多个银行、金融机构拥有独立的账户, 而各个银行、金融机构间很难有效、互信的共享用户数据, 这使得识别用户的多重身份很难做到, 并且某些受信中介可能会背叛用户导致用户身份匿名性被破坏. 另外, 在交易中, 交易的一方或者双方可能不想让另一方知晓自己的身份, 这也是对在线交易匿名性的要求.

交易的不可追踪性(不可联系性)

用户在交易中匿名的同时, 也会要求交易具有不可追踪性. 否则, 一旦与用户相关的交易被挖掘出来, 就会严重破坏用户的隐私, 例如推断出用户的账户余额、其常见的交易对象与频率等. 这些信息可以有效的被竞争者利用进而推断出用户的真实身份.

区块链的隐私属性

现有的区块链隐私属性是基于密码学的发展和比特币的设计与实现, 下面我们介绍一些区块链应用中需要的隐私属性.

假名

假名指一种伪装身份, 在比特币中, 区块链中的地址是网络中一个用户公钥的哈希值, 用户可以通过使用公钥哈希与比特币系统进行交互, 以此来保护自己的真实姓名. 所以用户使用的地址可以视为一个伪身份, 这样从某种角度上, 假名就成为了一种保护用户真实身份的隐私财产. 另一方面, 用户可以拥有任意多公钥, 也就拥有任意多的假名, 如同在银行中开多个账户. 假名可以通过公钥的方式达到较弱的匿名性, 但仍然存在泄漏身份信息的危险.

不可链接性

不可链接性是指无法高度可信地描述系统的两个观测值或两个观测实体之间的关系. 上面提到, 比特币中的区块链通过提供伪身份来保障用户的匿名性, 但伪身份(假名)不能提供交易的不可链接性保护. 我们可以想象, 如果用户只使用伪身份与系统进行交互, 那么只有同时保证匿名性和不可链接性才可以确保用户的完全匿名性. 当不可链接性没有得到满足时, 攻击者很容易发起反匿名推理攻击, 当掌握足够多的背景知识时攻击者将有很大概率获知用户的真实身份[1]. 例如在比特币系统中, 由于每笔交易和其涉及的发送者和接收者的地址都被记录在区块链中, 任何人可以遍历整个区块链获得某个交易地址的所有数据, 进而对用户进行追踪. 更严重的是, 有能力的攻击者可以将IP地址与交易连接起来, 这进一步的损害了用户的隐私.

交易的保密性和数据的隐私性

区块链的数据隐私是指区块链可以为存储在其上的所有数据或某些敏感数据提供机密性的属性. 虽然区块链最初是作为数字货币系统设计的, 但其应用场景要比虚拟货币广泛的多. 例如, 区块链已可以用于管理智能合约、有版权的作品以及商业或组织注册表的数字化. 所以, 所有的区块链应用中常见的一个安全属性就是交易信息的机密性, 例如交易内容和地址. 显而易见的是比特币系统不支持这个安全属性, 在比特币系统中, 交易内容和地址是完全公开的. 我们认为, 这个安全属性将有助于降低昵称与真实用户身份之间链接的可能性. 这可能有助于促进从整个区块链的公开可见到基于need-to-know的共享的转变.

区块链所使用的隐私和安全技术

在本节中, 我们将详细讨论可用于增强现有和未来区块链系统的安全性和隐私性的一系列技术.

混合协议

用户的匿名性并不能在比特币交易中得到保障. 用户在交易时虽然使用匿名地址, 但这个地址可以公开验证. 因此, 任何人都可以简单地分析出用户在比特币交易中使用的地址, 由此可将用户的交易与其他交易, 甚至与其真实身份联系起来, 从而泄露其所有交易. 我们可以使用混合服务加以避免. 从字面上讲, 混合是指随机将用户的比特币与其他用户的比特币进行交换, 这样旁观者就无法知道谁拥有这些比特币. 但这样还是不能防止硬币被盗. 我们将描述两种混合服务, 并分析它们的安全性和隐私属性.

混合币(Mixcoin)

混合币[2]由Bonneau等人于2014年提出, 这种货币能实现比特币和类比特币等加密货币的匿名支付. 混合币扩展了匿名设置, 允许所有用户同时混合硬币, 同时还提供了类似于传统混合通信的匿名性. 此外, 混合币使用了一种问责机制来检测盗窃行为, 用户可以通过调整激励机制, 合理使用混合币, 而无法偷窃比特币.

CoinJoin

CoinJoin[3]提出于2013年, 这是比特币交易的另一种匿名方法. 假设用户想要付款, 首先他找到另一个同样想付款的用户, 通过协商在一笔交易中共同付款. 这种联合支付的方式可以降低在一个在交易, 跟踪某个用户输入、输出及其确切资金流动方向的概率.

这需要用户与希望加入支付的人协商交易. 提供这一功能的第一代混合服务(如SharedCoin[4])使用了集中式服务器, 要求用户信任服务运营商, 不窃取或允许他人窃取比特币. 由于它们将保留交易日志并记录联合支付的所有参与者, 这种集中式服务仍可能存在泄露用户隐私的风险.

此外, CoinJoin实现也会降低匿名性. Kristov Atlas开发了一个名为“CoinJoin数独”的工具[5], 它可以识别SharedCoin交易并发现特定支付和收款人之间的关系, 这表明SharedCoin混合服务不能为交易提供很强的隐私性.

CoinShuffle[6]是由Tim Ruffing等人在2014年提出的, CoinShuffle是一种完全分散的硬币混合协议, 它进一步扩展了CoinJoin的概念, 通过在混合交易时避开了第三方, 从而确保防盗安全. 为了确保匿名性, CoinShuffle使用了一种新型可问责匿名组通信协议, 称为Dissent[7].

匿名签名

部分数字签名方案本身就具备匿名性, 这种签名方案可称为匿名签名. 其中, 最重要和最典型的匿名签名方案为群签名和环签名.

群签名

群签名首次提出于1991年[8]. 在一个组的任何成员都可以使用自己的私钥为整个组匿名签名, 任何拥有该组公钥的成员都可以检查和验证生成的签名, 并确认消息是由组员签名的. 在验证中, 只能确定签名者是否为组员而无法知道其真实身份.

群签名有一个群管理员, 负责管理添加组成员、处理争议事件等. 在区块链系统中, 我们需要一个权威实体创建和撤销群组, 动态添加或撤销群成员的成员资格.

环签名

环签名[9]还可以通过一组用户中的任何成员签名来实现匿名性. 在环匿名里, 很难确定组中的哪个成员使用其密钥对消息进行签名.

环签名的一个典型应用是CryptoNote[10]. CryptoNote利用其他几个密钥设计出发送者的公钥, 这样就无法知道谁实际签名. 由于使用环签名, 如果环成员数为n, 则对手猜出交易真正发送者的概率只有1/n. 2015年, 以太坊使用了环签名, 这使得用户具有匿名性[11].

环签名与群签名的区别主要有两点:第一, 由于环签名中没有群管理器, 因此在发生争议时无法获知签名者的真实身份. 第二, 任何用户都可以自己分组一个“环”. 因此, 环签名适用于公共区块链.

同态加密

同态加密 (Homomorphic Encryption, HE) 是一种非常强大的加密技术. 它可以直接在密文上执行某些类型的计算, 并确保对加密后的数据进行解密时, 计算结果必须与对明文进行相同的操作得到的结果相同.

利用同态加密处理数据的过程如图1所示:
图1: 同态加密处理数据的过程

  1. 用户对数据进行加密. 并把加密后的数据发送给云;
  2. 用户向云提交数据的处理方法, 这里用函数f来表示;
  3. 云在函数f下对数据进行处理, 并且将处理后的结果发送给用户;
  4. 用户对数据进行解密, 得到结果.

使用同态加密技术在区块链上存储数据不会显著的影响区块链属性. 这可确保对区块链上的数据进行加密, 解决与公共区块链相关的隐私问题. 使用同态加密技术提供隐私保护, 并允许通过公共区块链随时访问加密数据, 以便进行审核和其他目的, 例如管理员工开支. 以太坊智能合约为了获得更好的隐私性, 对存储在区块链中的数据采用的就是同态加密.

基于属性的加密

基于属性的加密 (Attribute-based encryption, ABE) 是一种加密方法. 如果用户的属性与密文的属性一致, 则可以使用用户的密钥解密加密数据. 抗共谋性是基于属性加密技术的一个重要安全特性. 它确保当恶意用户与其他用户串通时, 除可以用自己的私钥解密的数据之外不能访问其它数据. 基于属性的加密概念是在2005年提出的[12]. 从那时起, 许多人提出了对ABE的扩展, 包括拥有多个权限来共同生成用户私钥, 以及支持任意谓词的ABE模式.

基于属性的加密非常强大, 但由于缺乏对核心概念和有效实现的理解, 目前很少有应用程序部署它. 到目前为止, ABE还没有在区块链上部署任何形式的实时操作. 2011年, 一个分散的ABE模式提议[13]在区块链上使用ABE. 例如, 在区块链上, 权限可以通过访问令牌的所有权来表示. 网络中的所有节点都被授予与该令牌相关的特殊权限和特权. 令牌提供了一种跟踪谁具有某些属性的方法, 这种跟踪应该由分发令牌的权威实体以一种算法和一致的方式完成. 令牌可以被视为代表属性或资格的徽章, 并且应该作为不可转移的属性使用.

基于属性的加密不需要一个固定的权威. 在一个去中心化的网络中, 可能有多个权限并且完成相同的任务. 例如, 依靠证人来发挥这些权威的作用是可能的. 在区块链中, 随着SAFE网络等最近可能实现的技术, 利用区块链方法实现基于属性的加密仍然是一个公开的挑战.

安全多方计算

安全多方计算 (Secure Multi-Party Computation, SMC) 是解决一组互不信任的参与方之间保护隐私的协同计算问题, SMC要确保输入的独立性、计算的正确性、去中心化等特征, 同时不泄露各输入值给参与计算的其他成员. 主要是针对无可信第三方的情况下, 如何安全地计算一个约定函数的问题, 同时要求每个参与主体除了计算结果外不能得到其他实体任何的输入信息. 安全多方计算在电子选举、电子投票、电子拍卖、秘密共享、门限签名等场景中有着重要的作用.
图 2: 安全多方计算技术框架
安全多方计算最早是由华裔计算机科学家、图灵奖获得者姚启智教授通过百万富翁问题提出的. 该问题表述为:两个百万富翁Alice和Bob想知道他们两个谁更富有, 但他们都不想让对方知道自己财富的任何信息. 在双方都不提供真实财富信息的情况下, 如果比较两个人的财富多少, 并给出可信证明.
安全多方计算技术框架如图2所示.

各个MPC参与节点地位相同, 可以发起协同计算任务, 也可以选择参与其他方发起的计算任务. 路由寻址和计算逻辑传输由枢纽节点控制, 寻找相关数据同时传输计算逻辑. 各个MPC节点根据计算逻辑, 在本地数据库完成数据提取、计算, 并将输出计算结果路由到指定节点, 从而多方节点完成协同计算任务, 输出唯一性结果. 整个过程各方数据全部在本地, 并不提供给其他节点, 在保证数据隐私的情况下, 将计算结果反馈到整个计算任务系统, 从而各方得到正确的数据反馈.

非交互式零知识

零知识证明是由S. Goldwasser、S. Micali及C. Rackoff在20世纪80年代初提出的[14]. 它指的是证明者能够在不向验证者提供任何有用的信息的情况下, 使验证者相信某个论断是正确的. 零知识证明实质上是一种涉及两方或更多方的协议, 即两方或更多方完成一项任务所需采取的一系列步骤. 证明者向验证者证明并使其相信自己知道或拥有某一消息, 但证明过程不能向验证者泄漏任何关于被证明消息的信息.
图 3: Fiat-Shamir 变换
在区块链领域, 用户信奉着“Don't trust, verify it”(别相信, 验证它)的理念. 所有参与的节点都需要独立验证区块链上的数据是否正确, 如果我们要求证明者时刻在线等待着所有参与节点发起的挑战, 那么只有极少服务器可以作为证明者参与这一过程, 普通人将难以使用该系统. 因此, 我们需要通过承诺计算得到公开可信的随机数列. 实际上, 利用hash函数结果的随机性, 我们可以将承诺数据的hash计算结果作为随机数列用于生成挑战, 这也就是 Fiat-Shamir 变换, 具体过程图3所示.

基于可信执行环境(TEE)的智能合约

如果一个执行环境为应用程序的执行提供了一个完全隔离的环境, 可以有效地防止其他软件应用程序和操作系统篡改和了解在环境中运行的应用程序的状态, 那么这个执行环境就被称为可信执行环境(TEE). 英特尔软件保护扩展(SGX)是一个TEE实现的代表性技术. 例如, Ekiden[15]是一个基于SGX的保密智能合同解决方案. Ekiden将协商与共识分开, 在链外的计算节点上执行智能合约计算, 然后使用远程证明协议验证链上计算节点运行的正确性. 共识节点用于维护区块链, 不需要使用可信硬件. Enigma[16]在其当前版本中利用TEE, 允许用户使用分散的信用评分算法创建保护隐私的智能合同, 对信用评分的多个因素进行加权, 如账户的数量和类型、支付历史和信用利用.

基于游戏的智能合约

最近基于游戏的智能合同验证解决方案是发展迅速, TrueBit[17]和Arbitrum[18]是这种智能合约的代表. TrueBit使用交互式“验证游戏”来判断计算任务是否正确执行, 提供奖励来鼓励玩家检查计算任务并发现错误, 这样智能合约就可以安全地执行具有可验证属性的计算任务. 此外, 在每一轮“验证游戏”中, 验证者递归地检查一个越来越小的计算子集, 这大大降低了TrueBit节点的计算负担. Arbitrum设计了一种激励机制, 让各方达成离线协议, 这样只需要验证者验证合同的数字签名. 对于试图对虚拟机行为撒谎的不诚实方, Arbitrum设计了一个有效的基于挑战的协议来识别和惩罚不诚实方, 链外验证激励机制显著提高了智能合约的可扩展性和隐私性.

讨论

我们在表1中总结了上述每种技术的优缺点. 我们认为,在不同的环境中实现安全性和隐私性可以用不同的技术手段, 即: (1)没有一种技术是解决区块链安全性和隐私性的万能钥匙. (2)没有一种技术是十全十美的. (3)需要在安全、隐私与效率之间做权衡. 所以, 应该因地制宜的结合安全隐私需求与应用程序上下位来选择适当的安全和隐私技术, 且组合多种技术在大多数情况下都有更好的效果. 但同时也要注意到, 当引入新的技术时, 总会不可避免的引起其它问题或带来新的缺陷, 这都需要设计者做好评估与权衡.

技术应用优点缺点
混合混合币, Coinjoin防止用户地址被链接起来集中服务存在泄露用户隐私的风险
群签名PlatON签名者的身份可以隐藏在一组用户中, 在发生争议时, 签名者的身份可以被披露.需要一个可信第三方作为管理者.
环签名CryptoNote, Monero, Ethereum签名者的身份可以隐藏在一组用户中, 不需要可信第三方.在发生争议时, 签名者的身份无法被披露.
ABE可同时实现数据机密性和细粒度的访问控制.分布式环境中属性证书的颁发和撤销需要解决.
HEEthereum可通过直接对密文进行计算来保护隐私.只有一类操作可以有效地实现, 复杂函数的计算效率很低.
SMPCEnigma允许多方在私有数据上共同执行计算而不侵犯它们输入的隐私.只支持简单的函数, 复杂函数效率较低.
NIZKZcash用户可以很容易的证明有足够余额与NIZK转账, 而无需透露账户余额.效率较低.
基于TEE的智能合约Ekiden, Enigma可以通过TEE运行智能合约来保护隐私.计算节点需配备带有TEE的CPU如Intel的SGX. 仍需解决对SGX的攻击.
基于游戏的智能合约TrueBit, Arbitrum鼓励各方通过激励机制验证智能合约的正确性.仍然存在被恶意用户欺骗的风险.

结论

在本文中, 我们首先给出了在线交易背景下区块链应用所需的安全属性, 其次, 我们介绍了8种在基于区块链的系统中满足这些隐私要求的技术, 包括混合协议、匿名签名、同态加密、基于属性的加密、安全多方计算、非交互式零知识证明和智能合约的安全验证等. 随着技术的日益发展, 区块链越来越受到学术研究和业界的关注, 其安全性和隐私性吸引了巨大的兴趣. 我们认为, 深入了解区块链的安全和隐私属性, 有助于提高区块链的可信程度, 促进区块链领域中的技术创新.

参考文献

[1] Arvind Narayanan, Joseph Bonneau, Edward Felten, Andrew Miller, and Steven Goldfeder. 2016. Bitcoin and Cryptocurrency Technologies: A Comprehensive Introduction.
[2] Joseph Bonneau, Arvind Narayanan, Andrew Miller, Jeremy Clark, Joshua A. Kroll, and Edward W. Felten. [n.d.]. Mixcoin: Anonymity for Bitcoin with Accountable Mixes. 486–504
[3] Gregory Maxwell. 2013. CoinJoin: Bitcoin privacy for the real world. Retrieved from bitcointalk.org.
[4] H. Moniz, N. F. Neves, M. Correia, and P. Verissimo. [n.d.]. Experimental comparison of local and shared coin randomized consensus protocols. In SRDS 2006. 235–244
[5] Kristov Atlas. [n.d.]. CoinJoin Sudoku: Weaknesses in SharedCoin.
[6] Tim Ruffing, Pedro Moreno-Sanchez, and Aniket Kate. [n.d.]. CoinShuffle: Practical Decentralized Coin Mixing for Bitcoin. 345–364.
[7] Henry Corrigan-Gibbs and Bryan Ford. [n.d.]. Dissent: Accountable Anonymous Group Messaging. 340–350.
[8] David Chaum and Eugène van Heyst. [n.d.]. Group Signatures. 257–265.
[9] Ronald L. Rivest, Adi Shamir, and Yael Tauman. [n.d.]. How to Leak a Secret. 552–565.
[10] Nicolas van Saberhagen, Johannes Meier, Antonio M. Juarez, and Max Jameson. 2012. CryptoNote Signatures.
[11] [n.d.]. Monero. Retrieved from http://www.getmonero.org.
[12] Amit Sahai and Brent Waters. [n.d.]. Fuzzy Identity-Based Encryption. 457–473.
[13] Allison Lewko and Brent Waters. [n.d.]. Decentralizing attribute-based encryption. In EUROCRYPT 2011. 568–588.
[14] S. Goldwasser, S. Micali, and C. Rackoff. [n.d.]. The knowledge complexity of interactive proof-systems. In STOC 1985. 291–304.
[15] Raymond Cheng, Fan Zhang, Jernej Kos, Warren He, Nicholas Hynes, Noah M. Johnson, Ari Juels, Andrew Miller, and Dawn Song. 2018. Ekiden: A platform for confidentiality-preserving, trustworthy, and performant smart contract execution. CoRR abs/1804.05141 (2018).
[16] Guy Zyskind, Oz Nathan, and Alex Pentland. 2015. Enigma: Decentralized computation platform with guaranteed privacy. Comput. Sci. (2015).
[17] Jason Teutsch and Christian Reitwießner. 2017. TrueBit: A scalable verification solution for blockchains.
[18] Harry Kalodner, Steven Goldfeder, Xiaoqi Chen, S. Matthew Weinberg, and Edward W. Felten. [n.d.]. Arbitrum: Scalable, private smart contracts. In USENIX Security 2018. 1353–1370.

🔲 ☆

制作 Debian12 全自动安装镜像

制作 Debian12 全自动安装镜像

参考 附录 B. 使用预置自动进行安装DebianInstaller/Preseed/EditIso - Debian Wiki

准备工作

安装依赖包

安装 genisoimage, xorriso, qemu-system, 分别用于制作镜像,解压镜像和测试镜像

apt install genisoimage xorriso qemu-system

下载镜像

wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.1.0-amd64-netinst.iso

解压镜像

xorriso -osirrox on -indev debian-12.*.iso -extract / isofiles

制作全自动安全镜像

创建自动安装配置文件

将下面的内容保存为 preseed.cfg。可以看到root密码是123123,非常简单,但我关闭了ssh的密码登录,只能公钥登录,所以不用担心。

#_preseed_V1
d-i debian-installer/language string en
d-i debian-installer/country string CN
d-i debian-installer/locale string en_US.UTF-8
d-i keyboard-configuration/xkb-keymap select us
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string debian
d-i netcfg/get_domain string 
d-i netcfg/wireless_wep string
d-i mirror/protocol string http
d-i mirror/country string maunal
d-i mirror/http/hostname string mirrors.pku.edu.cn
d-i mirror/http/directory string /debian/
d-i mirror/http/proxy string
d-i passwd/root-login boolean true
d-i passwd/make-user boolean false
d-i passwd/root-password password 123123
d-i passwd/root-password-again password 123123
d-i clock-setup/utc boolean true
d-i time/zone string Asia/Shanghai
d-i clock-setup/ntp boolean true
d-i partman-auto/method string regular
d-i partman-auto-lvm/guided_size string max
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i partman-md/confirm boolean true
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i apt-setup/cdrom/set-first boolean false
d-i apt-setup/non-free boolean true
d-i apt-setup/contrib boolean true
d-i apt-setup/disable-cdrom-entries boolean true
d-i apt-setup/services-select multiselect security, updates, backports
d-i apt-setup/enable-source-repositories boolean false
d-i apt-setup/security_host string security.debian.org
d-i debian-installer/allow_unauthenticated boolean false
tasksel tasksel/first multiselect standard, ssh-server
d-i pkgsel/include string vim curl wget git htop sudo tmux qemu-guest-agent
d-i pkgsel/upgrade select full-upgrade
popularity-contest popularity-contest/participate boolean false
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i grub-installer/bootdev  string default
d-i finish-install/reboot_in_progress note
d-i cdrom-detect/eject boolean true
d-i preseed/late_command string \
  in-target mkdir -p /target/root/.ssh; \
  in-target chmod 700 /target/root/.ssh; \
  in-target chown root:root /target/root/.ssh; \
  >>/target/root/.ssh/authorized_keys echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCtWqoD3y180ud5b9JcTkeX3Xl7GqvB1BD9AjEQxHqiXXYgzS52ZOMEjXB6OF5RRY7QhP1UnziaEru9At2Sw0sDwwrsUYLDpVIAM9fqpiLxbuuoUJTlG20uR3KoIXvwYheFV/g0Dp88F1bBtxs1ER52rW5JXaycNaaOwvNXM4Zv53h8g2by52tRavp1AxABQ+gZo3Ptl9LeNoJY0s7NsNmQe4rsvxO6suJBM6lf7bnT6ZqcXHCa5T5Co1BbD2omaTSK243LzRCRG+cMuSfew5szRmAFAVVdpcHhvNTg8ZjNi+ZIfrbG282u/XzIuDjoh35UrmrmZYd4JxyaUpGmzG1MkbmkrjtpIcqr6byatHRXIXdh2JAEXZtkqUUVwwreVdSIo4nbPWUWNP9PJp+OcYO0NmN8L0CLI2HKsNp4SAJ89i1SRTOPn0p2M+HF3Se6YjDGvYpOiTvF9Udqgp94+Ux9xIPdSPsI+k6AdxrNI2YI9IsW8nQtVjD9VJVFwD92xWc0sM7FB2/7E28P4SKvzBrgEuheeOdkD0y8J0DOOwZo3HC2LkfpMYC8RY9gZBhwc5zq/56EEkc5xY0+fx3CO9tTOMVnwJXsvanqofAXJyPqspnQewAUKhTH75B0XDetom3CLyh51F7kzSMvKiFLBYhGfGsuRoNWO4iBG783mmBaXQ== cardno:18_042_835; \
  >>/target/etc/ssh/sshd_config.d/root.conf echo PermitRootLogin prohibit-password; \
  >>/target/etc/ssh/sshd_config.d/root.conf echo PasswordAuthentication no; \
  >>/target/etc/ssh/sshd_config.d/root.conf echo PubkeyAuthentication yes; \
  >>/target/etc/ssh/sshd_config.d/root.conf echo AuthorizedKeysFile .ssh/authorized_keys;

将预设文件添加到Initrd

chmod +w -R isofiles/install.amd/
gunzip isofiles/install.amd/initrd.gz

echo preseed.cfg | cpio -H newc -o -A -F isofiles/install.amd/initrd

gzip isofiles/install.amd/initrd
chmod -w -R isofiles/install.amd/

修改启动屏幕超时

修改启动参数

vim isofiles/isolinux/isolinux.cfg

默认超时为0,这表示一直等待到用户操作,这里超时改为5秒,表示5秒之后自动开始安装

#timeout 0
timeout 5

修改启动屏幕默认菜单

默认菜单选项是图形化安装,这里取消默认,修改图形化安装菜单

vim isofiles/isolinux/gtk.cfg

将内容改成以下

label installgui
        menu label ^Graphical install
        kernel /install.amd/vmlinuz
        append vga=788 initrd=/install.amd/gtk/initrd.gz --- quiet

把文本安装改为默认菜单选项,修改文本安装菜单

vim isofiles/isolinux/txt.cfg

将内容改成以下

default install
label install
        menu label ^Install
        menu default
        kernel /install.amd/vmlinuz
        append vga=788 initrd=/install.amd/initrd.gz --- quiet
timeout 300
ontimeout /install.amd/vmlinuz vga=788 initrd=/install.amd/initrd.gz --- quiet
menu autoboot Press a key, otherwise txt auto install will be started in # second{,s}...

删除默认30s倒计时启动项

rm isofiles/isolinux/spkgtk.cfg

重新生成md5sum.txt

cd isofiles
chmod +w md5sum.txt

find -follow -type f ! -name md5sum.txt -print0 | xargs -0 md5sum > md5sum.txt

chmod -w md5sum.txt
cd ..

创建新的可启动ISO镜像

genisoimage -r -J -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -o preseed-debian-12.1.0-amd64-netinst.iso isofiles

此时已经制作好了全自动安装镜像,可以用虚拟机测试一下,测试成功则可以传到服务器上进行安装了。

一键脚本

在当前目录准备好 Debian 安装镜像,preseed.cfg配置文件,以及修改后的 gtk.cfg, txt.cfg, isolinux.cfg 文件,然后执行下面的脚本即可。

#/bin/bash

set -ex

rm -rf isofiles
xorriso -osirrox on -indev debian-12.1.0-amd64-netinst.iso -extract / isofiles

chmod +w -R isofiles/install.amd/
gunzip isofiles/install.amd/initrd.gz

echo preseed.cfg | cpio -H newc -o -A -F isofiles/install.amd/initrd

gzip isofiles/install.amd/initrd
chmod -w -R isofiles/install.amd/

cp ./isolinux.cfg isofiles/isolinux/isolinux.cfg
cp ./gtk.cfg isofiles/isolinux/gtk.cfg
cp ./txt.cfg isofiles/isolinux/txt.cfg
rm isofiles/isolinux/spkgtk.cfg

cd isofiles
chmod +w md5sum.txt
find -follow -type f ! -name md5sum.txt -print0 | xargs -0 md5sum > md5sum.txt
chmod -w md5sum.txt
cd ..

genisoimage -r -J -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -o preseed-debian-12.1.0-amd64-netinst.iso isofiles
🔲 ⭐

Handsome 主题升级 todo

本文记录对各种插件 handsome 主题做的魔改

user-agent

松鼠大大 开发的一款插件,这里搬运一下防止源失效。

修改 Handsome 主题,component 目录下的 comments.php 代码文件,

<span class="comment-author vcard">
    <b class="fn"><?php echo $author; ?></b>
    <?php if (!($GLOBALS["off_star_comment"] == "true" || Utils::getExpertValue("off_star_comment"))):?>
    <a data-coid="<?php echo $comments->coid; ?>" class="post-comment-star text-muted star_talk"><i class="glyphicon <?php
        $stars = Typecho_Cookie::get('extend_say_stars');
        if (empty($stars)) {
            $stars = array();
        } else {
            $stars = explode(',', $stars);
        }

        if (!in_array($comments->coid, $stars)) {
            echo 'glyphicon-heart-empty';
        } else {
            echo 'glyphicon-heart';
        }
        ?>"></i>&nbsp;<span class="star_count"><?php
            $stars = $comments->stars;
            echo ($stars) ? $stars : "";
            ?></span></a>
    <?php endif; ?>
</span>

添加代码<?php UserAgent_Plugin::get_useragent($comments->agent,$comments->ip); ?>,修改后代码如下:

<span class="comment-author vcard">
    <b class="fn"><?php echo $author; ?></b>
    <?php if (!($GLOBALS["off_star_comment"] == "true" || Utils::getExpertValue("off_star_comment"))):?>
    <a data-coid="<?php echo $comments->coid; ?>" class="post-comment-star text-muted star_talk"><i class="glyphicon <?php
        $stars = Typecho_Cookie::get('extend_say_stars');
        if (empty($stars)) {
            $stars = array();
        } else {
            $stars = explode(',', $stars);
        }

        if (!in_array($comments->coid, $stars)) {
            echo 'glyphicon-heart-empty';
        } else {
            echo 'glyphicon-heart';
        }
        ?>"></i>&nbsp;<span class="star_count"><?php
            $stars = $comments->stars;
            echo ($stars) ? $stars : "";
            ?></span></a>
    <?php endif; ?>
    <?php UserAgent_Plugin::get_useragent($comments->agent,$comments->ip); ?>
</span>

随机头图

修改 Handsome 主题,libs/content 目录下的 CommonContent.php 代码文件,

    /**
     * 处理具体的头图显示逻辑:当有头图时候,显示随机图片还是第一个附件还是一张图片还是thumb字段
     * @param $widget $this变量
     * @param int $index
     * @param $howToThumb 显示缩略图的方式,0,1,2,3
     * @param $thumbField thumb字段
     * @return string
     */
    public static function whenSwitchHeaderImgSrc($widget, $index, $howToThumb, $thumbField,$extra_content= "")
    {

        if ($howToThumb == '4'){//该种方式解析速度最快
            if (!empty($thumbField)) {
                return $thumbField;
            } else {
                return "";
            }
        }
        $randomNum = unserialize(INDEX_IMAGE_ARRAY);

        // 随机缩略图路径
        $random = STATIC_PATH . 'img/sj/' . @$randomNum[$index];//如果有文章置顶,这里可能会导致index not undefined
        $pattern = '/\<img.*?src\=\"(.*?)\"[^>]*>/i';
        $patternMD = '/\!\[.*?\]\((http(s)?:\/\/.*?(jpg|png|JPEG|webp|jpeg|bmp|gif))/i';
        $patternMDfoot = '/\[.*?\]:\s*(http(s)?:\/\/.*?(jpg|png|JPEG|webp|jpeg|bmp|gif))/i';

        if ($howToThumb == '0') {
            return $random;
        } elseif ($howToThumb == '1' || $howToThumb == '2') {

            if (!empty($thumbField)) {
                return $thumbField;
            }

            //解析附件
            if ($widget!=null){
                $attach = @$widget->attachments(1)->attachment;
                if ($attach != null && isset($attach->isImage) && $attach->isImage == 1) {
                    return $attach->url;
                }
            }

            if ($widget != null){
                //解析文章内容,这个是最慢的
                $content = $widget->content;
            }else{
                $content = $extra_content;
            }


            if (preg_match_all($pattern, $content, $thumbUrl)) {
                $thumb = $thumbUrl[1][0];
            } elseif (preg_match_all($patternMD, $content, $thumbUrl)) {
                $thumb = $thumbUrl[1][0];
            } elseif (preg_match_all($patternMDfoot, $content, $thumbUrl)) {
                $thumb = $thumbUrl[1][0];
            } else {//文章中没有图片
                if ($howToThumb == '1') {
                    return '';
                } else {
                    return $random;
                }
            }
            return $thumb;

        } elseif ($howToThumb == '3') {
            if (!empty($thumbField)) {
                return $thumbField;
            } else {
                // return $random;
                return "https://www.dmoe.cc/random.php?".rand();
            }
        }else{
            return "";
        }
    }
🔲 ☆

GeekGame CTF Write Up

[TOC]

签到

简单做法

在 Windows 系统中使用 Edge 浏览器,直接在下面两行分别双击,选中整行,然后复制到浏览器地址栏即可得到对应的明文。然后将明文按照原有顺序上下排序,即可得到 FLAG

被坑做法

题目刚放出来的时候我用的 Mac 电脑。。。正如第二阶段提示,Safari 和 Mac系统自带的预览 都无法完整复制整行,导致我卡了一下午没做出签到题,心态差点炸掉2333

当时使用 strings 命令看到 PDF 中有下面这种花体字符的字体名是Wingdings-Regular

于是在网上下载了这个字体,挨个比对,发现和复制出来的没啥区别,心态爆炸+1

小北问答

题目答案
15
2407
32021-07-11T08:49:53+08:00
4OOO{this_is_the_welcome_flag}
52933523260166137923998409309647057493882806525577536
6submits
7AS59201
8区域光纤通信网与新型光通信系统国家重点实验室
  1. 利用高德地图搜索 北京大学理科 会看到 理科1号楼、理科2号楼和理科5号楼,(震惊🤯贵校竟然有理科5号楼,我呆了5年了才知道),发现没有4和6号楼,于是利用百度搜索,发现4号楼是生科、3号楼是毅夫2楼,并且没搜到6号楼。
  2. 我首先查看了第零届的 Github 仓库 但很遗憾在仓库里并没有找到总注册人数,只有前100的榜单。于是想到这种东西应该可以用来彰显成绩,在宣传的时候应该会用到,于是翻了翻信科的公众号找到推送 https://mp.weixin.qq.com/s/voAN7LX655Hy5w3_cZmraA 里写着共407人注册、有效参与人数334人。
  3. 印象中有个网站能看某个域名签发的所有免费证书,找了好久才找到这个网站,https://crt.sh/ ,然后搜索 geekgame.pku.edu.cn 即可看到历史签发

    可以看到4362003382 这个的过期时间和下一个没有接上,再转换一下时间格式即可。
  4. 第四题找了好久,主要是一开始没搜对关键词。。一开始用的https://rayhan0x01.github.io/ctf/2020/08/08/defcon-redteamvillage-ctf-tunneler-1,2,3,4,5,7,9.html 这个FLAG,结果做完其他的发现只对了6个,再次查找才找到 DEFCON 的官方 Pre-qualifier 网站, 然后进入DC28Quals 点击上方 Challenges 下载第一个动图中的 txt 即可
  5. 这问卡了好久,甚至尝试手动计算公式,再考虑到斜边的多种复杂情况后决定放弃,继续搜索。找到答案后对世界数学大师感到非常震撼。。。他给了一个(m,n) 棋盘上解个数的公式,利用python把 ^幂运算换成**然后直接计算即可。
  6. 肯定要找题目源码,利用第2问中提到的 第零届的 Github 仓库 ,找到对应的 源码 ,当时其实找了一会儿,主要是不知道 小北问答 的英文名是哪道题。。。
  7. 利用 ipip.net 查询计算中心的 IP 地址找到 3个ASN,随便点进第一个发现 Org Name 不是 PKU,在 Down Streams 中找到一个 Org Name 为 PKU 的
  8. 翻遍信科的所有机构,列了个表,找出最长的是区域光纤通信网与新型光通信系统国家重点实验室

共享的机器

打开题目,获得反编译代码如下:

def storage:
  stor0 is addr at storage 0
  stor1 is uint256 at storage 1
  stor2 is uint256 at storage 2
  stor3 is uint256 at storage 3

def _fallback() payable: # default function
  revert

def unknown7fbf5e5a(uint256 _param1, uint256 _param2) payable: 
  require calldata.size - 4 >= 64
  if stor0 != caller:
      if stor0 != tx.origin:
          if stor1 != sha3(caller):
              if stor1 != sha3(tx.origin):
                  revert with 0, 'caller must be owner'
  stor2 = _param1
  stor3 = _param2

def unknownded0677d(uint256 _param1) payable: 
  require calldata.size - 4 >= 32
  idx = 0
  s = 0
  while idx < 64:
      idx = idx + 1
      s = s or (Mask(256, -4 * idx, _param1) >> 4 * idx) + (5 * idx) + (7 * Mask(256, -4 * idx, stor2) >> 4 * idx) % 16 << 4 * idx
      continue 
  if stor3 != 0:
      revert with 0, 'this is not the real flag!'
  return 1

可以看到就是对输入的数据和stor2进行运算后再与stor3 比对。

这里遇到不知道是不是坑,上面的反编译是 etherscan.io 上的,和其他工具得到的反编译代码有微小差别,我做题时是用的其他工具的结果。

首先使用 web3.py 得到 store2store3

import web3
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://ropsten.infura.io/v3/83ebfc92383e429a9ea26572d16ba7b9"))

print(w3.eth.get_storage_at("0xa43028c702c3B119C749306461582bF647Fd770a", 0))
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00MR\x00\xa3W\x13\x91\xf3\xe6T\xdf\x15bq\xbf/\x01\xc0\xc5'

print(w3.eth.get_storage_at("0xa43028c702c3B119C749306461582bF647Fd770a", 1))
b"\xdai\x86\x8c\xde'\xbd\x8f2X\t\x8c\xce\xb1\xd0\x9ds\xd5\xc8P\x17V\xee;\\\xb2\xe7x*\xd7M\x98"

print(w3.eth.get_storage_at("0xa43028c702c3B119C749306461582bF647Fd770a", 2))
b'\x15\xee\xa4\xb2U\x1f\x0c\x96\xd0*]b\xf8L\xac\x81\x12i\rh\xc4{\x16\x81N"\x1b\x8a7\xd6\xc4\xd3'

print(w3.eth.get_storage_at("0xa43028c702c3B119C749306461582bF647Fd770a", 3))
b')>\xde\xa6acZ\xab\xcdm\xeb\xa6\x15\xab\x81:v\x10\xc1\xcf\xb9\xef\xb3\x1c\xccR$\xc0\xe4\xb3sr'

然后再将反编译代码翻译为 python 进行求解,这里卡了一段时间,主要是想从 store3store2 直接反推出 _param1 但由于中间是按位或,导致倒推很困难,基本上很难得到。。。

然后想到每次运算时会比上次多右移4位,所以可以从低位开始每4位爆破,每次只需从0到15依次测试即可,代码如下:


s2 = b'\x15\xee\xa4\xb2U\x1f\x0c\x96\xd0*]b\xf8L\xac\x81\x12i\rh\xc4{\x16\x81N"\x1b\x8a7\xd6\xc4\xd3'
s3 = b')>\xde\xa6acZ\xab\xcdm\xeb\xa6\x15\xab\x81:v\x10\xc1\xcf\xb9\xef\xb3\x1c\xccR$\xc0\xe4\xb3sr'


s2 = int.from_bytes(s2, "big")
s3 = int.from_bytes(s3, "big")

def func_0089(arg0, maxvar1):
    var0 = 0x00
    print(bin(arg0))
    for var1 in range(0, maxvar1):
        var0 = var0 | (((arg0 >> var1 * 0x04) + var1*0x05 + (s2>>var1*0x04)*0x07&0x0f)<<var1*0x04)
        print(f"var: {var0 & ((1<<4*(var1+1))-1)}\ns3: {s3 & ((1<<4*(var1+1))-1)}")
        if var0 & ((1<<4*(var1+1))-1) != s3 & ((1<<4*(var1+1))-1):
            return False
    return True
def calc():
    ans = 0

    for i in range(0, 64):
        print(f"calc i : {i}")
        for tmp in range(0, 16):
            if func_0089(ans + (tmp<<4*i), i+1):
                ans = ans + (tmp<<4*i)
                print(f"ans: {bin(ans)}")
                break
        else:
            print("error")
            exit(-1)
    return ans

ans = calc()
ans = ans.to_bytes((ans.bit_length()+7)//8, "big")

print(ans)

翻车的谜语人

FLAG1

利用 Wireshark 打开流量包,搜索 flag 字样,找到一个 JSON 格式的 HTTP 请求

{
    "name": "Untitled.ipynb", 
    "path": "Untitled.ipynb", 
    "last_modified": "2021-11-06T07:42:40.600988Z", 
    "created": "2021-11-06T07:42:40.600988Z", 
    "content": {
        "cells": [
            {
                "cell_type": "code", 
                "execution_count": 13, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "import zwsp_steg
from Crypto.Random import get_random_bytes"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 14, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "import binascii"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 15, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "def genflag():
    return 'flag{%s}'%binascii.hexlify(get_random_bytes(16)).decode()"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 16, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "flag1 = genflag()
flag2 = genflag()"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 17, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "key = get_random_bytes(len(flag1))"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 18, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [
                    {
                        "data": {
                            "text/plain": "b'\\xc4\\x07[\\xe5zy}b3\\x1aM\\xed\\t\\x14\\x1c\\xea\\x8f\\xfb\\xe52\\\\\\x80\\xb1\\x98\\x8a\\xb4\\xa6\\xdd;\\x92X\\x81\\xcd\\x86\\x86\\xc4\\xe0v'"
                        }, 
                        "execution_count": 18, 
                        "metadata": { }, 
                        "output_type": "execute_result"
                    }
                ], 
                "source": "key"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 19, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "def xor_each(k, b):
    assert len(k)==len(b)
    out = []
    for i in range(len(b)):
        out.append(b[i]^k[i])
    return bytes(out)"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 20, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "encoded_flag1 = xor_each(key, flag1.encode())
encoded_flag2 = xor_each(key, flag2.encode())"
            }, 
            {
                "cell_type": "code", 
                "execution_count": 22, 
                "metadata": {
                    "trusted": true
                }, 
                "outputs": [ ], 
                "source": "with open('flag2.txt', 'wb') as f:
    f.write(binascii.hexlify(encoded_flag2))"
            }
        ], 
        "metadata": {
            "kernelspec": {
                "display_name": "Python 3 (ipykernel)", 
                "language": "python", 
                "name": "python3"
            }, 
            "language_info": {
                "codemirror_mode": {
                    "name": "ipython", 
                    "version": 3
                }, 
                "file_extension": ".py", 
                "mimetype": "text/x-python", 
                "name": "python", 
                "nbconvert_exporter": "python", 
                "pygments_lexer": "ipython3", 
                "version": "3.8.3rc1"
            }
        }, 
        "nbformat": 4, 
        "nbformat_minor": 4
    }, 
    "format": "json", 
    "mimetype": null, 
    "size": 2502, 
    "writable": true, 
    "type": "notebook"
}

整理后得到一个 .ipynb 文件中的代码,进行还原

from Crypto.Random import get_random_bytes

import binascii

def genflag():
    return "flag{%s}"%binascii.hexlify(get_random_bytes(16)).decode()

flag1=genflag()
flag2=genflag()

key = get_random_bytes(len(flag1))

key = b'\x1e\xe0[u\xf2\xf2\x81\x01U_\x9d!yc\x8e\xce[X\r\x04\x94\xbc9\x1d\xd7\xf8\xde\xdcd\xb2Q\xa3\x8a?\x16\xe5\x8a9'

def xor_each(k, b):
    out = []
    for i in range(len(b)):
        out.append(b[i]^k[i])
    return bytes(out)

encode_flag2 = xor_each(key, flag2.encode())


with open('flag2.txt') as f:
    encode_flag2 = f.write()

注意,上面的 key 和截图中的不一致,主要是由于这个数据比较老,在流量中还能找到新的 Untitled.ipynb 文件,这个 key 是从新的文件中提取出来的。

然后继续在流量中找 flag1

可以得到 flag1.txt 的内容,然后计算即可。

flag1="788c3a1289cbe5383466f9184b07edac6a6b3b37f78e0f7ce79bece502d63091ef5b7087bc44"

key = b'\x1e\xe0[u\xf2\xf2\x81\x01U_\x9d!yc\x8e\xce[X\r\x04\x94\xbc9\x1d\xd7\xf8\xde\xdcd\xb2Q\xa3\x8a?\x16\xe5\x8a9'

def xor_each(k, b):
    out = []
    for i in range(len(b)):
        out.append(b[i]^k[i])
    return bytes(out)

flag1 = xor_each(key, bytes.fromhex(flag1))
print(flag1)

FLAG2

在流量中搜索 flag2 可以看到一个 HTTP 传输的压缩包,保存下来后解压发现有密码,继续在流量中搜索 flag2 发现了 websocket

在里面看到了压缩包是如何生成的,还原密码为

Wakarimasu! \`date\` \`uname -nom\` \`nproc\`

markdown 好难写这个密码,还是看截图吧

date 是当时的日期时间,在流量包中找到该压缩包的生成时间为2021-11-06T07:44:16,我在 wsl2 虚拟机上将时间调整到 6 号执行 date 命令得到如下格式Sat 6 Nov 2021 21:10:11 CST

uname -nom 得到 vm x86_64 GNU/Linux 其中 vm 是我的虚拟机名,在 websocket 包中可以找到 You 酱的虚拟机名,第二项是处理器架构,现在应该都是64位机,第三项是系统相关。

nproc 是处理器线程数,在 websocket 包中可以看到 You 酱的虚拟机是 8线程的

于是得到密码为 Wakarimasu! Sat 6 Nov 2021 07:44:16 CST you-kali-vm x86_64 GNU/Linux 8 改动的地方只有秒位需要,结果尝试了很久都无法解压成功,一度怀疑是压缩包有问题,还尝试了修复压缩包。。。

直到第二阶段提示放出后,才知道是 date 部分错了,正确的密码是Wakarimasu! Sat 06 Nov 2021 03:44:15 PM CST you-kali-vm x86_64 GNU/Linux 8

解压后得到一个 .wav 文件,通过 websocket 包得知,该文件是用 stegolsbflag2.txt 隐写进去得到的

于是使用命令stegolsb wavsteg -r -i xxx.wav -o encode_flag2.txt -n 1 -b 76 其中的 -n 参数和 -b 参数是在 websocket 包中得到的。

重复 FLAG1 中的解密步骤即可得到 FLAG2

在线解压网站

通过分析源码可以看到,当访问路径是文件时,会返回该文件对应的内容。而 FLAG 藏在根目录下,于是猜测是否可以利用Linux下的软链接读取flag,

touch /flag
ln -s /flag ./test

然后压缩 test 文件再上传至服务器,确实能得到 FLAG

早期人类的聊天室

第一阶段未解出来但分析了很多,第二阶段看到提示后即解。

首先分析源码发现可以任意读取文件,只需要在参数上拼接足够的../即可访问到根目录,尝试直接读取 flag 失败,应该是没有权限。

利用 linux 上的 /proc 目录读取自身的 cmdline 得到 uwsgi 配置文件路径,访问该文件发现服务器的进程的权限是 nobody ,这时想着能否提权,尝试了很多办法都不行,卡住。。。

在卡住的过程中,我遍历了 pid 得知服务器是用 supervisor 启动的,于是想到能否更改 uwsig 配置文件,然后重启 uwsgi 进程就能提权,但迫于无法写入 uwsgi 配置文件,且无法执行命令,卡住。。。

第二阶段看到提示后,才知道要利用 uwsgi 的 rce ,啊这🤯,用了好几年的 uwsgi 都不知道有漏洞。。。

于是使用服务器发送消息页面,将地址改为 127.0.0.1:3031 ,这个端口号是上面通过读取 uwsgi 配置文件得到的。

然后使用 uwsgi_exp.py 构造 payload, 首先修改 uwsgi 配置文件

echo '[uwsgi]
socket = :3031
chdir = /usr/src/ufctf
manage-script-name = true
mount = /=app:app
master = true
uid = root
gid = root
workers = 2
buffer-size = 65535
enable-threads = true
pidfile = /tmp/uwsgi.pid
' >/tmp/uwsgi-ctf.ini

由于 uwsgi 进程权限很低,创建新进程还是 nobody 所以只能考虑重启 uwsgi 进程,尝试了uwsgi --reload /tmp/uwsgi.pid 发现并没有提权成功,于是使用 uwsgi --stop /tmp/uwsgi.pid (我自己停止我自己总行了吧),借助于 supervisor 这个进程管理器成功得到新的 root 权限的 uwsgi 进程,然后直接读取 /flag 即可。

Flag 即服务

Flag0

卡了很久,主要是搜到了 RFC 3986, 以为浏览器和各种发送请求的工具都会遵循,后来偶然发现 Postman 并没有遵循该标准。。。于是利用 Postman 访问 /api/../package.json 得到 package.json 文件,从文件中得到 jsonaas-backend 的下载链接,从而得到源码。

阅读源码,发现对 FLAG0 有特殊约束

FLAG0===`flag{${0.1+0.2}$}`

在浏览器中执行 0.1+0.2 即可得到 FLAG0

FLAG1 和 FLAG2

尝试了很多方法都不行,简单说下自己尝试过的思路。

FLAG2 应该是 vm 沙盒逃逸,而且由于代码中只是在文件系统中 unlinkflag2.txt 所以还是可以用其他方法读取的。

FLAG1 尝试了 session 伪造,发现行不通;尝试构造 req.query 绕过验证失败。

静待大神们的 WP

诡异的网关

这题我应该是非预期解😊

打开软件后随便点点发现有个用户名叫 flag 且密码以星号表示,于是在网上找了个能用的星号查看器,然后就得到了 FLAG

静待大神们的 WP

密码学实践

这题出的太好了!

FLAG1

阅读代码发现和 Richard 谈话时, FLAG1 会直接拼接在已知明文后再加密发送给我们,于是查看加密算法,手推加密循环发现,密文和明文之间关系较为简单。

如果已知明文和密文,可以倒推出加密使用的key,然后再利用得到的key,解密 FLAG 部分的密文。

代码如下:

from Crypto.Util.number import bytes_to_long, long_to_bytes


def get(mess: bytes, oristr: bytes):
    pmess = mess[0:32]
    a0 = bytes_to_long(pmess[0:8])
    b0 = bytes_to_long(pmess[8:16])
    c0 = bytes_to_long(pmess[16:24])
    d0 = bytes_to_long(pmess[24:32])

    o0 = bytes_to_long(oristr[0:8])
    o1 = bytes_to_long(oristr[8:16])
    o2 = bytes_to_long(oristr[16:24])
    o3 = bytes_to_long(oristr[24:32])

    ka = c0 ^ a0 ^ o0
    kb = d0 ^ b0 ^ o1
    kc = a0 ^ o2
    kd = b0 ^ o3

    ori = b""
    for it in range(32, len(mess), 32):
        pmess = mess[it:it+32]
        a1 = bytes_to_long(pmess[0:8])
        b1 = bytes_to_long(pmess[8:16])
        c1 = bytes_to_long(pmess[16:24])
        d1 = bytes_to_long(pmess[24:32])
        a, b, c, d = c1^a1^ka, d1^b1^kb, a1^kc, b1^kd

        a = long_to_bytes(a, 8)
        b = long_to_bytes(b, 8)
        c = long_to_bytes(c, 8)
        d = long_to_bytes(d, 8)

        ori += a+b+c+d
        print(ori)


def pad(msg):
    n = 32 - len(msg) % 32
    return msg + bytes([n]) * n


enc = bytes.fromhex("f5b658a1fa3f7740e0c0a8bc67077fd1aaebaec98bfddb0f3a5e32b9fd3f8fa7f1ab14a4a9623e50e981b6a854423af589b3e8ccd6ebc1707f2227adcf3cead1ddbe5a92950f584b8de8d9db1a2f03aebee38cc9e58480320b4d4be4ef52899c")

get(enc, pad("Hello, Alice! I will give you two flags. The first is: ".encode("utf-8")))

FLAG2

原理应该是选择密文攻击

阅读代码,我们发现 God 会对我们的 name 和 key 加密返回 cert,而 Richard 会验证我们的 cert 当 name 是 Alice 时给我们发送 FLAG2。但 God 已经加密过 name 是 Alice 的,所以如果我们输入 name 是 Alice 会被拒绝。

由于 Richard 只验证 name ,于是我们考虑制造 name 为 Alice,key 随意的明文对应的密文。

观察 God 签发过程,首先会将 name 和 key 进行 packmess 然后拼接起来再使用 rsa 加密。

我们考虑最简单的情况,即我们想要的是 Alice\x00\x05\x00\x00 这样的明文对应的 cert,于是我们考虑发送给God的 name 为空,这样 God 发送的 cert 的明文就是 key 和 key的长度 拼接起来。

$$ 我们需要\ C\ 使得\ M=C^e\mod N \\ 其中\ M=Alice\backslash x00\backslash x05\backslash x00\backslash x00 \\ 我们选择任意的\ X 与\ N 互素(为了简单可以选 X=3 \ )\\ 我们计算\ Y=M*X^e\mod N \\ 将\ Y\ 发送给服务器计算\ Z=Y^d\mod N=M^d * X^{e*d}\mod N=C*X \mod N \\ 现在我们知道Z、X,计算C只需要使得 (Z+iN)\%X==0 即可 $$

注意⚠️:在构造出 Y 后,我们需要保证 Y 转换成 bytes 的长度减去2要小于 Y 的bytes的最后两个字节转换成的数字大小,且发送给服务器的 Y 应是 Y 转化成 bytes 后的前

代码如下:

from re import T
from pwn import *
from Crypto.Util.number import bytes_to_long,long_to_bytes
import binascii
from rsa import *
import gmpy2

r = connect("prob08.geekgame.pku.edu.cn", 10008)
r.sendlineafter(b"Please input your token:\n", b"Your Token")

print(r.recvline())  # What do you want to do?
print(r.recvline())  # 0. Talk to God.
print(r.recvline())  # 1. Talk to Richard.
r.sendline(b"0")
N = int(r.recvline().split()[-1].decode("utf-8"))  # My RSA N is: 
e = int(r.recvline().split()[-1].decode("utf-8"))  # My RSA e is: 
print("N: ", N)
print("e: ", e)

p = int.from_bytes(b"Alice\x00\x05\x00\x00", "big")
print(f"p: {p}")
x = 3
while True:
    tmp = p*pow(x, e, N) % N
    y = tmp.to_bytes((tmp.bit_length()+7)//8, "big")
    rlen = int.from_bytes(y[-2:], "big")
    if len(y)-2 > rlen:
        x+=1
        continue
    else:
        print(f"x:{x} y:{y}")
        break

y = b'\x00'*(rlen-len(y)+2) +y
y = y[-(2+rlen):-2]

print(r.recvline())  # What is your name?
r.sendline(b"")
print(b"")
print(r.recvline())  # What is your key?
r.sendline(y.hex().encode("utf-8"))
print(y)
print(r.recvline())  # Your certificate is:
fakecert = r.recvline().strip()
print(fakecert)
fakecert = int(fakecert.decode("utf-8"))
mess=pow(fakecert,e,N)
mess=mess.to_bytes((mess.bit_length()+7)//8,'big')
print("mess: ", mess)

i = 0
while True:
    tmp = fakecert + N*i
    if tmp % x == 0:
        break
    i += 1

C = (fakecert + N*i) // x
print(f"C: {C}")
mess=pow(C,e,N)
messm=mess.to_bytes((mess.bit_length()+7)//8,'big')
print(f"Hacked! {messm}")



print(r.recvline())  # What do you want to do?
print(r.recvline())  # 0. Talk to God.
print(r.recvline())  # 1. Talk to Richard.

r.sendline(b"1")
flag1enc = r.recvline() # Hello, Alice! I will give you two flags. The first is: 
notice_mess_enc =  r.recvline()  # Sorry, I forget to verify your identity. Please give me your certificate.

r.sendline(str(C).encode("utf-8"))

flag2enc = r.recvline() # I can give you the second flag now. It is: 
print(flag2enc)
# print(get_flag2(flag2enc))


r.close()

叶子的新歌

套娃心态炸掉。。

首先下载下来,在mac上会自动播放发现没啥东西,使用音频分析软件也没发现什么,耽误了好久,后来在windows上播放时发现右上角有提示Secret in Album Cover ,于是使用 potplayer 提取出封面图片,然后使用 stegosolove 找出使用 lsb 隐写进去的一个 png 文件,该 png 文件长得类似二维码,卡住。。。然后在linux 下使用 binwalk 发现有个压缩包,使用 binwalk -e 处理后得到一个 MIPSEL-BE ECOFF executable stripped -version 0.0 的文件,尝试使用 qemu 运行无果,卡住。。。

Q 小树洞的一大步

在使用了现代化框架 React 后的网站找 XSS 真难啊。。。尝试了很多方法都无法在网页上构建 XSS。

第二阶段看到提示后,又尝试了很多方法,还是行不通。我甚至逐行比对了 Q小树洞和 PKU树洞现在的 js 文件,还是没有任何发现。

审计代码中发现访问 /hole/#//setflag flag1=content 会将 LocalStorage 中的 flag1 设置为 content ,但并不会触发代码执行,且由于会弹出一个提示框,将导致 XSSBOT 无法继续执行。。

还发现 LocalStorage 中的 APPSWITCHER_ITEMS 中有一项 fix 会被 eval 语句执行,但由于跨域要求,无法在其他站点修改这个值,无法利用这个点。

静待大神们的 WP

射手鱼

.debug 文件完全不懂,静待大神们的 WP

字符串转义

第一阶段想到了计算 canary,然后调用print_flag但没动力做,之前完全没做过😮‍💨,静待大神们的 WP

最强大脑

不知道为什么进入compile_code() 编译的区域后会segment fault ,静待大神们的 WP

电子游戏概论

看着没人做出来,就没看,静待大神们的 WP

扫雷

练了一个 Qlearning 但效果不行,不太想调参,放弃,静待大神们的 WP

龙珠模拟器

应该是考察 java 里的 Random 类,对 java 不熟对算法也不熟,放弃,静待大神们的 WP

🔲 ⭐

Kitty 简介

Kitty 简介

Kitty is an open-source modular and extensible fuzzing framework written in python, inspired by OpenRCE’s Sulley and Michael Eddington’s (and now Deja Vu Security’s) Peach Fuzzer.

Kitty 是一个 python 版本的模糊测试框架。其目标是帮助模糊测试特定的受害者。受害者通常运行于非TCP/IP通道上的私有和内部协议。

特性

  1. 模块性:fuzzer的每一个部分都是独立的,这意味着你能够用相同的监视代码监控不同的程序,用相同的载荷生成工具(aka Data Model)生成的数据可以在不同的信道中传递;
  2. 扩展性:如过你想测试新的东西,不需要修改Kitty的核心代码。即便不是所有,大部分功能应该在用户代码中实现。这包括监视,控制以及和被fuzz的目标的通信;
  3. 丰富的数据模型:数据模型的核心要丰富,能够描述高级的数据结构,包括字符串,哈希,长度,条件及其它。而且和其它框架一样,还要设计好,将来需要的时候可以扩展;
  4. 状态性:支持多阶段的模糊测试。不仅要能描述单独消息的载荷应该是什么样,还要能描述消息的顺序,甚至按照顺序进行fuzzing;
  5. 客户端和服务端fuzzing:假设你有一个配对的程序栈,你可以对服务端和客户端进行fuzz。这听起来是一个很高的要求,但实际上不是:只需要你像通常一样能够和目标通信即可;
  6. 跨平台:可以在Linux,OS X和Windows上运行。

框架结构

kitty 主要架构如下图所示:

Fuzzer  +--- Model *--- Template *--- Field
        |
        +--- Target  +--- Controller
        |            |
        |            *--- Monitor
        |
        +--- Interface

Fuzzer

Fuzzer 是最高级的控制结构,由三部分组成 Interface(可视化)、Target(控制被测试的对象)、Model(数据模型)。Fuzzer 从 Model 获取改变后的数据,执行数据交换,从 Target 获取测试报告,在需要时执行更多的操作。一般我们采用自底向上的构造方法,即最后配置 Fuzzer。

根据被测试对象的属性,我们可以分别使用 ClientFuzzer 和 ServerFuzzer.

Fuzzer 类常用 api:

  • set_model(model) 为 Fuzzer 设置数据模型
  • set_target(target) 为 Fuzzer 设置控制结构
  • set_interface(interface) 为 Fuzzer 设置可视化
  • start() 开始模糊测试
  • stop() 结束模糊测试
  • set_delay_between_test(delay_secs) 设置测试之间的间隙

Model

数据模型定义了 Fuzzer 要发送的消息的结构。 通常将消息分成几个部分(如头部、长度、负载),并包含这几部分的类型(如字符串、校验码、十六进制编码的32位整数)和这些部分之间的关系。此外,数据模型还描述了不同消息被联系起来形成模糊会话的顺序。数据模型还可以指定模糊发送消息的顺序。

Target

Target 模块负责管理与受测者有关的一切。其主要功能包括:

  1. 管理 Monitors 和 Controller
  2. 当受测者是一个 server 时,Target 模块通过向服务器发送请求并处理服务器可能返回的响应来启动模糊会话。
  3. 当受测者是一个 client 时,Target 模块通过使客户端向服务器发起请求来启动模糊会话。服务器在stack的帮助下将向 client 发送一个模糊的响应。

Target 类常用 api:

  • set_controller(controller) 为 Target 设置控制器
  • add_monitor(monitor) 为 Target 添加一个监控器

Controller

Controller 负责将受测者控制到合适的状态以做好测试的准备。 Controller 也需要执行基本的监控,并需要报告受测者是否准备好进行下一次测试。

Monitor

Monitor 监控受测者的行为,它可以监控网络流量、内存消耗、串行输出或者其它任何内容。

kitty.monitors 包提供了基本的监视器类 BaseMonitor,一般需要对其进行继承而不要直接实例化。

继承了 BaseMonitor 的子类,建议对以下函数进行继承:

  • __init__() 函数应先调用父类的构造函数进行初始化
  • setup() 函数应对子类新增的变量进行配置后再调用父类的setup函数
  • _monitor_func()函数会在setup函数调用并在新线程内进行无限循环直至发送终止命令,所以此函数一般用来进行数据收集
  • teardown() 函数应对子类新增变量进行清理工作,并调用父类构造函数
  • pre_test() 函数应先调用父类函数再对子类新增变量进行每次测试前的初始化
  • post_test() 函数应对收集到的数据进行处理生成报告,并调用父类构造函数

在每次测试前,应对上次收集到的数据进行清理防止上次测试结束时数据清理工作不彻底,一般来说直接将私有变量初始化即可。每次测试后进行数据分析,然后也应对收集到的数据进行清理。

启动过程分析

一次模糊会话的测试过程主要由以下几步组成

  1. 定义测试过程中发送给受测者的数据模型model
  2. 实例化Controller子类
  3. 实例化一系列Monitor子类
  4. 实例化Target子类
  5. 为 Target 设置 controller,并增加 monitors
  6. 实例化Fuzzer类
  7. 为Fuzzer设置model和target
  8. 执行Fuzzer.start()开始测试
  9. 执行Fuzzer.stop()停止测试

Fuzzer.start() 进行分析

这里只对受测者是server时进行分析

进入 serverFuzzer.start()

  1. 进行初始化工作
  2. 打印start消息日志
  3. 执行 target.setup()

    1. 执行 controller.setup()
    2. 对所有的monitor执行setup()
  4. 进行环境测试
  5. 执行 _start()

    1. 进入循环,循环终止判断条件为_next_mutation()函数返回值

      1. 根据model获取sequence
      2. 执行_run_sequence(sequence)

        1. 执行_check_pause()等待用户命令
        2. 执行_pre_test()

          1. 执行target.pre_test()

            1. 执行 controller.pre_test()
            2. 执行 monitor.pre_test()
        3. 根据 sequence 里的数据执行 _transmit() 函数

          1. 执行target.transmit()函数发送数据

            1. 执行 _send_to_target()函数发送数据
            2. 如果需要等待response并返回
        4. 执行_post_test()

          1. 执行target.post_test()

            1. 执行controller.post_test()
            2. 执行monitor.pre_test()
            3. 生成报告
          2. 获得报告
          3. 进行报告分析
    2. 打印end消息日志
❌