讨论PythonWeb开发中可能会遇到的安全问题之SQL注入
前言
好久没写文章了,最近一直都是在看文章。
近期写了好几个PythonWeb项目,在实现需求的过程中,一直在思考PythonWeb开发过程中会遇到哪些常见的Web安全问题呢?这些问题又在什么情况下会被利用者GetShell呢?下面就分类来讨论这些问题:(这也是重新启用博客的第一篇文章,顺便投给90Sec也不知道能不能过QAQ。)
常见漏洞分析
SQLI
SQL注入所产生的条件是用户输入可构造sql语句并带入数据库执行。在Web应用中,容易产生SQL注入的输入一般是GET或POST请求参数。在PythonWeb开发中,以Flask框架为例,Flask里获取GET或POST请求数据的方式分别是request.args.get('id', 0, type=int)
和request.form.get('id', 0, type=int)
两种方式,另外Flask还支持在URL路由里带入变量:@app.route('/news/<int:id>')
,当程序员定义了这样的URL,则id这个变量在该视图里就是可以调用的。两种方法获取都是可以限定参数的类型,前者如果程序指定type为int,当用户传入无法转换成整形的字符串时,就返回None(若指定了默认值则为默认值,例子的默认值为0),后者出现这种情况则直接返回404.
PythonWeb开发中,在处理数据库的过程中经常使用orm库进行数据库处理,orm库是防SQL注入的好手。Flask和Tornado经常使用Sqlalchemy,而Django有自己自带的orm引擎。举一个用Sqlalchemy建模型类,并使用模型类查询用户数据的例子:
1 | from sqlalchemy import create_engine |
正常的查询与数据展示:
1 | 'test').first() user = session.query(user_t).filter(user_t.username== |
在数据库执行的sql语句为:
1 | SELECT user_t.user_id AS user_t_user_id, user_t.username AS user_t_username, user_t.userpassword AS user_t_userpassword, user_t.createtime AS user_t_createtime |
如果我们构造sql注入测试语句,并传入Sqlalchemy的查询语句中,看一下返回。
1 | "test'").first() user = session.query(user_t).filter(user_t.username== |
那么在数据库中执行的sql语句是什么呢?
1 | SELECT user_t.user_id AS user_t_user_id, user_t.username AS user_t_username, user_t.userpassword AS user_t_userpassword, user_t.createtime AS user_t_createtime |
由此可见在当Sqlalchemy接收到字符串进行查询时,在构造SQL语句的时候,会默认使用单引号包裹字符串,如果字符串内含有单引号的话,会使用\
进行转义。从而达到过滤单引号的效果。
我们知道原生的sql语句在进行字符串拼接的情况下,容易产生sql注入,那Sqlalchemy是否支持执行sql语句呢?答案是肯定的,下面是Sqlalchemy执行sql语句的一个例子。
1 | In [18]: from sqlalchemy import text |
那么这种情况下,会造成sql注入吗?同样我们传入test'
字符串,看看是否会进行对其进行过滤。
1 | In [22]: data = session.execute(sql, {'username':"test'"}).fetchall() |
在数据库执行的sql语句为SELECT * from user_t WHERE username = 'test\''
,可见Sqlalchemy对其进行了相同的处理。那么是不是使用Sqlalchemy的情况下就不用产生sql注入了呢?显然,如果正确使用Sqlchemy的话,出现sql注入的情况会大大的降低,但是愚蠢的sql语句处理方法,同样会导致sql注入。如果不使用execute传入参数,而是使用python格式化字符串或拼接字符串的话,出现sql注入的概率会大大增加。示例代码:
1 | "test'" sqli_payload = |
报错,从错误信息上或查看数据库记录可见,单引号被成功带进了sql语句中。因此我们就可以构造payload获取数据。例如:sqli_payload = "test' union select user(),1,2,3#"
或sqli_payload = "test' union SELECT host,user,1,2 FROM mysql.user LIMIT 1 OFFSET 1#"
。
1 | In [22]: sqli_payload = "test' union select user(),1,2,3#" |
结合上面提到的Flask传入参数的方法,我们可以整理在Flask+Sqlalchemy
的情况下,比较容易产生sql注入的情况。
- 获取get、post请求参数没有限定type或指定type为str
- 同样的,定义url参数没有限定参数类型或指定参数回调为str
- 使用用户可控的参数进行sql语句格式化或拼接并带入数据库执行的
综合以上几点,我们写一个基于flask的单文件web小程序。
1 | from flask import Flask, request, render_template_string |
注入测试:
总结与思考
- 想要在FlaskWeb应用里面发现漏洞,不仅要注意get和post请求的参数,有可能出现问题的变量隐含在url中。
- PythonWeb开发中,即使使用orm引擎,也有可能导致sql注入。
- Sqlalchemy使用单引号包裹传进来的字符串变量,并使用
\
过滤字符串中的单引号。那么宽字符注入在使用本文的环境中是否可行呢?答案是否定的,Flask默认会将所有传入的字符串转为unicode,但不排除使用别的PythonWeb框架结合Sqlalchemy会产生宽字符注入的情况。
最后再说点啥
Web应用包含sql注入的情况,通常的想法会使用sql注入写文件拿webshell。但是写webshell的情况,在多数的PythonWeb框架或PythonWeb生产环境中并不管用。当然这并不代表,sql注入的危害性在PythonWeb环境中会降低,你依旧可以使用它来进行很多危险的行为。PythonWeb框架会产生的安全问题也有很多有趣的地方值得我们思考,我会继续分析其他的诸如XSS,SSRF等漏洞在PythonWeb上面所表现的特点,也会分析诸如Pickle反序列化,Flask强大的Debug模式等Python特性可能产生的安全问题。
总之路还很长,还得继续加油啊…
下一章XSS…
本博客所有内容只用于安全研究,请勿用于恶意攻击。
本文URL: "https://blog.neargle.com/2016/07/22/pythonweb-framework-dev-vulnerable/index.html"