วิธีที่ปลอดภัยที่สุดสำหรับการเขียนทางโปรแกรมไปยังไฟล์ที่มีสิทธิ์รูทคืออะไร


35

แอปพลิเคชันขนาดใหญ่ต้องการในเวลาหนึ่งเพื่อทำการเขียนจำนวนเล็กน้อยไปยังไฟล์ที่ต้องการการอนุญาตรูต มันไม่ได้เป็นไฟล์จริงๆ แต่เป็นส่วนต่อประสานฮาร์ดแวร์ที่สัมผัสกับลีนุกซ์เป็นไฟล์

เพื่อหลีกเลี่ยงการให้สิทธิ์รูทแก่แอปพลิเคชันทั้งหมดฉันเขียนสคริปต์ทุบตีซึ่งทำหน้าที่สำคัญ ตัวอย่างเช่นสคริปต์ต่อไปนี้จะเปิดใช้งานพอร์ต 17 ของอินเตอร์เฟสฮาร์ดแวร์เป็นเอาต์พุต:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

อย่างไรก็ตามตามที่suidถูกปิดการใช้งานสำหรับสคริปต์ทุบตีในระบบของฉันฉันสงสัยว่าอะไรคือวิธีที่ดีที่สุดในการบรรลุเป้าหมาย

  1. ใช้วิธีแก้ปัญหาที่แสดงไว้ที่นี่

  2. เรียกใช้สคริปต์ด้วยsudoจากแอปพลิเคชันหลักและแก้ไขรายการ sudoers ตามเพื่อหลีกเลี่ยงการต้องใช้รหัสผ่านเมื่อเรียกใช้สคริปต์ ฉันเล็กน้อยอึดอัดที่จะให้สิทธิ์ sudo echoไป

  3. เพียงแค่เขียนโปรแกรม C ด้วยfprintfและตั้งเป็น suid root ฮาร์ดโค้ดสตริงและชื่อไฟล์และตรวจสอบให้แน่ใจว่าเฉพาะ root เท่านั้นที่สามารถแก้ไขได้ หรืออ่านสตริงจากไฟล์ข้อความเช่นเดียวกันตรวจสอบให้แน่ใจว่าไม่มีใครสามารถแก้ไขไฟล์ได้

  4. วิธีแก้ปัญหาอื่นที่ไม่ได้เกิดขึ้นกับฉันและปลอดภัยกว่าหรือง่ายกว่านั้นคือวิธีที่นำเสนอข้างต้น


10
ทำไมคุณไม่เริ่มโปรแกรมด้วยสิทธิ์พิเศษเปิดไฟล์และปล่อยสิทธิ นั่นคือวิธีที่เว็บเซิร์ฟเวอร์ทุกอันหรืออย่างนั้นทำเพื่อซ็อกเก็ต อย่างมีประสิทธิภาพคุณไม่ได้ทำงานในฐานะรูทหรือเป็นผู้ช่วยที่จำเป็น
Damon

คำตอบ:


33

คุณไม่จำเป็นต้องให้การเข้าถึงsudo echoในความเป็นจริงนั้นไม่มีจุดหมายเพราะเช่นเดียวกับsudo echo foo > barการเปลี่ยนเส้นทางจะทำในฐานะผู้ใช้ดั้งเดิมไม่ใช่ในฐานะรูท

โทรหาสคริปต์ขนาดเล็กโดยsudoอนุญาตให้NOPASSWD:เข้าถึงเฉพาะสคริปต์นั้น (และสคริปต์อื่น ๆ ที่คล้ายกัน) ของผู้ใช้ที่ต้องการเข้าถึงสคริปต์ดังกล่าว

นี่เป็นวิธีที่ดีที่สุด / ปลอดภัยที่สุดในการใช้งานsudoเสมอ แยกคำสั่งจำนวนเล็กน้อยที่ต้องการสิทธิ์รูทเป็นสคริปต์แยกต่างหากของตนเองและอนุญาตให้ผู้ใช้ที่ไม่น่าเชื่อถือหรือเชื่อถือได้เพียงบางส่วนเท่านั้นที่รันสคริปต์นั้นในฐานะรูท

sudoสคริปต์ที่สามารถใช้งานได้ขนาดเล็กไม่ควรใช้ args (หรืออินพุต) จากผู้ใช้ (เช่นโปรแกรมอื่น ๆ ที่เรียกใช้ควรมีตัวเลือกและรหัสถาวรยาก) หรือควรตรวจสอบข้อโต้แย้ง / อินพุตที่ต้องระมัดระวังอย่างมาก ยอมรับจากผู้ใช้

