Linux - 使用setuid位进行提权

Posted 王万林 Ben

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux - 使用setuid位进行提权相关的知识,希望对你有一定的参考价值。

Linux - 使用setuid位进行提权

业务需求

设计一个计分程序,程序文件的owner=thesre;计分文件的owner=thesre,mode=644。
要求:

  • 该计分程序可以被其它非root账号执行,并对计分文件进行更新;

分析需求

由于计分文件的权限为644,只能被其owner更新。而上述要求中,又提到说其它非root账号通过执行该计分程序对计分文件进行更新。
这里,我们就要利用setuid的优势来完成该程序的开发了。

setuid能做什么

简单地说setuid,就是运行程序时,能够临时将权限提升至程序owner的权限,然后做一些只有程序owner才能有权限的操作。
像普通用户执行passwd修改密码、执行at命令设置一次性定时任务,都是利用这个原理来实现提权的操作。

下面我们就来看看本需求的代码实现。

代码

/* filename: caber-toss.c */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>


/* Remember the effective and real UIDs. */
static uid_t euid, ruid;
char* SCORES_FILE = "/tmp/scores.txt";
char* cuserid(char *string);

/* Restore the effective UID to its original value. */
void
do_setuid (void)
{
  int status;

#ifdef _POSIX_SAVED_IDS
  status = seteuid (euid);
#else
  status = setreuid (ruid, euid);
#endif
  if (status < 0) {
    fprintf (stderr, "Couldn't set uid.\\n");
    exit (status);
  }
}


/* Set the effective UID to the real UID. */
void
undo_setuid (void)
{
  int status;

#ifdef _POSIX_SAVED_IDS
  status = seteuid (ruid);
#else
  status = setreuid (euid, ruid);
#endif
  if (status < 0) {
    fprintf (stderr, "Couldn't set uid.\\n");
    exit (status);
  }
}


/* Record the score. */
int
record_score (int score)
{
  FILE *stream;
  char *myname;

  /* Open the scores file. */
  do_setuid ();
  stream = fopen (SCORES_FILE, "a");
  undo_setuid ();

  /* Write the score to the file. */
  if (stream) {
      myname = cuserid (NULL);
      if (score < 0)
        fprintf (stream, "%10s: Couldn't lift the caber.\\n", myname);
      else
        fprintf (stream, "%10s: %d feet.\\n", myname, score);
      fclose (stream);
      return 0;
  }
  else
    return -1;
}


/* Main program. */
int
main (int argc, char *argv[])
{
  int feet_in_int;
  /* Parse arguments */
  if ( argc == 2 ) {
    feet_in_int = atoi(argv[1]);
    printf ("You're adding %d feet.\\n", feet_in_int);
  }
  else {
    printf ("# of args is wrong.\\n");
    exit(-1);
  }
  /* Remember the real and effective user IDs.  */
  ruid = getuid ();
  euid = geteuid ();
  printf ("Current ruid is %d, euid is %d\\n", ruid, euid);
  undo_setuid ();

  /* Do the game and record the score with checking exception.  */
  if (record_score (feet_in_int) == -1) {
    printf("Error: failed to write file %s.\\n", SCORES_FILE);
  }
}

编译

thesre@HP-Z420-Workstation:~/c_programming$ gcc -o ./caber-toss caber-toss.c 
thesre@HP-Z420-Workstation:~/c_programming$ ls -al ./caber-toss
-rwxrwxr-x 1 thesre thesre 17408 6月  28 22:34 ./caber-toss

测试

程序文件未加setuid

thesre@HP-Z420-Workstation:~/c_programming$ ls -al ./caber-toss
-rwxrwxr-x 1 thesre thesre 17408 6月  28 22:34 ./caber-toss

thesre账号运行,

thesre@HP-Z420-Workstation:~/c_programming$ ./caber-toss 2
You're adding 2 feet.
Current ruid is 1000, euid is 1000
thesre@HP-Z420-Workstation:~/c_programming$ cat /tmp/scores.txt
    thesre: 2 feet.
thesre@HP-Z420-Workstation:~/c_programming$ ls -al /tmp/scores.txt
-rw-rw-r-- 1 thesre thesre 20 6月  28 22:35 /tmp/scores.txt

thesre02(非计分文件owner)运行,可以是看到更新失败的!

thesre02@HP-Z420-Workstation:~$ id
用户id=1002(thesre02) 组id=1002(thesre02)=1002(thesre02)
thesre02@HP-Z420-Workstation:~$ ~thesre/c_programming/caber-toss 5
You're adding 5 feet.
Current ruid is 1002, euid is 1002
Error: failed to write file /tmp/scores.txt.
thesre02@HP-Z420-Workstation:~$

程序文件加setuid

thesre@HP-Z420-Workstation:~/c_programming$ chmod u+s ./caber-toss
thesre@HP-Z420-Workstation:~/c_programming$ ls -al ./caber-toss
-rwsrwxr-x 1 thesre thesre 17408 6月  28 22:34 ./caber-toss

thesre账号运行,

thesre@HP-Z420-Workstation:~/c_programming$ ./caber-toss 10
You're adding 10 feet.
Current ruid is 1000, euid is 1000
thesre@HP-Z420-Workstation:~/c_programming$ cat /tmp/scores.txt
    thesre: 2 feet.
    thesre: 10 feet.
thesre@HP-Z420-Workstation:~/c_programming$ 

thesre02(非计分文件owner)运行,这次我们成功地将/tmp/scores.txt文件更新!!!

thesre@HP-Z420-Workstation:~/c_programming$ su - thesre02
Password: 
thesre02@HP-Z420-Workstation:~$ ~thesre/c_programming/caber-toss 20
You're adding 20 feet.
Current ruid is 1002, euid is 1000
thesre02@HP-Z420-Workstation:~$ cat /tmp/scores.txt
    thesre: 2 feet.
    thesre: 10 feet.
  thesre02: 20 feet.
thesre02@HP-Z420-Workstation:~$ 

参考资料

https://www.gnu.org/software/libc/manual/html_node/Setuid-Program-Example.html

https://en.wikipedia.org/wiki/Setuid

https://man7.org/linux/man-pages/man2/setuid.2.html

以上是关于Linux - 使用setuid位进行提权的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Perl 的 Archive::Tar 保存 tar 档案中的 setuid 位?

漏洞预警公告Linux pkexec 本地提权 (CVE-2021-4034)

为什么许多Linux发行版使用setuid代替功能?

linux sudo命令失败 提示sudo:/usr/bin/sudo 必须属于用户 ID 0(的用户)并且设置 setuid 位

记录一次setcap提权失败的经历

linux的setuid权限解释