Access Control 13-19题解题思路
今天这篇文章来分享一下 pwn.college 中的 Access Control 章节第14至19题的解题思路。
第14题至19提主要考察对 Bell-LaPadula 保密模型的理解,其实简单来说就两条:
- No Read Up:A subject cannot read an object with a higher security level.
- No Write Down:A subject cannot write to an object with a lower security level.
对于类别(Category)来说:
- 读:The subject’s categories must include all of the object’s categories.
- 写:The subject’s categories must be a subset of the object’s categories.
这一个系类的题都是在运行/challenge/run
后,根据问题输入yes
或者no
回答。
第13-14题
X
Mandatory Access Control question without categories
In this series of challenges, you will be working with various access control systems. Break the system to get the flag.
In this challenge you’ll be answering questions about the standard Bell–LaPadula model of Mandatory Access Control.
Answer the question about the model to get the flag.
In this challenge, your goal is to answer 1 questions correctly in 120 seconds about the following Mandatory Access Control (MAC) system: 4 Levels (first is highest aka more sensitive): TS S C UC Q 1. Can a Subject with level UC read an Object with level UC?
这里我们直接输入yes
,然后回车就能拿到 flag 了。毕竟就一道题,随便猜一个应该也能蒙对。
第15-16题
X
Mandatory Access Control question with categories
In this series of challenges, you will be working with various access control systems. Break the system to get the flag.
In this challenge you’ll be answering questions about the category-based Bell–LaPadula model of Mandatory Access Control.
Answer the question about the model to get the flag.
In this challenge, your goal is to answer 1 questions correctly in 120 seconds about the following Mandatory Access Control (MAC) system: 4 Levels (first is highest aka more sensitive): TS S C UC 4 Categories: UFO NATO NUC ACE Q 1. Can a Subject with level C and categories {UFO, NATO, ACE} write an Object with level C and categories {NATO, NUC}?
同样的,这里我们直接输入no
,然后回车就能拿到 flag 了。毕竟就一道题,随便猜一个应该也能蒙对。
其实15-16题与13-14题相比就多了需要对类别的判断。
到目前为止,其实完全用不到 Bell-LaPadula 保密模型的概念,因为每次出现的题目都是一样的,只要瞎猜就能出 flag。不过这种想法从第17题开始就不行了。
第17-18题
我们首先看下题目描述:
Automate answering 20/64
Mandatory Access Control questions with categories in one second
要我们在1秒内把20/40
题解出来,这很显然是在提醒我们要写个脚本自动化这个流程。
接下来我们run
一下挑战看看,应该会发现下面的特点:
- 4 Levels 是固定的(TS,S,C,UC)
- 4 Categories 也是固定的(UFO,ACE,NATO,NUC)
- 每次得到的题目是不固定的
- 第一次
run
:Can a Subject with level C and categories {} write an Object with level C and categories {UFO, ACE, NUC}? - 第二次
run
:Can a Subject with level TS and categories {NATO} read an Object with level TS and categories {NATO, NUC}? - 第三次
run
:Can a Subject with level TS and categories {ACE, UFO} read an Object with level TS and categories {NATO}?
- 第一次
获取到上面这些信息应该会有如下思路:
- 获取题目文本
- 通过某种方式自动提取 Subject Level,Subject Categories,Object Level, Object Categories 以及 Action(读或写)。
- 根据 Bell-LaPadula 保密模型的定义判断问题的答案。
- 通过某种方式与这个程序直接交互,即发送
yes
或no
。
获取题目文本
在提取到题目的关键字之前,我们肯定要获取到题目的文本。这里我们用?
作为关键字,如果我们找到了某行包含?
,那么我们就认为这行是个题目。经过测试,这个思路是可行的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import subprocess
process = subprocess.Popen(
['/opt/pwn.college/python', '/challenge/run'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
while True:
line = process.stdout.readline() # Read one line from output
if not line: # If no more output, break
break
print(line.strip()) # Print the prompt
if "?" in line: # Detecting a question prompt (adjust this condition as needed)
question = line.split('.')[1] # only want the question, no question number
print('question is: ' + question)
break
# Close the process
process.stdin.close()
process.wait()
提取关键信息
然后需要提取关键字,这里直接用正则匹配。下面代码是能用的,不过在不确定的情况下,我们先找到一个题目把我们需要的内容打印出来看看是否正确。
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
import re
import subprocess
def extract_info(question):
# Define regex patterns to extract security levels and categories
level_pattern = r'level ([A-Z]+)'
categories_pattern = r'categories \{([^}]*)\}'
# Find all security levels (first for Subject, second for Object)
levels = re.findall(level_pattern, question)
subject_level = levels[0] if len(levels) > 0 else None
object_level = levels[1] if len(levels) > 1 else None
# Find all category sets (first for Subject, second for Object)
categories = re.findall(categories_pattern, question)
subject_categories = set(map(str.strip, categories[0].split(','))) if len(categories) > 0 and categories[0] else set()
object_categories = set(map(str.strip, categories[1].split(','))) if len(categories) > 1 and categories[1] else set()
# Extract the action
action_pattern = r'\b(write|read)\b'
action_match = re.search(action_pattern, question, re.IGNORECASE)
action = action_match.group(0).lower() if action_match else None
return subject_level, subject_categories, object_level, object_categories, action
process = subprocess.Popen(
['/opt/pwn.college/python', '/challenge/run'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
while True:
line = process.stdout.readline() # Read one line from output
if not line: # If no more output, break
break
print(line.strip()) # Print the prompt
if "?" in line: # Detecting a question prompt (adjust this condition as needed)
question = line.split('.')[1] # only want the question, no question number
print('question is: ' + question)
subject_level, subject_categories, object_level, object_categories, action = extract_info(question)
print(f'subject_level: {subject_level}')
print(f'subject_categories: {subject_categories}')
print(f'object_level: {object_level}')
print(f'object_categories: {object_categories}')
print(f'action: {action}')
break
# Close the process
process.stdin.close()
process.wait()
根据定义判断问题的答案
这个没啥好说的,就是根据定义判定答案。
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
def can_read(subject_level, subject_categories, object_level, object_categories):
# Define security level hierarchy (higher index means higher level)
levels = {"UC": 0, "C": 1, "S": 2, "TS": 3} # U: Unclassified, C: Confidential, S: Secret, TS: Top Secret
# Bell-LaPadula: No Read Up (NRU)
if levels[subject_level] < levels[object_level]:
return False # Subject cannot read an object at a higher level
# Subject must have at least all categories of the object
if not object_categories.issubset(subject_categories):
return False
return True
def can_write(subject_level, subject_categories, object_level, object_categories):
# Define security level hierarchy
levels = {"UC": 0, "C": 1, "S": 2, "TS": 3}
# Bell-LaPadula: No Write Down (NWD)
if levels[subject_level] > levels[object_level]:
return False # Subject cannot write to a lower-level object
# Subject categories must be a subset of the object's categories
if not subject_categories.issubset(object_categories):
return False
return True
发送答案
这里用了 process.stdin.write
直接发送判定出的答案。以下是完整的脚本:
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
import subprocess
import re
def extract_info(question):
# Define regex patterns to extract security levels and categories
level_pattern = r'level ([A-Z]+)'
categories_pattern = r'categories \{([^}]*)\}'
# Find all security levels (first for Subject, second for Object)
levels = re.findall(level_pattern, question)
subject_level = levels[0] if len(levels) > 0 else None
object_level = levels[1] if len(levels) > 1 else None
# Find all category sets (first for Subject, second for Object)
categories = re.findall(categories_pattern, question)
subject_categories = set(map(str.strip, categories[0].split(','))) if len(categories) > 0 and categories[0] else set()
object_categories = set(map(str.strip, categories[1].split(','))) if len(categories) > 1 and categories[1] else set()
# Extract the action
action_pattern = r'\b(write|read)\b'
action_match = re.search(action_pattern, question, re.IGNORECASE)
action = action_match.group(0).lower() if action_match else None
return subject_level, subject_categories, object_level, object_categories, action
def can_read(subject_level, subject_categories, object_level, object_categories):
# Define security level hierarchy (higher index means higher level)
levels = {"UC": 0, "C": 1, "S": 2, "TS": 3} # U: Unclassified, C: Confidential, S: Secret, TS: Top Secret
# Bell-LaPadula: No Read Up (NRU)
if levels[subject_level] < levels[object_level]:
return False # Subject cannot read an object at a higher level
# Subject must have at least all categories of the object
if not object_categories.issubset(subject_categories):
return False
return True
def can_write(subject_level, subject_categories, object_level, object_categories):
# Define security level hierarchy
levels = {"UC": 0, "C": 1, "S": 2, "TS": 3}
# Bell-LaPadula: No Write Down (NWD)
if levels[subject_level] > levels[object_level]:
return False # Subject cannot write to a lower-level object
# Subject categories must be a subset of the object's categories
if not subject_categories.issubset(object_categories):
return False
return True
process = subprocess.Popen(
['/opt/pwn.college/python', '/challenge/run'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
while True:
line = process.stdout.readline() # Read one line from output
if not line: # If no more output, break
break
print(line.strip()) # Print the prompt
if "?" in line: # Detecting a question prompt (adjust this condition as needed)
question = line.split('.')[1] # only want the question, no question number
print('question is: ' + question)
subject_level, subject_categories, object_level, object_categories, action = extract_info(question)
print(f'subject_level: {subject_level}')
print(f'subject_categories: {subject_categories}')
print(f'object_level: {object_level}')
print(f'object_categories: {object_categories}')
print(f'action: {action}')
# send based on action
if action == 'read':
is_true = can_read(subject_level, subject_categories, object_level, object_categories)
key = 'yes' if is_true else 'no'
process.stdin.write(key + '\n')
process.stdin.flush()
else:
is_true = can_write(subject_level, subject_categories, object_level, object_categories)
key = 'yes' if is_true else 'no'
process.stdin.write(key + '\n')
process.stdin.flush()
print('Is my answer correct? ' + process.stdout.readline())
# break
# Close the process
process.stdin.close()
process.wait()
第19题
第19题是17-18题的升级版,同样的,我们run
一下挑战看看,应该会发现下面的特点:
- 4 Levels 是不固定的(与17-18题的不同之处)
- 40 Categories 也是不固定的(与17-18题的不同之处)
- 每次得到的题目是不固定的
相当于 4 Levels 与 40 Categories 都变成随机值了,但是由于我们根据 Bell-LaPadula 保密模型判定无需 4 Levels 本身,所以只需要提取到 40 Categories 即可。思路也非常明确:
- 获取到 40 Categories
- 修改
can_read
和can_write
函数以支持变化的40 Categories
获取动态类别
先找到关键字40 Levels
,然后把它存成之前levels = {"UC": 0, "C": 1, "S": 2, "TS": 3}
的样子。
1
2
3
4
5
if '40 Levels' in line:
for i in range(40, -1, -1):
level = process.stdout.readline().strip()
levels[level] = i
print(levels)
修改函数
这里直接把定义死的levels
注释掉,通过参数传进来就行。can_write
和can_read
都需要改,不过我这里就贴一个作为例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
def can_write(levels, subject_level, subject_categories, object_level, object_categories):
# Define security level hierarchy
# levels = {"UC": 0, "C": 1, "S": 2, "TS": 3}
# Bell-LaPadula: No Write Down (NWD)
if levels[subject_level] > levels[object_level]:
return False # Subject cannot write to a lower-level object
# Subject categories must be a subset of the object's categories
if not subject_categories.issubset(object_categories):
return False
return True
以下是完整脚本:
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
import subprocess
import re
def extract_info(question):
# Define regex patterns to extract security levels and categories
level_pattern = r'level ([A-Za-z]+)'
categories_pattern = r'categories \{([^}]*)\}'
# Find all security levels (first for Subject, second for Object)
levels = re.findall(level_pattern, question)
subject_level = levels[0] if len(levels) > 0 else None
object_level = levels[1] if len(levels) > 1 else None
# Find all category sets (first for Subject, second for Object)
categories = re.findall(categories_pattern, question)
subject_categories = set(map(str.strip, categories[0].split(','))) if len(categories) > 0 and categories[0] else set()
object_categories = set(map(str.strip, categories[1].split(','))) if len(categories) > 1 and categories[1] else set()
# Extract the action
action_pattern = r'\b(write|read)\b'
action_match = re.search(action_pattern, question, re.IGNORECASE)
action = action_match.group(0).lower() if action_match else None
return subject_level, subject_categories, object_level, object_categories, action
def can_read(levels, subject_level, subject_categories, object_level, object_categories):
# Define security level hierarchy (higher index means higher level)
#levels = {"UC": 0, "C": 1, "S": 2, "TS": 3} # U: Unclassified, C: Confidential, S: Secret, TS: Top Secret
# Bell-LaPadula: No Read Up (NRU)
if levels[subject_level] < levels[object_level]:
return False # Subject cannot read an object at a higher level
# Subject must have at least all categories of the object
if not object_categories.issubset(subject_categories):
return False
return True
def can_write(levels, subject_level, subject_categories, object_level, object_categories):
# Define security level hierarchy
# levels = {"UC": 0, "C": 1, "S": 2, "TS": 3}
# Bell-LaPadula: No Write Down (NWD)
if levels[subject_level] > levels[object_level]:
return False # Subject cannot write to a lower-level object
# Subject categories must be a subset of the object's categories
if not subject_categories.issubset(object_categories):
return False
return True
process = subprocess.Popen(
['/opt/pwn.college/python', '/challenge/run'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
levels = {}
while True:
line = process.stdout.readline() # Read one line from output
if not line: # If no more output, break
break
print(line.strip()) # Print the prompt
# read 40 levels
if '40 Levels' in line:
for i in range(40, -1, -1):
level = process.stdout.readline().strip()
levels[level] = i
print(levels)
if "?" in line: # Detecting a question prompt (adjust this condition as needed)
question = line.split('.')[1] # only want the question, no question number
print('question is: ' + question)
subject_level, subject_categories, object_level, object_categories, action = extract_info(question)
print(f'subject_level: {subject_level}')
print(f'subject_categories: {subject_categories}')
print(f'object_level: {object_level}')
print(f'object_categories: {object_categories}')
print(f'action: {action}')
# break
# send based on action
if action == 'read':
is_true = can_read(levels, subject_level, subject_categories, object_level, object_categories)
key = 'yes' if is_true else 'no'
process.stdin.write(key + '\n')
process.stdin.flush()
else:
is_true = can_write(levels, subject_level, subject_categories, object_level, object_categories)
key = 'yes' if is_true else 'no'
process.stdin.write(key + '\n')
process.stdin.flush()
print('Is my answer correct? ' + process.stdout.readline())
# break
# Close the process
process.stdin.close()
process.wait()
总结
总结部分主要讨论一下我尝试过的方法。
因为我没有看题,最初的想法是直接爆破,于是写出了如下杰作:
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
import subprocess
import json
key = {}
current_question_nbr = 0
# reload the key
try:
with open('key.json', 'r') as f:
key = json.load(f)
except FileNotFoundError:
pass
process = subprocess.Popen(
['/opt/pwn.college/python', '/challenge/run'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
while True:
line = process.stdout.readline() # Read one line from output
if not line: # If no more output, break
break
print(line.strip()) # Print the prompt
if "?" in line: # Detecting a question prompt (adjust this condition as needed)
question = line.split('.')[1] # only want the question, no question number
print('question is: ' + question)
# no try needed, have the key stored
if question in key:
process.stdin.write(key[question] + '\n')
process.stdin.flush()
current_question_nbr += 1
else: # do not have the key in record, try random
try_key = 'yes'
process.stdin.write(try_key + '\n') # Send response
process.stdin.flush() # Ensure it's sent immediately
is_correct = process.stdout.readline()
print('The answer is: ' + is_correct)
if 'Incorrect' in is_correct:
key[question] = 'no'
break
else:
key[question] = try_key
# Write to a file
with open('key.json', 'w') as f:
json.dump(key, f)
# Close the process
process.stdin.close()
process.wait()
当时我 Navie 的认为它每次出现的题目都一样(毕竟13-16题确实是),只用写个字典把答案存起来就行。直到我运行了几次才发现,这导出的 JSON 怎么越来越大……仔细一看于是发现是随机的……
于是基本上重写了整个脚本……