zoukankan      html  css  js  c++  java
  • CTF线下awd攻防文件监控脚本

    CTF线下awd攻防赛中常用一个文件监控脚本来保护文件,但是就博主对于该脚本的审计分析

    发现如下的问题:

    1.记录文件的路径未修改导致log暴露
    原文件备份文件夹:drops_JWI96TY7ZKNMQPDRUOSG0FLH41A3C5EXVB82/bak_EAR1IBM0JT9HZ75WU4Y3Q8KLPCX26NDFOGVS
    (猥琐一点可以直接删除掉,不给对方自行恢复的机会)
    文件监控记录文件:drops_JWI96TY7ZKNMQPDRUOSG0FLH41A3C5EXVB82/log_WMY4RVTLAJFB28960SC3KZX7EUP1IHOQN5GD/log.txt
    (该文件记录了删除文件记录、恢复文件记录、上传文件记录等,可以用来偷其他战队的一些脚本)
    上传文件删除后保存文件夹:drops_JWI96TY7ZKNMQPDRUOSG0FLH41A3C5EXVB82/webshell_WMY4RVTLAJFB28960SC3KZX7EUP1IHOQN5GD
    (该脚本会把上传的文件直接删除,并且将上传文件以“原文件名+.txt”保存在该目录下)
    被篡改文件备份保存夹:diff_UMTGPJO17F82K35Z0LEDA6QB9WH4IYRXVSCN/diff_UMTGPJO17F82K35Z0LEDA6QB9WH4IYRXVSCN
    (被修改文件会被恢复,并且篡改后的文件会保存到该文件夹下,文件名为“原文件名+.txt”)
    2.多次篡改同一文件可成功篡改
    根据本人对于脚本代码的审计,发现作者在恢复被篡改文件时错误使用了move函数
    即变成了将备份中的被篡改文件移动到原文件目录下,使得备份中的被篡改文件消失
    当同一文件被篡改两次时,脚本便无法从备份中再找到被篡改文件的备份(第一次篡改恢复时备份已经移动到了原文件目录下)
    问题代码定位在这里:

    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)
    #这里直接使用了move将备份的文件移动至原目录下,导致备份中该文件消失,在第二次篡改时便无法再从备份中move相同的文件

    3.删除文件后脚本无法恢复文件

    这里在博主修复时发现了比较尴尬的一点,文件一旦被删除恢复后会被另一个监控上传文件的功能当作新上传的文件直接删除,形成了一个条件竞争,

    在对代码进行分析后,发现该脚本是通过白名单来监控文件的,解决方案就是在恢复删除文件之后将该文件重新加入白名单

    考虑到以上的问题,博主对脚本进行了一次修复优化,优化后的文件监控脚本源码如下:

      1 # -*- coding: utf-8 -*-
      2 import os
      3 import re
      4 import hashlib
      5 import shutil
      6 import ntpath
      7 import time
      8 import sys
      9 
     10 # 设置系统字符集,防止写入log时出现错误
     11 reload(sys)
     12 sys.setdefaultencoding('utf-8')
     13 CWD = os.getcwd()
     14 FILE_MD5_DICT = {}      # 文件MD5字典
     15 ORIGIN_FILE_LIST = []
     16 
     17 # 特殊文件路径字符串
     18 Special_path_str = 'drops_B0503373BDA6E3C5CD4E5118C02ED13A' #drops_md5(icecoke1024)
     19 bakstring = 'back_CA7CB46E9223293531C04586F3448350'          #bak_md5(icecoke1)
     20 logstring = 'log_8998F445923C88FF441813F0F320962C'          #log_md5(icecoke2)
     21 webshellstring = 'webshell_988A15AB87447653EFB4329A90FF45C5'#webshell_md5(icecoke3)
     22 difffile = 'difference_3C95FA5FB01141398896EDAA8D667802'          #diff_md5(icecoke4)
     23 
     24 Special_string = 'drops_log'  # 免死金牌
     25 UNICODE_ENCODING = "utf-8"
     26 INVALID_UNICODE_CHAR_FORMAT = r"?%02x"
     27 
     28 # 文件路径字典
     29 spec_base_path = os.path.realpath(os.path.join(CWD, Special_path_str))
     30 Special_path = {
     31     'bak' : os.path.realpath(os.path.join(spec_base_path, bakstring)),
     32     'log' : os.path.realpath(os.path.join(spec_base_path, logstring)),
     33     'webshell' : os.path.realpath(os.path.join(spec_base_path, webshellstring)),
     34     'difffile' : os.path.realpath(os.path.join(spec_base_path, difffile)),
     35 }
     36 
     37 def isListLike(value):
     38     return isinstance(value, (list, tuple, set))
     39 
     40 # 获取Unicode编码
     41 def getUnicode(value, encoding=None, noneToNull=False):
     42 
     43     if noneToNull and value is None:
     44         return NULL
     45 
     46     if isListLike(value):
     47         value = list(getUnicode(_, encoding, noneToNull) for _ in value)
     48         return value
     49 
     50     if isinstance(value, unicode):
     51         return value
     52     elif isinstance(value, basestring):
     53         while True:
     54             try:
     55                 return unicode(value, encoding or UNICODE_ENCODING)
     56             except UnicodeDecodeError, ex:
     57                 try:
     58                     return unicode(value, UNICODE_ENCODING)
     59                 except:
     60                     value = value[:ex.start] + "".join(INVALID_UNICODE_CHAR_FORMAT % ord(_) for _ in value[ex.start:ex.end]) + value[ex.end:]
     61     else:
     62         try:
     63             return unicode(value)
     64         except UnicodeDecodeError:
     65             return unicode(str(value), errors="ignore")
     66 
     67 # 目录创建
     68 def mkdir_p(path):
     69     import errno
     70     try:
     71         os.makedirs(path)
     72     except OSError as exc:
     73         if exc.errno == errno.EEXIST and os.path.isdir(path):
     74             pass
     75         else: raise
     76 
     77 # 获取当前所有文件路径
     78 def getfilelist(cwd):
     79     filelist = []
     80     for root,subdirs, files in os.walk(cwd):
     81         for filepath in files:
     82             originalfile = os.path.join(root, filepath)
     83             if Special_path_str not in originalfile:
     84                 filelist.append(originalfile)
     85     return filelist
     86 
     87 # 计算机文件MD5值
     88 def calcMD5(filepath):
     89     try:
     90         with open(filepath,'rb') as f:
     91             md5obj = hashlib.md5()
     92             md5obj.update(f.read())
     93             hash = md5obj.hexdigest()
     94             return hash
     95 # 文件MD5消失即为文件被删除,恢复文件
     96     except Exception, e:
     97         print u'[*] 文件被删除 : ' + getUnicode(filepath)
     98     shutil.copyfile(os.path.join(Special_path['bak'], ntpath.basename(filepath)), filepath)
     99     for value in Special_path:
    100         mkdir_p(Special_path[value])
    101         ORIGIN_FILE_LIST = getfilelist(CWD)
    102         FILE_MD5_DICT = getfilemd5dict(ORIGIN_FILE_LIST)
    103     print u'[+] 被删除文件已恢复!'
    104     try:
    105          f = open(os.path.join(Special_path['log'], 'log.txt'), 'a')
    106          f.write('deleted_file: ' + getUnicode(filepath) + ' 时间: ' + getUnicode(time.ctime()) + '
    ')
    107          f.close()
    108     except Exception as e:
    109          print u'[-] 记录失败 : 被删除文件: ' + getUnicode(filepath)
    110          pass
    111 
    112 # 获取所有文件MD5
    113 def getfilemd5dict(filelist = []):
    114     filemd5dict = {}
    115     for ori_file in filelist:
    116         if Special_path_str not in ori_file:
    117             md5 = calcMD5(os.path.realpath(ori_file))
    118             if md5:
    119                 filemd5dict[ori_file] = md5
    120     return filemd5dict
    121 
    122 # 备份所有文件
    123 def backup_file(filelist=[]):
    124     for filepath in filelist:
    125         if Special_path_str not in filepath:
    126             shutil.copy2(filepath, Special_path['bak'])
    127 
    128 if __name__ == '__main__':
    129     print u'---------持续监测文件中------------'
    130     for value in Special_path:
    131         mkdir_p(Special_path[value])
    132     # 获取所有文件路径,并获取所有文件的MD5,同时备份所有文件
    133     ORIGIN_FILE_LIST = getfilelist(CWD)
    134     FILE_MD5_DICT = getfilemd5dict(ORIGIN_FILE_LIST)
    135     backup_file(ORIGIN_FILE_LIST) 
    136     print u'[*] 所有文件已备份完毕!'
    137     while True:
    138         file_list = getfilelist(CWD)
    139         # 移除新上传文件
    140         diff_file_list = list(set(file_list) ^ set(ORIGIN_FILE_LIST))
    141         if len(diff_file_list) != 0:
    142             for filepath in diff_file_list:
    143                 try:
    144                     f = open(filepath, 'r').read()
    145                 except Exception, e:
    146                     break
    147                 if Special_string not in f:
    148                     try:
    149                         print u'[*] 查杀疑似WebShell上传文件: ' + getUnicode(filepath)
    150                         shutil.move(filepath, os.path.join(Special_path['webshell'], ntpath.basename(filepath) + '.txt'))
    151                         print u'[+] 新上传文件已删除!'
    152                     except Exception as e:
    153                         print u'[!] 移动文件失败, "%s" 疑似WebShell,请及时处理.'%getUnicode(filepath)
    154                     try:
    155                         f = open(os.path.join(Special_path['log'], 'log.txt'), 'a')
    156                         f.write('new_file: ' + getUnicode(filepath) + ' 时间: ' + str(time.ctime()) + '
    ')
    157                         f.close()
    158                     except Exception as e:
    159                         print u'[-] 记录失败 : 上传文件: ' + getUnicode(e)
    160 
    161         # 防止任意文件被修改,还原被修改文件
    162         md5_dict = getfilemd5dict(ORIGIN_FILE_LIST)
    163         for filekey in md5_dict:
    164             if md5_dict[filekey] != FILE_MD5_DICT[filekey]:
    165                 try:
    166                     f = open(filekey, 'r').read()
    167                 except Exception, e:
    168                     break
    169                 if Special_string not in f:
    170                     try:
    171                         print u'[*] 该文件被修改 : ' + getUnicode(filekey)
    172                         shutil.move(filekey, os.path.join(Special_path['difffile'], ntpath.basename(filekey) + '.txt'))
    173                         shutil.copyfile(os.path.join(Special_path['bak'], ntpath.basename(filekey)), filekey)
    174                         print u'[+] 文件已复原!'
    175                     except Exception as e:
    176                         print u'[!] 移动文件失败, "%s" 疑似WebShell,请及时处理.'%getUnicode(filekey)
    177                     try:
    178                         f = open(os.path.join(Special_path['log'], 'log.txt'), 'a')
    179                         f.write('difference_file: ' + getUnicode(filekey) + ' 时间: ' + getUnicode(time.ctime()) + '
    ')
    180                         f.close()
    181                     except Exception as e:
    182                         print u'[-] 记录失败 : 被修改文件: ' + getUnicode(filekey)
    183                         pass
    184         time.sleep(2)
  • 相关阅读:
    Python的collections之defaultdict的使用及其优势
    Python的collections之namedtuple的使用及其优势
    【转】Python 代码批量抓取免费高清图片!
    Python之Django之views中视图代码重复查询的优化
    【转】把sqlite3数据导入到MySQL中
    【转】项目搬迁,快捷导出环境依赖包到requirements.txt
    聊聊模板方法模式,装饰器模式以及AOP
    [算法]股票问题
    [数据]matplotlib总结
    [算法]谷歌笔试题:Beautiful Numbers
  • 原文地址:https://www.cnblogs.com/yesec/p/11297635.html
Copyright © 2011-2022 走看看