adduser 和 useradd 的区别

需求

在 armbian 系统里新建个账号。

这当然是个简单的任务,问题是发现居然同时存在 adduser 和 useradd 两个命令。

解决选择恐惧症

  1. adduser 不是可执行程序。
1
2
3
4
5
which adduser
/usr/sbin/adduser

ldd /usr/sbin/adduser
not a dynamic executable
  1. useradd 是可执行程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
which useradd
/usr/sbin/useradd

ldd /usr/sbin/useradd
linux-vdso.so.1 (0xbea59000)
libaudit.so.1 => /lib/arm-linux-gnueabihf/libaudit.so.1 (0xb6ea1000)
libselinux.so.1 => /lib/arm-linux-gnueabihf/libselinux.so.1 (0xb6e77000)
libsemanage.so.1 => /usr/lib/arm-linux-gnueabihf/libsemanage.so.1 (0xb6e3f000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6d45000)
/lib/ld-linux-armhf.so.3 (0xb6efb000)
libcap-ng.so.0 => /lib/arm-linux-gnueabihf/libcap-ng.so.0 (0xb6d31000)
libpcre.so.3 => /lib/arm-linux-gnueabihf/libpcre.so.3 (0xb6cd4000)
libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6cc1000)
libsepol.so.1 => /lib/arm-linux-gnueabihf/libsepol.so.1 (0xb6c44000)
libbz2.so.1.0 => /lib/arm-linux-gnueabihf/libbz2.so.1.0 (0xb6c28000)
libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6c03000)
  1. 推测 adduser 是脚本,内部调用 useradd。求证之!
1
2
3
4
5
6
7
8
9
head -1 /usr/sbin/adduser
#!/usr/bin/perl