เป็นคนหวาดระแวงในการตรวจสอบ - แทนที่จะมองหาสิ่งที่ 'ไม่ดีที่รู้จัก' เพื่อแยกออกให้สิ่งที่ 'รู้จักดี' เท่านั้นและยกเลิกในสิ่งที่ไม่ตรงกันหรือมีข้อผิดพลาดหรืออะไรก็ตามที่น่าสงสัยจากระยะไกล

การตรวจสอบควรจะเกิดขึ้นเป็นช่วงต้นในสคริปต์ที่เป็นไปได้ (โดยเฉพาะอย่างยิ่งก่อนที่จะไม่อะไรอื่นเป็นหลัก)


ฉันจริงๆควรจะได้กล่าวถึงเรื่องนี้เมื่อครั้งแรกที่ผมเขียนคำตอบนี้ แต่ถ้าสคริปต์ของคุณเป็นสคริปต์เปลือกมันต้องถูกต้องพูดทุกตัวแปร โปรดใช้ความระมัดระวังโดยเฉพาะอย่างยิ่งที่จะพูดตัวแปรที่มีการป้อนข้อมูลที่จัดทำโดยผู้ใช้ในการใด ๆทาง แต่ไม่ถือว่าตัวแปรบางส่วนมีความปลอดภัยQUOTE พวกเขาทั้งหมด

ซึ่งรวมถึงตัวแปรสภาพแวดล้อมการควบคุมที่อาจเกิดขึ้นโดยผู้ใช้ (เช่น"$PATH", "$HOME", "$USER"ฯลฯ และแน่นอนรวมทั้ง"$QUERY_STRING"และ"HTTP_USER_AGENT"ฯลฯ ในสคริปต์ CGI ก) อันที่จริงแค่อ้างพวกเขาทั้งหมด หากคุณมีการสร้างบรรทัดคำสั่งที่มีการขัดแย้งหลายใช้อาร์เรย์สร้างรายการ args และอ้างว่า "${myarray[@]}"-

ฉันเคยพูดว่า "อ้างพวกเขาทั้งหมด" บ่อยเพียงพอหรือยัง จำได้ ทำมัน.


18
คุณลืมที่จะพูดถึงว่าสคริปต์ควรเป็นเจ้าของโดย root พร้อมสิทธิ์ 500
Wildcard

13
อย่างน้อยก็ให้ลบสิทธิ์การเขียนเพื่อความดี นั่นคือประเด็นของฉันจริงๆ ที่เหลือก็แค่แข็ง
Wildcard

10
โปรแกรม C ที่เขียนขึ้นอย่างรอบคอบจะมีพื้นผิวการโจมตีที่เล็กกว่าเชลล์สคริปต์
user253751

7
@immibis อาจเป็นไปได้ แต่การเขียนและดีบักจะใช้เวลานานกว่าเชลล์สคริปต์ และต้องมีคอมไพเลอร์ C (ซึ่งถูกห้ามในเซิร์ฟเวอร์ที่ใช้งานจริงบางแห่งเพื่อลดความเสี่ยงด้านความปลอดภัยโดยทำให้ผู้โจมตีรวบรวมการหาช่องโหว่ได้ยากขึ้น) นอกจากนี้ IMO เชลล์สคริปต์ที่เขียนโดยผู้ดูแลระบบระดับต้นถึงระดับกลางหรือโปรแกรมเมอร์มีโอกาสน้อยที่จะถูกเอาเปรียบกว่าโปรแกรม C ที่เขียนโดยคนที่มีทักษะคล้ายกันโดยเฉพาะอย่างยิ่งหากต้องยอมรับและตรวจสอบข้อมูลที่ผู้ใช้จัดหา
cas

