脚本提权类问题

脚本提权类问题,大致分为两种:

  1. root等高权限用户会去执行的脚本,低权限用户对该脚本拥有写权限,将导致低权限用户输入的命令被高权限执行;
  2. 以root等高权限用户作为属主的脚本中,低权限用户对该脚本未拥有写权限,但是该脚本中调用了低权限用户拥有写权限的脚本也会导致提权;

扫描脚本1

该脚本原理比较简单,针对前面说的第二点脚本提权类问题,就是全局搜索属主为root的sh文件,然后循环遍历其中的文件内容是否存在调用属主为其他用户的脚本,若有则判定为存在脚本提权问题。

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
#coding=utf-8

import os
import sys

def scan(path, highuser, lowuser):
cmd1 = "find " + path + " -name \"*.sh\" -user " + highuser + " 2>/dev/null"
print "[*]Running command: " + cmd1
result = os.popen(cmd1).read()
files = result.split("\n")
for file in files:
if not os.path.exists(file):
continue
try:
f = open(file, "r")
flag = 0
scriptname = ""
for line in f.readlines():
content = line.split()
for c in content:
if c.endswith(".sh"):
name = os.path.basename(c)
cmd2 = "find / -name " + name + " -user " + lowuser + " 2>/dev/null"
print "[*]Running command: " + cmd2
result = os.popen(cmd2).read()
if result.find(name) != -1:
flag = 1
scriptname = name
break
if flag == 1:
break
if flag == 1:
output = "[+]Found Root Script:" + file + "\n[!]Check:[" + scriptname + "]\n\n"
print output
with open("scan_result.txt", "a") as rfile:
rfile.write(output)
scriptname = ""
flag = 0
except IOError as e:
print "[-]Error: " + file + " | " + e
finally:
if f:
f.close()

if __name__ == '__main__':
if len(sys.argv) != 4:
print "[*]Usage: python RootScriptScan.py [Dir Path] [High Privilege Username] [Low Privilege Username]"
else:
scan(sys.argv[1], sys.argv[2], sys.argv[3])
print "[*]Finished."

该脚本需要输入三个参数,分别是扫描的目录位置、高权限用户名和低权限用户名。

扫描效果:

扫描脚本2

代码如下,注释中已有说明:

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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
#!/usr/bin/python
#coding:utf-8
import os,re,time,sys
from os import path

"""

使用方法:
python find_risky_root_run_scripts.py XXX (XXX为整数,指配置工具运行的秒数)

功能介绍:
1. 可以识别一段时间内所有root运行过的脚本,包括绝对路径和相对路径;
2. 排查以上脚本的属主权限、三组文件权限;
3. 遍历以上脚本的所有父目录,排查所有父目录的属主权限、三组目录权限。
4. 可以打印所有有目录漏洞的脚本列表及对应的漏洞目录,以及汇总呈现所有有漏洞目录的报表

"""


def get_dir_privilege(dir_name):

try:
query_privilege_shellcmd = 'ls -al %s | awk "NR==2"'%(dir_name)
privilege_result = os.popen(query_privilege_shellcmd).read().split()

#Debug
# 定位特定目录与预期结果不符合的debug代码,得出原因是应该是ls -al ,而不是ls -l, 少了个a导致行号不对
#if dir_name == '/opt/HUAWEI/cgp':
# print query_privilege_shellcmd
# print privilege_result

#u,g,o,owner,group = privilege_result[0][1:4],privilege_result[0][4:7],privilege_result[0][7:10],privilege_result[2],privilege_result[3]
u,g,o,owner,group = privilege_result[0][1:4],privilege_result[0][5:6],privilege_result[0][8:9],privilege_result[2],privilege_result[3]
return {'u':u,'g':g,'o':o,'owner':owner,'group':group}
except:
return {}



def check_dir_vulnerable(dirname):

is_dir_vulnerable = False

dir_privilege = get_dir_privilege(dirname)


# 1.目录属主非root
if dir_privilege and dir_privilege['owner'] != 'root':
is_dir_vulnerable = True
#print("dir_is_vulnerable, owner=%s"%(dir_privilege['owner']))

# 2. root属主,但其他组可写
if dir_privilege and dir_privilege['owner'] == 'root' and ( (dir_privilege['group'] == 'root' and dir_privilege['o'] != '-' ) or (dir_privilege['group'] != 'root' and (dir_privilege['g'] != '-' or dir_privilege['o'] != '-' )) ):
is_dir_vulnerable = True
#print("dir_is_vulnerable=%s group=%s others=%s"%(dirname,dir_privilege['g'],dir_privilege['o']))

return is_dir_vulnerable



def get_one_root_script_dirs(full_path_to_filename):

# Debug: 首先打印当前文件路径
#print("filename=%s" %(full_path_to_filename))

all_one_root_script_dirs = set()

# Debug: 考虑极端根目录的场景
# full_path_to_filename = os.path.dirname(full_path_to_filename)

# 所有的父目录(根目录除外) 还要防止输入相对路径导致while死循环("."的父目录还是".")
while full_path_to_filename != "/" and full_path_to_filename != ".":
full_path_to_filename = os.path.dirname(full_path_to_filename)
#print(full_path_to_filename)
all_one_root_script_dirs.add(full_path_to_filename.strip())

return list(all_one_root_script_dirs)



def check_dirs_vulnerable(one_root_script):

is_dirs_vulnerable = False

# 遍历所有上级目录
one_root_script_dirs = set()
one_root_script_dirs = get_one_root_script_dirs(one_root_script)
for dir in one_root_script_dirs:
is_dirs_vulnerable_tmp = False

is_dirs_vulnerable_tmp = check_dir_vulnerable(dir)
if is_dirs_vulnerable_tmp:
is_dirs_vulnerable = True
# Debug
#print("dir is vulnerable= %s"%(dir))

# Debug
#if is_dirs_vulnerable:
# print(one_root_script)

return is_dirs_vulnerable


def get_dirs_vulnerable(one_root_script):

is_dirs_vulnerable = False

one_root_script_dirs_vulnerable = set()

# 遍历所有上级目录
one_root_script_dirs = set()
one_root_script_dirs = get_one_root_script_dirs(one_root_script)
for dir in one_root_script_dirs:
is_dirs_vulnerable_tmp = False

is_dirs_vulnerable_tmp = check_dir_vulnerable(dir)
if is_dirs_vulnerable_tmp:
is_dirs_vulnerable = True
one_root_script_dirs_vulnerable.add(dir.strip())
# Debug
#print("dir is vulnerable= %s"%(dir))

# Debug
#if is_dirs_vulnerable:
# print(one_root_script)

#return is_dirs_vulnerable
return one_root_script_dirs_vulnerable


def get_file_privilege(file_name):

try:
query_script_privilege_shellcmd = 'ls -l %s'%(file_name)
privilege_result = os.popen(query_script_privilege_shellcmd).read().split()
#u,g,o,owner,group = privilege_result[0][1:4],privilege_result[0][4:7],privilege_result[0][7:10],privilege_result[2],privilege_result[3]
u,g,o,owner,group = privilege_result[0][1:4],privilege_result[0][5:6],privilege_result[0][8:9],privilege_result[2],privilege_result[3]
return {'u':u,'g':g,'o':o,'owner':owner,'group':group}
except:
return None

def check_file_vulnerable(filename):

is_file_vulnerable = False
file_privilege = get_file_privilege(filename)


# 1.文件属主非root
if file_privilege and file_privilege['owner'] != 'root':
is_file_vulnerable = True

# 2. root属主,但其他组可写
# if file_privilege['owner'] == 'root' and file_privilege['o'] == 'w':
if file_privilege and file_privilege['owner'] == 'root' and ( (file_privilege['group'] == 'root' and file_privilege['o'] != '-' ) or (file_privilege['group'] != 'root' and (file_privilege['g'] != '-' or file_privilege['o'] != '-' )) ):
is_file_vulnerable = True
# Debug
#print("file_is_vulnerable=%s group=%s others=%s"%(filename,file_privilege['g'],file_privilege['o']))

return is_file_vulnerable



def get_all_root_running_script(scan_period):
# 《 Python控制台输出带颜色的文字方法: https://www.cnblogs.com/yinjia/p/5559702.html 》
# 格式: \033[显示方式;前景色;背景色m
# 例如:
# \033[1;31;40m <1-高亮显示 31-前景色红色 40-背景色黑色>
# \033[0m <采用终端默认设置,即取消颜色设置>
print '\033[5;32m[*] Collect all root running scripts in %d seconds ......\033[0m\n'%scan_period


root_running_script_list_all = set()

root_running_script_list_exists = set()
root_running_script_list_no_exist = set()


#waiting_reverse_loookup_list = []
waiting_reverse_loookup_list = set()
waiting_reverse_loookup_list_debug = set()

#query_root_running_script_by_shellcmd = 'ps -U root -u root u | egrep "*\.sh*" | grep -v egrep'
query_root_running_script_by_shellcmd = 'ps -U root -u root u | egrep ".*\.py.*|.*\.sh.*" | grep -v egrep'

