Just Do IT !

CTF线下AWD攻防步骤总结

字数统计: 2.7k阅读时长: 12 min
2019/10/24 Share

AWD竞赛

image

前半个小时应熟悉配置环境。准备网线、网线转接口

最好的防御就是攻击,一定要做好安全加固

本次记录的目的是为了给自己一个月来的准备弄个交代,感觉自己跟大佬们差的很远,只拿了三等,往后的路还很长,还是需要亿点点的努力,无限进步.

赛前准备

常用工具:

  • Burpsuite
  • sqlmap
  • nmap、masscan
  • nc
  • D盾
  • Xshell、Xftp
  • 菜刀或蚁剑
  • Chrome、Firefox各类插件

一句话木马:

  • php
  • asp
  • aspx
  • jsp
  • 内存马

代码比较工具:

BeyondCompare(windows)

Kaleidoscope(mac)

提前准备好各种cms的poc exp (因为你们不允许联网)phpwin phpcms dz

CMS_POC

防守步骤

0x00 ssh登陆,修改密码

普通用户提权成root,根据kernel版本号找到对应的poc,exp提权

0x01 下载源码,备份,/var/www/html 目录

定时备份源码:

1
2
3
4
5
6
7
8
9
#!/bin/bash
while [ 1 ]
do
time=`/bin/date +%H-%M-%S`
bak_file="/var/www/$time.tar.gz"
webdir="/var/www/html"
tar zcvf $bak_file $webdir >/dev/null 2>&1 &
sleep 60 //一分钟备份一次
done

备份数据库

1、找数据库配置文件,找到db_name、db_userdb_passwd

2、登陆数据库,命令备份数据库:

mysqldump -u db_user -p db_passwd db_name > bak.sql//备份指定数据库

cd /var/lib/mysql

mysqldump -u db_user -p db_passwd > bak.sql//先进入数据库目录再备份

mysqldump --all-databases > bak.sql //备份所有数据库

3、还原数据库:

mysql -u db_user -p db_passwd db_name < bak.sql //还原指定数据库

cd /var/lib/mysql

mysql -u db_user db_passwd < bak.sql //先进入数据库目录再还原

还有一种方法:

mysql -u db_name -p
input passwd 进入mysql控制台mysql>
mysql> show databases;看看有哪些数据库
mysql> create database test ; 建立要还原的数据库
mysql> use test;切换到刚创建的数据库
mysql> source test.sql; 导入数据库

关闭 MySQL 远程登录

# mysql -u root -p
mysql> use mysql;
mysql> update user set host = 'localhost' where user='root' and host='%';
mysql> flush privileges;
mysql> exit;

隐藏用户

查看home目录下是否有其他用户
/etc/passwd是否有/bin/bash的用户

0x02 扫描预留后门,御剑,k8飞刀,D盾,扫描目录,发现后门,注释代码

# 简单的查找后门
find . -name '*.php' | xargs grep -n 'eval('
find . -name '*.php' | xargs grep -n 'assert('
find . -name '*.php' | xargs grep -n 'system('

0x03 修改数据库密码

mysql -u root -p
Show databases;
use mysql;
set password for root@localhost = password('123'); 或者
update user set password = PASSWORD('要更换的密码') where user = 'root';
flush privileges;
show tables;可能有flag。
select * from typecho_flag;

0x04 关闭不必要的端口,要求的服务端口不能关

netstat -napt ,lsof -i 查看端口,
kill -9 PID 杀掉进程
nmap -sV ip地址(-sV参数可以探测目标主机的服务器版本)
nmap扫描对方开启的端口,21,22,21,3306,进行爆破

msfconsole进入metasploit,hydra可以对22端口进行爆破

0x05 代码审计,使用seay源代码审计,或者Rips代码审计

可以参考我写的这篇博客

Rips工具使用

0x06 木马查杀

关于木马

  • 关注服务的可用性状况
  • 查看文件监控情况
  • 在被攻击的时候进行响应,保存相应的流量,查找/清除后门

建议使用apache用户来删除

通过文件监控查看系统异常情况

文件监控脚本

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# -*- coding: utf-8 -*-
#use: python file_check.py ./

import os
import hashlib
import shutil
import ntpath
import time

CWD = os.getcwd()
FILE_MD5_DICT = {} # 文件MD5字典
ORIGIN_FILE_LIST = []


# 特殊文件路径字符串
Special_path_str = 'drops_JWI96TY7ZKNMQPDRUOSG0FLH41A3C5EXVB82'
bakstring = 'bak_EAR1IBM0JT9HZ75WU4Y3Q8KLPCX26NDFOGVS'
logstring = 'log_WMY4RVTLAJFB28960SC3KZX7EUP1IHOQN5GD'
webshellstring = 'webshell_WMY4RVTLAJFB28960SC3KZX7EUP1IHOQN5GD'
difffile = 'diff_UMTGPJO17F82K35Z0LEDA6QB9WH4IYRXVSCN'

Special_string = 'drops_log' # 免死金牌
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 NULL

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))
if len(diff_file_list) != 0:
# import pdb;pdb.set_trace()
for filepath in diff_file_list:
try:
f = open(filepath, 'r').read()
except Exception, e:
break
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 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)
# print '[*] ' + getUnicode(time.ctime())

