Search code examples
pythoncgirootsudolighttpd

Running python cgi script with sudo on lighttpd


thanks for reading!

I connected an adafruit neopixel to my Raspberry Pi Zero (1. generation) and got them working with test python code.

As the next step I wanted to generate a webpage with buttons controlling the neopixel. I mostly followed this tutorial https://www.hackster.io/mjrobot/iot-controlling-a-raspberry-pi-robot-over-internet-6988d4#toc-step-5--installing-the-lighttpd-webserver-8

At first I got a simple bash cgi script running, which created and wrote the current time into a file. Switching to a python cgi script went fairly easy without changing any configurations file, which left me wondering. But running the test python code from the html is simply not working. As with previous problems I started reading and tinkering but it seems that any solution I tried, doesn't work for me.

I can't recount (working and reading the past days on this) everything I did but I added www-data to the sudoer group, I created a file called 010_www-data-nopasswd in the /etc/sudoers.d directory with www-data ALL=(ALL) NOPASSWD: ALL as content.

I added www-data to the groups gpio, i2c and spi. I ran sudo visudo and added www-data ALL=(ALL:ALL) ALL and www-data ALL = NOPASSWD: /var/www/lighttpd/cgi-bin/neopixelTest.py and still it won't work.

I tried bash cgi script to call the test python script with sudo and it works! So I think it boils down to this.

I've read, that in the config files there is a line like ".py" => "/usr/bin/python" telling lighty to call /usr/bin/python for cgi scripts ending with .py, so I came up with the idea to put sudo into this line, so that basically every python script gets run as sudo. Really not a good thing, but I think this whole project is more quick and dirty and better than running lighty as root. But I can't find this line.

This is my /etc/lighttpd/lighttpd.conf file.

 1 server.modules = (
 2     "mod_indexfile",
 3     "mod_access",
 4     "mod_alias",
 5     "mod_redirect",
 6 )
 7
 8 server.document-root        = "/var/www/lighttpd"
 9 server.upload-dirs          = ( "/var/cache/lighttpd/uploads" )
10 server.errorlog             = "/var/log/lighttpd/error.log"
11 server.pid-file             = "/var/run/lighttpd.pid"
12 server.username             = "www-data"
13 server.groupname            = "www-data"
14 server.port                 = 80
15
16 # strict parsing and normalization of URL for consistency and security
17 # https://redmine.lighttpd.net/projects/lighttpd/wiki/Server_http-parseoptsDetails
18 # (might need to explicitly set "url-path-2f-decode" = "disable"
19 #  if a specific application is encoding URLs inside url-path)
20 server.http-parseopts = (
21   "header-strict"           => "enable",# default
22   "host-strict"             => "enable",# default
23   "host-normalize"          => "enable",# default
24   "url-normalize-unreserved"=> "enable",# recommended highly
25   "url-normalize-required"  => "enable",# recommended
26   "url-ctrls-reject"        => "enable",# recommended
27   "url-path-2f-decode"      => "enable",# recommended highly (unless breaks app)
28  #"url-path-2f-reject"      => "enable",
29   "url-path-dotseg-remove"  => "enable",# recommended highly (unless breaks app)
30  #"url-path-dotseg-reject"  => "enable",
31  #"url-query-20-plus"       => "enable",# consistency in query string
32 )
33
34 index-file.names            = ( "index.php", "index.html" )
35 url.access-deny             = ( "~", ".inc" )
36 static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
37
38 compress.cache-dir          = "/var/cache/lighttpd/compress/"
39 compress.filetype           = ( "application/javascript", "text/css", "text/html", "text/plain" )
40
41 # default listening port for IPv6 falls back to the IPv4 port
42 include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
43 include_shell "/usr/share/lighttpd/create-mime.conf.pl"
44 include "/etc/lighttpd/conf-enabled/*.conf"
45
46 #server.compat-module-load   = "disable"
47 server.modules += (
48     "mod_compress",
49     "mod_dirlisting",
50     "mod_staticfile",
51 )

This is my /etc/lighttpd/conf-enabled/10-cgi.conf file.


 1 # /usr/share/doc/lighttpd/cgi.txt
 2
 3 server.modules += ( "mod_cgi" )
 4
 5 $HTTP["url"] =~ "^/cgi-bin/" {
 6     cgi.assign = ( "" => "" )
 7     alias.url += ( "/cgi-bin/" => "/var/www/lighttpd/cgi-bin/" )
 8 }
 9
10 ## Warning this represents a security risk, as it allow to execute any file
11 ## with a .pl/.py even outside of /usr/lib/cgi-bin.
12 #
13 #cgi.assign      = (
14 #   ".pl"  => "/usr/bin/perl",
15 #   ".py"  => "/usr/bin/python",
16 #)
17

