Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save lantw44/86fca5b956607cc9b2fde8839b6323f9 to your computer and use it in GitHub Desktop.

Select an option

Save lantw44/86fca5b956607cc9b2fde8839b6323f9 to your computer and use it in GitHub Desktop.
作者 lantw44 ([=============>]) 看板 lantw44
標題 [-LX-] 網路報稅健保卡元件
時間 Sat Jun 2 13:37:43 2018
───────────────────────────────────────
今年是我第一次遇到申報綜合所得稅這件事,雖然這次的資料很少,現場填表和人工送件
其實很快就辦完了,只是看到其他人在用網頁報稅,好奇來看看這系統是怎麼做的。
一直以來都聽說透過網頁報稅最大的問題是要如何透過瀏覽器存取本機的讀卡機。以往在
瀏覽器安裝外掛還很常見的時候,大概就是叫使用者安裝 ActiveX 或 NPAPI 外掛吧?以
前聽說有些人提議用 Java 比較容易跨平臺,但 Java 依然是個 NPAPI 外掛,是個現在
各大瀏覽器都想丟掉的東西。
於是今年網路報稅是怎麼解決這個問題呢?WebUSB API 嗎?好像不太對,這似乎會變成
實作整個裝置驅動程式,我也有點好奇現在有什麼網站有在用這類的 API,各瀏覽器支援
又如何。總之設定步驟有個「安裝健保卡元件」和「設定為可信任伺服器」以後,就猜測
大概是要使用者自行下載和安裝一些東西了。
進到下載頁面,亂碼……又是 Big5。上面有 Windows、macOS、Linux 版本的下載連結,
其中 Linux 版有提供 Ubuntu 和 Fedora 兩個版本。本來期待這兩個應該分別是 .deb
和 .rpm 下載連結,結果居然是 .zip,有點意外。
$ unar mLNHIICC_Setup.fedora.zip
mLNHIICC_Setup.fedora.zip: Zip
健保卡元件_Linux(Fedora)安裝手冊.pdf (399482 B)... OK.
Successfully extracted to "mLNHIICC_Setup.fedora".
所以說是在 zip 裡面又包了一層 tar.gz,安裝手冊應該是跟網頁上提供的一樣吧。總之
就繼續解開看看裡面有些什麼。
$ unar mLNHIICC_Setup.20160206.fedora25.tar.gz
mLNHIICC_Setup.20160206.fedora25.tar.gz: Tar in Gzip
mLNHIICC_Setup.20160206/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/ReStart_NHIICC.sh (66 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/x32/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/x32/mLNHIICC (1794468 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/img/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/img/check.png (1965 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/img/clear.png (958 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/img/cross.png (2003 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/bak/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/bak/ESample2.html (7901 B)... O
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/ESample.html (8272 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/ESample2.html (8207 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/js/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/html/js/stuff.js (2552 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/x64/ (dir)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/x64/mLNHIICC (2450272 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/Install (1948 B)... OK.
mLNHIICC_Setup.20160206/mLNHIICC_Setup/UnInstall (187 B)... OK.
Successfully extracted to "mLNHIICC_Setup.20160206".
對,裡面真的沒有 RPM,而是兩個 shell script 分別叫 Install 和 UnInstall。一直
以來我對這種不是發行版提供的軟體都有些懷疑,例如收到 RPM 檔總會先看看有沒有在
奇怪的地方塞檔案,preinstall 和 postinstall script 有沒有做些不該做的事情。這
裡的 Install 和 UnInstall script 當然也不例外,我是不會直接執行的。
$ cat Install
#!/bin/bash
sudo killall mLNHIICC;
為什麼要 sudo?為什麼我要用 root 身份執行這個不知道能不能信任的健保卡元件?
if [ ! -e "/usr/local/share/NHIICC/mLNHIICC" ] ; then
mkdir /usr/local/share/NHIICC ;
case $(uname -m) in
x86_64)
cp ./x64/mLNHIICC /usr/local/share/NHIICC/mLNHIICC ;
echo "x86_64"
;;
i*86)
cp ./x32/mLNHIICC /usr/local/share/NHIICC/mLNHIICC ;
echo "32 bit versioni"
;;
拼錯字了,看起來是緊急上線的產品。還有為什麼 i*86 是對應 x32?一般來說 x32 指
的並不是 x86 32-bit 而是在 x86 64-bit 上用 32-bit 指標來節省記憶體用量吧。
*)
cp ./x32/mLNHIICC /usr/local/share/NHIICC/mLNHIICC ;
echo "unknow OS bits , If You are 64 Run next command:";
echo "cp ./x64/mLNHIICC /usr/local/share/NHIICC/mLNHIICC";
所以說都在判斷 CPU 類型了為什麼還可以裝到 /usr/local/share?share 這個資料夾,
或說 datadir,不是用來存 read-only architecture-independent data 的地方嗎?既
然是 ELF 可執行檔應該要塞進 bin,真的不喜歡讓它出現在 PATH 也是 libexec 吧。
;;
esac
#cp ./ReStart_NHIICC.sh /usr/local/share/NHIICC/ReStart_NHIICC.sh ;
chmod 755 /usr/local/share/NHIICC/mLNHIICC ;
cp -R ./html /usr/local/share/NHIICC ;
fi
以上問題過多,我大概是不會執行這個 script 了。以下還有用 /etc/debian_version
和 /etc/redhat-release 判斷發行版改 /etc/rc.local 的東西。對我來說這些東西也是
寫得一團亂,為什麼都有 /etc/os-release 了還要在不同發行版看不同的檔案,還有為
什麼我會想要在開機時就自動啟動這很可能一年只使用一次的「健保卡元件」,而且還是
用 root 身份執行。最後,很不意外的,最後一行是這個:
sudo /usr/local/share/NHIICC/mLNHIICC &
在開始執行之前,還是先多觀察一點東西吧。
首先來確認健保卡插進讀卡機真的可以讀到,我是用筆電內建的讀卡機:
$ pcsc_scan
PC/SC device scanner
V 1.5.2 (c) 2001-2017, Ludovic Rousseau <[email protected]>
Using reader plug'n play mechanism
Scanning present readers...
0: Alcor Micro AU9560 00 00
Thu May 31 22:35:39 2018
Reader 0: Alcor Micro AU9560 00 00
Card state: Card inserted, Shared Mode,
ATR: 3B FD 13 00 00 81 31 FE 45 4A 43 4F 50 32 31 76 32 33 31 47 44 54 E1
......
Possibly identified card (using /usr/share/pcsc/smartcard_list.txt):
3B FD 13 00 00 81 31 FE 45 4A 43 4F 50 32 31 76 32 33 31 47 44 54 E1
National Health Insurance Card, Taiwan
看起來是有讀到健保卡。再來看看健保卡元件本身到底是什麼東西:
$ file x*/*
x32/mLNHIICC: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32,
BuildID[sha1]=3acd73c2f3279902e67bdc46427575a8af8601a5, with debug_info, not
stripped
x64/mLNHIICC: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux
2.6.32, BuildID[sha1]=b02c24ab1ece4912aae71fe975e2ba9a54d11f61, not stripped
呼應前面的 Install script,x32 不是我們想的那個 x32,而是 x86 32-bit。
確認 interpreter 是指向我們信任的地方以後,ldd 看看用了什麼函式庫:
$ ldd x*/*
x32/mLNHIICC:
linux-gate.so.1 (0xf7fa9000)
libdl.so.2 => /lib/libdl.so.2 (0xf7f40000)
libpcsclite.so.1 => not found
libpthread.so.0 => /lib/libpthread.so.0 (0xf7f21000)
libm.so.6 => /lib/libm.so.6 (0xf7e1f000)
libssl.so.1.0.0 => not found
libc.so.6 => /lib/libc.so.6 (0xf7c7c000)
/lib/ld-linux.so.2 (0xf7fab000)
x64/mLNHIICC:
linux-vdso.so.1 (0x00007fff4bb85000)
libm.so.6 => /lib64/libm.so.6 (0x00007f6149c14000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f6149a10000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f61497f1000)
libpcsclite.so.1 => /lib64/libpcsclite.so.1 (0x00007f61495e6000)
libc.so.6 => /lib64/libc.so.6 (0x00007f6149227000)
/lib64/ld-linux-x86-64.so.2 (0x00007f614a3f5000)
librt.so.1 => /lib64/librt.so.1 (0x00007f614901f000)
很不意外的需要 OpenSSL 和 PC/SC Lite 函式庫。沒有依賴 GUI 的東西看起來是個單純
在背景服務的程式,不綁特定瀏覽器,想起來也是不錯的。
好,現在就來執行看看,可是我不會把你塞到 /usr/local/share,我很懶得為一個寫得
不怎麼樣的 Install script 事後清理檔案。只有一個可執行檔我猜應該是不太會有路
徑問題吧。
$ ./mLNHIICC
$ ps aux | grep mL
什麼都沒出現,是執行到哪裡去了?我可不想用 sudo 執行。
$ strace ./mLNHIICC
......
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f0eca566a50) = 9755
exit_group(0) = ?
+++ exited with 0 +++
看起來是 fork 掉了。對我來說 fork 一次叫子程序接手做正事是個舊式的 hack,是一
種製造 process 管理問題的過時用法。還有,既然都要 fork 了,為什麼 Install 裡面
還要加 & 來執行?
再來一次,這次看看子程序做了什麼。失敗卻完全不解釋原因讓人感覺很糟。
$ strace -f ./mLNHIICC
......
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f36bda6ea50) = 3508
strace: Process 3508 attached
[pid 3507] exit_group(0) = ?
[pid 3507] +++ exited with 0 +++
[pid 3508] close(1024) = -1 EBADF (錯誤的檔案敘述項)
[pid 3508] close(1023) = -1 EBADF (錯誤的檔案敘述項)
close(1022) = -1 EBADF (錯誤的檔案敘述項)
close(1021) = -1 EBADF (錯誤的檔案敘述項)
close(1020) = -1 EBADF (錯誤的檔案敘述項)
close(1019) = -1 EBADF (錯誤的檔案敘述項)
close(1018) = -1 EBADF (錯誤的檔案敘述項)
......
close(2) = 0
close(1) = 0
close(0) = 0
openat(AT_FDCWD, "/dev/null", O_RDWR) = 0
dup(0) = 1
dup(0) = 2
把 stdio 全部關光光然後導向 /dev/null?難怪完全沒有訊息。
umask(027) = 022
看起來還不錯?
chdir("/tmp") = 0
openat(AT_FDCWD, "exampled.lock", O_RDWR|O_CREAT, 0640) = 3
等一下,在 /tmp 這種大家都可以寫入的資料夾新增檔案不加 O_EXCL 真的沒有問題嗎?
當然也許開發者可以說報稅網頁只該在個人電腦上使用,所以沒有其他使用者可以利用這
個漏洞,但總覺得這種不良習慣不應該出現。
還有 exampled 是什麼檔名,這個程式在內部是個範例所以叫 exampled?
fcntl(3, F_SETLK, {l_type=F_WRLCK, l_whence=SEEK_CUR, l_start=0, l_len=0}) = 0
getpid() = 9867
write(3, "9867\n", 5) = 5
猜測這是 PID。先說一下這裡有點前後不一致,因為這些 strace 片段是不同次執行湊起
來的,所以每段看到的 PID 可能會不一樣。
開了檔案真的有寫入內容,所以這真的是個洞。
openat(AT_FDCWD, "/etc/hosts", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=1572, ...}) = 0
read(4, "127.0.0.1 localhost localhost."..., 4096) = 1572
read(4, "", 4096) = 0
close(4) = 0
openat(AT_FDCWD, "/etc/hosts", O_WRONLY|O_CREAT|O_APPEND, 0666) = -1
EACCES (拒絕不符權限的操作)
所以說你為什麼要改 /etc/hosts?我依然不會給你 root 權限,備份 /etc/hosts 設定
ACL 等一下讓你繼續跑。
exit_group(-2) = ?
+++ exited with 254 +++
對 exit 傳負數是怎麼回事……
# setfacl -m u:lantw44:rw /etc/hosts
$ strace -f ./mLNHIICC
......
openat(AT_FDCWD, "/etc/hosts", O_WRONLY|O_CREAT|O_APPEND, 0666) = 4
lseek(4, 0, SEEK_END) = 1572
fstat(4, {st_mode=S_IFREG|0664, st_size=1572, ...}) = 0
write(4, "\n127.0.0.1 iccert.nhi.gov.tw\n", 32) = 32
close(4) = 0
居然把一個神秘的主機名稱指向本機?我猜等一下它會用這名稱開伺服器給網頁使用。
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=790, ...}) = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=790, ...}) = 0
read(4, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\5\0\0\0\5\0\0\0\0"...,
4096) = 790
lseek(4, -485, SEEK_CUR) = 305
read(4, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\5\0\0\0\5\0\0\0\0"...,
4096) = 485
close(4) = 0
openat(AT_FDCWD, "//tmp//mNHIICC.err.20180531.log",
O_WRONLY|O_CREAT|O_APPEND, 0666) = 4
lseek(4, 0, SEEK_END) = 6906
fstat(4, {st_mode=S_IFREG|0640, st_size=6906, ...}) = 0
write(4, "20180531:225221,SetServiceStatus"..., 78) = 78
close(4)
所以說確實是有寫 log 可以用來 debug 的,只是頻繁的開關檔案不知道是為了什麼,然
後檔案路徑用兩條斜線又是怎麼回事。
$ netstat -lnp
tcp 0 0 0.0.0.0:7777 0.0.0.0:* LISTEN 10212/./mLNHIICC
$ ss -lnp
tcp LISTEN 0 1 0.0.0.0:7777 0.0.0.0:* users:(("mLNHIICC",pid=10212,fd=4)
回來看看 strace 說什麼。
......
[pid 3510] socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 4
[pid 3510] bind(4, {sa_family=AF_INET, sin_port=htons(7777),
sin_addr=inet_addr("0.0.0.0")}, 16) = 0
[pid 3510] listen(4, 1) = 0
[pid 3510] accept(4, <unfinished ...>
在本機開了一個 IPv4 的 TCP server 在 port 7777 上等著。只是這個服務只有本機要
用,為什麼是用 0.0.0.0 讓全世界都可以連?這樣真的沒問題嗎……
感覺平時多設定一點防火牆還是不錯的。
......
[pid 3517] socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0) = 10
[pid 3517] connect(10, {sa_family=AF_UNIX,
sun_path="/var/run/pcscd/pcscd.comm"}, 28) = 0
看起來確實有去連讀卡機。
按照安裝教學所說,這時候應該要打開瀏覽器到:
file:///usr/local/share/NHIICC/html/ESample.html
確認看看「健保卡元件」是否有正常運作。
因為我沒有執行 Install script 所以就手動開 html/ESample.html 這檔案,畫面上出
現紅色的 Not connected 和幾個寫著 Click Me! 的按鈕。
這時候想到那個什麼「設定為可信任伺服器」的步驟。按下去的網址是:
https://iccert.nhi.gov.tw:7777
沒錯就是剛才我們執行的那個「健保卡元件」的網址。所謂的「設定為可信任伺服器」就
是看著憑證錯誤頁面叫瀏覽器忽略錯誤繼續瀏覽。當然,本機服務憑證沒過好像也是預料
中的事情。至於為什麼本機服務還要 HTTPS?我猜可能是避免在 HTTPS 的報稅網頁用普
通的 HTTP 連線會被瀏覽器擋下來吧。
回到 ESample.html 重新整理,出現綠色的 Connected to server,瀏覽器說它建立了一
個 WebSocket 連線到 wss://iccert.nhi.gov.tw:7777/echo。
之後繼續觀察這個「健保卡元件」還有沒有其他網路活動,看到這個:
[pid 3517] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP)
= 9
[pid 3517] connect(9, {sa_family=AF_INET, sin_port=htons(53),
sin_addr=inet_addr("192.168.1.130")}, 16) = 0
192.168.1.130 是我設定的 DNS server。
[pid 3517] poll([{fd=9, events=POLLOUT}], 1, 0) = 1 ([{fd=9,
revents=POLLOUT}])
[pid 3517] sendto(9, "\f\0\1\0\0\1\0\0\0\0\0\0\tcloudicap\3nhi\3gov\2t"...,
38, MSG_NOSIGNAL, NULL, 0) = 38
看起來是在查詢 cloudicap.nhi.gov.tw。DNS 的字串格式是先給長度再給內容。
[pid 3517] poll([{fd=9, events=POLLIN}], 1, 5000) = 1 ([{fd=9,
revents=POLLIN}])
[pid 3517] ioctl(9, FIONREAD, [180]) = 0
[pid 3517] recvfrom(9,
"\f\0\201\200\0\1\0\1\0\2\0\4\tcloudicap\3nhi\3gov\2t"..., 1024, 0,
{sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.1.130")},
[28->16]) = 180
[pid 3517] close(9) = 0
[pid 3517] connect(8, {sa_family=AF_INET, sin_port=htons(443),
sin_addr=inet_addr("210.69.214.206")}, 16) = 0
[pid 3517] ioctl(8, FIONBIO, [0]) = 0
總之是連上 https://cloudicap.nhi.gov.tw 了。之後傳了什麼資料我就不知道了。
最後,別忘了用完以後把 /etc/hosts 裡用不到的項目刪掉,並且把權限改回來。
$ <editor> /etc/hosts
# setfacl -b /etc/hosts
結論:我覺得在本機開 WebSocket server 然後改 /etc/hosts 其實還蠻有創意的。不過
如果可以把程式做得更好,安裝 script 不要一直寫讓人有疑慮的東西應該會更好。
也許可以看看有什麼地方可以回報問題吧。不過現在已經是六月了,希望訊息還有人看。
--
※ 發信站: 批踢踢兔(ptt2.cc), 來自: 140.112.30.70
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment