Skip to content

Commit d9393b0

Browse files
authored
[radius]: Use execl instead of popen in RADIUS NSS code to fix vulnerability. (#15512)
Why I did it #15284 fixes a case of shell escape exploit for TACACS+. This applies to RADIUS as well. RADIUS creates an unconfirmed user locally on the switch while attempting authentication. popen() is used to execute useradd,usermod and userdel commands. This exposes a vulnerability where a tactically designed username (which could contain explicit linux commands) can lead to getting executed as root. An example of such a username could be "asd";echo>remoteRCE2;#". This leads to remoteRCE2 getting created in "/". How I did it All calls to popen() used to execute useradd, usermod and userdel are replaced with fork()/execl(). How to verify it Prior to the fix, following is the behavior: [s@i vm] ssh "asd";echo>remoteRCE2;#"@1.1.1.1 asd";echo>remoteRCE2;#@1.1.1.1's password: Permission denied, please try again. On the SONiC switch, root@sonic:/# ls accton_as7816_monitor.log home lib64 remoteRCE2 sys bin host libx32 root tmp boot initrd.img media run usr cache.tgz initrd.img.old mnt sbin var dev lib opt sonic vmlinuz etc lib32 proc srv vmlinuz.old root@sonic:/# ls -l With the fix: [s@i vm] ssh "asd";echo>remoteRCE2;#"@1.1.1.1 asd";echo>remoteRCE2;#@1.1.1.1's password: Permission denied, please try again. root@sonic:/# ls accton_as7816_monitor.log etc lib mnt sbin usr bin home lib32 opt sonic var boot host lib64 proc srv vmlinuz cache.tgz initrd.img libx32 root sys vmlinuz.old dev initrd.img.old media run tmp Verified that RADIUS authentication works as expected for valid users as well.
1 parent 7bdd0d8 commit d9393b0

File tree

1 file changed

+143
-71
lines changed

1 file changed

+143
-71
lines changed

src/radius/nss/libnss-radius/nss_radius_common.c

Lines changed: 143 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
2525
#include <sys/file.h>
2626
#include <regex.h>
2727
#include <time.h>
28+
#include <sys/wait.h>
2829

2930
#include "nss_radius_common.h"
3031

@@ -167,6 +168,124 @@ static void init_rnm(RADIUS_NSS_CONF_B * conf) {
167168

168169
}
169170

171+
static int user_add(const char* name, char* gid, char* sec_grp, char* gecos,
172+
char* home, char* shell, const char* unconfirmed_user, int many_to_one) {
173+
pid_t pid, w;
174+
int status = 0;
175+
int wstatus;
176+
char cmd[64];
177+
178+
snprintf(cmd, 63, "%s", USERADD);
179+
180+
pid = fork();
181+
182+
if(pid > 0) {
183+
do {
184+
w = waitpid(pid, &wstatus, WUNTRACED | WCONTINUED);
185+
if (w == -1)
186+
return -1;
187+
} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
188+
if WIFEXITED(wstatus)
189+
return WEXITSTATUS(wstatus);
190+
else
191+
return -1;
192+
193+
// Child
194+
195+
} else if(pid == 0) {
196+
197+
if (many_to_one)
198+
execl(cmd, cmd, "-g", gid, "-G", sec_grp, "-c", gecos, "-m", "-s", shell, name, NULL);
199+
else
200+
execl(cmd, cmd, "-U", "-G", sec_grp, "-c", unconfirmed_user, "-d", home, "-m", "-s", shell, name, NULL);
201+
syslog(LOG_ERR, "exec of %s failed with errno=%d", cmd, errno);
202+
return -1;
203+
204+
// Error
205+
} else {
206+
fprintf(stderr, "error forking the child\n");
207+
return -1;
208+
}
209+
210+
return status;
211+
}
212+
213+
static int user_del(const char* name) {
214+
pid_t pid, w;
215+
int status = 0;
216+
int wstatus;
217+
char cmd[64];
218+
219+
snprintf(cmd, 63, "%s", USERDEL);
220+
221+
pid = fork();
222+
223+
if(pid > 0) {
224+
do {
225+
w = waitpid(pid, &wstatus, WUNTRACED | WCONTINUED);
226+
if (w == -1)
227+
return -1;
228+
} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
229+
if WIFEXITED(wstatus)
230+
return WEXITSTATUS(wstatus);
231+
else
232+
return -1;
233+
234+
// Child
235+
236+
} else if(pid == 0) {
237+
238+
execl(cmd, cmd, "-r", name, NULL);
239+
syslog(LOG_ERR, "exec of %s failed with errno=%d", cmd, errno);
240+
return -1;
241+
242+
// Error
243+
} else {
244+
fprintf(stderr, "error forking the child\n");
245+
return -1;
246+
}
247+
248+
return status;
249+
}
250+
251+
static int user_mod(const char* name, char* sec_grp) {
252+
pid_t pid, w;
253+
int status = 0;
254+
int wstatus;
255+
char cmd[64];
256+
257+
snprintf(cmd, 63, "%s", USERMOD);
258+
259+
pid = fork();
260+
261+
if(pid > 0) {
262+
do {
263+
w = waitpid(pid, &wstatus, WUNTRACED | WCONTINUED);
264+
if (w == -1)
265+
return -1;
266+
} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
267+
if WIFEXITED(wstatus)
268+
return WEXITSTATUS(wstatus);
269+
else
270+
return -1;
271+
272+
// Child
273+
274+
} else if(pid == 0) {
275+
276+
execl(cmd, cmd, "-G", sec_grp, "-c", name, name, NULL);
277+
syslog(LOG_ERR, "exec of %s failed with errno=%d", cmd, errno);
278+
return -1;
279+
280+
// Error
281+
} else {
282+
fprintf(stderr, "error forking the child\n");
283+
return -1;
284+
}
285+
286+
return status;
287+
}
288+
170289
int parse_nss_config(RADIUS_NSS_CONF_B * conf, char * prog,
171290
char * file_buf, int file_buf_sz, int * errnop, int * plockfd) {
172291

@@ -379,22 +498,6 @@ int unparse_nss_config(RADIUS_NSS_CONF_B * conf, int * errnop, int * plockfd) {
379498
return 0;
380499
}
381500

382-
static int invoke_popen(RADIUS_NSS_CONF_B * conf, char * cmd) {
383-
FILE * fp;
384-
int status = 0;
385-
386-
if (conf->debug)
387-
syslog(LOG_DEBUG, "%s:%s", conf->prog, cmd);
388-
389-
if (((fp = popen(cmd, "r")) == NULL) || (pclose(fp) == -1)) {
390-
syslog(LOG_ERR, "%s: %s: popen()/pclose() failed %p, errno=%d",
391-
conf->prog, cmd, fp, errno);
392-
status = errno;
393-
}
394-
395-
return status;
396-
}
397-
398501
static int radius_getpwnam_r_cleanup(int status, FILE * fp) {
399502
if (fp)
400503
fclose(fp);
@@ -434,10 +537,8 @@ static int radius_update_user_cleanup(int status) {
434537
int radius_update_user(RADIUS_NSS_CONF_B * conf, const char * user, int mpl) {
435538

436539
char buf[BUFLEN];
437-
char usermod[4096];
438540
struct passwd pw, *result = NULL;
439541
RADIUS_NSS_MPL * rnm = NULL;
440-
int written = 0;
441542
int status;
442543

443544
/* Verify uid is not in the reserved range (<=1000).
@@ -466,82 +567,53 @@ int radius_update_user(RADIUS_NSS_CONF_B * conf, const char * user, int mpl) {
466567
if (conf->trace)
467568
dump_rnm(mpl, rnm, "update");
468569

469-
written = snprintf(usermod, sizeof(usermod),
470-
"%s -G %s -c \"%s\" \"%s\"", USERMOD, rnm->groups, user, user);
471-
472-
if (written >= sizeof(usermod)) {
473-
syslog(LOG_ERR,
474-
"%s: truncated usermod cmd. Skipping:\"%s\"\n", conf->prog, usermod);
475-
return radius_update_user_cleanup(STATUS_E2BIG);
570+
if(0 != user_mod(user, rnm->groups)) {
571+
syslog(LOG_ERR, "%s: %s %s failed", conf->prog, USERMOD, user);
572+
return -1;
476573
}
477-
478-
return radius_update_user_cleanup(invoke_popen(conf, usermod));
479-
}
480-
481-
static int radius_create_user_cleanup(int status) {
482-
return status;
574+
return 0;
483575
}
484576

485577
int radius_create_user(RADIUS_NSS_CONF_B * conf, const char * user, int mpl,
486578
int unconfirmed) {
487579

488-
char buf[BUFLEN];
489-
char useradd[4096];
580+
char buf[BUFLEN] = {0};
490581
RADIUS_NSS_MPL * rnm = &((conf->rnm)[mpl-1]);
491-
int written = 0;
492582

493583
if (conf->trace)
494584
dump_rnm(mpl, rnm, "create");
495585

586+
if(strlen(user) > 32) {
587+
syslog(LOG_ERR, "%s: Username too long", conf->prog);
588+
return -1;
589+
}
496590

497-
if (conf->many_to_one) {
591+
syslog(LOG_INFO, "%s: Creating user \"%s\"", conf->prog, user);
498592

499-
written = snprintf(useradd, sizeof(useradd),
500-
"%s -g %d -G %s -c \"%s\" -m -s %s \"%s\"",
501-
USERADD, rnm->gid, rnm->groups, rnm->gecos, rnm->shell, user);
593+
char sgid[10] = {0};
594+
char home[64] = {0};
595+
snprintf(sgid, 10, "%d", rnm->gid);
596+
snprintf(home, 63, "/home/%s", user);
502597

503-
} else {
598+
snprintf(buf, sizeof(buf), "Unconfirmed-%ld", time(NULL));
504599

505-
snprintf(buf, sizeof(buf), "Unconfirmed-%ld", time(NULL));
506-
written = snprintf(useradd, sizeof(useradd),
507-
"%s -U -G %s -c \"%s\" -d \"/home/%s\" -m -s %s \"%s\"",
508-
USERADD, rnm->groups, unconfirmed ? buf : user, user,
509-
rnm->shell, user);
510-
511-
}
600+
if(0 != user_add(user, sgid, rnm->groups, rnm->gecos, home, rnm->shell, unconfirmed ? buf : user, conf->many_to_one)) {
601+
syslog(LOG_ERR, "%s: %s %s failed", conf->prog, USERADD, user);
512602

513-
if (written >= sizeof(useradd)) {
514-
syslog(LOG_ERR,
515-
"%s: truncated useradd cmd. Skipping:\"%s\"\n", conf->prog, useradd);
516-
return radius_create_user_cleanup(STATUS_E2BIG);
603+
return -1;
517604
}
518-
519-
syslog(LOG_INFO, "%s: Creating user \"%s\"", conf->prog, user);
520-
521-
return radius_create_user_cleanup(invoke_popen(conf, useradd));
522-
}
523-
524-
static int radius_delete_user_cleanup(int status) {
525-
return status;
605+
return 0;
526606
}
527607

528608
int radius_delete_user(RADIUS_NSS_CONF_B * conf, const char * user) {
529609

530-
char buf[BUFLEN];
531-
char userdel[4096];
532-
int written = 0;
533-
534-
written = snprintf(userdel, sizeof(userdel), "%s -r \"%s\"", USERDEL, user);
535-
536-
if (written >= sizeof(userdel)) {
537-
syslog(LOG_ERR,
538-
"%s: truncated userdel cmd. Skipping:\"%s\"\n", conf->prog, userdel);
539-
return radius_delete_user_cleanup(STATUS_E2BIG);
540-
}
541-
542610
syslog(LOG_INFO, "%s: Deleting user \"%s\"", conf->prog, user);
611+
if(0 != user_del(user)) {
612+
syslog(LOG_ERR, "%s: %s %s failed", conf->prog, USERDEL, user);
543613

544-
return radius_delete_user_cleanup(invoke_popen(conf, userdel));
614+
return -1;
615+
}
616+
return 0;
545617
}
546618

547619
int radius_clear_unconfirmed_users_cleanup(int status, FILE * fp) {

0 commit comments

Comments
 (0)