I know, there is the ".py" => "/usr/bin/python" line, but it is commented out.

This is my /etc/lighttpd/conf-enabled/10-fastcgi.conf file.

 1 # /usr/share/doc/lighttpd/fastcgi.txt.gz
 2 # http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ConfigurationOptions#mod_fastcgi-fastcgi
 3
 4 server.modules += ( "mod_fastcgi" )
 5

I think this whole thing isn't even using fastcgi.

Last but not least, here is my html file.

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4
 5         <!-- answer to favicon.ico request, see https://stackoverflow.com/a/38917888/15081525  -->
 6         <link rel="icon" href="data:,">
 7
 8         <meta charset="utf-8">
 9         <!-- description for google -->
10         <meta name="description" content="This is a website to control the RPi zero">
11         <title>RPi Zero index</title>
12     </head>
13
14     <style>
15         body {background-color: lightyellow}
16         h1 {color:blue}
17         button {
18             color: black;
19             background: lightgrey;
20             border: 1px solid #000;
21             border-radius: 8px;
22             position: center;
23         }
24     </style>
25
26     <body>
27         <div style="text-align:center">
28             <h1>Hello world!</h1>
29             <br>
30             <p>please press test button</p>
31             <br>
32             <button onclick="alerttest('hello world!')">Alert!</button>
33             </p>
34             <button onclick="alert('wazzup!')">wazzup!</button>
35             </p>
36             <!-- button to call .cgi-script - it works  -->
37             <button style="height: 50px; width: 125px; font-size: 25px" onclick="test_func()">test</button>
38             </p>
39             <!-- button to call .cgi-script which calls python scripts - it works  -->
40             <button style="height: 50px; width: 125px; font-size: 25px" onclick="test_func2()">test2</button>
41             </p>
42             <!-- button to call python script directly - it works  -->
43             <button style="height: 50px; width: 125px; font-size:25px" onclick="test_py()">python</button>
44             </p>
45             <!-- button to call neopixelTest.py  -->
46             <button style="height: 50px; width: 125px; font-size:25px" onclick="test_neopixel()">neopixel</button>
47
48             <!--
49             <img src="/hello.png">
50             -->
51
52         </div>
53
54         <script>
55             var xmlhttp;
56             xmlhttp=new XMLHttpRequest();
57
58             function test_func() {
59                 xmlhttp.open("GET","cgi-bin/test.cgi",true);
60                 // prevent XML syntax error (in Firefox console, does not prevent execution or something, so only cosmetic co$
61                 xmlhttp.overrideMimeType('application/javascript');
62                 xmlhttp.send();
63             }
64
64
65             function alerttest(parameter) {
66                 alert(parameter);
67             }
68
69             function test_func2() {
70                 xmlhttp.open("GET","cgi-bin/python.cgi",true);
71                 // prevent XML syntax error (in Firefox console, does not prevent execution or something, so only cosmetic co$
72                 xmlhttp.overrideMimeType('application/javascript');
73                 xmlhttp.send();
74             }
75
76             function test_py() {
77                 xmlhttp.open("GET","cgi-bin/pythontest.py",true);
78                 // prevent XML syntax error (in Firefox console, does not prevent execution or something, so only cosmetic co$
79                 xmlhttp.overrideMimeType('application/javascript');
80                 xmlhttp.send();
81             }
82
83             function test_neopixel() {
84                 xmlhttp.open("GET","cgi-bin/neopixelTest.py",true);
85                 // prevent XML syntax error (in Firefox console, does not prevent execution or something, so only cosmetic co$
86                 xmlhttp.overrideMimeType('application/javascript');
87                 xmlhttp.send();
88             }
89         </script>
90     </body>
91 </html>
92

The neopixelTest.py file is basically this file https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel/blob/master/examples/neopixel_rpi_simpletest.py. I just added #! /usr/bin/python to the top of the code.

I know, I'm an absolute beginner, but I would still appreciate every help or idea! Thanks in advance!


Solution

  • cgi.assign = ( "" => "" ) tells lighttpd to execute the cgi scripts directly (so they must be marked executable (chmod +x)) and should have #!/usr/bin/python3 or similar as the first line.

    For the specific CGI scripts that need to run as root, you might create a wrapper script called my-script-name in cgi-bin which exec's sudo <renamed-original-script>

    Another alternative is to put all privileged scripts into a subdirectory, and create a lighttpd condition

    $HTTP["url"] =~ "^/cgi-bin/priv/" {
        cgi.assign = ( "" => "/path/to/my-sudo-wrapper-script" )
    }
    

    and my-sudo-wrapper-script will have to get the CGI target $SCRIPT_NAME from the environment to exec.

    A third option is to run your python code as a daemon, running as root, and have lighttpd connect to it via FastCGI. That would allow your code to run as root, but lighttpd to continue to run as www-data