grep useradd /usr/sbin/adduser
my $useradd = &which('useradd');
&systemcall($useradd, '-d', $home_dir, '-g', $ingroup_name, '-s',
my $useradd = &which('useradd');
&systemcall($useradd, '-d', $home_dir, '-g', $ingroup_name, '-s',
# useradd without -p has left the account disabled (password string is '!')

这说明 adduser 是 perl 脚本,内部确实调用 useradd。

  1. 直觉告诉 UMU,应该用 adduser,如果 useradd 很好用,不会有 adduser 存在的必要。

优化思维【6】安全性

前情

前五篇,主要考虑性能优化,只有第二篇与安全性相关。

其实区块链业界一直不缺乏黑客,最近看过不少安全事故导致惨重代价,所以想总结点安全性方面的优化思路。(本篇比较务虚,只是大体思路。)总的来说,为了安全是必须付出实现或者性能代价的。实现代价是开发、测试阶段就要投入更多精力,性能代价是因为考虑更多,有可能消耗更大运行资源。但从长期来看,这些代价都是必须的。

产品价值与安全意识

开发者可能有能力做一定安全防范,但如果他认为产品没有价值,没必要防范,就可能明明有能力防住,实际却被黑翻车。要不要注重安全性,是设计阶段就应该交代清楚的。

夫兵久而国利者,未之有也。故不能尽知用兵之害者,则不能尽知用兵之利也。——《孙子兵法》

做任何事情先考虑失败。——李嘉诚

程序员版解读:安全怎么能大意,甚至忽视?那都是侥幸心理,只要您的产品、服务有价值,长期看都会被破解、攻击。开发者如果不能知悉黑客可能的攻击点,并衡量被攻破的代价,他必然也不清楚自己写的代码的真正价值。

实际开发过程中,有些领导者会故意隐藏关于产品价值的信息,这实际上可能导致安全考虑不到位。这种情况就应该配备一个在安全方面经验丰富的审查者。

一句话总结:越有价值的东西,就越应该注重安全。

知识深度

一般黑客都是上层、底层皆通,尤其擅长底层。很少听说只做增删查改业务的人能够黑掉什么东西、偷到数字货币,因为同样只做增删查改业务的人就具备防止这种级别的攻击手段。

比如古老的 SQL 注入漏洞,即便是入门级的 Web 开发也能理解并防护,用预编译语句、存储过程、改用 ORM 就天然免疫。他们无法防护的往往来自更底层的 Web Server 的漏洞,比如 Apache、Nginx 某个版本有 bug,刚好中枪。

再举个例子,用 C/C++ 写 UDP 服务程序,“先把它实现,能用就行”,“不就 socket 嘛?很容易!”于是没有考虑 socket 等资源的生存周期,没料到黑客可以伪造 UDP 包源地址,实现出来的就可能有拒绝服务攻击 (Denial of Service,DoS) 漏洞

总之,为了性能或安全的优化,开发者往往需要往底层钻。为性能,主要是研究底层模块与之配合,达到消除瓶颈目的;为安全,则是不让对底层设计的不了解,导致实现不够严谨周密而产生漏洞。

知识广度

经常听到这样的段子:

千万不要跟程序员说,你的代码有 bug。

他的第一反应是你的环境有问题,第二就是你是傻逼不会用吧!

你要跟他这么说:这个程序运行的怎么运行的跟预期不一样,是我操作有问题吗?

这货就会第一反应,我擦,这是不是出 bug 了?

这段子里其实间接反映一个程序员经常遇到的问题:自己测试没问题,一到用户侧或者线上就莫名出问题。环境不同,是最大原因。比如 Linux 的发行版众多;著名的 Android 碎片化;iPhone 型号随时间推移也越来越多了……

另一个大原因则是依赖。比如古老的 DLL Hell。类似的问题在 macOS、Linux 上也都存在。有一次 UMU 把 macOS 的 OpenSSL 升级到 1.1,结果 1.0 居然被删掉,导致原来编译的依赖 1.0 的 eos 就无法运行了。

再以 eos 为例,它依赖不少库,这些依赖库本身也可能有 bug,也要升级。又比如 ipfs,熟悉下来,发现其依赖树很广。作为开发者,引入一个依赖时,肯定需要操心会不会同时引入 bug。一般解决方式是:采用被大量验证的著名库,尽快跟进最新稳定版本。

总之,为了安全,设计者可能需要了解更多方面的知识,并不仅限于表面上需要的那些。开发者还要与时俱进,积极消灭潜在的漏洞。

开发语言

高级语言程序员可能很少听说缓冲区溢出,即使有,多半也是这门语言的宿主、解释器的 bug。而 C/C++ 等能直接操作指针的语言,就可能听到栈溢出、空指针、野指针等。采用高级语言确实会在程序执行时的安全性上省心不少,能把更多精力放在流程安全、业务安全上。

从架构上说,应该让不同语言只用于它擅长的领域。比如用 Rust 实现底层模块,用 Go 实现上层业务。这种分层选择语言的方式,充分利用 Rust 的安全性和底层开发能力,还可以让分工更清晰、沟通更愉快。

为什么 EOS 私钥有不同长度?

问题

这里有两个 EOS 私钥,它们长度居然不一样?

  • PVT_K1_1EEr5aW5162skbocDSMDgoWn9jna6HPSr1TwEMR6PNXbPtRky

  • PVT_K1_2bfGi9rYsXQSXXTvJbDAPhHLQUojjaNLomdm3cEJ1XTzMqUt3V

为什么私钥有不同长度?而公钥就都是固定长度呢?

知识点

  • 私钥是一个大型随机数,而公钥则是私钥乘以椭圆曲线上的基点后对应的点。对于 secp256k1 来说,是 256 位,并且 < n 的整数。

  • n 须为质数,Order of G,是使得 n * G = 0 的最⼩正整数,n 是安全性最⼤的决定因素。对于 secp256k1 来说,n
    = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141。

  • 不是每个数都安全,比如小的数肯定是不安全的,黑客可以从 1 开始枚举,不够大的数很快就被找到对应关系,也可以从 n 倒着枚举,所以太大的也不安全。(PS:临近一些特别数的数也不安全……)一般来说,私钥的安全范围是 [0x0080000000000000000000000000000000000000000000000000000000000000, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff]。

工具

https://github.com/UMU618/secp256k1-tools

使用 private-2-public.js 可以把私钥转为它代表的数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
DEBUG=* node private-2-public.js PVT_K1_1EEr5aW5162skbocDSMDgoWn9jna6HPSr1TwEMR6PNXbPtRky
secp256k1-tools:key-util pvt = 84ade57e2b35cca8972562fcc6d1f6f2fbf078c4f2cfb532eb4d740767c5a8 +0ms
secp256k1-tools:key-util x = 2110b8d675240f5d548d166cc06b22f44c671d762711a3a67027b74cd166ab76 +9ms
secp256k1-tools:key-util y = 20ac68b75ad8b0e4bc3ec5705ebaf57c69d2d8268504d6aa95fdebfd0b7db831 +0ms
PVT_K1_1EEr5aW5162skbocDSMDgoWn9jna6HPSr1TwEMR6PNXbPtRky
PUB_K1_75o92oRgWSgx3XzTDYPj1e3hFSRhMnKaUdW8ZZpxJXkhfiGBHS

DEBUG=* node private-2-public.js PVT_K1_2bfGi9rYsXQSXXTvJbDAPhHLQUojjaNLomdm3cEJ1XTzMqUt3V
secp256k1-tools:key-util pvt = d2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d +0ms
secp256k1-tools:key-util x = c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf +8ms
secp256k1-tools:key-util y = eeceff7130fd352c698d2279967e2397f045479940bb4e7fb178fd9212fca8c0 +1ms
PVT_K1_2bfGi9rYsXQSXXTvJbDAPhHLQUojjaNLomdm3cEJ1XTzMqUt3V
PUB_K1_6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5BoDq63

分析

1. 数值比较

  • PVT_K1_1EEr5aW5162skbocDSMDgoWn9jna6HPSr1TwEMR6PNXbPtRky,有 56 个字符,去掉前缀和校验码后有 45 个字符,它代表 0x84ade57e2b35cca8972562fcc6d1f6f2fbf078c4f2cfb532eb4d740767c5a8;

  • PVT_K1_2bfGi9rYsXQSXXTvJbDAPhHLQUojjaNLomdm3cEJ1XTzMqUt3V,有 57 个字符,去掉前缀和校验码后有 46 个字符,它代表 d2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d。

可以清楚地看出前者短一个字符,数值也相应比较小。

2. BASE58 编码的原理

BASE58 的字符集:123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz,其中 ‘1’ 代表 0,‘z’ 代表 57。把待编码的数字不断除以 58,并将余数用对应的字符表示。举个小点的数字做例子:618

1
2
3
618 / 58 = 10 .. 38 -> f

10 / 58 = 0 .. 10 -> B

拼接余数得 fB,再反转得 Bf。

3. BASE58 编码位数关系

一个数编码后,应该长于或等于比它小的数。我们可以通过简单的数学计算得出 45 个字符的 BASE58 编码可以表示的最大数:

zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = 0xc33ed2d1fbdd3bfe9c22b96164d38cf0d640e1c0ee8b61c39c57899fffffffffff

所以 <= 0xc33ed2d1fbdd3bfe9c22b96164d38cf0d640e1c0ee8b61c39c57899fffffffffff 的私钥编码后是 56 个字符;大于者 59 个字符。

4. 旧格式私钥

  • 旧格式私钥:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3

  • 对应新格式:PVT_K1_2bfGi9rYsXQSXXTvJbDAPhHLQUojjaNLomdm3cEJ1XTzMqUt3V

同理,只是格式不同罢了。不再展开。

5. 为什么公钥是固定长度呢?

因为公钥有个表示奇偶性的前缀,0x02 或者 0x03,所以它的大小范围被限定,没能相差一个 BASE58 字符。

相关文章

基于 ECC 的私钥转为公钥的过程

ECC Node.js

nfs-ganesha 端口绑定

需求

前文《在 Armbian 安装 NFS 服务端》介绍 nfs-ganesha 的安装配置。经过几天使用,发现效果还可以,但一直都是在局域网内使用,突然有一天想在公司访问家里的 NFS 共享……

问题

直接 mount 会一直卡着。

开放 111、2049 端口,再 mount,还是卡着。

通过反复重启 nfs-ganesha 并 netstat -nalp | grep ganesha.nfsd 观测,发现 mountd 端口不固定!给开放端口配置带来困难。

解决

Armbian 配置

将 mountd 端口绑定,比如 2618,但配置的方法和常规 Debian 服务器的内核级 NFS Server 不同。修改 /etc/services 添加 mountd 绑定是无用的,应该编辑 /etc/ganesha/ganesha.conf,添加以下配置:

1
2
3
4
NFS_CORE_PARAM
{
MNT_Port = 2618;
}

改完重启服务:

1
systemctl restart nfs-ganesha.service

防护墙配置

UMU 用的是 OpenWRT 路由器作为家庭网络出口,firewall 配置文件是 /etc/config/firewall,添加以下几行:

1
2
3
4
5
6
7
8
9
10
config rule
option src 'wan'
option name 'Allow-NFS'
option dest '*'
option target 'ACCEPT'
option dest_port '111 2049 2618'
option family 'ipv6'
option start_time '00:09:00'
option stop_time '00:21:00'
option enabled '0'

以上配置开启了 111、2049、2618 三个端口的转发,其中 udp/tcp 111 是 portmap 端口,udp/tcp 2049 是 nfsd 端口,udp/tcp 2618 是上一步绑定的 mountd 端口。

/etc/init.d/firewall restart 重启后就可以在办公室通过 IPv6 访问家里的 NFS 共享了。

参考

https://github.com/nfs-ganesha/nfs-ganesha/blob/next/src/config_samples/config.txt

在 Armbian 安装 NFS 服务端

需求

有个刷了 Armbian 的玩客云想当文件共享服务器。

问题

某些版本的 Armbian 内核不支持 nfsd,刚好稣就刷到!如果按照 debian 服务器玩法——安装 kernel 版服务端,是无法正常工作的:

1
apt install nfs-common nfs-kernel-server

启动时会提示:

1
2
3
4
5
6
mount: /proc/fs/nfsd: unknown filesystem type 'nfsd'.
proc-fs-nfsd.mount: Mount process exited, code=exited, status=32/n/a
proc-fs-nfsd.mount: Failed with result 'exit-code'.
nfs-mountd.service: Job nfs-mountd.service/start failed with result 'dependency'.
nfs-idmapd.service: Job nfs-idmapd.service/start failed with result 'dependency'.
nfs-server.service: Job nfs-server.service/start failed with result 'dependency'.

解决

服务端

使用用户态的 nfs-ganesha。

1
apt install nfs-ganesha nfs-ganesha-vfs

配置文件为 /etc/ganesh/ganesha.conf。nfs-ganesha-vfs 包另带一个 vfs.conf 参考模板。以下配置创建读写共享 /root/share 和只读共享 /opt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
EXPORT_DEFAULTS
{
Protocols = 4;
}

EXPORT
{
Export_Id = 77;
Protocols = 3, 4;
Path = /root/share;
Pseudo = /root/share;
Access_Type = RW;
FSAL {
Name = VFS;
}
}

EXPORT
{
Export_Id = 78;
Path = /opt;
Pseudo = /opt;
Access_Type = RO;
FSAL {
Name = VFS;
}
}

改完重启服务:

1
systemctl restart nfs-ganesha.service

客户端

  • Debian
1
2
3
4
5
6
7
8
9
sudo apt install nfs-common

showmount -e u1
Export list for u1:
/root/share (everyone)

sudo mkdir /mnt/share
# sudo mount -t nfs u1:/root/share /mnt/share
sudo mount.nfs u1:/root/share /mnt/share

注意:如果提示 mount.nfs: No such device,说明内核没有 NFS 模块,洗洗睡了,换 Windows 10 吧!

  • Windows 10

安装 NFS 客户端

浏览 NFS 共享目录

NFS 属性

注意:Windows 10 目前只有 NFS v3 客户端。服务端如果只开 v4 协议,则 Windows 10 将无法访问。

参考

https://github.com/nfs-ganesha/nfs-ganesha/blob/next/src/config_samples/config.txt

https://github.com/nfs-ganesha/nfs-ganesha/blob/next/src/config_samples/export.txt

解决通过 TTL 登录 Armbian 时的 Login incorrect

问题

用 TTL 连接刷 Armbian buster 的盒子,然后用 putty 和 plink 登录,一输入 root 回车,就报 Login incorrect!但通过 ssh 远程登录却没任何问题。

原因

ttyAML0 不在 /etc/securetty 里。

分析过程

一开始听运维小伙伴说:最可能的原因是键盘 Caps 开启了。轻松排除。

后来发现输入大写的 ROOT,反而提示输入 password,这让稣想到“枚举用户”攻击。开始思考,是不是通过 TTL 登录被 Armbian 认为是不安全的?

于是学习 securetty 相关知识,发现确实在某些不安全场合 root 是不被允许登录的,因为系统管理员一旦通过不安全渠道输入密码,那么密码就可能被盗取,所以一输入 root,就应该立刻报错,而不该继续让输入密码。而输入其它不存在的用户时(比如大写的 ROOT),反而应该让继续输入密码,最后再提示登录失败,因为如果提示用户不存在,会让黑客穷举出系统里有什么账号。

1
2
3
grep securetty /etc/pam.d/login
# Disallows root logins except on tty's listed in /etc/securetty
auth [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] pam_securetty.so

后来注意到 TTL 用的 tty 名字是 ttyAML0,grep ttyAML0 /etc/securetty 果然不存在。

解决

echo ttyAML0 >> /etc/securetty 搞定。

让 git 使用 Windows 10 OpenSSH

问题

在 Windows 10 安装 git 的同时,开启系统自带的 OpenSSH,则系统里存在两套 ssh,git 会默认使用它自己的那套。

分析

由于 Windows 10 的 sshd、ssh-agent 做成服务,比较容易管理,而且微软改造的版本会更注重安全,所以 UMU 决定舍弃 git 带的那套。

解决

1
git config --global core.sshcommand "C:/Windows/System32/OpenSSH/ssh.exe"

以上方法是自定义私钥路径时惯用的(加 -i 参数),但这里我们只改变 ssh 本身路径。参考文档:

core.sshCommand
If this variable is set, git fetch and git push will use the specified command instead of ssh when they need to connect to a remote system. The command is in the same form as the GIT_SSH_COMMAND environment variable and is overridden when the environment variable is set.

另一种比较暴力的方式:把 git 那套 ssh 指向 Windows 10 OpenSSH。

用管理员权限运行 cmd,输入:

1
2
3
4
5
6
cd "%ProgramFiles%\git\usr\bin"
ren ssh.exe -ssh.exe
mklink ssh.exe %windir%\System32\OpenSSH\ssh.exe

ren scp.exe -scp.exe
mklink scp.exe %windir%\System32\OpenSSH\scp.exe

git ssh 链接到 OpenSSH

TTL 线连接树莓派

需求

有以下物品:

  • PL2303 串口线(TTL 线)

  • Windows 10 PC

  • 树莓派 Model B

求:PL2303 串口线是好是坏?

解决

1. 使 Windows 10 正常驱动 PL2303

把 TTL 线插入 PC USB 口,Windows 10 会自动安装驱动,然而 Prolific USB-to-Serial Comm Port 版本 3.8.31.0 [2019/7/30]Prolific USB-to-Serial Comm Port 版本 3.8.18.0 [2017/10/17] 都不能正常工作。这说明 PL2303 芯片已经被淘汰……买 TTL 线请选当下流行的其它芯片。

Prolific USB-to-Serial Comm Port 版本 3.8.31.0 [2019/7/30] 不能正常工作

Prolific USB-to-Serial Comm Port 版本 3.8.18.0 [2017/10/17] 不能正常工作

搜“PL2303_Prolific_GPS_1013_20090319”,装“Prolific USB-to-Serial Comm Port 版本 3.3.2.105 [2008/10/27]”,可正常工作。

Prolific USB-to-Serial Comm Port 版本 3.3.2.105 [2008/10/27] 可正常工作

2. 配置串口

波特率设为 115200,其它默认,最终参数如图:

串口参数

高级

3. TTL 线连树莓派 GPIO

如果树莓派有用 MicroUSB 供电,则 VCC(红)可以不接,只把 GND(黑)、RX(白)、TX(绿)分别接到树莓派的 P6、P8、P10;如果需要直接用 GPIO 供电,则把 VCC(红)插到 P2 或 P4。

PL2303 接线颜色 树莓派 GPIO 树莓派针脚
VCC 5V P2, P4
RX GPIO14(UART_TXD) P8
TX 绿 GPIO15(UART_RXD) P10
GND GND P6, P39

GPIO

TTL 接 GPIO:红接 1 或 2,黑接 3,白接 4,绿接 5

树莓派 GPIO 供电

4. 用 PuTTY 连接串口

使用 plink 或 putty 皆可,注意:需使用管理员权限运行。

1
plink -serial \\.\COM1 -sercfg 115200,8,n,1,n

plink

PuTTY

COM1 - PuTTY

相关

在树莓派上编译 go-ipfs

诗盗·七鹰鸣

《#诗盗#·七鹰鸣》:七鹰鸣十几夜,语纷纷,戮殇兮,应人欲,度顽魂。几夜温酒,家何处?游牧童谣,只许樱花处问。

注解

改编自唐代杜牧的《清明》:

清明时节雨纷纷,路上行人欲断魂。
借问酒家何处有,牧童遥指杏花村。

跟 UMU 一起玩 OpenWRT(入门篇18):更换 opkg 源

问题

默认源在国内访问速度普遍比较慢。

PS: 本篇理论上应该几年前就写的……以前经常用台湾省的网络,没发现,现在补一篇。

解决

  1. 更换清华大学源
1
2
sed -i 's/downloads\.openwrt\.org/mirrors\.tuna\.tsinghua\.edu\.cn\/openwrt/g' /etc/opkg/distfeeds.conf
opkg update
  1. 使用 https
1
2
opkg install libustream-mbedtls
sed -i 's/http:/https:/g' /etc/opkg/distfeeds.conf

相关

如果您想把整个软件源下载到本地,可以参考:https://github.com/UMU618/openwrt-opkg-cache