├── .gitattributes ├── README.md ├── images ├── connect.png ├── ignore.png └── usage.png └── webshell.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## [](#header-31)一个简单的python webshell 3 | 4 | 5 | 众所周知蚁剑是一款很好的文件管理工具和部署工具,黑客可以直接使用蚁剑来连接webshell,那为什么不能用蚁剑来管理自己的服务器呢? 6 | 7 | 使用方法:只需要下载 webshell.py 并使用 python3 运行即可,自动根据机器生成加密的连接路径、连接参数,然后用蚁剑的 CMDLINUX 类型连接,并选择忽略HTTPS证书,非常方便快捷 8 | 9 | 安装 10 | ``` 11 | git clone https://github.com/Leeyangee/python_webshell 12 | ``` 13 | 运行 14 | ``` 15 | python3 webshell.py 16 | ``` 17 | ![/usage.png](/images/usage.png) 18 | 19 | 使用蚁剑连接 20 | 21 | ![/connect.png](/images/connect.png) 22 | ![/ignore.png](/images/ignore.png) 23 | 24 | `*一定要忽略HTTPS证书` 25 | 26 | 27 | 第一次生成的连接路径、连接参数保存在 config/webshell.cfg 文件中,若需要更改请删除该文件,下次使用自动生成新的 28 | 29 | -------- 30 | 31 | 1.2: 更强大的安全性: 在连接 URL 中添加了逻辑校验参数 passwa ,以防止今后可能会出现的 FastAPI 路径、参数泄露漏洞. 32 | 33 | 1.1: 更全面的配置项,舍弃 os.popen 添加了 stderr 输出 34 | 35 | 1.0: 修改了错误 36 | -------------------------------------------------------------------------------- /images/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leeyangee/python_webshell/69209ce14adfa26f82f533aeb7a04dfbad05a135/images/connect.png -------------------------------------------------------------------------------- /images/ignore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leeyangee/python_webshell/69209ce14adfa26f82f533aeb7a04dfbad05a135/images/ignore.png -------------------------------------------------------------------------------- /images/usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leeyangee/python_webshell/69209ce14adfa26f82f533aeb7a04dfbad05a135/images/usage.png -------------------------------------------------------------------------------- /webshell.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # 在此配置运行参数 4 | 5 | 私钥路径 = "" #用户私钥路径,不填即为默认 6 | 证书路径 = "" #用户证书路径,不填即为默认 7 | 端口 = 222 8 | 调试模式 = False #若该值为 True,则输出命令行命令 9 | 配置项路径 = "./config" 10 | 绑定IP = "0.0.0.0" 11 | 12 | 13 | if __name__ == "__main__": 14 | import os 15 | from fastapi import FastAPI, Form, Request, HTTPException 16 | from fastapi.responses import HTMLResponse,JSONResponse 17 | import uvicorn 18 | import string 19 | import random 20 | from fastapi.exceptions import RequestValidationError 21 | from ast import literal_eval 22 | import base64 23 | import subprocess 24 | 25 | #初始一些必要 helper 26 | def P(b: str) -> str: return os.path.join(配置项路径, b) #拼接配置项路径下面的 b 文件路径 27 | def rdStr(length: int) -> str: return ''.join(random.choice(string.ascii_letters) for _ in range(length)) # length 长度随机字符串产生器 28 | 29 | #判断配置项路径是否存在,若不存在则创建 30 | if not os.path.isdir(配置项路径): 31 | os.makedirs(配置项路径) 32 | 33 | #校验是否输入私钥路径证书路径,若没有则启用默认 34 | if len(私钥路径) + len(证书路径) == 0: 35 | 私钥路径, 证书路径 = P("private.key"), P("certificate.crt") 36 | 私钥, 证书 = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBclZ3NFhlcXNwRFc2b1p0L3RpWUdxeDBuSE1kZWx2bjFlQ3dUQm54WFR6ZHZrVDFXClpRVjVXY1U2cVprc0VPeUI0NW1vUndMc3E3QXRON2xsZDdSZWIxaWhmRzFuTEE5TzlpTVBVMC9BKytob1dremwKTnVQTUNoZDRGQkcweEdielNtLy9SYTQ5b09nN1RDTXZoNm1XT3VMSVVmQTAxWTVQSFhySTlZclg0a1ZQWGNvbQptV1RFMUtML0JBaW1CRUdoWnBVMGRlenR0Y0V0bkVHUVJSckZVeGlRSTUrVFdkd05KZFEvVjFuNVFDTml0d3VEClNmSDVnT1RJNGVEK1lKMy82VzlVSGdKb1ozVEc2V2ZETkhtaUlkM25sYnZJZEVNQlRNM0dIZHZNQmF2OWNmNlUKTmNRQnJyMjBmTWVHWU10MVVHdDhVRGdIWlI4TVBpL0RvMDN2V3dJREFRQUJBb0lCQUZjdnlpbHhuT0g1STZPagpVTmVLeUUvR21hVWZuN0xPZzJXc3hPUytKQ3J5OW1sVkw5NGVvcWxEUi8yRHkyVzNqSnVxNjdiTEFieFhIbWFvCkZ4L2MxcUdwTVk0aWQ0RHhNZ1VZSE4wSmYwVXgzVW1NNHJwNFZtVzg1K2N3QUhuR1RSVml2aitOSWZHSzJrZXcKYW5jUUV6NEtVRFRsV2EwTnBUeHVSQ001Y05tYXJoSzNVY2g4WjBYNzhzQWgzRG54SW9nY2NodjJIRU9GL21OawpjOHIzZGhkSXBlUmwwcnoydnpTZ0hEMWdqWHdLNjRlVGs0UzQ4STJNZGIyWThEOU1YRUZrcWU3aHNzT1dTcjhMClBmVmhLM0lHMzY0OUtDRHlBTUFtNnJ2b00vcUt1QjlIY1Bock9LTGFZdTBFb2M1Y2tJSG1leEUxWHZRNVRrQVkKdkpuTjRnRUNnWUVBMk5Ja3NVSVZsbytwS2N2Y1VaekVHeHlhSU5JTFk1ZHFhWGdUMUxrdHVJUkpPQnhyREN4NgpQZ3l5NzZPczJFRk5waUNMNTlTVit4SVZ0OVpEVE4wYnh3bFJDV1gyaWs0bDFWa1FBVjFlYTJ5ZmVlSzh1VWRCCm4wYTF6Y0RlYkNLVW5Wb1FqL05XTnJoRm9zQnkrVktNUEJ4WXE0UGNBWU1BSUtXbStTcVduMDhDZ1lFQXpLK2sKcllOd25KcXl6a3Bud29xdUJKb0ZFRGd5b0Fvd2VjRDgxb1NoZkFwNFlDRDJkZ0dRa0tBWU81YmV3TlhpV29TYwpPbFp4YzdId1dJdy9ZMGhoc280WGZrT2pOOGcyZVptZDNxcWZybHRnbHdyQnZtbnh0aU9jWEFIZ0srdU5TZVU2CjhHQ0lRU2tHTVA2aCswTHpiU2sxSTJFY0JZOXFkVmNjOHRjVXpEVUNnWUIxRE4xcUpEYnJWUnNKeVZoalpySW4Kd01Va09zQ1RQMGJmTVVmamF5VFhtL0s3Sy94T1VpU2NJdGJtc0FHN3JXVng3ZGdaaTVaOE9FTXBQNjZOYkVCMgpydnpraWZzU3B5V2RpN3NzUVcrYnBzUmhWSnAxbTVZOW1qckRuUDkyZVNTcDNkbGJIUTdKODZrRU44alorRXBrCm1SajFYdllDaXVvaXRjcnljSjluMlFLQmdHTzhtbU9xRG05Z3Vndm5HWlFqK3hObThWeXI1WTh5SjlqTC91ZVEKalJkaUNySGNuZnQyVzdqOUtaR3Z2QzcraVdOT0Jzb3VZTzNkSUo0bENLWWFHUERtWi9Bd2lSR2ZUMXdGVEhXNQpja0dGYVJWd09tUE1QK2xlaE13WVplRkEwQUhYM3RaT1UxWmM1UlZ1bmdOTzVrcWtyNjNqbmNIZjFpSURKcW4xCnY3NlpBb0dBVlVSYjM1blAyNTNHRS9UZnB5eW8wNWRuMjJIR096OFc4eFNxQy9ld2EwSStHSHFWYTA0N1oxWFkKanV2VUpidFFtNXo4VCtwcWF6ZEY3ZmJCTjNPYTJYTnJndFE5WXhSVHJmcThUcHJIVGgzcHM1V2VNQ1VYRXhsZAo1U0hoOVMxVmZWNTFzQy9qdkdzckt4MU5xckxRbDJFYjZva1lIa1NpZVhTeHlnL2d1QUk9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==", "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU0Q0NRRFo1SjdpRXNLSE5UQU5CZ2txaGtpRzl3MEJBUXNGQURCRk1Rc3dDUVlEVlFRR0V3SXcKTURFTE1Ba0dBMVVFQ0F3Q01EQXhDekFKQmdOVkJBY01BakF3TVJ3d0dnWURWUVFLREJORVpXWmhkV3gwSUVOdgpiWEJoYm5rZ1RIUmtNQjRYRFRJME1EWXdOVEExTlRjek5Wb1hEVEkxTURZd05UQTFOVGN6TlZvd1JURUxNQWtHCkExVUVCaE1DTURBeEN6QUpCZ05WQkFnTUFqQXdNUXN3Q1FZRFZRUUhEQUl3TURFY01Cb0dBMVVFQ2d3VFJHVm0KWVhWc2RDQkRiMjF3WVc1NUlFeDBaRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQgpBSzFjT0YzcXJLUTF1cUdiZjdZbUJxc2RKeHpIWHBiNTlYZ3NFd1o4VjA4M2I1RTlWbVVGZVZuRk9xbVpMQkRzCmdlT1pxRWNDN0t1d0xUZTVaWGUwWG05WW9YeHRaeXdQVHZZakQxTlB3UHZvYUZwTTVUYmp6QW9YZUJRUnRNUm0KODBwdi8wV3VQYURvTzB3akw0ZXBsanJpeUZId05OV09UeDE2eVBXSzErSkZUMTNLSnBsa3hOU2kvd1FJcGdSQgpvV2FWTkhYczdiWEJMWnhCa0VVYXhWTVlrQ09mazFuY0RTWFVQMWRaK1VBallyY0xnMG54K1lEa3lPSGcvbUNkCi8rbHZWQjRDYUdkMHh1bG53elI1b2lIZDU1Vzd5SFJEQVV6TnhoM2J6QVdyL1hIK2xEWEVBYTY5dEh6SGhtREwKZFZCcmZGQTRCMlVmREQ0dnc2Tk43MXNDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNV3haRXlwbwpLbmhZOE5aOUF1SFpERzl5eW5yUFdiNVFtTzRoOTRjcFJXdVlRQm8veVUwUkxLMHFNU0VJK2Q5QnpxR2tiOGNDCjlsQUVzSUI3UmY4QUpLK2duQWdmUXNVRlV3RU0za05lcWV3bGNsNDk5Y0FHMVhtOWRLd28xZGFLYTZOV0JrOEcKOVdvNTBsOG1aN2syM1ZRSU1aaUJNUytIMTN1UGpIaGNQWU9QWEY3RklZM3IxYUZDUzVPRHd3RXdwZXpqYzB2bApDOE9RREF6aFhzR3E5cDBVbGhoYVM1Z2s5cWNmb3hNYkUwTmpXYzBPNmhjOHB3ZmxzbU9vYUNROXNWYnlBSWVuCmJGUDZyV3FKNTZlMGRHaG4wN0xtNkZLejR6azU1UTk5ZGNBWDJ2T1VLQ0xaaDBJQlY3S0w0Z3RiYVFadHhLMGgKSElDdFVEb2ZmRmZBcEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" 37 | 38 | if not os.path.exists(私钥路径): 39 | with open(私钥路径, 'w') as f: 40 | f.write(base64.b64decode(私钥).decode()) 41 | 42 | if not os.path.exists(证书路径): 43 | with open(证书路径, 'w') as f: 44 | f.write(base64.b64decode(证书).decode()) 45 | 46 | #webshell.cfg Helper 47 | class Config(object): 48 | @staticmethod 49 | def getDocs() -> dict: 50 | try: 51 | #检查 webshell.cfg 是否存在,其中元素是否正常 52 | with open(P("webshell.cfg"), 'r') as f: 53 | data = f.read() 54 | data1 = literal_eval(base64.b64decode(data).decode()) 55 | data2 = data1["PATH"] + data1["QUERY"] + data1['PASSWARD'] 56 | except: 57 | with open(P("webshell.cfg"), "w") as f: 58 | origin = { 59 | "PATH": f"/{rdStr(64)}", 60 | "QUERY": f"query_{rdStr(24)}", 61 | "PASSWARD": f"{rdStr(32)}" 62 | } 63 | f.write(base64.b64encode(str(origin).encode()).decode()) 64 | 65 | with open(P("webshell.cfg"), 'r') as f: 66 | data = f.read() 67 | return literal_eval(base64.b64decode(data).decode()) 68 | 69 | @staticmethod 70 | def getPATH() -> str: 71 | return Config.getDocs()["PATH"] 72 | 73 | @staticmethod 74 | def getQUERY() -> str: 75 | return Config.getDocs()["QUERY"] 76 | 77 | @staticmethod 78 | def getPASSWARD() -> str: 79 | return Config.getDocs()['PASSWARD'] 80 | 81 | PATH, QUERY, PASSWARD = Config.getPATH(), Config.getQUERY(), Config.getPASSWARD() 82 | 83 | #向 FastAPI 框架注册 84 | app = FastAPI(docs_url=None, redoc_url=None) 85 | 86 | #处理命令的函数 87 | def handler(command: str) -> str: 88 | if 调试模式: 89 | print(command) 90 | ''' 91 | #r1 = os.popen(command) 92 | #r2 = r1.buffer.read().decode('utf-8', errors='replace') 93 | ''' 94 | r1 = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 95 | r2 = (r1.stdout + r1.stderr).decode(errors="replace") 96 | return r2 97 | 98 | exec(f''' 99 | @app.get("{PATH}/{"{passwa}"}", response_class=HTMLResponse, include_in_schema=False) 100 | async def shell_get(passwa: str, {QUERY}: str = ""): 101 | if passwa == PASSWARD: 102 | return handler({QUERY}) 103 | 104 | @app.post("{PATH}/{"{passwa}"}", response_class=HTMLResponse, include_in_schema=False) 105 | async def shell_post(passwa: str, {QUERY}: str = Form(...)): 106 | if passwa == PASSWARD: 107 | return handler({QUERY}) 108 | ''') 109 | 110 | print(f''' 111 | -------------------- 112 | 113 | 连接路径: https://{"{你的IP}"}:{端口}{PATH}/{PASSWARD} 114 | 115 | 连接密码(参数): {QUERY} 116 | 117 | -------------------- 118 | ''') 119 | uvicorn.run(app, host=绑定IP, port=端口, ssl_keyfile=私钥路径, ssl_certfile=证书路径) 120 | 121 | --------------------------------------------------------------------------------