start_time = int(time.time())
while int(time.time()) - start_time <= scan_period:
# 在此处正则添加要匹配的文件后缀
# for i in re.findall(r"/\S*\.sh|/\S*\.py",os.popen(query_root_running_script_by_shellcmd).read()):
for i in re.findall(r"\S+\.sh|\S+\.py",os.popen(query_root_running_script_by_shellcmd).read()):
if i.count('/') <= 1:
# 《 正则表达式30分钟入门教程: https://deerchao.net/tutorials/regex/regex.htm 》
# 《 Python中正则表达式re.match的用法: https://blog.csdn.net/piglite/article/details/81121323 》
# 匹配到相对路径,需要反查可能的绝对路径,将文件名添加到列表,加入前利用正则表达式先过滤掉非正常文件名),待扫描周期结束后一次性查找
#waiting_reverse_loookup_list.append(i.split('/')[-1].strip())
if re.match('^[a-zA-Z.\-_]+$',i) or re.match('^[.][/]',i):
waiting_reverse_loookup_list.add(i.split('/')[-1].strip())
elif( re.match('^[^/]',i)):
# 匹配到不以"/"开头的路径,虽然包括多个"/"符号,同样按照相对路径处理
waiting_reverse_loookup_list.add(i.split('/')[-1].strip())
else:
# 匹配到绝对路径,直接记录
root_running_script_list_all.add(i.strip())

# 由文件名反查绝对路径
if waiting_reverse_loookup_list:
# Debug: 查找需要反查的路径,用于训练
print '本程序抓取的采用相对路径运行的程序有:'
print (waiting_reverse_loookup_list)

lookup_filenames = '|'.join(waiting_reverse_loookup_list)

# Debug: 查找需要反查的路径,用于训练
#print (lookup_filenames)

if lookup_filenames:
reverse_lookup_cmd = 'find / \( -path /proc -o -path /var \) -prune -o -print | grep -E "^*/(%s)$"'%(lookup_filenames)
for i in os.popen(reverse_lookup_cmd).readlines():
# 将反查路径得来的结果放入列表;在存在相同文件名的情况下,可能会产生误报(概率极小)
root_running_script_list_all.add(i.strip())

#Debug: 将反查的结果放入不处理的临时Debug列表waiting_reverse_loookup_list_debug中,这样后续处理的都是绝对路径,达到不反查相对路径的效果,无同名误报
#waiting_reverse_loookup_list_debug.add(i.strip())



# Debug: 当前过滤机制下很难出现不存在的脚本调用,构造一个用于测试此部分逻辑功能
#root_running_script_list_no_exist.add("/root/x00202367")

# 过滤不存在的路径
if root_running_script_list_all:
for script in root_running_script_list_all:
#if os.path.exists(script):
if os.path.isfile(script):
root_running_script_list_exists.add(script.strip())
else:
root_running_script_list_no_exist.add(script.strip())


# 输出所有存在的脚本的路径
#if root_running_script_list_exists:
# 将root_running_script_list_exists输出到控制台
#print("\n-----begin of root_running_script_list_exists:-----")
#print(root_running_script_list_exists)
#for one_root_script in root_running_script_list_exists:
# print(one_root_script)
#print("-----end of root_running_script_list_exists!-----\n")

# 将root_running_script_list_exists写入文件
#with open('./root_running_script_list_exists.txt', 'a+') as f:
# for one_root_script in root_running_script_list_exists:
# f.write("\n%s"%(one_root_script))


# 输出不存在的脚本的路径
if root_running_script_list_no_exist:
# 将root_running_script_list_no_exist输出到控制台
print("\033[34m\n")
#print("\n-----begin of root_running_script_list_no_exist:-----")
print("[*]Please check the following paths manully:")
#print(root_running_script_list_no_exist)
for one_root_script in root_running_script_list_no_exist:
print(one_root_script)
#print("-----end of root_running_script_list_no_exist!-----\n")
print("\033[0m")

# 将root_running_script_list_no_exist写入文件
#with open('./root_running_script_list_no_exist.txt', 'a+') as f:
# for one_root_script in root_running_script_list_no_exist:
# f.write("%s\n"%(one_root_script))


return list(root_running_script_list_exists)



def find_risky_root_running_scripts(scan_period):

#获取所有正在root运行的文件路径
root_running_script_list = get_all_root_running_script(scan_period)

#root_running_script_list_with_risk = set()
root_running_script_list_with_dirs_risk = set()
root_running_script_list_with_file_risk = set()