3
@JonasWielicki - ฉันคิดว่าตอนนี้เราดีและเป็นอาณาจักรแห่งความคิดเห็นมากกว่าความเป็นจริง คุณสามารถสร้างอากิวเมนต์ที่ถูกต้องสำหรับเชลล์หรือ C (หรือ perl หรือ python หรือ awk ฯลฯ ) ซึ่งมีแนวโน้มที่จะเกิดข้อผิดพลาดที่เป็นประโยชน์ได้ไม่มากก็น้อย เมื่อใดก็ตามมันขึ้นอยู่กับทักษะและความสนใจของโปรแกรมเมอร์เป็นหลักในรายละเอียด (และความเหนื่อยล้า มันเป็นความจริงแม้ว่าภาษาระดับต่ำกว่ามักจะต้องการรหัสเพิ่มเติมที่จะเขียนเพื่อให้บรรลุสิ่งที่สามารถทำได้ในบรรทัดที่น้อยกว่าของรหัสในภาษาระดับสูงกว่า .... และแต่ละ LoC เป็นโอกาสสำหรับ ผิดพลาดที่จะทำ
cas

16

ตรวจสอบเจ้าของไฟล์ gpio:

ls -l /sys/class/gpio/

เป็นไปได้มากที่คุณจะพบว่าพวกเขาเป็นเจ้าของโดยกลุ่มgpio:

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

ในกรณีนี้คุณสามารถเพิ่มผู้ใช้ของคุณในgpioกลุ่มเพื่อให้สิทธิ์การเข้าถึงโดยไม่ต้องใช้ sudo:

sudo usermod -aG gpio myusername

คุณจะต้องออกจากระบบและลงชื่อเข้าใช้อีกครั้งหลังจากนั้นการเปลี่ยนแปลงจึงจะมีผล


มันไม่ทำงาน อันที่จริงเจ้าของกลุ่มของทุกสิ่งใน/sys/class/gpio/gpio แต่แม้หลังจากเพิ่มตัวเองลงในกลุ่มนั้นฉันยังคงได้รับ "ปฏิเสธสิทธิ์" ทุกครั้งที่ฉันพยายามเขียนอะไรที่นั่น
vsz

1
ปัญหาคือว่าไฟล์ใน/sys/class/gpio/นั้นเป็นเพียงการเชื่อมโยงไปยัง/sys/devices/platform/soc/<some temporary name>/gpioที่ทั้งเจ้าของและกลุ่มเป็นรูท
vsz

4
@vsz คุณลองchgrp gpio /sys/devices/platform/soc/*/gpioหรือยัง อาจมีบางสิ่งเช่นนั้นที่สามารถใส่ในไฟล์เริ่มต้น
jpa

ใช่ แต่มันไม่ง่ายอย่างนั้น เนื่องจากมันถูกสร้างขึ้นมาในวิธีที่ต่างกันฉันจึงต้องใช้บางอย่างเช่นchgrp gpio `readlink -f /sys/class/gpio/gpio18`/*
vsz

7

ทางออกหนึ่งสำหรับสิ่งนี้ (ใช้เฉพาะบนเดสก์ท็อป Linux แต่ยังใช้ในกรณีอื่น ๆ ) คือการใช้D-Busเพื่อเปิดใช้งานบริการขนาดเล็กที่ทำงานในฐานะรูทและpolkitเพื่อทำการอนุญาต นี่เป็นพื้นฐานที่ polkit ออกแบบมาเพื่อ; จากเอกสารแนะนำเบื้องต้น :

polkit จัดทำ API การอนุญาตที่มีวัตถุประสงค์เพื่อใช้งานโดยโปรแกรมที่มีสิทธิพิเศษ (“ MECHANISMS”) ที่เสนอบริการสำหรับโปรแกรมที่ไม่มีสิทธิ์ (“ ลูกค้า”) ดูหน้าคู่มือ polkit สำหรับสถาปัตยกรรมระบบและภาพใหญ่

แทนที่จะเรียกใช้โปรแกรมตัวช่วยของคุณโปรแกรมขนาดใหญ่ที่ไม่มีสิทธิพิเศษจะส่งคำขอบนรถบัส ผู้ช่วยของคุณอาจจะถูกเรียกใช้เป็นภูตเริ่มต้นที่การบูตระบบหรือดีกว่าจะใช้งานได้ตามความจำเป็นโดย systemd จากนั้นผู้ช่วยนั้นจะใช้ polkit เพื่อตรวจสอบว่าคำขอมาจากสถานที่ที่ได้รับอนุญาต (หรือในกรณีนี้หากรู้สึกว่าเกินความเป็นจริงคุณสามารถใช้กลไกการพิสูจน์ตัวตน / การอนุญาตใช้รหัสอื่น ๆ ได้)

ผมพบว่าบทความพื้นฐานที่ดีในการสื่อสารผ่านทาง D-Busและในขณะที่ฉันไม่ได้ทดสอบมันปรากฏขึ้นนี้จะเป็นตัวอย่างที่พื้นฐานของการเพิ่ม polkit ในการผสม

ในวิธีการนี้ไม่จำเป็นต้องทำเครื่องหมาย setuid


5

วิธีหนึ่งในการทำเช่นนี้คือการทำให้โปรแกรม setuid-root เขียนใน C ซึ่งทำหน้าที่เฉพาะสิ่งที่จำเป็นและไม่มีอะไรเพิ่มเติม ในกรณีของคุณไม่จำเป็นต้องดูข้อมูลของผู้ใช้เลย

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}

ไม่มีทางที่จะทำลายสิ่งนี้ผ่านตัวแปรสภาพแวดล้อมหรืออะไรก็ได้เพราะสิ่งที่มันทำคือการเรียกระบบคู่

ข้อเสีย: คุณควรตรวจสอบค่าส่งคืนของการเรียกของระบบทุกครั้ง

Upside: การตรวจสอบข้อผิดพลาดเป็นเรื่องง่ายมาก: หากมีข้อผิดพลาดใด ๆ เพียงแค่perrorและประกันตัวออก: ออกด้วยสถานะที่ไม่เป็นศูนย์ straceหากมีข้อผิดพลาดในการตรวจสอบด้วย คุณไม่ต้องการโปรแกรมนี้เพื่อให้ข้อความแสดงข้อผิดพลาดที่ดีจริงๆ


2
ฉันอาจถูกล่อลวงให้เปิดทั้งสองไฟล์ก่อนที่จะเขียนอะไรเพื่อป้องกันการขาดวินาที และฉันอาจเรียกใช้โปรแกรมนี้ผ่านsudoดังนั้นจึงไม่จำเป็นต้อง setuid บังเอิญมันเป็นสำหรับ<fcntl.h> open()
Jonathan Leffler

3

Bless tee แทน echo สำหรับ sudo เป็นวิธีการทั่วไปในการเข้าถึงสถานการณ์ที่คุณจำเป็นต้อง จำกัด รูต perms การเปลี่ยนเส้นทางไปยัง / dev / null คือการหยุดเอาต์พุตใด ๆ ที่รั่ว - ทีทำในสิ่งที่คุณต้องการ

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null

5
หากคุณอนุญาตให้teeเรียกใช้sudoคุณจะอนุญาตให้ไฟล์ใด ๆ ถูกเขียนทับหรือต่อท้าย (รวมถึงตัวอย่างเช่น/etc/passwd- เพียงผนวกบัญชี uid = 0 ใหม่) คุณอาจอนุญาตให้เรียกใช้คำสั่งทั้งหมดด้วยsudo(BTW /etc/sudoersอยู่ในชุดของ 'ไฟล์ใดก็ได้' เพื่อให้สามารถเขียนทับได้sudo tee)
cas

2
เห็นได้ชัดว่าคุณสามารถ จำกัด ไฟล์ที่teeสามารถเขียนได้ตามที่อธิบายไว้ในคำตอบนี้สำหรับคำถามแยกต่างหาก เพียงให้แน่ใจว่าคุณอ่านความคิดเห็นด้วยเนื่องจากบางคนมีปัญหากับไวยากรณ์ดั้งเดิมที่ใช้และแนะนำการแก้ไขปัญหานั้น
อเล็กซ์

1
@alex ใช่คุณสามารถทำได้ด้วยคำสั่งใด ๆ ใน sudo - จำกัด args ที่อนุญาต กำหนดค่าจะได้รับมากยาวและซับซ้อนถ้าคุณต้องการที่จะอนุญาตหรืออะไรก็ตามที่จะทำงานบนไฟล์จำนวนมากด้วยtee sudo
cas

0

คุณสามารถสร้างสคริปต์ที่ทำงานที่คุณต้องการได้ จากนั้นสร้างบัญชีผู้ใช้ใหม่ที่สามารถเข้าสู่ระบบได้โดยการระบุรหัสที่ใช้โดย OpenSSH

หมายเหตุ: ทุกคนจะสามารถเรียกใช้สคริปต์ได้หากพวกเขามีไฟล์คีย์ดังนั้นตรวจสอบให้แน่ใจว่าทุกคนที่คุณต้องการป้องกันไม่ให้ดำเนินการกับไฟล์คีย์ OpenSSH

ในการกำหนดค่า OpenSSH (ในไฟล์ authorized_keys) ก่อนระบุคีย์ให้ระบุคำสั่ง (ตามด้วยช่องว่าง) ดังที่อธิบายไว้ในข้อความ "ตัวอย่าง" นี้:

command="myscript" keydata optionalComment

ตัวเลือกการกำหนดค่านี้สามารถ จำกัด ปุ่ม OpenSSH นั้นให้เรียกใช้คำสั่งเฉพาะเท่านั้น ตอนนี้คุณมีสิทธิ์อนุญาต sudo แต่การกำหนดค่า OpenSSH เป็นส่วนหนึ่งของโซลูชันที่ใช้เพื่อ จำกัด / จำกัด สิ่งที่ผู้ใช้นั้นสามารถทำได้เพื่อให้ผู้ใช้ไม่ได้ใช้คำสั่งอื่น สิ่งนี้ยังไม่มีความซับซ้อนของไฟล์การกำหนดค่า "sudo" ดังนั้นหากคุณใช้ OpenBSD หรือหาก "doas" ใหม่ของ OpenBSD ("do as") เป็นที่นิยมมากขึ้นเรื่อย ๆ (กับรุ่นอนาคตของระบบปฏิบัติการที่คุณใช้) คุณไม่จำเป็นต้องถูกท้าทายด้วยความซับซ้อนมากในการกำหนดค่า sudo

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.