от Sudo(7-06-2004)

рейтинг (15)   [ добре ]  [ зле ]

Printer Friendly Вариант за отпечатване

Реших да напиша тази статия по повод неотдавнашна дискусия водена в www.linux-bg.org за това как да пуснем през Apache-то скрипт който да изпълнява административни функции, разбирай изисква superuser права.
Е имаше и друг повод, трябваше ми и на мен подобно решение:) Трябваше да предоствя WEB интерфейс за спиране/пускане на Internet достъп към дадена мрежа, а това аз мога да направя само с iptables.
Разбира се удобството на UNIX/Linux е в това че можем да решим задачата по няколко начина "thats why I love my Linux". Това решение което представям не претендира да е най-доброто, нито най-лесното, нито дори най-сигурното, винги когато става въпрос за ескалация на привилегии трябва много ама много да се внимава какво всъщност правим или както често четем по разни лицензи, readme-та и т.н. USE IT ON YOUR OWN RISK !!!
А сега към условието на задачата:
1. Имам скрипт (писан на Perl) който генерира прост HTML вход/изход където потребителя пуска и спира Internet достъпа.
2. Имам bash-скрипт който реално върши това с помоща на iptables, скрипта приема един параметър [start|stop].
3. И естествено най-интересната част от задачата, как така от Apache което е стартирано с правата на потребител nobody UID=99 (Slackware 9.1) да получим права UID=0, нужни за iptables.
Първото нещо за което се сетих беше естествено suid бита на изпълнимите файлове (Perl скрипта или bash скрипта). Е за suid-нат Perl скрипт ми трябва и suidperl, а такъв стандартно в Slack-a нямам. Остана suid на bash скрипта, chmod 4755 my_buggy_script, изпълнявам и ... да ама НЕ :(
Естествено СЛЕД като настъпах мотиката няколко пъти се зарових в документация, чичко Гугъл разпитвах и стигнах до някои интересни неща:
1. bash v.2 и нагоре не търпи suid bit, дропва привилегиите и това е. Тук една малка скоба при Debian не било точно така ами не знам как си, ама сега пък цял Debian да слагам заради един бит пък бил той и suid ... :)
2. Можело да си компилирам Apache-то с поддръжка на suexec, хубаво ама и там едни страшни работи пише ...
3. И разбира се най-хубавото: не съм първия който се сблъсква с подобен проблем.
Е стигнахме и до решението.
Между Perl скрипта и bash скрипта се слага едно междинно ниво - парсер (малка C програма) която да ескалира привилегиите до нужното ни нам UID=0 така щото bash-a да си мисли че root го стартира.
Ето я и самата програма, нищо сложно в нея: дефинираме си вътре скрипта който искаме да се изпълни, вдигаме привилегиите и изпълняваме скрипта с параметрите които Perl скрипта е подал.
----- my_suexec.c -----
#include<stdio.h>
#include<unistd.h>
#include<string.h>
                                                                               
int main(int argc, char ** argv) {
char *action = "/usr/local/bin/my_buggy_script";
                                                                               
       if (argc != 2) return 0;
       setuid(0); // Here we go as root
       if ( strcmp(argv[1], "start") == 0 ) {
               execv(action, argv);
       }
       else if ( strcmp(argv[1], "stop") == 0 ) {
               execv(action, argv);
       }
       else {
               printf("%s \n", "Bad params!");
       }
       return 0;
}
-----end my_suexec.c -----
компилираме: gcc -s -o my_suexec my_suexec.c
cp my_suexec /usr/local/sbin
Тук много важно е да направим:
chmod 4755 my_suexec
иначе горното "setuid(0)" няма от къде да си вземе правата, и сме готови.
Естествено някой може да каже "Какво всъщност правиш ти? Това което хората са написали на 600+ реда с всичките му там проверки ти си написал на 5 реда?" Да така е с една малка подробност командата която ще се изпълни е hard-copy вътре в програмката, а не се приема като параметър. Друг ще каже: да ама по сигурно е "execv(action,NULL)" да така е, но отдолу скрипта трябва да е интелигентен за да знае start или stop да пусне. Изобщо, това както казах НЕ Е универсалното решение на проблема с ескалацията на привилегиите, това е само отправна точка за тези от вас които имат нужда от нещо подобно.

Ето и цялата постановка за тези които ги интересува:
---status.cgi---
#!/usr/bin/perl
use CGI;
$q =  new CGI;
$Action = $q->param('Action');   # v promenlivata Action vliza POST-a ot formata na html-a (start/stop)
                                                                               
       if ( $Action eq "Start" ) {
               `/usr/local/sbin/my_suexec start`;
       }
       elsif ( $Action eq "Stop" ) {
               `/usr/local/sbin/my_suexec stop`;
       }
       else {
               print "Bad params!\n";
       }
       ...
       Малко HTML тук
...
---end status.cgi---    
                                                                 
Програмата описана по-горе...

---my_buggy_script---
inet_start() {
 # Create dummy file showing that Inet is ON
 if [ ! -f /tmp/inet_dummy ] ; then
   echo 1 > /tmp/inet_dummy
 fi
 /usr/sbin/iptables -t nat -A POSTROUTING -s localnet/24 -o $EXTIF -j MASQUERADE
 /usr/sbin/iptables -t nat -A PREROUTING -s localnet/24 -p tcp --dport 80 -d any/0 -j REDIRECT --to-ports 8080
}
                                                                               
inet_stop() {
 # Delete dummy file showing that Inet is OFF
 if [ -f /tmp/inet_dummy ] ; then
   rm /tmp/inet_dummy
 fi
 /usr/sbin/iptables -t nat -I PREROUTING -s localnet/24 -p tcp --dport 80 -d any/0 -j REDIRECT --to-ports 9090 #ie > /dev/null
 /usr/sbin/iptables -t nat -I POSTROUTING -s localnet/24 -o $EXTIF -j DROP
}

case "$1" in
'start')
 inet_start
 ;;
'stop')
 inet_stop
 ;;
*)
 echo "usage $0 start|stop"
esac
---end my_buggy_script---

P.S. Днес едни такива "глобални" проблеми се обсъждат в сайта така че се опасявам и аз да не отнеса нещо силно емоционално, но ако имате предложения, подобрения или въобще нещо свързано с темата на статията, моля заповядайте, предполагам че все някой може да има някаква полза от това писание :)


<< Пример за употреба на Access Control Lists с Линукс | Slackware ядро + ALSA >>