#判断所有文件的文件权限及目录权限
for one_root_script in root_running_script_list:

#is_script_vulnerable = False
is_script_dirs_vulnerable = False
is_script_file_vulnerable = False

is_script_dirs_vulnerable = check_dirs_vulnerable(one_root_script)
is_script_file_vulnerable = check_file_vulnerable(one_root_script)

if is_script_dirs_vulnerable:
#is_script_vulnerable = True
root_running_script_list_with_dirs_risk.add(one_root_script)

if is_script_file_vulnerable:
#is_script_vulnerable = True
root_running_script_list_with_file_risk.add(one_root_script)

#if is_script_vulnerable:
# root_running_script_list_with_risk.add(one_root_script)



# 由root_running_script_list_with_dirs_risk汇总出所有的risky_dirs_of_root_running_scripts
risky_dirs_of_root_running_scripts = set()


# 输出所有有目录提权漏洞的脚本路径,以及有问题的目录
# 《 python中sorted()和set()去重,排序: https://www.cnblogs.com/sen-c7/p/10414427.html 》
if root_running_script_list_with_dirs_risk:
root_running_script_list_with_dirs_risk_sorted = sorted(root_running_script_list_with_dirs_risk)
print("\033[1;31m\n\n")
print("[*]root_running_script_list_with_dirs_risk_sorted, and their dirs:")
for script in root_running_script_list_with_dirs_risk_sorted:
# 首先打印脚本路径
print("\n\n%s:" %script)
# 其次打印该脚本有问题的目录,并汇总到risky_dirs_of_root_running_scripts中
risky_dirs_of_root_running_script = get_dirs_vulnerable(script)
risky_dirs_of_root_running_script_sorted = sorted(risky_dirs_of_root_running_script, reverse=True)
if risky_dirs_of_root_running_script_sorted:
for dir in risky_dirs_of_root_running_script_sorted:
print (dir)
risky_dirs_of_root_running_scripts.add(dir.strip())
print("\033[0m")

# 输出汇总后的有问题的目录
if risky_dirs_of_root_running_scripts:
risky_dirs_of_root_running_scripts_sorted = sorted(risky_dirs_of_root_running_scripts)
print("\033[1;31m\n\n")
print("[*]risky_dirs_of_root_running_scripts_sorted:")
for dir in risky_dirs_of_root_running_scripts_sorted:
print (dir)
print("\033[0m")

# 输出所有有文件权限提权或者目录提权的脚本路径
#if root_running_script_list_with_risk:
# 将root_running_script_list_with_risk输出到控制台
#print("\033[1;31m\n")
#print("[*]root_running_script_list_with_risk:")
#for one_root_script in root_running_script_list_with_risk:
# print(one_root_script)
#print("\033[0m")

# 将root_running_script_list_with_risk写入文件
#with open('./root_running_script_list_with_risk.txt', 'a+') as f:
# for one_root_script in root_running_script_list_with_risk:
# f.write(one_root_script)


# 输出所有有目录提权的脚本路径
if root_running_script_list_with_dirs_risk:
root_running_script_list_with_dirs_risk_sorted = sorted(root_running_script_list_with_dirs_risk)
# 将root_running_script_list_with_dirs_risk_sorted输出到控制台
print("\033[1;31m\n")
print("[*]root_running_script_list_with_dirs_risk_sorted:")
for one_root_script in root_running_script_list_with_dirs_risk_sorted:
print(one_root_script)
print("\033[0m")

# 将root_running_script_list_with_dirs_risk_sorted写入文件
#with open('./root_running_script_list_with_dirs_risk_sorted.txt', 'a+') as f:
# for one_root_script in root_running_script_list_with_dirs_risk_sorted:
# f.write(one_root_script)


# 输出所有有文件权限提权的脚本路径
if root_running_script_list_with_file_risk:
root_running_script_list_with_file_risk_sorted = sorted(root_running_script_list_with_file_risk)
# 将root_running_script_list_with_file_risk_sorted输出到控制台
print("\033[1;31m\n")
print("[*]root_running_script_list_with_file_risk_sorted:")
for one_root_script in root_running_script_list_with_file_risk_sorted:
print(one_root_script)
print("\033[0m")

# 将root_running_script_list_with_file_risk_sorted写入文件
#with open('./root_running_script_list_with_file_risk_sorted.txt', 'a+') as f:
# for one_root_script in root_running_script_list_with_file_risk_sorted:
# f.write(one_root_script)



if __name__ == '__main__':
find_risky_root_running_scripts(int(sys.argv[1]))
print '\n'