不死马清理:

ps aux www|grep shell.php

找到pid后杀掉进程就可以,你删掉脚本是起不了作用的,因为php执行的时候已经把脚本读进去解释成opcode运行了

重启php等web服务

用一个ignore_user_abort(true)脚本,一直竞争写入(断断续续)。usleep要低于对方不死马设置的值。

创建一个和不死马生成的马一样名字的文件夹。

不死马、waf、抓流量的waf

不死马使用原理就是不断将的自己写入,造成进程占用,被删除后一秒就已经生成新的了,还有就是.XXX文件建立隐藏文件不死马。
waf,脚本waf可以防止一般情况下的危险字符,但是不能报太大希望,

require_once('waf.php');

抓流量waf,挂载后可以让防守队员_实时关注日志,一旦发现被getflag了,就通过日志相应的手法攻击别人

waf脚本

PHPWaf用来记录访问请求,通过分析访问请求来判断对方的攻击方式,作用是感知漏洞,判断攻击方式,从而修复漏洞。

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
<?php
//部署waf可能会导致服务不可用,需要谨慎部署。
error_reporting(0);
define('LOG_FILENAME','log.txt');
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"];

//rewirte shell which uploaded by others, you can do more
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']);//fix a bug
$input = array("Get"=>$get, "Post"=>$post, "Cookie"=>$cookie, "File"=>$files, "Header"=>$header);
//deal with
$pattern = "select|insert|update|delete|and|or|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex";
$pattern .= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern .="|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern .="|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|",$pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach($vpattern as $value){
foreach ($v as $kk => $vv) {
if (preg_match( "/$value/i", $vv )){
$bool = true;
logging($input);
break;
}
}
if($bool) break;
}
if($bool) break;
}
}

function logging($var){
file_put_contents(LOG_FILENAME, "\r\n".time()."\r\n".print_r($var, true), FILE_APPEND);
// die() or unset($_GET) or unset($_POST) or unset($_COOKIE);
}
waf();
?>

waf的激活方式:

1. php.ini选项:auto_prepend_file =与auto_append_file =包含waf,需要权限较高。

2. config.php 中包含waf

3. 让所有的文件都包含waf

0x07流量分析

  1. 查看当前访问量前十的链接

cat /var/log/apache2/access.log | cut -f4 -d | sort | uniq -c | sort -k -r | head - 

  1. 文件监控增删改查的文件使用脚本Monitor(一个简单的文件监控示例脚本,可以监控创建、删除、移动、属性修改操作,自动删除新增文件或目录。已使用pyinstaller打包成了linux可执行程序)

  2. chattr +i 命令锁死网站目录和文件,防止其他队伍删除网站宕机。注:添加后不取消特殊权限 即使是root用户也无法删除/修改文件

chattr -R +i /var/www/html  
chattr -R +i /var/www/html/*   
(取消命令将+号改成-   chattr -R -i /var/www/html)
  1. 使用waf全局包含waf,注意waf的日志和weblogger日志与第二项文件监控冲突,建议建立文件夹将日志和weblogger日志放到指定文件夹中,避开文件监控,
sudo find /var/www/html/path_you_want -type f -path "*.php" | xargs sed -i "s/<?php/<?php\nrequire_once('\/tmp\/waf.php');\n/g"
#意思就是查找需要加waf的目录下所有php文件,在头部添加一句,用require_once函数引入/tmp/waf.php文件。因为sed命令利用 / 区分文件中的原字符串和修改的字符串,所以我们要对 / 进行转义。类似于在单引号中再次使用单引号时我们也要用反斜杠转义。
  1. 后台一定要登陆后台,有没有弱口令,修改成强口令。
  1. 不只是后台,phpmyadmin、测试页面容易出现sql注入,rce之类的这些

AWD资源小合集:

https://neversec.top/20190415/how-to-awd.html

参考网站:

https://blog.csdn.net/qq_42114918/article/details/82785960#comments

https://blog.csdn.net/qq_43625917/article/details/96158713

http://www.admintony.com/AWD%E8%A5%BF%E7%9F%B3%E6%B2%B9%E7%BA%BF%E4%B8%8B%E8%B5%9B%E6%80%BB%E7%BB%93.html

https://blog.csdn.net/like98k/article/details/80261603

http://www.8sec.cc/index.php/archives/97/

CATALOG
  1. 1. AWD竞赛
  2. 2. 赛前准备
  3. 3. 防守步骤
    1. 3.1. 0x00 ssh登陆,修改密码
    2. 3.2. 0x01 下载源码,备份,/var/www/html 目录
      1. 3.2.1. 备份数据库
    3. 3.3. 0x02 扫描预留后门,御剑,k8飞刀,D盾,扫描目录,发现后门,注释代码
    4. 3.4. 0x03 修改数据库密码
    5. 3.5. 0x04 关闭不必要的端口,要求的服务端口不能关
    6. 3.6. 0x05 代码审计,使用seay源代码审计,或者Rips代码审计
    7. 3.7. 0x06 木马查杀
      1. 3.7.1. 通过文件监控查看系统异常情况
      2. 3.7.2. 不死马清理:
      3. 3.7.3. 不死马、waf、抓流量的waf
    8. 3.8. 0x07流量分析
  4. 4. AWD资源小合集: