-
-
[原创]pwnable.kr - input2 & pkexec(CVE-2021-4034)漏洞
-
发表于: 2023-4-11 23:42 12263
-
分为5个stage,需要依次解决
命令行执行程序调用的是execve
系统调用,其函数原型如下所示,第一个参数是运行程序文件名,第二个参数是argv[]
,第三个参数是envp[]
.同时,检测的是main函数的argc
和argv
.
main函数的第一个参数argc
表示的是命令行参数个数。argv
则是命令行参数 argv[0]= filename
。
翻译一下:
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
the memcmp() function compares the first n bytes (each interpreted as unsigned char) of the memory areas s1 and s2.
即:
fd = 0 很好解决,stdin,直接输入即可;fd = 2的输入不是很好解决,但是pwntools
提供了对应的接口,如果没有pwntools,应该怎么实现?
因此有:
上述execve函数已经讲过,因此直接添加即可:
基本文件流操作:
需要注意的是,能写入的目录只有/tmp,如何才能读取到想要的文件是需要考虑一下的。
需要注意的是,最后一步由于远程防火墙问题,pwntools是连不上去的,只有提交到本地之后才可以,另外,由于我们的目录在/tmp
,而读取的flag
并不在,需要想办法获取对应的内容,一个好的方法是软连接。
本道题包含的知识点比较多文件IO,进程通信,网络通信均有涉及,如果对此不是很了解建议翻阅CSAPP相关章节。
另在整理本题时想到一个比较简单的漏洞和本题有一定相似性,借此把之前的简单分析也放出来,可以看看真实漏洞和习题的异同。
结论:
问题:
如果执行 argc 1 2 3 4 5
得到结果是:
如果执行execve("./argc", NULL, NULL)
得到的结果是:
如果将argc.c
中for (int i = 0; i < argc; i++)
改为 for (int i = 0; i < 8; i++)
执行argc 1 2 3 4 5
结果是
后面的结果是什么?
如果设定argv和envp参数,执行execve.c
得到的结果是:
execve系统调用中连续的参数为argc argv 以及 envp
如下图所示:
结论:
如果存在应用,直接使用argv[1]而未对参数进行校验,那么如果此时使用execve
执行命令 传递参数为NULL,*
那么此时:
可以查看一篇slide
小结:
如果普通用户执行具有suid权限应用比如passwd,那么其efftive id = root
,real id = user
. 即具有suid权限应用可以被普通用户启动,并且运行权限会被修改为root。
那么如果想办法使用环境变量启动一个bash,这个bash就能具有root权限,可利用环境变量有哪些?
LD_PRELOAD
看起来很简单?直接使用LD_PRELOAD是否可行?
NO
结论:环境变量中,不安全的环境变量不会被引入
那么如何把shell写进入呢?
漏洞发现者提出了一种巧妙的方法
g_printerr()
会调用glibc的iconv_open()
函数将UTF-8格式的信息转为其他格式。
为了转换格式,iconv_open()
会调用一个共享库函数。
之后会调用对应文件中的gconv()
函数和gconv_init()
函数。
但是通过环境变量GCONV_PATH
可以强制指定iconv_open()
读取某一个特定的共享库
问题:GCONV_PATH
是不安全函数,不会被加载到程序中。
pkexec可以将GCONV_PATH
环境变量重新载入。 为什么?
到目前位置所知道的信息:
到这里,基本上就已经明确了所有步骤:
回到源码,分析调用链。
假设存在环境变量 PATH=name
,如果name
存在,并且name
目录中有一个名为path
的文件,传递给envp[0]。
如果PATH=name=.
并且有一个目录name=.
,此目录中有一个shell.so:.
,那么这个shell.so:.
会被传递给argv[1]
->envp[0]
如果在开始,使用execve()
执行pkexec,并且传递第一个参数为NULL
,那么 在第二步,一开始读取的argv[1] 实际上是envp[0]
.
并且后续会找到一个可执行文件传递给s,继而传递给argv[1]
,注意argv[1]指向的实际上是第一个环境变量。
通过这种方式,就能修改掉第一个环境变量。
构造环境变量如上图,按POC所叙述,
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int
main(
int
argc, char
*
argv[], char
*
envp[]){
printf(
"Welcome to pwnable.kr\n"
);
printf(
"Let's see if you know how to give input to program\n"
);
printf(
"Just give me correct inputs then you will get the flag :)\n"
);
/
/
argv
if
(argc !
=
100
)
return
0
;
if
(strcmp(argv[
'A'
],
"\x00"
))
return
0
;
if
(strcmp(argv[
'B'
],
"\x20\x0a\x0d"
))
return
0
;
printf(
"Stage 1 clear!\n"
);
/
/
stdio
char buf[
4
];
read(
0
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x00\xff"
,
4
))
return
0
;
read(
2
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x02\xff"
,
4
))
return
0
;
printf(
"Stage 2 clear!\n"
);
/
/
env
if
(strcmp(
"\xca\xfe\xba\xbe"
, getenv(
"\xde\xad\xbe\xef"
)))
return
0
;
printf(
"Stage 3 clear!\n"
);
/
/
file
FILE
*
fp
=
fopen(
"\x0a"
,
"r"
);
if
(!fp)
return
0
;
if
( fread(buf,
4
,
1
, fp)!
=
1
)
return
0
;
if
( memcmp(buf,
"\x00\x00\x00\x00"
,
4
) )
return
0
;
fclose(fp);
printf(
"Stage 4 clear!\n"
);
/
/
network
int
sd, cd;
struct sockaddr_in saddr, caddr;
sd
=
socket(AF_INET, SOCK_STREAM,
0
);
if
(sd
=
=
-
1
){
printf(
"socket error, tell admin\n"
);
return
0
;
}
saddr.sin_family
=
AF_INET;
saddr.sin_addr.s_addr
=
INADDR_ANY;
saddr.sin_port
=
htons( atoi(argv[
'C'
]) );
if
(bind(sd, (struct sockaddr
*
)&saddr, sizeof(saddr)) <
0
){
printf(
"bind error, use another port\n"
);
return
1
;
}
listen(sd,
1
);
int
c
=
sizeof(struct sockaddr_in);
cd
=
accept(sd, (struct sockaddr
*
)&caddr, (socklen_t
*
)&c);
if
(cd <
0
){
printf(
"accept error, tell admin\n"
);
return
0
;
}
if
( recv(cd, buf,
4
,
0
) !
=
4
)
return
0
;
if
(memcmp(buf,
"\xde\xad\xbe\xef"
,
4
))
return
0
;
printf(
"Stage 5 clear!\n"
);
/
/
here's your flag
system(
"/bin/cat flag"
);
return
0
;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int
main(
int
argc, char
*
argv[], char
*
envp[]){
printf(
"Welcome to pwnable.kr\n"
);
printf(
"Let's see if you know how to give input to program\n"
);
printf(
"Just give me correct inputs then you will get the flag :)\n"
);
/
/
argv
if
(argc !
=
100
)
return
0
;
if
(strcmp(argv[
'A'
],
"\x00"
))
return
0
;
if
(strcmp(argv[
'B'
],
"\x20\x0a\x0d"
))
return
0
;
printf(
"Stage 1 clear!\n"
);
/
/
stdio
char buf[
4
];
read(
0
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x00\xff"
,
4
))
return
0
;
read(
2
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x02\xff"
,
4
))
return
0
;
printf(
"Stage 2 clear!\n"
);
/
/
env
if
(strcmp(
"\xca\xfe\xba\xbe"
, getenv(
"\xde\xad\xbe\xef"
)))
return
0
;
printf(
"Stage 3 clear!\n"
);
/
/
file
FILE
*
fp
=
fopen(
"\x0a"
,
"r"
);
if
(!fp)
return
0
;
if
( fread(buf,
4
,
1
, fp)!
=
1
)
return
0
;
if
( memcmp(buf,
"\x00\x00\x00\x00"
,
4
) )
return
0
;
fclose(fp);
printf(
"Stage 4 clear!\n"
);
/
/
network
int
sd, cd;
struct sockaddr_in saddr, caddr;
sd
=
socket(AF_INET, SOCK_STREAM,
0
);
if
(sd
=
=
-
1
){
printf(
"socket error, tell admin\n"
);
return
0
;
}
saddr.sin_family
=
AF_INET;
saddr.sin_addr.s_addr
=
INADDR_ANY;
saddr.sin_port
=
htons( atoi(argv[
'C'
]) );
if
(bind(sd, (struct sockaddr
*
)&saddr, sizeof(saddr)) <
0
){
printf(
"bind error, use another port\n"
);
return
1
;
}
listen(sd,
1
);
int
c
=
sizeof(struct sockaddr_in);
cd
=
accept(sd, (struct sockaddr
*
)&caddr, (socklen_t
*
)&c);
if
(cd <
0
){
printf(
"accept error, tell admin\n"
);
return
0
;
}
if
( recv(cd, buf,
4
,
0
) !
=
4
)
return
0
;
if
(memcmp(buf,
"\xde\xad\xbe\xef"
,
4
))
return
0
;
printf(
"Stage 5 clear!\n"
);
/
/
here's your flag
system(
"/bin/cat flag"
);
return
0
;
}
int
execve(const char
*
filename, char
*
const argv[],
char
*
const envp[]);
int
main(
int
argc, char
*
argv[ ] ) {
/
*
…
*
/
}
int
execve(const char
*
filename, char
*
const argv[],
char
*
const envp[]);
int
main(
int
argc, char
*
argv[ ] ) {
/
*
…
*
/
}
/
/
argv
if
(argc !
=
100
)
return
0
;
if
(strcmp(argv[
'A'
],
"\x00"
))
return
0
;
if
(strcmp(argv[
'B'
],
"\x20\x0a\x0d"
))
return
0
;
printf(
"Stage 1 clear!\n"
);
/
/
argv
if
(argc !
=
100
)
return
0
;
if
(strcmp(argv[
'A'
],
"\x00"
))
return
0
;
if
(strcmp(argv[
'B'
],
"\x20\x0a\x0d"
))
return
0
;
printf(
"Stage 1 clear!\n"
);
char
*
argv[
101
]
=
{
"/home/input2/input"
, [
1
...
99
]
=
"A"
, NULL};
argv[
'A'
]
=
"\x00"
;
argv[
'B'
]
=
"\x20\x0a\x0d"
;
execve(
"/home/input2/input"
,argv,NULL);
char
*
argv[
101
]
=
{
"/home/input2/input"
, [
1
...
99
]
=
"A"
, NULL};
argv[
'A'
]
=
"\x00"
;
argv[
'B'
]
=
"\x20\x0a\x0d"
;
execve(
"/home/input2/input"
,argv,NULL);
/
/
stdio
char buf[
4
];
read(
0
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x00\xff"
,
4
))
return
0
;
read(
2
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x02\xff"
,
4
))
return
0
;
printf(
"Stage 2 clear!\n"
);
/
/
stdio
char buf[
4
];
read(
0
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x00\xff"
,
4
))
return
0
;
read(
2
, buf,
4
);
if
(memcmp(buf,
"\x00\x0a\x02\xff"
,
4
))
return
0
;
printf(
"Stage 2 clear!\n"
);
ssize_t read(
int
fd, void
*
buf, size_t count);
ssize_t read(
int
fd, void
*
buf, size_t count);
int
memcmp(const void
*
s1, const void
*
s2, size_t n);
int
memcmp(const void
*
s1, const void
*
s2, size_t n);
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int
main() {
int
fd1
=
open
(
"./stdin"
, O_RDONLY);
int
fd2
=
open
(
"./stderr"
, O_RDONLY);
dup2(fd1,
0
);
/
/
将文件描述符fd1重定向到标准输入
dup2(fd2,
2
);
/
/
将文件描述符fd2重定向到标准错误
close(fd1);
close(fd2);
char
*
argv[
101
]
=
{
"/home/input2/input"
, [
1
...
99
]
=
"A"
, NULL};
argv[
'A'
]
=
"\x00"
;
argv[
'B'
]
=
"\x20\x0a\x0d"
;
execve(
"/home/input2/input"
,argv,NULL);
return
0
;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int
main() {
int
fd1
=
open
(
"./stdin"
, O_RDONLY);
int
fd2
=
open
(
"./stderr"
, O_RDONLY);
dup2(fd1,
0
);
/
/
将文件描述符fd1重定向到标准输入
dup2(fd2,
2
);
/
/
将文件描述符fd2重定向到标准错误
close(fd1);
close(fd2);
char
*
argv[
101
]
=
{
"/home/input2/input"
, [
1
...
99
]
=
"A"
, NULL};
argv[
'A'
]
=
"\x00"
;
argv[
'B'
]
=
"\x20\x0a\x0d"
;
execve(
"/home/input2/input"
,argv,NULL);
return
0
;
}
/
/
env
if
(strcmp(
"\xca\xfe\xba\xbe"
, getenv(
"\xde\xad\xbe\xef"
)))
return
0
;
printf(
"Stage 3 clear!\n"
);
/
/
env
if
(strcmp(
"\xca\xfe\xba\xbe"
, getenv(
"\xde\xad\xbe\xef"
)))
return
0
;
printf(
"Stage 3 clear!\n"
);
char
*
env[
2
]
=
{
"\xde\xad\xbe\xef=\xca\xfe\xba\xbe"
, NULL};
execve(
"/home/input/input"
,argv,env);
char
*
env[
2
]
=
{
"\xde\xad\xbe\xef=\xca\xfe\xba\xbe"
, NULL};
execve(
"/home/input/input"
,argv,env);
/
/
file
FILE
*
fp
=
fopen(
"\x0a"
,
"r"
);
if
(!fp)
return
0
;
if
( fread(buf,
4
,
1
, fp)!
=
1
)
return
0
;
if
( memcmp(buf,
"\x00\x00\x00\x00"
,
4
) )
return
0
;
fclose(fp);
printf(
"Stage 4 clear!\n"
);
/
/
file
FILE
*
fp
=
fopen(
"\x0a"
,
"r"
);
if
(!fp)
return
0
;
if
( fread(buf,
4
,
1
, fp)!
=
1
)
return
0
;
if
( memcmp(buf,
"\x00\x00\x00\x00"
,
4
) )
return
0
;
fclose(fp);
printf(
"Stage 4 clear!\n"
);
FILE
*
fp
=
fopen(
"\x0a"
,
"w"
);
fwrite(
"\x00\x00\x00\x00"
,
4
,
1
,fp);
fclose(fp);
FILE
*
fp
=
fopen(
"\x0a"
,
"w"
);
fwrite(
"\x00\x00\x00\x00"
,
4
,
1
,fp);
fclose(fp);
/
/
network
int
sd, cd;
struct sockaddr_in saddr, caddr;
sd
=
socket(AF_INET, SOCK_STREAM,
0
);
if
(sd
=
=
-
1
){
printf(
"socket error, tell admin\n"
);
return
0
;
}
saddr.sin_family
=
AF_INET;
saddr.sin_addr.s_addr
=
INADDR_ANY;
saddr.sin_port
=
htons( atoi(argv[
'C'
]) );
if
(bind(sd, (struct sockaddr
*
)&saddr, sizeof(saddr)) <
0
){
printf(
"bind error, use another port\n"
);
return
1
;
}
listen(sd,
1
);
int
c
=
sizeof(struct sockaddr_in);
cd
=
accept(sd, (struct sockaddr
*
)&caddr, (socklen_t
*
)&c);
if
(cd <
0
){
printf(
"accept error, tell admin\n"
);
return
0
;
}
if
( recv(cd, buf,
4
,
0
) !
=
4
)
return
0
;
if
(memcmp(buf,
"\xde\xad\xbe\xef"
,
4
))
return
0
;
printf(
"Stage 5 clear!\n"
);
/
/
network
int
sd, cd;
struct sockaddr_in saddr, caddr;
sd
=
socket(AF_INET, SOCK_STREAM,
0
);
if
(sd
=
=
-
1
){
printf(
"socket error, tell admin\n"
);
return
0
;
}
saddr.sin_family
=
AF_INET;
saddr.sin_addr.s_addr
=
INADDR_ANY;
saddr.sin_port
=
htons( atoi(argv[
'C'
]) );
if
(bind(sd, (struct sockaddr
*
)&saddr, sizeof(saddr)) <
0
){
printf(
"bind error, use another port\n"
);
return
1
;
}
listen(sd,
1
);
int
c
=
sizeof(struct sockaddr_in);
cd
=
accept(sd, (struct sockaddr
*
)&caddr, (socklen_t
*
)&c);
if
(cd <
0
){
printf(
"accept error, tell admin\n"
);
return
0
;
}
if
( recv(cd, buf,
4
,
0
) !
=
4
)
return
0
;
if
(memcmp(buf,
"\xde\xad\xbe\xef"
,
4
))
return
0
;
printf(
"Stage 5 clear!\n"
);
argv[
'C'
]
=
"10010"
;
int
sockfd;
struct sockaddr_in server;
sockfd
=
socket(AF_INET,SOCK_STREAM,
0
);
if
( sockfd <
0
){
perror(
"Cannot create the socket"
);
exit(
1
);
}
server.sin_family
=
AF_INET;
server.sin_addr.s_addr
=
inet_addr(
"pwnable.kr"
);
server.sin_port
=
htons(
55555
);
if
( connect(sockfd, (struct sockaddr
*
) &server, sizeof(server)) <
0
){
perror(
"Problem connecting"
);
exit(
1
);
}
char buf[
4
]
=
"\xde\xad\xbe\xef"
;
write(sockfd,buf,
4
);
close(sockfd);
argv[
'C'
]
=
"10010"
;
int
sockfd;
struct sockaddr_in server;
sockfd
=
socket(AF_INET,SOCK_STREAM,
0
);
if
( sockfd <
0
){
perror(
"Cannot create the socket"
);
exit(
1
);
}
server.sin_family
=
AF_INET;
server.sin_addr.s_addr
=
inet_addr(
"pwnable.kr"
);
server.sin_port
=
htons(
55555
);
if
( connect(sockfd, (struct sockaddr
*
) &server, sizeof(server)) <
0
){
perror(
"Problem connecting"
);
exit(
1
);
}
char buf[
4
]
=
"\xde\xad\xbe\xef"
;
write(sockfd,buf,
4
);
close(sockfd);
/
*
argc.c
*
/
#include <stdio.h>
#include <unistd.h>
int
main (
int
argc, char
*
*
argv) {
printf(
"argc: %d\n"
, argc);
for
(
int
i
=
0
; i < argc; i
+
+
)
{
if
(argv[i] !
=
NULL)
{
printf(
"argv[%d]: %s\n"
, i, argv[i]);
}
else
{
printf(
"argv[%d]: NULL\n"
, i);
}
}
return
0
;
}
/
*
argc.c
*
/
#include <stdio.h>
#include <unistd.h>
int
main (
int
argc, char
*
*
argv) {
printf(
"argc: %d\n"
, argc);
for
(
int
i
=
0
; i < argc; i
+
+
)
{
if
(argv[i] !
=
NULL)
{
printf(
"argv[%d]: %s\n"
, i, argv[i]);
}
else
{
printf(
"argv[%d]: NULL\n"
, i);
}
}
return
0
;
}
{~
/
pkexec
-
CVE
-
2021
-
4034
} greetings, earthling [
33.059kb
]$ ☞ .
/
argc
1
2
3
4
5
argc:
6
argv: .
/
argc
argv:
1
argv:
2
argv:
3
argv:
4
argv:
5
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课