OpenWrt web interface security enhancement

This article introduces an approach to enhance web interface security on OpenWrt. Though it acts clumsy somewhere, it is a useful reference for some guys seeking similar solutions, I think…

And, many thanks to the guys sharing their achievements which involved in this article!

This solution targets to provide 3 users, which named root, admin, user; and root owns the whole system access ability (of course), admin can execute several system commands and some modules’ check&change capability, and user can only check the designated modules.

0 Introduction

 

Reference: http://www.cnblogs.com/gnuhpc/archive/2013/08/31/3293643.html

The following figure depicts how web interfaces works on OpenWrt. Three main modules are involved, httpd takes cares about packages transmission and parsing, LuCI in charge of web page presentation, and UCI executes the final configure actions.

 

When a http package arrives the system, uhttpd analysis it’s request first, and start a LuCI process to execute it, system command or UCI are involved based on its request content.

1 Add users

Edit /etc/password to add users:

root:x:0:0:root:/root:/bin/ash

daemon:*:1:1:daemon:/var:/bin/false

ftp:*:55:55:ftp:/home/ftp:/bin/false

network:*:101:101:network:/var:/bin/false

nobody:*:65534:65534:nobody:/var:/bin/false

admin:x:1000:1000::/:/bin/ash

user:x:1001:1001::/:/bin/ash

Edit /etc/group to add admin to root group.

root:x:0:root,admin

daemon:x:1:

adm:x:4:

mail:x:8:

audio:x:29:

www-data:x:33:

ftp:x:55:

users:x:100:

network:x:101:

nogroup:x:65534:

Edit /etc/shadow to impose password for each user.

The coresponding files in build environment are located in ‘package/base-files/files/etc/

2 Enable ‘su’ command

The default busybox excludes ‘su’, we need it to execute commands via different users.

Use ‘make menuconfig’ (base system -> busybox ->Login/Password Management Utility -> su),

or you can either edit ‘.config’ directly.

3 Reassign users’ authorities

The busybox is an integrated tool, and this solution discriminate users’ authorities with such a trick that make a copy of busybox and impose it different permission configurations:

-rwxr-xr-x. 1 smbtest smbtest 435712 Feb  9 15:38 busyboxl

lrwxrwxrwx. 1 smbtest smbtest      8 Feb  9 16:17 ash -> busyboxl

lrwxrwxrwx. 1 smbtest smbtest      8 Feb  9 16:17 sh -> busyboxl

-rwsr-xr--. 1 smbtest smbtest 435712 Feb  9 15:38 busybox

lrwxrwxrwx. 1 smbtest smbtest      7 Feb  9 16:17 cat -> busybox

lrwxrwxrwx. 1 smbtest smbtest      7 Feb  9 16:17 chgrp -> busybox

lrwxrwxrwx. 1 smbtest smbtest      7 Feb  9 16:17 chmod -> busybox

……

Note: 1 ash & sh are required by user to access web page and execute some basic scripts;

            2 attribute of busybox maybe changed during build, please make sure it when system finish start-up

To make it during building, the following lines can be copied to ‘package/busybox/patches/copy_busybox.patch’:

diff -ru old/Makefile.custom new/Makefile.custom

--- a/Makefile.custom   2012-02-05 03:24:55.000000000 +0800

+++ b/Makefile.custom   2015-02-09 15:16:53.928314941 +0800

@@ -26,6 +26,18 @@

 install: $(srctree)/applets/install.sh busybox busybox.links

    $(Q)DO_INSTALL_LIBS="$(strip $(LIBBUSYBOX_SONAME) $(DO_INSTALL_LIBS))" \

        $(SHELL) $< $(CONFIG_PREFIX) $(INSTALL_OPTS)

+

+    #DUPLICATED BUSYBOX start: discriminate authority of admin and user

+   cp busybox $(srctree)/ipkg-install/bin/busyboxl

+   chmod 4750 $(srctree)/ipkg-install/bin/busybox

+   chmod 755 $(srctree)/ipkg-install/bin/busyboxl

+

+   rm $(srctree)/ipkg-install/bin/sh

+   rm $(srctree)/ipkg-install/bin/ash

+   ln -s busyboxl $(srctree)/ipkg-install/bin/sh

+   ln -s busyboxl $(srctree)/ipkg-install/bin/ash

+    # DUPLICATED BUSYBOX end.

+

 ifeq ($(strip $(CONFIG_FEATURE_SUID)),y)

    @echo

    @echo

4 Enable multi-user login via web interface

Reference: https://forum.openwrt.org/viewtopic.php?pid=163013#p163013

edit /usr/lib/lua/luci/controller/admin/index.lua and change line 28 to read
        page.sysauth = {"admin","user","root"}

edit /usr/lib/lua/luci/controller/admin/system.lua and change line 326 to read
       stat = luci.sys.user.setpasswd("admin", p1)

edit /usr/lib/lua/luci/controller/admin/servicectl.lua line 18 to read
      entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"admin","user","root"}

The coresponding files in build environment are located in ‘luci/modules/admin-full/luasrc/controller/admin/…

5 Invoke LuCI by different users

Till now, different users can access web pages, but the reactions are same since the LuCI process is invoked by uhttpd, which owned by root.

The following lines can be copied to ‘package/uhttpd/patches/multi-user.patch’, to start LuCI process according to the current login user.

--- a/uhttpd.c

+++ b/uhttpd.c

@@ -243,6 +243,56 @@

                return bound;

 }

 

