ScanWebShell web 应用开发

ScanWebShell 开发文档

介绍

ScanWebShell 为基于机器学习的PHP-WebShell扫描工具,该版本为web服务形式。支持多用户独立使用和利用celery来配合扫描任务。 对于机器学习相关的部分,我并不是这一部分的主要负责人。

链接🔗: https://github.com/fe1w0/ScanWebShell

  • indeximage-20210421173150850
  • job/countimage-20210421180506546

总体设计

后端设计如下:

URL视图模板说明
/index/ScanWebShell.views.indexuser/index.html主页与项目信息
/user/login/user.views.loginuser/login.html登录
/user/register/user.views.registeruser/register.html注册
/user/logout/user.views.logout无需专门的页面登出
/job/uploadjob.views.upload_filejob/upload.html上传文件
/job/countjob.views.countResultjob/count.html显示所有任务信息
/job/search/?task_idjob.views.searchResultjob/search.html根据task_id 返回任务的详细信息
/job/scan?filejob.views.scan_filejob/scan.html扫描文件
  • index 项目信息
  • user 与用户相关的处理
  • login 登录
  • register 登录
  • forget 忘记密码
  • logut 登出
  • job 任务相关处理
  • upload 上传webshell文件
  • scan 对上传的文件进行扫描
  • count 统计当前用户所有的扫描任务与已上传的文件
  • search 具体描述某一任务,重点为改任务的结果

其中,仅index允许游客访问。

环境为开发环境,上传的是生产环境

User 应用设计

后端设计

参考的项目

User 其MVT中的Moduleview部分,参考于基于Django2.2可重用登录与注册系统

模型如下:

class User(models.Model):
    """
    用户模型
    """
    name =  models.CharField(max_length=128,unique=True)
    password = models.CharField(max_length=256)
    email = models.EmailField(unique=True)
    c_time = models.DateTimeField(auto_now_add=True)
    has_confirmed = models.BooleanField(default=False)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["-c_time"]
        verbose_name = "用户"
        verbose_name_plural = "用户"


class ConfirmString(models.Model):
    """
    邮箱确认模型
    """
    code = models.CharField(max_length=256)
    user = models.OneToOneField('User', on_delete=models.CASCADE)
    c_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.user.name + ":   " + self.code

    class Meta:

        ordering = ["-c_time"]
        verbose_name = "确认码"
        verbose_name_plural = "确认码"

其他功能

之后的View部分是在基于Django2.2可重用登录与注册系统的基础上,补充部分功能:

  • 忘记密码
  • 重置密码
  • django simple captcha refresh

忘记密码

其中重置密码没有独立出来,是属于忘记密码的一部分

相关模型如下:

class ConfirmString(models.Model):
    """
    邮箱确认模型
    """
    code = models.CharField(max_length=256)
    user = models.OneToOneField('User', on_delete=models.CASCADE)
    c_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.user.name + ":   " + self.code

    class Meta:

        ordering = ["-c_time"]
        verbose_name = "确认码"
        verbose_name_plural = "确认码"

应用逻辑如下:

  • 用户在user/forget/index的表单中,添加需要重置密码的用户邮箱
  • 若无改用户,则弹出无该用户的警告
  • 有该邮箱,则往用户邮箱发送重置密码的链接,此时
  • 重置密码的链接大致为user/forget/confirm/?code=*
  • 当code是在ConfirmString实例中时,将userhas_confirmed,使其在重置密码期间无法登录,之后携带code转到user/forget/change/?code=*
  • 若数据库中没有该code,则拒绝
  • user/forget/change/?code=*中,根据code查询一对一匹配的user,再根据添加的表单修改密码,之后confirm.user.save()confirm.delete()

django simple captcha refresh

在原项目基础上,需要修改Templateurls.py

  • urls.py

captcha.views 内置就有刷新验证码的方法

from captcha.views import captcha_refresh  # 验证码刷新功能,captcha_refresh为captcha.views内置方法,不需要我们单独写