+#ifndef MULTI_USER

+extern void mu_dbg(char *info);

+void mu_dbg(char *info)

+{

+    #if 0

+    int fd = open("/debug",O_RDWR|O_APPEND);

+    char buf[128] = {0};

+    int len = strlen(info);

+

+    if (fd<0) {

+        fd = open("/debug",O_CREAT|O_RDWR);

+    }

+

+    memcpy(buf,info,len);

+    buf[len] = ‘\n‘;

+

+    write(fd,buf,len+1);

+    close(fd);

+    #endif

+}

+

+static char CUR_USER[32] = {0};

+

+extern void get_cur_user(char *user);

+void get_cur_user(char *user) {

+    if ( strlen(CUR_USER)==0 ) {

+        mu_dbg("CUR_USER empty");

+        strcpy(user,"admin");

+    } else {

+        memcpy(user,CUR_USER,strlen(CUR_USER));

+    }

+}

+

+static void mu_get_user(const struct client *cl, char *user) {

+    char *usrhead = cl->httpbuf.ptr;

+    char *pwdhead = strfind(usrhead,strlen(usrhead),"&password=",strlen("&password="));

+

+    if ( pwdhead != NULL ) {

+        usrhead += strlen("username=");

+        memcpy( user, usrhead, pwdhead-usrhead );

+    }

+   

+    mu_dbg(user);

+}

+

+static void mu_clear_luci_cache() {

+    system("rm -Rf /tmp/luci-*");

+}

+#endif

+

 static struct http_request * uh_http_header_parse(struct client *cl,

                                                                                                                                                                                                  char *buffer, int buflen)

 {

@@ -281,7 +331,23 @@

                                if (method && !strcmp(method, "GET"))

                                                req->method = UH_HTTP_MSG_GET;

                                else if (method && !strcmp(method, "POST"))

-                                              req->method = UH_HTTP_MSG_POST;

+                             #ifndef MULTI_USER

+        {

+            char user[32] = {0};

+            mu_get_user(cl,user);

+

+            if ( strlen(user)!=0 ) {/* if the POST is for log in*/

+                if( strncmp(user,CUR_USER,4)!=0 ) {

+                    mu_clear_luci_cache();

+                    memcpy(CUR_USER,user,32);

+                }

+            }   

+

+            req->method = UH_HTTP_MSG_POST;

+        }

+        #else

+            req->method = UH_HTTP_MSG_POST;

+        #endif

                                else if (method && !strcmp(method, "HEAD"))

                                                req->method = UH_HTTP_MSG_HEAD;

                                else

--- a/uhttpd-cgi.c

+++ b/uhttpd-cgi.c

@@ -486,11 +486,31 @@

                                                if (chdir(pi->root))

                                                                perror("chdir()");

 

+            #ifndef MULTI_USER

+            {

+                extern void get_cur_user(char *user);

+                extern void mu_dbg(char *info);

+               

+                char user[32] = {0};

+                char info[32] = {0};

+

+                get_cur_user(user);

+              

+                sprintf(info,"exec via %s",user);

+                mu_dbg(info);

+

+                if (ip!=NULL) {

+                    execl("/bin/su","su",user,"-c",ip->path,pi->phys,NULL);

+                } else {

+                    execl("/bin/su","su",user,"-c",pi->phys,NULL);

+                }

+            }

+            #else

                                                if (ip != NULL)

                                                                execl(ip->path, ip->path, pi->phys, NULL);

                                                else

                                                                execl(pi->phys, pi->phys, NULL);

-

+            #endif

                                                /* in case it fails ... */

                                                printf("Status: 500 Internal Server Error\r\n\r\n"

                                                                   "Unable to launch the requested CGI program:\n"

7 Endow UCI configure authority to admin

Even admin possess root group’s authority, it cannot execute uci set or uci commit since such executions require more (I don’t known the exact answer though). Here ‘su’ is deployed again to solve this problem. Modify ‘usr/lib/lua/luci/cbi.lua’ like the followings:

@Map.parse

                if self.config == "designated_module" then

                    os.execute("echo root_password | su root -c \"uci commit\"")

                else

                    self.uci:commit(config)

@Map.set

                if self.config == " designated_module" then

                    cmd = "uci set "..self.config.."."..section.."."..option.."="..value

                    return os.execute("echo root_password | su root -c \"%s\"" % cmd)

                else

                    return self.uci:set(self.config, section, option, value)

                end

The corresponding file is located in ‘luci/libs/web/luasrc’

8 Modify Web pages’ appearance

In fact, I did this before Section.5. Maybe there is more decent approach to get the logined user.

Copy the following lines after line 370 in ‘usr/lib/lua/luci/dispatchers.lua’ to record the current user.

                if user then

                    -- if the "luci_user" is created by "user", "chmod" cannot take effect since limited authority.

                    -- so "admin" has to "chmod" again via "su".

                    os.execute("echo root_password | su root -c \"chmod 666 /var/luci_user\"")

                    os.execute("echo %s > /var/luci_user" % user)

                    os.execute("chmod 666 /var/luci_user")

                end

and then modify specific files about the appearance setences, such as

entry(…) in ‘usr/lib/lua/luci/controller/admin/xxx.lua’ is enable sub-tab on a certain page,which can be commented to cancel the sub-tab, and s:option(Value, …) in ‘usr/lib/lua/luci/model/cbi/xxx/xxx.lua’ represents a EditBox, the parameter Value can be change to DummyValue to displace the EditBox with a TextView.

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。