urlpatterns = [
...
    path('refresh/', captcha_refresh),      # 点击可以刷新验证码

]
  • Template
{#刷新验证码的脚本,放到body部分的最后面即可#}
<script>
        $('.captcha').click(function () {
            $.getJSON('/captcha/refresh/',function (result) {
                $('.captcha').attr('src',result['image_url']);
                $('#id_captcha_0').val(result['key']);
            });
        });
</script>

前端设计

前端设计上是基本参考于bootstrapdoc 5.0 example.

  • index.html
image-20210421173150850
  • login.html
image-20210421173225760

Job 应用设计

后端设计

Job的应用设计上,个人在设计时,分为一些几个功能:

  • Upload 上传WebShell 文件
  • Count 统计当前用户的上传文件和扫描任务
  • Scan 根据file文件创建扫描任务
  • Search 根据task_id查询扫描任务结果

在四个任务中,uploadcountsearch设计相对简单,网上参考也相对较多,这里只是简单介绍。而scan中的设计相对麻烦,本质上是利用celery来处理扫描任务。

upload

models中设计相关模型,且添加装饰器,用于在admin可以方便地同时删除文件对象和磁盘中的文件。

  • models.py
class ModelWithFileField(models.Model):
    tmp_file = models.FileField(upload_to = './FileUpload/')# 上传目录为 FileUpload
    file_user = models.ForeignKey(User, on_delete=models.CASCADE,null=True)
    '''
    值得注意的一点是,FileUpload中已经存在相同文件名的文件时,会对上传文件的文件名重命名 
    如 1.png 转为 1_fIZVhN3.png
    且存储的文件为 1_fIZVhN3.png
    '''
    c_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.tmp_file.name

    class Meta:
        ordering = ["-c_time"]
        verbose_name = "文件"
        verbose_name_plural = "文件"

# 添加装饰器
@receiver(post_delete,sender=ModelWithFileField)
def delete_upload_files(sender, instance, **kwargs):
    files = getattr(instance, 'tmp_file')
    if not files:
        return
    fname = os.path.join(settings.MEDIA_ROOT, str(files))
    if os.path.isfile(fname):
        os.remove(fname)
  • views.py
def upload_file(request):
    """
    上传文件
    :param request:
    :return:
    """
    if not request.session.get('is_login', None):  # 不允许重复登录
        return redirect('/user/index/')
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            user_id = request.session.get('user_id')
            if user_id:
                tmp_user = models.User.objects.get(id=user_id)
                instance = ModelWithFileField(tmp_file=request.FILES['file'], file_user=tmp_user)
                instance.save()

                message = "上传成功!\n存储的文件名为:\n" + instance.tmp_file.name
                return render(request, 'job/upload.html', {'message_success': message})
            else:
                return render(request, 'job/upload.html', {'message_warning': "上传失败"})
    else:
        form = UploadFileForm()
    return render(request, 'job/upload.html', {'form': form})

scan

scan设计思路如下:

  1. 利用celeryredis,作为任务调度模块
  2. 当scan成功访问,file文件存在和无相关任务时,后台分别创建ScanTaskField实例和启动celery中的 scanTask.delay(file_name=file_name)
  3. celery中任务完成,自动更新ScanTaskField实例(同样需要添加装饰器)

参考文档docs.celeryproject.org/en/v5.0.5/django/first-steps-with-django

前端设计

前端设计上同样是基本参考于bootstrapdoc 5.0 example.

  • job/countimage-20210421180506546
  • job/uploadimage-20210421180545851

Usage

  • 下载
git clone https://github.com/fe1w0/ScanWebShell.git
cd ScanWebShell
  • 配置环境
    • php vld 插件安装
      http://pecl.php.net/package/vld
      安装后php -m来确定是否安装
    • settings.py
cp ScanWebShell/settings.example.py  ScanWebShell/settings.py 

出于安全角度,SECRET_KEY参数强烈建议修改,修改方法如下:

#进入Django shell
#python3 manage.py shell
#加载utils模块
from django.core.management import utils
#生成密钥
utils.get_random_secret_key()

邮箱(用于注册和重置密码功能)还需要在settings.py中配置如下参数:

image-20210421180717445
python3 -m pip install -r requirements.txt
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py collectstatic
python3 manage.py createsuperuser

celery中设置workerredis,需要

docker pull redis:latest
docker run --name=redis -d -p 6379:6379 redis
  • celery启动
celery -A ScanWebShell worker -l info # 可以配合tmux或后台运行工具
  • runserver
python3 manage.py runserver 0.0.0.0:8000

后续

比较有意思的是,当我在服务器上测试时,腾讯云也很快速地检测到了WebShell(复杂的WebShell没有进一步尝试)。。。厉害

评论

  1. 3月前
    2021-4-27 11:20:59

    感谢分享 赞一个

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